Skip to content

release/23.x: [lld][WebAssembly] Fix a number of issues with --cooperative-multithreading - #217096

Closed
alexcrichton wants to merge 4 commits into
llvm:release/23.xfrom
alexcrichton:backport
Closed

release/23.x: [lld][WebAssembly] Fix a number of issues with --cooperative-multithreading#217096
alexcrichton wants to merge 4 commits into
llvm:release/23.xfrom
alexcrichton:backport

Conversation

@alexcrichton

Copy link
Copy Markdown
Contributor

This PR is a combined backport of four separate fixes to the handling of the wasm-ld's --cooperative-threading flag, introduced in #200855. These fixes are needed to get this flag working for a new WASIp3 target that colleagues and I have been working on. The PRs here are:

My hope is to get this merged for the LLVM 23 release next week to ensure that various LLVM toolchains can all get the fixes at the same time, notably the Rust toolchain and wasi-sdk. These are all fixes which landed after the original release/23.x branch point but are intended to be minor fixes for --cooperative-threading.

I'll note that procedurally I'm doing a manual PR here and I'm not certain if this is the best way to do this, so please let me know if there's a different procedure to follow.

alexcrichton and others added 4 commits August 18, 2026 11:13
…lvm#208263)

This commit is a change to `wasm-ld`'s behavior when the
`--cooperative-threading` flag is passed to the linker. The change here
is to by default work as if `--export-table` was passed as well. This is
required conventionally on this target because the table is where
function pointers are read from in the component model
`thread.new-indirect` intrinsic. If the table is not exported then
there's no way to turn the core module into a component so it's
effectively required. This behavior only applies to when the table isn't
otherwise imported, for example in shared libraries.

The other motivation behind this change is that it'll avoid the need to
manually specify `-Wl,--export-table` when compiling for the
`wasm32-wasip3` target. This additionally avoids the need for the Clang
driver to figure out if flags like `--import-table` were otherwise
passed. Basically it seemed best to put this in `wasm-ld` itself to
avoid as little juggling of pieces as necessary.

cc WebAssembly/wasi-libc#808
…llvm#206831)

With `--gc-sections` (the default), `wasm-ld` garbage-collects functions
that are only reachable through `__wasm_get_tls_base` /
`__wasm_set_tls_base` in the cooperative-threading (libcall
thread-context) configuration. This produces a linked module that is
invalid or behaves incorrectly: the relocation inside
`__wasm_set_tls_base` is left dangling / mis-resolved, so callers trap
at runtime (e.g. `validation error: ... values remaining on stack at end
of block`, or a call to an unrelated function).

