Skip to content

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

Merged
vados-cosmonic merged 3 commits intobytecodealliance:mainfrom
wondenge:fix(bindgen)-use-IndexMap-for-deterministic-export-iteration-order
Apr 11, 2026
Merged

fix(bindgen): use IndexMap for deterministic export iteration order#1373
vados-cosmonic merged 3 commits 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
@vados-cosmonic
Copy link
Copy Markdown
Collaborator

Great catch @wondenge, thanks for the contribution!

I'm going to add a little bit (probably a test) and then get this merged.

Copy link
Copy Markdown
Collaborator

@vados-cosmonic vados-cosmonic left a comment

Choose a reason for hiding this comment

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

LGTM 🚀

@vados-cosmonic vados-cosmonic force-pushed the fix(bindgen)-use-IndexMap-for-deterministic-export-iteration-order branch from f0a5c73 to c27e5a6 Compare April 11, 2026 00:21
@vados-cosmonic vados-cosmonic added this pull request to the merge queue Apr 11, 2026
Merged via the queue into bytecodealliance:main with commit bc8a558 Apr 11, 2026
37 checks passed
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.

2 participants