__attribute__((used)) with -mconstructor-aliases fails to emit Ctor_Complete (C1)

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view

__attribute__((used)) with -mconstructor-aliases fails to emit Ctor_Complete (C1)

Fangrui Song via cfe-dev
Hi all, wanted to share some behavior I'm seeing that seems unintentional, and I'm wondering if there's a simple fix.

As the title suggests, not all versions of an __attribute__((used)) tagged constructor get emitted when compiling with -mconstructor-aliases (which is the default when using the "clang" driver). Same with destructors. I mostly tested with the triple "x86_64-unknown-linux-gnu", but WebAssembly, at least, appears to behave the same way. Ctor_Base (the one using C2 in the mangled name), always get emitted, but C1 does not. There's a test at llvm/clang/test/CodeGenCXX/attr-used.cpp that specifically checks to make sure C1 gets emitted, but it doesn't use the -mconstructor-aliases flag (and, in fact, fails if you try). I did most of my testing on the master branch, but it appeared the same on several versions I sampled back to 5.0.

I traced compilation on the following minimal test case:

class X0 {
    __attribute__((used)) X0() {}

When CodeGenModule::EmitTopLevelDecl() was called for X0(), it called ItaniumCXXABI::EmitCXXConstructors(), which called CodeGenModule::EmitGlobal() for first Ctor_Base and then Ctor_Complete. Normally, EmitGlobal() would try to defer emitting the inline functions until they were used somewhere else, but ASTContext::DeclMustBeEmitted() checks for __attribute__((used)) and forces CodeGenModule::EmitGlobalDefinition() to be called for both of them. So far so good, but then EmitGlobalDefinition() calls into ItaniumCXXABI::emitCXXStructor(), and this is where things start to go wrong. If you compile without the -mconstructor-aliases flag, emitCXXStructor() checks getCodegenToUse(), which immediately returns Emit, both ctor versions get emitted, and you pass your tests. With the -mconstructor-aliases flag, however, getCodegenToUse() wends its way down to eventually calling GlobalValue::isDiscardableIfUnused(), which returns true, and getCodegenToUse() ends up returning RAUW (Replace All Uses With). Ctor_Base gets emitted to comdat, but Ctor_Complete gets CodeGenModule::addReplacement'd with Ctor_Base, and doesn't make it to the object file.

Intuitively, a function tagged as __attribute__((used)) seems like it shouldn't be isDiscardableIfUnused, though I can also imagine that argued the other way, but RAUW is even harder to justify. Adding a check for hasAttr<UsedAttr>() on the line that checks for hasAttr<DLLExportAttr>() in ASTContext::adjustGVALinkageForAttributes() results in RAUW becoming COMDAT, and C1 being emitted as an alias for C2 (along with a simple C5 comdat), which seems like the right outcome, though I'm less sure if it's the right way of getting there. I'm a novice to the Clang internals, but I'm up for looking into this deeper with some guidance.

In the meantime, is there a way to suppress clang frontend flags via the clang driver?


cfe-dev mailing list
[hidden email]