Skip to content

Make wac plug output deterministic#212

Open
techieyann wants to merge 1 commit into
bytecodealliance:mainfrom
techieyann:deterministic-plug-order
Open

Make wac plug output deterministic#212
techieyann wants to merge 1 commit into
bytecodealliance:mainfrom
techieyann:deterministic-plug-order

Conversation

@techieyann

Copy link
Copy Markdown

wac plug collected its plugs into a std::collections::HashMap and then iterated it to register and instantiate them. Rust's HashMap has randomised iteration order, so the order in which plug components were emitted into the composed output varied between runs on byte-identical inputs.

The outputs were always valid and semantically equivalent — each plug's payload was embedded verbatim, and only its position in the outer component section (and the corresponding instantiate indices) moved — but they were not byte-identical, which makes the command unusable in content-addressed or reproducible build pipelines.

This switches to indexmap::IndexMap, already a dependency of the CLI crate, which preserves insertion order. Plugs are now registered in command-line order, so the output is byte-identical across runs and the argument order becomes meaningful.

Verification

Same inputs throughout (one 4-dependency and one 2-dependency socket component):

case before after
4 plugs, 30 runs 16 distinct digests 1
2 plugs, 20 runs 2 distinct digests 1
2 plugs, argv reversed same 2 digests 1, distinct from the above

Output passes wasm-tools validate in all cases.

The last row is worth noting: the two digests that previously appeared at random for two plugs turn out to be exactly the two argument permutations. After the change, argument order selects between them deterministically.

Tests

I have not added a regression test — there is no existing harness for the plug command, and a determinism test needs either golden files or a repeated-invocation loop. Happy to add one in whichever style you would prefer.

Fixes #211

`wac plug` collected its plugs into a `std::collections::HashMap` and then
iterated it to register and instantiate them. Rust's `HashMap` has randomised
iteration order, so the order in which plug components were emitted into the
composed output varied between runs on byte-identical inputs.

The outputs were always valid and semantically equivalent -- each plug's
payload was embedded verbatim, and only its position in the outer component
section (and the corresponding `instantiate` indices) moved -- but they were
not byte-identical, which makes the command unusable in content-addressed or
reproducible build pipelines.

Observed with wac-cli 0.10.1 on linux/amd64 and darwin/arm64:

  - 2 plugs, 20 runs  -> 2 distinct output digests
  - 4 plugs, 30 runs  -> 16 distinct output digests (4! = 24 permutations)

Command-line order did not help: running `--plug a --plug b` and
`--plug b --plug a` produced the same set of digests in the same proportion,
so the argument order was discarded before emission.

Use `indexmap::IndexMap` (already a dependency) instead, which preserves
insertion order. Plugs are now registered in command-line order, so the output
is byte-identical across runs and the argument order is meaningful.

With this change, on the same inputs:

  - 2 plugs, 20 runs  -> 1 digest
  - 4 plugs, 30 runs  -> 1 digest
  - reversing the two `--plug` arguments yields a different, stable digest

Fixes bytecodealliance#211

Signed-off-by: Ian McEachern <techieyann@gmail.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.

wac plug output is not reproducible: plug components are emitted in nondeterministic order

1 participant