Page MenuHomePhabricator

downgrade strong type info names to weak_odr linkage
Needs ReviewPublic

Authored by rsmith on May 18 2018, 3:26 PM.

Details

Summary

After https://reviews.llvm.org/D46665 / rC332028, we now emit linkonce_odr definitions of type info names for incomplete class types. This results in formal violations of LLVM's linkage rules (which, for example, cause ASan's ODR violation checker to complain) if the type info name is declared with external linkage in another IR module. To solve this, downgrade any type info names that we would give external linkage to instead have weak_odr linkage. Exception: if we would emit a non-unique type_info name anyway, leave the linkage alone.

This required a bit of rearrangement: the uniqueness of the name can no longer depend on the linkage of the name, because the linkage of the name now depends on the uniqueness of the name! Fortunately, the uniqueness doesn't *really* depend on the linkage of the name, it only depends on the uniqueness of the type. I've split out the type uniqueness / name uniqueness / type_info uniqueness calculations to try to make this a bit clearer.

Diff Detail

Repository
rC Clang

Event Timeline

rsmith created this revision.May 18 2018, 3:26 PM

This results in formal violations of LLVM's linkage rules

I'm pretty sure this isn't actually a violation of LLVM's linkage rules as they are described in LangRef... at least, my understanding is that "equivalent" doesn't imply anything about linkage. (If this should be a violation, we need to clarify the rules.)

Incomplete classes are a curse. I don't suppose we can just modify the language specification to make it illegal to use typeid(Incomplete*)? Or make equality/hashing undefined in these cases?

test/CodeGenCXX/arm64.cpp
48

The way the Darwin ARM64 ABI handles non-unique RTTI is by setting a flag indicating that clients should do string comparisons/hashes; it does not rely on the type name symbols being uniqued. So this should stay external and the _ZTI definition should change to set the bit.

test/CodeGenCXX/windows-itanium-type-info.cpp
31

Is this actually okay? I didn't think dllexport supported weak symbols.

Incomplete classes are a curse. I don't suppose we can just modify the language specification to make it illegal to use typeid(Incomplete*)? Or make equality/hashing undefined in these cases?

Honestly, I'm somewhat inclined to just declare this for Darwin and make us non-conformant. It's not like this has ever previously worked, and this patch is going to be disruptive for our internal C++ developers by introducing new global weak symbols that they can't get rid of except by disabling RTTI. (It would always have been a problem if they were actually using RTTI, of course, but one of the whole problems with RTTI is that you live with the consequences whether you use it or not.)

Sorry @rsmith, I was just about to tackle this.

I'm pretty sure this isn't actually a violation of LLVM's linkage rules as they are described in LangRef... at least, my understanding is that "equivalent" doesn't imply anything about linkage. (If this should be a violation, we need to clarify the rules.)

This is a fair point. (Though I would note that the LangRef doesn't even say that you can't have two external definitions of the same symbol, so it would clearly benefit from some clarification in this area.)

We could certainly teach ASan to just ignore "odr violations" on type info names, if we think that the IR generated here is in fact correct.

If we go down that path, do we still need changes for the Darwin ARM64 case? (That is, is it OK to emit some weak_odr definitions along with an external definition for the same _ZTS symbol, and not use the 'non-unique type info name' flag? Or should we still be treating that case -- and, as a consequence, all external-linkage _ZTS symbols for classes -- as non-unique?)

test/CodeGenCXX/arm64.cpp
48

OK. I was trying to avoid introducing more cases where we do string comparisons, but if you prefer string comparisons over link-time deduplication in all cases for that target, that seems easy enough to support.

(Out of curiosity, does the link-time deduplication not reliably work on that platform? If so, how do you deal with the other cases that require deduplication? If not, what's the motivation for using string comparisons?)

test/CodeGenCXX/windows-itanium-type-info.cpp
31

Good question, I don't know. Do we have some way to specify "weak_odr within this DSO but strong at the DSO boundary"? I suppose a COMDAT would give that effect.

rjmccall added inline comments.May 18 2018, 7:27 PM
test/CodeGenCXX/arm64.cpp
48

Static and dynamic linking work the same on ARM64 as on any other Mach-O platform. As you say, there are language features that depend on symbols resolving to the same object; we haven't changed that. The issue is that, while deduplication in the *static* linker is great, deduplication in the *dynamic* linker means extra, unavoidable work at load time — mostly, paging in a bunch of data from the symbol table. For an implicitly-activated language feature like RTTI, that can add up to a lot of overhead, which hits you regardless of whether you actually ever use any of the language features that depend on it. On Darwin we really like dynamic libraries, and we really like processes to launch quickly. So the ARM64 ABI says that RTTI which would otherwise need to be potentially deduplicated across a dynamic-library boundary (because the type is external and not hidden) is instead hidden and must be compared using the string data — a classic load-time vs. execution-time trade-off, in this case easy to make because the features that depend on dynamic type matching are rarely used and generally slow anyway.

The GCC type_info ABI uses string comparisons for a completely different purpose: they assume that users can't be trusted to mark visibility correctly on types, so they use string comparisons as a fallback. Darwin ARM64 is still as strict about type visibility as we are on every other platform, so if you use -fvisibility=hidden and forget to mark a type as visible, dynamic_cast and EH will fail across library boundaries.

It occurs to me that all this might be unnecessary, though:

  1. It's never possible to ask for the type_info of an incomplete class type directly.
  2. The features that use RTTI which can actually do pointer conversions don't allow pointers to incomplete class types, either — at best, you can have pointers-to-pointers.
  3. As far as I know, none of those features would ever depend on the pointee type_info object of a pointer-to-pointer. For example, you cannot do a dynamic_cast from a Base** to a Derived**. They only care about the direct pointee type, which cannot be incomplete.
  4. I think the only feature that allows a pointer to an incomplete class type is typeid.
  5. None of the standard operations on a type_info actually cares about the pointee type_info. They care about the name symbol for the pointer type, but that already has to be deduplicated (modulo the ARM64 trick) because we don't eagerly emit RTTI for pointer types.
  6. You can get at the pointee type_info with the cxxabi.h interface, but I would argue that we should not make the ABI worse solely for the benefit of clients of the cxxabi.h interface, especially in a corner case of a corner case (a pointer to an incomplete type which would have turned out to be a dynamic class with a key function).
  7. So I think we could actually get away with always using internal linkage for the ZTSes of incomplete class types, as long as we continue to use the standard rule for the ZTSes of *pointers* to incomplete class types, because the only thing that would break would be the cxxabi.h interface.
  8. And if we find a way to convince LLVM / ASan to let us use weak linkage even if there might also be a strong definition out there, we don't even have that problem long-term.
jgorbe resigned from this revision.Mar 26 2019, 5:14 PM
Herald added a project: Restricted Project. · View Herald TranscriptMar 26 2019, 5:14 PM