5 ms·
If you already have LTO, can't the compiler determine this information for devirtualization purposes on its own?
by i80and 2y ago
If you already have LTO, can't the compiler determine this information for devirtualization purposes on its own?
- nickwanninger 2y agoAt the level that LLVM's LTO operates, no information about classes or objects is left, so LLVM itself can't really devirtualize C++ methods in most cases
- nwallin 2y agoYou appear to be correct. Clang does not devirtualize in LTO, but GCC does. Personally I consider this very strange. $ cat animal.h cat.cpp main.cpp // animal.h #pragma once class animal { public: virtual ~animal() {} virtual void speak() = 0; }; animal& get_mystery_animal(); // cat.cpp #include "animal.h" #include <cstdio> class cat final : public animal { public: ~cat() override{} void speak() override{ puts("meow"); } }; static cat garfield{}; animal& get_mystery_animal() { return garfield; } // main.cpp #include "animal.h" int main() { animal& a = get_mystery_animal(); a.speak(); } $ make clean && CXX=clang++ make -j && objdump --disassemble=main -C lto_test rm -f *.o lto_test clang++ -c -flto -O3 -g cat.cpp -o cat.o clang++ -c -flto -O3 -g main.cpp -o main.o clang++ -flto -O3 -g cat.o main.o -o lto_test lto_test: file format elf64-x86-64 Disassembly of section .init: Disassembly of section .plt: Disassembly of section .plt.got: Disassembly of section .text: 00000000000011b0 <main>: 11b0: 50 push %rax 11b1: 48 8b 05 58 2e 00 00 mov 0x2e58(%rip),%rax # 4010 <garfield> 11b8: 48 8d 3d 51 2e 00 00 lea 0x2e51(%rip),%rdi # 4010 <garfield> 11bf: ff 50 10 call *0x10(%rax) 11c2: 31 c0 xor %eax,%eax 11c4: 59 pop %rcx 11c5: c3 ret Disassembly of section .fini: $ make clean && CXX=g++ make -j && objdump --disassemble=main -C lto_test|sed -e 's,^, ,' rm -f *.o lto_test g++ -c -flto -O3 -g cat.cpp -o cat.o g++ -c -flto -O3 -g main.cpp -o main.o g++ -flto -O3 -g cat.o main.o -o lto_test lto_test: file format elf64-x86-64 Disassembly of section .init: Disassembly of section .plt: Disassembly of section .plt.got: Disassembly of section .text: 0000000000001090 <main>: 1090: 48 83 ec 08 sub $0x8,%rsp 1094: 48 8d 3d 75 2f 00 00 lea 0x2f75(%rip),%rdi # 4010 <garfield> 109b: e8 50 01 00 00 call 11f0 <cat::speak()> 10a0: 31 c0 xor %eax,%eax 10a2: 48 83 c4 08 add $0x8,%rsp 10a6: c3 ret Disassembly of section .fini:
- ranger_danger 2y agoWhat if you add -fwhole-program-vtables on clang?
- JonChesterfield 2y agoI think this is a bug. There's dedicated metadata that's supposed to end up on the indirect call to list the possible targets and when that list of possible targets is this short it should be turning into a switch over concrete targets. Don't have time to dig into the IR now but it might be worth posting to the github llvm issues.
- wiml 2y agoIf your runtime environment has dynamic linking, then the LTO pass can't always be sure that a subclass won't be introduced later that overrides the method.
- adzm 2y agoMSVC with LTO and PGO will inline virtual calls in some situations along with a check for the expected vtable, bypassing the inlined code and calling the virtual function normally if it is an unexpected value.
- bluGill 2y agonot if there is a shared libray or other plugin. Then you coannot determine until runtime if there is an override.
- ot 2y agoIn general the compiler/linker cannot assume that derived classes won't arrive later through a shared object. You can tell it "I won't do that" though with additional flags, like Clang's -fwhole-program-vtables, and even then it's not that simple. There was an effort in Clang to better support whole program devirtualization, but I haven't been following what kind of progress has been made: https://groups.google.com/g/llvm-dev/c/6LfIiAo9g68?pli=1 https://groups.google.com/g/llvm-dev/c/6LfIiAo9g68?pli=1
- Slix 2y agoThis optimization option isn't on by default? That sounds like a lot of missed optimization. Most programs aren't going to be loading from shared libraries. Maybe I can set this option at work. Though it's scary because I'd have to be certain.
- soooosn 2y agoI think you have answered your own question: If turning on the setting is scary for you in a very localized project at your company, imagine how scary it would be to turn on by default for everybody :-P
- Thiez 2y agoThe JVM can actually perform this optimization optimistically and can undo it if the assumption is violated at runtime. So Java's 'everything is virtual by default' approach doesn't hurt. Of course relying an a sufficiently smart JIT comes with its own trade-offs.
- JonChesterfield 2y agoOptimization means "make it faster without changing behaviour in ways I don't like". Clang can't generally default that one to on because it doesn't know whether you're going to splice in more code it can't see at runtime. Lots of code gets slower if it might need to be called from something not currently in the compiler's scope. That's essentially what ABI overhead is. If there isn't already, there should be a compiler flag that says "this is the whole program, have at it" which implies the vtables option.
- samus 2y agoThis is one of the cases where JIT compiling can shine. You can use a bazillion interfaces to decouple application code, and the JIT will optimize the calls after it found out which implementation is used. This works as long as there is only one or two of them actually active at runtime.
- account42 2y agoYou don't need a JIT to do whole program optimization.
- samus 2y agoAOT whole program optimization has two limits: * It is possible with `dlopen()` to load code objects that violate the assumptions made during compilation. * The presence of runtime configuration mechanisms and application input can make it impossible to anticipate things like the choice of implementations of an interface. One can always strive to reduce such situations, but it might simply not be necessary if a JIT is present.