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 {
public:
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() on the line that checks for hasAttr() 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?
John