Conversation
Every wasm32 output starts as a relocatable object that another linker
consumes afterwards, but parts of that object embedded absolute data
addresses. A shared link (`wasm-ld -shared`, emscripten `-sSIDE_MODULE`)
rejects those, so an `output: Archive` platform could not be handed to
emcc to build a side module:
wasm-ld: error: relocation R_WASM_MEMORY_ADDR_SLEB cannot be used
against symbol `roc.msg.1`; recompile with -fPIC
Two independent sources, one per backend:
- `llvmObjectUsesPic` enabled PIC only for `output: Shared`, so the LLVM
app object for `output: Archive` was compiled non-PIC (`.roc.bytes.N`
sites under --opt=speed/size). It now returns true for every wasm32
target, since none of them produce a directly-runnable artifact.
- `WasmCodeGen` hardcoded `memory_addr_sleb` / `table_index_sleb` for
generated static-data and function-table addresses (`roc.msg.N` sites
under --opt=dev). Relocatable output now emits the `__memory_base` /
`__table_base` relative forms instead.
Final in-memory codegen is deliberately unchanged: it still emits an
absolute const and keeps the relocation edge only so DCE can trace data
liveness.
The final-link path defines both base globals as constant zero, so the
wasm produced for `output: Shared`/`Exe` still addresses data absolutely.
LLVM already emitted this same sequence 100+ times per build, so the
merge and relocation-patching paths were already exercised by it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
| test "wasm32 LLVM objects are always position-independent" { | ||
| try std.testing.expect(llvmObjectUsesPic(.wasm32, .archive, false)); | ||
| try std.testing.expect(llvmObjectUsesPic(.wasm32, .exe, false)); | ||
| try std.testing.expect(llvmObjectUsesPic(.wasm32, .shared, false)); | ||
| } |
There was a problem hiding this comment.
Shared-link behavior remains untested
This test only checks that llvmObjectUsesPic returns true; it never compiles or links a relocatable object. The behavior introduced here depends on the emitted global.get sequence, relocation encodings, base-global imports, and linker handling. Without an end-to-end test that links representative static-data and function-table references through wasm-ld -shared or the SIDE_MODULE flow for both affected backends, the incompatibility this PR fixes—or a later error in relocation or addend encoding—could regress while this test continues to pass.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
The PIC change shipped with only a policy test asserting that
`llvmObjectUsesPic` returns true, which restates the branch it checks and
never compiles or links anything. The relocation encodings, the
`global.get` sequence and the base-global imports had no coverage, so the
incompatibility this fixes could regress silently:
wasm-ld: error: relocation R_WASM_MEMORY_ADDR_SLEB cannot be used
against symbol `roc.msg.1`; recompile with -fPIC
Two layers, one per source of the bug:
- WasmCodeGen tests assert that a relocatable object emits
`global.get __memory_base` / `__table_base` plus the relative
relocation, that no absolute form is emitted, and that both operands
are padded LEBs at the offsets the linker patches. These run at the
earliest phase that can observe the bug.
- `wasm_pic_check` scans a built wasm32 archive for absolute data and
table relocations. `run-test-archive` now runs it over both backends;
the dev-backend archive alone cannot cover `llvmObjectUsesPic`, so the
app is also built with --opt=speed. The tool additionally fails when an
object has no PIC relocations at all, so it cannot pass vacuously on an
object whose relocations were never parsed.
Verified by reverting each half: the codegen tests fail (2 fail, 0 crash)
and the archive check reports the absolute relocation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every wasm32 output starts as a relocatable object that another linker consumes afterwards, but parts of that object embedded absolute data addresses. A shared link (
wasm-ld -shared, emscripten-sSIDE_MODULE) rejects those, so anoutput: Archiveplatform could not be handed to emcc to build a side module:Two independent sources, one per backend:
llvmObjectUsesPicenabled PIC only foroutput: Shared, so the LLVM app object foroutput: Archivewas compiled non-PIC (.roc.bytes.Nsites under --opt=speed/size). It now returns true for every wasm32 target, since none of them produce a directly-runnable artifact.WasmCodeGenhardcodedmemory_addr_sleb/table_index_slebfor generated static-data and function-table addresses (roc.msg.Nsites under --opt=dev). Relocatable output now emits the__memory_base/__table_baserelative forms instead.Final in-memory codegen is deliberately unchanged: it still emits an absolute const and keeps the relocation edge only so DCE can trace data liveness.
The final-link path defines both base globals as constant zero, so the wasm produced for
output: Shared/Exestill addresses data absolutely. LLVM already emitted this same sequence 100+ times per build, so the merge and relocation-patching paths were already exercised by it.