Thinlto in libLTO question/bug

Hi, I’m trying to use thinlto through the legacy (libLTO) interface, and it seems to me there’s a bug in the algorithm for deciding what functions to export. In particular, what I’m observing, is that if a function foo is not imported into any other module then it’s not exported from the defining module.

The net effect is that when we run thinLTOInternalizeAndPromoteInIndex (from ThinLTOCodeGenerator::run()), the function foo is internalized because it’s not present in either the ExportLists (populated by ComputeCrossModuleImport()) or GUIDPreservedSymbols (populated by the linker).

Hi @teresajohnson, am I missing something?
I’m not able to reproduce the problem with llvm-lto on linux but I can on AIX.
On both platforms, I see we set foo’s Summary’s linkage to internal in thinLTOInternalizeAndPromoteGUID, but somehow on linux this is not reflected in the IR (i.e. foo is still external).

Here’s a testcase:

> cat t1.c 
int foo();
int main() {   return foo();  };
> cat t2.c 
int foo() { return 22; }
> clang -c -flto=thin -O0 t1.c t2.c
> llvm-lto t1.o t2.o -thinlto-action=run -exported-symbol=main

noinline attribute (from the -O0) will cause foo not to get imported.

Thanks

@cachemeifyoucan might be able to help more with legacy LTO handling.

I’m more familiar with the new linker resolution based LTO interfaces, which also requires the corresponding tool (llvm-lto2) to specify whether each symbol is prevailing, which in turn causes the LTO handling to keep the symbol if it is marked as such.

What might be needed for testing through llvm-lto is to list all of the symbols that can’t be internalized via --exported-symbol. I see that the legacy ThinLTO has an interface for marking symbols as cross referenced (ThinLTOCodeGenerator::crossReferenceSymbol), but it is not invoked by llvm-lto, only preserveSymbol() - however this may be because the implementation is the same for both of these methods currently.

As for why it is working properly on linux despite the index marking it for internalization, I’m not sure there unfortunately. The actual IR internalization is done by llvm::thinLTOInternalizeModule, so you might want to take a look at that.

It’s not surprising that it wouldn’t be in the export lists populated by ComputeCrossModuleImport since there is no importing here.

Thank you for your reply.
So, I understand why a linker needs to tell llvm about references in the non-bitcode files (using thinlto_codegen_add_must_preserve_symbol) … because llvm has no access to the non-bitcode files.
Is it the intention of the thin-lto design to require the linker to look at the bitcode files and inform llvm about cross references between bitcode files? If so, why?
My intuition is that llvm doesn’t need the linker; it can compute this information (conservatively in case of weak symbols) by walking the call graph of the bitcode files, starting with the symbols that are preserved by the linker and marking all reachable code as preserved/exported.

The actual IR internalization is done by llvm::thinLTOInternalizeModule, so you might want to take a look at that.

thanks, I will.

Is it the intention of the thin-lto design to require the linker to look at the bitcode files and inform llvm about cross references between bitcode files? If so, why?

The linker needs to get the symbols from the bitcode files in any case, to do its normal symbol resolution and ensure all definitions are available, etc. So there is a hook into LLVM that it uses to obtain symbols from Bitcode files. The linker can then easily tell LTO which symbols are referenced from each file (it also needs to pass back info on what is prevailing, what is referenced from native objects, etc). So we already know this and don’t need to use the index.

My intuition is that llvm doesn’t need the linker; it can compute this information (conservatively in case of weak symbols) by walking the call graph of the bitcode files, starting with the symbols that are preserved by the linker and marking all reachable code as preserved/exported.

We do walk the index to see what is reachable but for the opposite reason - to see what is dead and unreachable and can therefore be internalized and removed. Note that not all reachable code needs to be exported, some can stay internal to their module. But we don’t need to figure out what needs to be referenced outside its module this way since as noted above we get this info from the linker.

That is correct that linker need to tell thinLTO codegen about all the symbols that needs to preserved (cannot internalized) even the requirement is from another bitcode file. One way to think about this is that this is a simplified version of linker resolution info from new linker interface, telling the codegen about referenced symbols, except you can’t tell which file it comes from and what kind of reference it is with the legacy API.

Also note the precise information from thin index is computed from the resolution info from linker, not the other way around.