Given the following scenario:
// Cat.cpp struct Animal { virtual void makeNoise() const = 0; }; struct Cat : Animal { void makeNoise() const override; }; extern "C" int puts(char const *); void Cat::makeNoise() const { puts("Meow"); } void doThingWithCat(Animal *a) { static_cast<Cat *>(a)->makeNoise(); } // CatUser.cpp struct Animal { virtual void makeNoise() const = 0; }; struct Cat : Animal { void makeNoise() const override; }; void doThingWithCat(Animal *a); void useDoThingWithCat() { Cat *d = new Cat; doThingWithCat(d); } // cat.ver { global: _Z17useDoThingWithCatv; local: *; }; $ clang++ Cat.cpp CatUser.cpp -fpic -flto=thin -fwhole-program-vtables -shared -O3 -fuse-ld=lld -Wl,--lto-whole-program-visibility -Wl,--version-script,cat.ver
We cannot devirtualize Cat::makeNoise. The issue is complex:
Due to -fsplit-lto-unit and usage of type metadata, we place the Cat
vtable declaration into module 0 and the Cat vtable definition with type
metadata into module 1, causing duplicate entries (Undefined followed by
Defined) in the lto::InputFile::symbols() output.
In BitcodeFile::parse, after processing the Undefined then the
Defined, the final state is Defined.
In BitcodeCompiler::add, for the first symbol, computeBinding
returns STB_LOCAL, then we reset it to Undefined because it is
prevailing (versionId is preserved). For the second symbol, because
the state is now Undefined, computeBinding returns STB_GLOBAL,
causing ExportDynamic to be true and suppressing devirtualization.
In D77280, the computeBinding change used a stricter isDefined()
condition to make weak`Lazy symbol work.
This patch relaxes the condition to weaker !isLazy() to keep it
working while making the devirtualization work as well.