In cooperative-threading mode (`--cooperative-threading`, added in
llvm#200855), per-task thread context is accessed through libcalls rather
than wasm globals. `wasm-ld` synthesizes `__wasm_init_tls` /
`__wasm_init_memory`, which invoke `__wasm_get_tls_base` and
`__wasm_set_tls_base` via **raw `call` instructions that carry no
relocations**. To keep those accessors in the output, the linker marks
them live with `Symbol::markLive()`.

## Error and Repro

```bash
Error: failed to compile: wasm[0]::function[16]::__wasm_set_tls_base

Caused by:
    0: WebAssembly translation error
    1: Invalid input WebAssembly code at offset 778: type mismatch: values remaining on stack at end of block
```

import was dropped and __wasm_set_tls_base was rewritten from

```wat
(func $__wasm_set_tls_base (param i32)
  local.get 0
  call $__wasm_component_model_builtin_context_set_1)   ;; correct
```

to

```wat
(func $__wasm_set_tls_base (param i32)
  local.get 0
  call $__wasm_component_model_builtin_context_get_0)   ;; wrong: get_0 is () -> i32
```

Minimal linker-only repro (no runtime needed) is:
```bash
wasm-ld --cooperative-threading tls.o libc.a   # context-set-1 dropped, set_tls_base calls get_0
wasm-ld --cooperative-threading --no-gc-sections tls.o libc.a   # correct
```

## Root cause

`Symbol::markLive()` sets the live flag (and the chunk's live bit) but
does not push the defining chunk onto the mark queue. The mark phase
only follows relocations of chunks that were enqueued via
`MarkLive::enqueue()`. As a result the accessors' own relocations are
never traversed.

This is the same situation already handled for constructors reached
through the relocation-less `__wasm_call_ctors`, which `MarkLive::run()`
enqueues explicitly.

## Fix

In `MarkLive::run()`, enqueue the defining chunks of
`__wasm_get_tls_base` and `__wasm_set_tls_base` before `mark()`, so
their relocations are followed.

```cpp
for (Symbol *sym : {static_cast<Symbol *>(ctx.sym.getTLSBase),
                    static_cast<Symbol *>(ctx.sym.setTLSBase)})
  if (sym)
    if (InputChunk *c = sym->getChunk())
      enqueue(c);
```

The symbols are only set in the libcall-thread-context configuration and
are null otherwise, so the loop is a no-op for all other builds.

## Testing

Adds `lld/test/wasm/cooperative-threading-gc.s`, which links with
`--cooperative-threading --gc-sections` and checks (via `obj2yaml`) that
the import called by `__wasm_set_tls_base` survives GC. Without this
change the test drops the import and rewrites `__wasm_set_tls_base` to
call an unrelated function; with it the import is retained and the call
is correct.

```sh
ninja check-lld-wasm
```

## Note

Human-in-the-loop with claude opus 4.8. I iterated with this patch and
got to a working component running with a wasi-sdk fork.
This commit fixes a few issues that have surfaced in `wasm-ld`'s
handling of `--cooperative-threading` with `-shared`. Two primary issues
fixed are:

* The `__wasm_init_memory` function was not valid as it referenced
nonexistent locals. This was fixed by adjusting how locals are declared
to make this a bit more flexible.
* Combining data segments for PIC without extended-const is generalized
to only operate on active data segments and is now orthogonal to
threading. With coop threads there's a mixture of passive/active
segments (TLS is passive, other data is active) and the active segments
still need combining while TLS stays passive.

The latter fix ended up touching a few more areas. The first is that
`.tdata` sections are now sorted just before `.bss`, the end section,
rather than first. This is done to ensure that active segments when
combined can indeed start at a relative address of 0 (as required
without extended-const). This change resulted in a number of adjustments
for tests as the data is now sorted differently. Additionally
relocations in TLS segments needed adjusting since the previous
implementation implicitly only worked if `.tdata` is first.
This commit updates how `wasm-ld` initializes the `__init_tls_base`
global during module instantiation. Previously this global was left
entirely unmodified meaning that it was always 0. This change updates
the `__wasm_init_memory` function to set this global dynamically in PIC
mode based on the TLS address calculation, or in non-PIC mode the
generation of `__wasm_init_memory` correctly sets it to the desired TLS
address.

cc WebAssembly/wasi-libc#819
@alexcrichton

Copy link
Copy Markdown
Contributor Author

cc @sbc100

@llvmorg-github-actions

llvmorg-github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-lld

@llvm/pr-subscribers-lld-wasm

Author: Alex Crichton (alexcrichton)

Changes

This PR is a combined backport of four separate fixes to the handling of the wasm-ld's --cooperative-threading flag, introduced in #200855. These fixes are needed to get this flag working for a new WASIp3 target that colleagues and I have been working on. The PRs here are:

My hope is to get this merged for the LLVM 23 release next week to ensure that various LLVM toolchains can all get the fixes at the same time, notably the Rust toolchain and wasi-sdk. These are all fixes which landed after the original release/23.x branch point but are intended to be minor fixes for --cooperative-threading.

I'll note that procedurally I'm doing a manual PR here and I'm not certain if this is the best way to do this, so please let me know if there's a different procedure to follow.


Patch is 37.98 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/217096.diff

14 Files Affected:

  • (modified) lld/test/wasm/compress-relocs.s (+6-6)
  • (modified) lld/test/wasm/compress-relocs64.s (+6-6)
  • (added) lld/test/wasm/cooperative-threading-gc.s (+57)
  • (modified) lld/test/wasm/cooperative-threading.s (+174-12)
  • (modified) lld/test/wasm/data-segments.ll (+24-24)
  • (modified) lld/test/wasm/runtime-relocations-himem.s (+2-2)
  • (modified) lld/test/wasm/tls-non-shared-memory.s (+7-7)
  • (modified) lld/test/wasm/tls-relocations.s (+2-2)
  • (modified) lld/wasm/Driver.cpp (+10-3)
  • (modified) lld/wasm/InputChunks.cpp (+7)
  • (modified) lld/wasm/MarkLive.cpp (+14)
  • (modified) lld/wasm/OutputSections.cpp (+3-5)
  • (modified) lld/wasm/OutputSegment.h (+7)
  • (modified) lld/wasm/Writer.cpp (+75-32)
diff --git a/lld/test/wasm/compress-relocs.s b/lld/test/wasm/compress-relocs.s
index 37f1b3b170ff7..e872b941416af 100644
--- a/lld/test/wasm/compress-relocs.s
+++ b/lld/test/wasm/compress-relocs.s
@@ -47,16 +47,16 @@ test_memory_and_indirect_call_relocs:
   end_function
 
 # CHECK:    test_memory_and_indirect_call_relocs
-# CHECK:      41 90 80 84 80 00                 i32.const      65552
+# CHECK:      41 80 80 84 80 00                 i32.const      65536
 # CHECK:      11 80 80 80 80 00 80 80 80 80 00  call_indirect  0
-# CHECK:      28 02 94 80 84 80 00              i32.load       65556
+# CHECK:      28 02 84 80 84 80 00              i32.load       65540
 # CHECK:      11 81 80 80 80 00 80 80 80 80 00  call_indirect  1
 # CHECK:      41 81 80 80 80 00                 i32.const      1
 # CHECK:      11 80 80 80 80 00 80 80 80 80 00  call_indirect  0
 # COMPRESS: test_memory_and_indirect_call_relocs
-# COMPRESS:   41 90 80 04                       i32.const      65552
+# COMPRESS:   41 80 80 04                       i32.const      65536
 # COMPRESS:   11 00 00                          call_indirect  0
-# COMPRESS:   28 02 94 80 04                    i32.load       65556
+# COMPRESS:   28 02 84 80 04                    i32.load       65540
 # COMPRESS:   11 01 00                          call_indirect  1
 # COMPRESS:   41 01                             i32.const      1
 # COMPRESS:   11 00 00                          call_indirect  0
@@ -91,11 +91,11 @@ test_relative_relocs:
   end_function
 
 # CHECK:    test_relative_relocs
-# CHECK:      41 90 80 84 80 00  i32.const  65552
+# CHECK:      41 80 80 84 80 00  i32.const  65536
 # CHECK:      41 81 80 80 80 00  i32.const  1
 # CHECK:      41 83 80 80 80 00  i32.const  3
 # COMPRESS: test_relative_relocs
-# COMPRESS:   41 90 80 04        i32.const  65552
+# COMPRESS:   41 80 80 04        i32.const  65536
 # COMPRESS:   41 01              i32.const  1
 # COMPRESS:   41 03              i32.const  3
 
diff --git a/lld/test/wasm/compress-relocs64.s b/lld/test/wasm/compress-relocs64.s
index f3ff646cc3b1c..2dd18d604df41 100644
--- a/lld/test/wasm/compress-relocs64.s
+++ b/lld/test/wasm/compress-relocs64.s
@@ -36,12 +36,12 @@ test_memory_and_indirect_call_relocs:
   end_function
 
 # CHECK:    test_memory_and_indirect_call_relocs
-# CHECK:      42 90 80 84 80 80 80 80 80 80 00     i64.const 65552
-# CHECK:      29 03 98 80 84 80 80 80 80 80 80 00  i64.load  65560
+# CHECK:      42 80 80 84 80 80 80 80 80 80 00     i64.const 65536
+# CHECK:      29 03 88 80 84 80 80 80 80 80 80 00  i64.load  65544
 # CHECK:      42 81 80 80 80 80 80 80 80 80 00     i64.const 1
 # COMPRESS: test_memory_and_indirect_call_relocs
-# COMPRESS:   42 90 80 04                          i64.const 65552
-# COMPRESS:   29 03 98 80 04                       i64.load  65560
+# COMPRESS:   42 80 80 04                          i64.const 65536
+# COMPRESS:   29 03 88 80 04                       i64.load  65544
 # COMPRESS:   42 01                                i64.const 1
 
   .globl test_relative_relocs
@@ -56,11 +56,11 @@ test_relative_relocs:
   end_function
 
 # CHECK:    test_relative_relocs
-# CHECK:      42 90 80 84 80 80 80 80 80 80 00  i64.const 65552
+# CHECK:      42 80 80 84 80 80 80 80 80 80 00  i64.const 65536
 # CHECK:      42 81 80 80 80 80 80 80 80 80 00  i64.const 1
 # CHECK:      42 83 80 80 80 80 80 80 80 80 00  i64.const 3
 # COMPRESS: test_relative_relocs
-# COMPRESS:   42 90 80 04                       i64.const 65552
+# COMPRESS:   42 80 80 04                       i64.const 65536
 # COMPRESS:   42 01                             i64.const 1
 # COMPRESS:   42 03                             i64.const 3
 
diff --git a/lld/test/wasm/cooperative-threading-gc.s b/lld/test/wasm/cooperative-threading-gc.s
new file mode 100644
index 0000000000000..2af2460939aad
--- /dev/null
+++ b/lld/test/wasm/cooperative-threading-gc.s
@@ -0,0 +1,57 @@
+# Verify that --gc-sections preserves functions reachable only through the
+# thread-context accessors __wasm_{get,set}_tls_base. These accessors are
+# invoked by synthetic init functions (e.g. __wasm_init_tls) via `call`
+# instructions that carry no relocations, so the linker marks the accessors
+# live explicitly. Their own relocations must still be followed during GC,
+# otherwise functions they call (here modeling the cooperative-threading
+# context.set builtin) are incorrectly collected and the call is mis-resolved.
+
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
+# RUN: wasm-ld --cooperative-threading --gc-sections -o %t.wasm %t.o
+# RUN: obj2yaml %t.wasm | FileCheck %s
+
+    .functype set_helper (i32) -> ()
+    .import_module set_helper, "env"
+    .import_name set_helper, "set_helper"
+
+.globl __wasm_get_tls_base
+__wasm_get_tls_base:
+    .functype __wasm_get_tls_base () -> (i32)
+    i32.const 0
+    end_function
+
+# Reachable only via the synthetic __wasm_init_tls. Its call to `set_helper`
+# must keep `set_helper` live.
+.globl __wasm_set_tls_base
+__wasm_set_tls_base:
+    .functype __wasm_set_tls_base (i32) -> ()
+    local.get 0
+    call set_helper
+    end_function
+
+.globl _start
+_start:
+    .functype _start () -> (i32)
+    call __wasm_get_tls_base
+    i32.const tls1@TLSREL
+    i32.add
+    i32.load 0
+    end_function
+
+.section  .tdata.tls1,"",@
+.globl  tls1
+tls1:
+    .int32  1
+    .size tls1, 4
+
+.section  .custom_section.target_features,"",@
+    .int8 2
+    .int8 43
+    .int8 11
+    .ascii  "bulk-memory"
+    .int8 43
+    .int8 7
+    .ascii  "atomics"
+
+# The imported helper called by __wasm_set_tls_base must survive GC.
+# CHECK: Field:           set_helper
diff --git a/lld/test/wasm/cooperative-threading.s b/lld/test/wasm/cooperative-threading.s
index 8b0f7eb1c256f..af484b5e403d9 100644
--- a/lld/test/wasm/cooperative-threading.s
+++ b/lld/test/wasm/cooperative-threading.s
@@ -2,7 +2,7 @@
 # thread-context globals (__init_stack_pointer, __init_tls_base, etc.) and
 # works without --shared-memory and atomics.
 
-# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
+# RUN: llvm-mc -mattr=+call-indirect-overlong -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
 # RUN: wasm-ld --cooperative-threading -no-gc-sections -o %t.wasm %t.o
 # RUN: obj2yaml %t.wasm | FileCheck %s
 # RUN: llvm-objdump -d --no-print-imm-hex --no-show-raw-insn %t.wasm | FileCheck %s --check-prefix=DIS
@@ -11,12 +11,22 @@
 # RUN: not wasm-ld --cooperative-threading --shared-memory %t.o -o %t2.wasm 2>&1 | FileCheck %s --check-prefix=INCOMPAT
 # INCOMPAT: --cooperative-threading is incompatible with --shared-memory
 
+.globl __indirect_function_table
+.tabletype __indirect_function_table, funcref
+
 .globl         __wasm_get_tls_base
 __wasm_get_tls_base:
   .functype   __wasm_get_tls_base () -> (i32)
   i32.const 0
   end_function
 
+.globl do_call_indirect
+do_call_indirect:
+  .functype do_call_indirect () -> ()
+  i32.const 1
+  call_indirect __indirect_function_table, () -> ()
+  end_function
+
 .globl _start
 _start:
   .functype _start () -> (i32)
@@ -57,6 +67,13 @@ foo:
   .int32  0
   .size foo, 4
 
+.section  .rodata.baz,"",@
+.globl  baz
+.p2align  2
+baz:
+  .int32  1
+  .size baz, 4
+
 .section  .custom_section.target_features,"",@
   .int8 2
   .int8 43
@@ -66,28 +83,68 @@ foo:
   .int8 7
   .ascii  "atomics"
 
+# CHECK:      - Type:            TABLE
+# CHECK-NEXT:   Tables:
+# CHECK-NEXT:     - Index:           0
+# CHECK-NEXT:       ElemType:        FUNCREF
+
 # Memory must NOT be marked as shared.
 # CHECK:      - Type:            MEMORY
 # CHECK-NEXT:   Memories:
 # CHECK-NEXT:     - Minimum:         0x2
 # CHECK-NOT:       Shared
 
-# Only TLS needs a passive data segment; .data stays active and .bss gets no
-# segment at all since memory is only instantiated once and starts zeroed.
+# Ensure __init_stack_pointer, __init_tls_base, and __tls_size are all correct.
+# CHECK:      - Type:            GLOBAL
+# CHECK-NEXT:   Globals:
+# CHECK-NEXT:     - Index:           0
+# CHECK-NEXT:       Type:            I32
+# CHECK-NEXT:       Mutable:         false
+# CHECK-NEXT:       InitExpr:
+# CHECK-NEXT:         Opcode:          I32_CONST
+# CHECK-NEXT:         Value:           65536
+# CHECK-NEXT:     - Index:           1
+# CHECK-NEXT:       Type:            I32
+# CHECK-NEXT:       Mutable:         true
+# CHECK-NEXT:       InitExpr:
+# CHECK-NEXT:         Opcode:          I32_CONST
+# CHECK-NEXT:         Value:           65544
+# CHECK-NEXT:     - Index:           2
+# CHECK-NEXT:       Type:            I32
+# CHECK-NEXT:       Mutable:         false
+# CHECK-NEXT:       InitExpr:
+# CHECK-NEXT:         Opcode:          I32_CONST
+# CHECK-NEXT:         Value:           8
+
+# The function table is exported by default.
+# CHECK:      - Type:            EXPORT
+# CHECK:          - Name:            __indirect_function_table
+# CHECK-NEXT:       Kind:            TABLE
+# CHECK-NEXT:       Index:           0
+
+# Only TLS needs a passive data segment; .rodata and .data stay active and
+# .bss gets no segment at all since memory is only instantiated once and
+# starts zeroed. The TLS segment is sorted last.
 # CHECK:        - Type:            DATACOUNT
-# CHECK-NEXT:     Count:           2
+# CHECK-NEXT:     Count:           3
 
 # CHECK:        - Type:            DATA{{$}}
 # CHECK-NEXT:     Segments:
-# CHECK-NEXT:       - SectionOffset:   3
-# CHECK-NEXT:         InitFlags:       1
-# CHECK-NEXT:         Content:         '0100000002000000'
-# CHECK-NEXT:       - SectionOffset:   18
+# CHECK-NEXT:       - SectionOffset:   8
 # CHECK-NEXT:         InitFlags:       0
 # CHECK-NEXT:         Offset:
 # CHECK-NEXT:           Opcode:          I32_CONST
-# CHECK-NEXT:           Value:           {{[0-9]+}}
+# CHECK-NEXT:           Value:           65536
+# CHECK-NEXT:         Content:         '01000000'
+# CHECK-NEXT:       - SectionOffset:   19
+# CHECK-NEXT:         InitFlags:       0
+# CHECK-NEXT:         Offset:
+# CHECK-NEXT:           Opcode:          I32_CONST
+# CHECK-NEXT:           Value:           65540
 # CHECK-NEXT:         Content:         2A000000
+# CHECK-NEXT:       - SectionOffset:   25
+# CHECK-NEXT:         InitFlags:       1
+# CHECK-NEXT:         Content:         '0100000002000000'
 # CHECK-NEXT:   - Type:            CUSTOM
 
 # Globals should use the libcall ABI naming, not the global ABI.
@@ -102,9 +159,14 @@ foo:
 # CHECK-NEXT:        Name:            __tls_align
 
 # DIS-LABEL: <__wasm_init_memory>:
-# DIS:         memory.init     0, 0
-# DIS-NOT:     memory.fill
-# DIS-NOT:     memory.init
+# DIS-EMPTY:
+# DIS-NEXT:      i32.const       65544
+# DIS-NEXT:      i32.const       65544
+# DIS-NEXT:      call    0
+# DIS-NEXT:      i32.const       0
+# DIS-NEXT:      i32.const       8
+# DIS-NEXT:      memory.init     2, 0
+# DIS-NEXT:      end
 
 # DIS-LABEL: <_start>:
 # DIS-EMPTY:
@@ -118,3 +180,103 @@ foo:
 # DIS-NEXT:       i32.load        0
 # DIS-NEXT:       i32.add
 # DIS-NEXT:       end
+
+# When the table is imported instead there is no need to also export it.
+# RUN: wasm-ld --cooperative-threading --import-table -no-gc-sections -o %t3.wasm %t.o
+# RUN: obj2yaml %t3.wasm | FileCheck %s --check-prefix=IMPORT-TABLE
+
+# IMPORT-TABLE:      - Type:            IMPORT
+# IMPORT-TABLE:          - Module:          env
+# IMPORT-TABLE-NEXT:       Field:           __indirect_function_table
+# IMPORT-TABLE-NEXT:       Kind:            TABLE
+# IMPORT-TABLE-NOT:        Kind:            TABLE
+
+# Test --cooperative-threading combined with PIC output.
+# RUN: wasm-ld -shared --cooperative-threading -no-gc-sections -o %t.so %t.o
+# RUN: obj2yaml %t.so | FileCheck %s --check-prefix=PIC
+# RUN: llvm-objdump --disassemble-symbols=__wasm_init_memory --no-show-raw-insn --no-leading-addr %t.so | FileCheck %s --check-prefix=PIC-DIS
+
+# The stack pointer is imported under the libcall ABI name and
+# __wasm_set_tls_base is imported for TLS initialization.
+# PIC:       - Type:            IMPORT
+# PIC:           Field:           __init_stack_pointer
+# PIC-NEXT:      Kind:            GLOBAL
+# PIC-NEXT:      GlobalType:      I32
+# PIC-NEXT:      GlobalMutable:   false
+# PIC:           Field:           __memory_base
+# PIC:           Field:           __table_base
+# PIC:           Field:           __wasm_set_tls_base
+# PIC-NEXT:      Kind:            FUNCTION
+
+# The PIC `__init_tls_base` global (global 3) is mutable and initialized ot
+# 0 since its final value is calculated once `__memory_base` is provided.
+# PIC:       - Type:            GLOBAL
+# PIC-NEXT:    Globals:
+# PIC-NEXT:      - Index:           3
+# PIC-NEXT:        Type:            I32
+# PIC-NEXT:        Mutable:         true
+# PIC-NEXT:        InitExpr:
+# PIC-NEXT:          Opcode:          I32_CONST
+# PIC-NEXT:          Value:           0
+# PIC-NEXT:      - Index:           4
+# PIC-NEXT:        Type:            I32
+# PIC-NEXT:        Mutable:         false
+# PIC-NEXT:        InitExpr:
+# PIC-NEXT:          Opcode:          I32_CONST
+# PIC-NEXT:          Value:           8
+
+# In PIC mode the active .rodata and .data segments are combined into a single
+# active segment at __memory_base; the TLS segment remains passive.
+# PIC:       - Type:            DATACOUNT
+# PIC-NEXT:    Count:           2
+# PIC:       - Type:            DATA{{$}}
+# PIC-NEXT:    Segments:
+# PIC-NEXT:      - SectionOffset:   6
+# PIC-NEXT:        InitFlags:       0
+# PIC-NEXT:        Offset:
+# PIC-NEXT:          Opcode:          GLOBAL_GET
+# PIC-NEXT:          Index:           {{[0-9]+}}
+# PIC-NEXT:        Content:         010000002A000000
+# PIC-NEXT:      - SectionOffset:   {{[0-9]+}}
+# PIC-NEXT:        InitFlags:       1
+# PIC-NEXT:        Content:         '0100000002000000'
+# PIC-NEXT:  - Type:            CUSTOM
+
+# PIC:       GlobalNames:
+# PIC-NEXT:      - Index:           0
+# PIC-NEXT:        Name:            __init_stack_pointer
+# PIC-NEXT:      - Index:           1
+# PIC-NEXT:        Name:            __memory_base
+# PIC-NEXT:      - Index:           2
+# PIC-NEXT:        Name:            __table_base
+# PIC-NEXT:      - Index:           3
+# PIC-NEXT:        Name:            __init_tls_base
+# PIC-NEXT:      - Index:           4
+# PIC-NEXT:        Name:            __tls_size
+# PIC-NEXT:      - Index:           5
+# PIC-NEXT:        Name:            __tls_align
+
+# Memory initialization in PIC mode has a few responsibilities: it calculates
+# the TLS address and puts it in a local, stores it into the __init_tls_base
+# global, `__wasm_set_tls_base` is called, TLS is initialized, and then finally
+# BSS is zero'd out.
+# PIC-DIS:      <__wasm_init_memory>:
+# PIC-DIS-NEXT:   .local i32
+# PIC-DIS-NEXT:   i32.const 8
+# PIC-DIS-NEXT:   global.get 1
+# PIC-DIS-NEXT:   i32.add
+# PIC-DIS-NEXT:   local.tee 0
+# PIC-DIS-NEXT:   local.get 0
+# PIC-DIS-NEXT:   global.set 3
+# PIC-DIS-NEXT:   call {{[0-9]+}}
+# PIC-DIS-NEXT:   local.get 0
+# PIC-DIS-NEXT:   i32.const 0
+# PIC-DIS-NEXT:   i32.const 8
+# PIC-DIS-NEXT:   memory.init 1, 0
+# PIC-DIS-NEXT:   i32.const 16
+# PIC-DIS-NEXT:   global.get {{[0-9]+}}
+# PIC-DIS-NEXT:   i32.add
+# PIC-DIS-NEXT:   i32.const 0
+# PIC-DIS-NEXT:   i32.const 4
+# PIC-DIS-NEXT:   memory.fill 0
+# PIC-DIS-NEXT:   end
diff --git a/lld/test/wasm/data-segments.ll b/lld/test/wasm/data-segments.ll
index 7a18fd5efb655..5073e9c4cc8d1 100644
--- a/lld/test/wasm/data-segments.ll
+++ b/lld/test/wasm/data-segments.ll
@@ -93,7 +93,7 @@
 ; ACTIVE-PIC-NEXT:        Offset:
 ; ACTIVE-PIC-NEXT:          Opcode:          GLOBAL_GET
 ; ACTIVE-PIC-NEXT:          Index:           1
-; ACTIVE-PIC-NEXT:        Content:         63000000636F6E7374616E74000000002B00000068656C6C6F00676F6F646279650000002A000000
+; ACTIVE-PIC-NEXT:        Content:         636F6E7374616E74000000002B00000068656C6C6F00676F6F646279650000002A00000063000000
 
 ; PASSIVE-LABEL: - Type:            START
 ; PASSIVE-NEXT:    StartFunction:   2
@@ -114,13 +114,13 @@
 ; PASSIVE-NEXT:    Segments:
 ; PASSIVE-NEXT:      - SectionOffset:   3
 ; PASSIVE-NEXT:        InitFlags:       1
-; PASSIVE-NEXT:        Content:         '63000000'
-; PASSIVE-NEXT:      - SectionOffset:   9
-; PASSIVE-NEXT:        InitFlags:       1
 ; PASSIVE-NEXT:        Content:         636F6E7374616E74000000002B
-; PASSIVE-NEXT:      - SectionOffset:   24
+; PASSIVE-NEXT:      - SectionOffset:   18
 ; PASSIVE-NEXT:        InitFlags:       1
 ; PASSIVE-NEXT:        Content:         68656C6C6F00676F6F646279650000002A000000
+; PASSIVE-NEXT:      - SectionOffset:   40
+; PASSIVE-NEXT:        InitFlags:       1
+; PASSIVE-NEXT:        Content:         '63000000'
 ; PASSIVE-NEXT:  - Type:            CUSTOM
 ; PASSIVE-NEXT:    Name:            name
 ; PASSIVE-NEXT:    FunctionNames:
@@ -153,13 +153,13 @@
 ; PASSIVE-PIC-NEXT:    Segments:
 ; PASSIVE-PIC-NEXT:      - SectionOffset:   3
 ; PASSIVE-PIC-NEXT:        InitFlags:       1
-; PASSIVE-PIC-NEXT:        Content:         '63000000'
-; PASSIVE-PIC-NEXT:      - SectionOffset:   9
-; PASSIVE-PIC-NEXT:        InitFlags:       1
 ; PASSIVE-PIC-NEXT:        Content:         636F6E7374616E74000000002B
-; PASSIVE-PIC-NEXT:      - SectionOffset:   24
+; PASSIVE-PIC-NEXT:      - SectionOffset:   18
 ; PASSIVE-PIC-NEXT:        InitFlags:       1
 ; PASSIVE-PIC-NEXT:        Content:         68656C6C6F00676F6F646279650000002A000000
+; PASSIVE-PIC-NEXT:      - SectionOffset:   40
+; PASSIVE-PIC-NEXT:        InitFlags:       1
+; PASSIVE-PIC-NEXT:        Content:         '63000000'
 ; PASSIVE-PIC-NEXT:  - Type:            CUSTOM
 ; PASSIVE-PIC-NEXT:    Name:            name
 ; PASSIVE-PIC-NEXT:    FunctionNames:
@@ -212,34 +212,34 @@
 ; DIS-NEXT:            end
 
 ; NOPIC-DIS-NEXT:      [[PTR]].const   65536
-; NOPIC-DIS-NEXT:      [[PTR]].const   65536
-; NOPIC-DIS-NEXT:      global.set      1
 ; PIC-DIS-NEXT:        [[PTR]].const   0
 ; PIC-DIS-NEXT:        global.get      1
 ; PIC-DIS-NEXT:        [[PTR]].add
-; PIC-DIS-NEXT:        local.tee       1
-; PIC-DIS-NEXT:        global.set      {{\d*}}
-; PIC-DIS-NEXT:        local.get       1
+
 ; DIS-NEXT:            i32.const       0
-; DIS-NEXT:            i32.const       4
-; DIS-NEXT:            memory.init  0, 0
+; DIS-NEXT:            i32.const       13
+; DIS-NEXT:            memory.init     0, 0
 
-; NOPIC-DIS-NEXT:      [[PTR]].const   65540
-; PIC-DIS-NEXT:        [[PTR]].const   4
+; NOPIC-DIS-NEXT:      [[PTR]].const   65552
+; PIC-DIS-NEXT:        [[PTR]].const   16
 ; PIC-DIS-NEXT:        global.get      1
 ; PIC-DIS-NEXT:        [[PTR]].add
 
 ; DIS-NEXT:            i32.const       0
-; DIS-NEXT:            i32.const       13
+; DIS-NEXT:            i32.const       20
 ; DIS-NEXT:            memory.init     1, 0
 
-; NOPIC-DIS-NEXT:      [[PTR]].const   65556
-; PIC-DIS-NEXT:        [[PTR]].const   20
+; NOPIC-DIS-NEXT:      [[PTR]].const   65572
+; NOPIC-DIS-NEXT:      [[PTR]].const   65572
+; NOPIC-DIS-NEXT:      global.set      1
+; PIC-DIS-NEXT:        [[PTR]].const   36
 ; PIC-DIS-NEXT:        global.get      1
 ; PIC-DIS-NEXT:        [[PTR]].add
-
+; PIC-DIS-NEXT:        local.tee       1
+; PIC-DIS-NEXT:        global.set      {{\d*}}
+; PIC-DIS-NEXT:        local.get       1
 ; DIS-NEXT:            i32.const       0
-; DIS-NEXT:            i32.const       20
+; DIS-NEXT:            i32.const       4
 ; DIS-NEXT:            memory.init     2, 0
 ; NOPIC-DIS-NEXT:      [[PTR]].const   65576
 ; PIC-DIS-NEXT:        [[PTR]].const   40
@@ -272,6 +272,6 @@
 ; DIS-NEXT:           memory.atomic.wait32    0
 ; DIS-NEXT:           drop
 ; DIS-NEXT:          end
+; DIS-NEXT:          data.drop       0
 ; DIS-NEXT:          data.drop       1
-; DIS-NEXT:          data.drop       2
 ; DIS-NEXT:         end
diff --git a/lld/test/wasm/runtime-relocations-himem.s b/lld/test/wasm/runtime-relocations-himem.s
index 2d39a204c7904..2026e62a5af86 100644
--- a/lld/test/wasm/runtime-relocations-himem.s
+++ b/lld/test/wasm/runtime-relocations-himem.s
@@ -47,14 +47,14 @@ data_sym:
 
 # CHECK: <__wasm_apply_data_relocs>:
 # CHECK-EMPTY:
-# CHECK-NEXT:  i32.const -2147483636
+# CHECK-NEXT:  i32.const -2147483644
 # CHECK-NEXT:  global.get 0
 # CHECK-NEXT:  i32.store 0
 # CHECK-NEXT:  end
 
 # CHECK: <__wasm_apply_tls_relocs>:
 # CHECK-EMPTY:
-# CHECK-NEXT:  i32.const -2147483644
+# CHECK-NEXT:  i32.const -2147483636
 # CHECK-NEXT:  global.get 0
 # CHECK-NE...
[truncated]

@sbc100

sbc100 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

As the maintainer of wasm-ld I would not be opposed to this late backport, if making such an exception is even possible.

My rationale that not only are these change isolated the Wasm backend, they are almost completely limited to the behaviousd of the new --cooperative-threading which is new in LLVM 23 and therefore has zero existing users.

@sbc100 sbc100 added this to the LLVM 23.x Release milestone Aug 18, 2026
@github-project-automation github-project-automation Bot moved this to Needs Triage in LLVM Release Status Aug 18, 2026
@dyung dyung moved this from Needs Triage to Needs Review in LLVM Release Status Aug 19, 2026
@dyung

dyung commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Reviewing the change I think we can take it, but it needs a review first. @sbc100 can you review/approve the change if you would like it included on the release branch?

@github-project-automation github-project-automation Bot moved this from Needs Review to Needs Merge in LLVM Release Status Aug 19, 2026
@sbc100

sbc100 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Should be good to land now?

@dyung

dyung commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

I was not able to update the PR source branch, so I manually committed the changes to the 23.x branch with the following commits:

@dyung dyung closed this Aug 20, 2026
@dyung dyung moved this from Needs Merge to Done in LLVM Release Status Aug 20, 2026
@alexcrichton

Copy link
Copy Markdown
Contributor Author

Thanks @dyung! Sorry I didn't intentionally uncheck the "allow maintainers to edit" checkbox so I'm not sure why it was unchecked... Regardless though looks good to me, again thank you! (and @sbc100 thanks for your support too!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Development

Successfully merging this pull request may close these issues.

4 participants