[cfe-dev] __attribute__((used)) with -mconstructor-aliases fails to emit Ctor_Complete (C1)

John Edwards via cfe-dev cfe-dev at lists.llvm.org
Thu May 21 02:54:45 PDT 2020


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<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?

John
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20200521/bd78528a/attachment-0001.html>


More information about the cfe-dev mailing list