[ThinLTO] Distributed backends keep dllimport on declarations that resolve within the link unit; codegen diverges from in-process ThinLTO (COFF)
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
The below AI analysis is the second of two issues that are preventing DTLTO from working with our DLL setup and crashing. I'm currently using `/OPT:NOREF` and `/ignore:4217` as a workaround but the generated code is not optimal. I'll link the other issue in the comments.
---
In-process ThinLTO on COFF clears the dllimport storage class of a declaration whose prevailing definition is inside the link unit; the backend emits a direct call. Distributed backends (clang -fthinlto-index=, including DTLTO) keep the dllimport storage and emit call [__imp_X]. The linker fulfills __imp_X as a local import, producing one "locally defined symbol imported" warning per reference. Consequences:
1. Codegen divergence from in-process ThinLTO: an IAT-indirect call at each such site instead of a direct call. In one large application, the distributed link contained 78,017 local-import slots where the in-process link emits direct calls and no warnings.
2. Under /OPT:REF, the referenced definitions are stripped and the slots contain the image base ().
Repro:
```c++
// def.cpp
extern "C" __declspec(noinline) int target(int x) { volatile int v = x; return v * 7; }
extern "C" __declspec(noinline) int keeper(int x) { volatile int v = x; return v + 1; }
```
```c++
// user.cpp
extern "C" __declspec(dllimport) int target(int);
extern "C" int keeper(int);
extern "C" int entry() { return target(1) + keeper(2); }
```
`clang-cl -c -O2 -flto=thin def.cpp`
`clang-cl -c -O2 -flto=thin user.cpp`
:: in-process: direct call to target, no warnings
`lld-link /entry:entry /subsystem:console /out:a.exe def.obj user.obj`
:: distributed: user's backend emits call [__imp_target]; local-import warning at final link
`lld-link /entry:entry /subsystem:console /out:b.exe /thinlto-distributor: /thinlto-remote-compiler:\clang-cl.exe def.obj user.obj`
The distributed backend object for user.cpp contains U __imp_target; the in-process equivalent has a direct relocation to target. noinline prevents the call from being removed by cross-module import and inlining; the divergence itself is independent of importing (in-process still emits a direct call with /mllvm:-import-instr-limit=0).
Mechanism (current main):
- The clearing happens in FunctionImportGlobalProcessing::processGlobalForThinLTO (FunctionImportUtils.cpp). It only fires when the backend's index has a summary for the symbol — VI below is null otherwise:
```c++
} else if (VI && VI.isDSOLocal(ImportIndex.withDSOLocalPropagation())) {
// If all summaries are dso_local, symbol gets resolved to a known local
// definition.
GV.setDSOLocal(true);
if (GV.hasDLLImportStorageClass())
GV.setDLLStorageClass(GlobalValue::DefaultStorageClass);
}
```
- The in-process backend uses the full combined index; the summary and its dso_local resolution (D35702) are present.
- Distributed backends read the per-module index from gatherImportedSummariesForModule (FunctionImport.cpp), which contains the module's own summaries plus summaries of symbols selected for import. A referenced-but-not-imported external has no summary there; VI is null; the dllimport storage reaches codegen.
Suggested direction: include summary entries, or at least the dso_local resolution, for referenced-but-not-imported globals when writing per-module indexes.
Verified with clang/lld 23.1.0; the cited code matches current main.
Contributor guide
Research direction
Start with the two-file clang-cl/lld-link ThinLTO repro and compare the in-process and distributed backend objects. Read FunctionImportUtils.cpp at FunctionImportGlobalProcessing::processGlobalForThinLTO, then trace per-module index construction in FunctionImport.cpp at gatherImportedSummariesForModule. Done means distributed codegen resolves the internal target directly, without __imp_target or the local-import warning, matching in-process ThinLTO.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100