Skip to content

fix(bindgen): use IndexMap for deterministic export iteration order#1373

Open
wondenge wants to merge 1 commit intobytecodealliance:mainfrom
wondenge:fix(bindgen)-use-IndexMap-for-deterministic-export-iteration-order
Open

fix(bindgen): use IndexMap for deterministic export iteration order#1373
wondenge wants to merge 1 commit intobytecodealliance:mainfrom
wondenge:fix(bindgen)-use-IndexMap-for-deterministic-export-iteration-order

Conversation

@wondenge
Copy link
Copy Markdown
Contributor

Description

Running jco transpile on the same unchanged component twice can produce different JS output. The postReturn and callback variable assignments pick different representative export names across runs. This shows up when multiple cabi_post_* or [callback] exports alias the same core function, which wit-bindgen does pretty commonly for shared deallocation logic. The wasm is correct and runtime behaviour is fine. It is just the JS picking a different name each time, which breaks reproducible builds and makes diffing transpiled output unreliable.

Translation::exports() returns a HashMap<String, EntityIndex>, and when core_export_var_name iterates it with find_map to resolve a function index back to an export name, HashMap's iteration order varies between runs (Rust randomizes the hash seed per process). This changed from &IndexMap to HashMap in 39e6972d during the wasmtime deps update when Module.exports moved to Atom keyed maps, the conversion to String keys needed a new map, and HashMap was picked instead of IndexMap.

This PR switches the return type back to IndexMap so export iteration preserves wasm binary order. indexmap = "2" is added to Cargo.toml as it's already in Cargo.lock (resolves to 2.13.0) as a transitive dependency, so no new crates to pull in. Verified across 5 transpile runs on the same component and the output is now identical every time. Also tested with an instrumented component (additional imports, trampolines, multi-memory) same result, fully deterministic.

Before: Two runs of the same component produced different JS:

-    postReturn0 = exports1['cabi_post_example:repro/handler@0.1.0#check-string'];
+    postReturn0 = exports1['cabi_post_example:repro/handler@0.1.0#check-alias'];

After: Five runs, identical output every time diff is now empty:

$ diff /tmp/run1/component.js /tmp/run2/component.js
$

@wondenge wondenge marked this pull request as ready for review April 10, 2026 16:49
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