[lld][WebAssembly] Propagate +atomics for ThinLTO when using --shared-memory (#188381)

When compiling WebAssembly with ThinLTO, functions are partitioned into
isolated `.bc` modules and dispatched to individual LTO backend threads.
During code generation, the `CoalesceFeaturesAndStripAtomics` pass
iterates over the module to gather the union of target features (like
`+atomics`) attached to defined functions. In particular when not using
threads, it lowers away atomics and TLS variables to their
single-threaded equivalents.

However, if a partitioned module only contains globally defined TLS
variables (e.g. there are no functions, or all functions were fully
inlined or stripped by dropDeadSymbols before ThinLTO optimization), the
module becomes completely devoid of function definitions. The coalescing
pass then falls back to fetching features from the `TargetMachine`.
Because in LTO the `TargetMachine` defaults to a generic target without
atomics enabled, the TLS is lowered away and the `wasm-feature-atomics`
flag is omitted from the resulting ThinLTO object partition, causing
`wasm-ld` to immediately reject it.

To fix this we take advantage of the fact that the linker always knows
whether threads are being used (via the --shared-memory flag). When
using shared memory, we enable +atomics and +bulk-memory in the
TargetMachine that is used for the backend, and the feature coalescing
pass will correctly detect the use of therads.
This only makes sense for atomics because of the global linker
configuration; for other features we wouldn't be able to do this, but we
don't rewrite away any other features anyway.
This commit is contained in:
Derek Schuff 2026-03-24 17:30:39 -07:00 committed by GitHub
parent dd9885c0f3
commit b60a39e3c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 0 deletions

View File

@ -0,0 +1,40 @@
; RUN: split-file %s %t
; RUN: opt -module-summary %t/main.ll -o %t.main.o
; RUN: opt -module-summary %t/empty.ll -o %t.empty.o
; RUN: wasm-ld --shared-memory %t.main.o %t.empty.o -o %t.wasm
; RUN: llvm-nm %t.wasm | FileCheck %s
; This test ensures that when an unused function with target-features="+atomics,+bulk-memory"
; is stripped during ThinLTO, the resulting empty module does not fall back to generic TargetMachine
; features and incorrectly omit the atomics/bulk-memory features from the generated custom sections.
;
; Since --shared-memory is passed to wasm-ld, `+atomics` and `+bulk-memory` are propagated
; to the LTO Config via MAttrs, bypassing the functional absence of attributes.
; CHECK-NOT: error:
; CHECK: _start
;--- main.ll
target triple = "wasm32-unknown-emscripten"
define void @_start() #0 {
entry:
ret void
}
attributes #0 = { "target-features"="+atomics,+bulk-memory,+mutable-globals,+sign-ext" }
;--- empty.ll
target triple = "wasm32-unknown-emscripten"
; Thread-local variable that forces generation of TLS layout
@my_tls = thread_local global i32 42, align 4
; This function will be removed by dropDeadSymbols because it's unused,
; taking its target-features attribute block along with it.
define void @unused() #0 {
entry:
ret void
}
attributes #0 = { "target-features"="+atomics,+bulk-memory,+mutable-globals,+sign-ext" }

View File

@ -53,6 +53,19 @@ static lto::Config createConfig() {
c.OptLevel = ctx.arg.ltoo;
c.CPU = getCPUStr();
c.MAttrs = getMAttrs();
// If shared memory is enabled, ensure the TargetMachine backend is
// instantiated with atomics and bulk-memory features so that empty
// partitioned ThinLTO modules don't incorrectly strip TLS variables or fall
// back to defaults. This bypasses a bug where deleted functions take their
// target-features away. This is only necessary for atomics (and not other
// features) because Atomics and TLS are the only features we lower away
// during codegen.
if (ctx.arg.sharedMemory) {
c.MAttrs.push_back("+atomics");
c.MAttrs.push_back("+bulk-memory");
}
c.CGOptLevel = ctx.arg.ltoCgo;
c.DebugPassManager = ctx.arg.ltoDebugPassManager;
c.AlwaysEmitRegularLTOObj = !ctx.arg.ltoObjPath.empty();