Skip to content

Emit position-independent wasm for relocatable objects - #11474

Open
Anton-4 wants to merge 2 commits into
mainfrom
wasm32-pic-relocatable-objects
Open

Anton-4 wants to merge 2 commits into
mainfrom
wasm32-pic-relocatable-objects

Conversation

@Anton-4

@Anton-4 Anton-4 commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

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.

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>
@greptile-apps

greptile-apps Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The implementation appears safe to merge, with only the non-blocking absence of a real shared-link integration test remaining.

Findings

  1. P2 Shared-link behavior remains untested

Summary

The PR makes wasm32 relocatable objects position-independent across both code-generation backends.

  • LLVM wasm32 objects now always enable PIC.
  • The development backend emits memory and table references relative to __memory_base and __table_base.
  • New unit and archive-level tests verify emitted relocation forms for both backends.
  • The archive checks still stop short of performing the shared/SIDE_MODULE link covered by the existing unresolved review thread.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Roc wasm32 build] --> B{Backend}
    B -->|Development| C[Relative memory/table relocations]
    B -->|LLVM| D[PIC object generation]
    C --> E[Relocatable wasm archive]
    D --> E
    E --> F[Foreign wasm linker]
    F --> G[Shared module or final wasm]
Loading

Reviews (2) · Last reviewed commit: "Cover wasm PIC output with emission and ..."

Comment thread src/cli/main.zig
Comment on lines +9757 to 9761
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));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant