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.verWe 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.