Skip to content

feat(compiler): add codegen identity for config- and feature-dependent bytecode - #177

Open
dmitry123 wants to merge 1 commit into
develfrom
claude/bytecode-cargo-features-flu-1107-90b6c6
Open

feat(compiler): add codegen identity for config- and feature-dependent bytecode#177
dmitry123 wants to merge 1 commit into
develfrom
claude/bytecode-cargo-features-flu-1107-90b6c6

Conversation

@dmitry123

Copy link
Copy Markdown
Member

Closes FLU-1107.

Problem

The same wasm input compiles to different rwasm bytecode depending on how the compiling binary was built, and the serialized module records none of it:

  • impl_fpu_opcode! (src/isa/mod.rs) substitutes Trap(IllegalOpcode) for every float instruction at compile time when the fpu feature is off, so an fpu build and a default build emit different modules (and different hashes) for one contract.
  • CompilationConfig flags — code_snippets, the fuel flags, max_allowed_memory_pages, the router/linker — decide which functions are emitted and where fuel charges land, shifting every downstream instruction offset.
  • The module header (0xEF 0x52 0x01) versions the wire format, not the compiler configuration, so nothing identifies the build that produced the bytes.

Changes

CompilationConfig::codegen_identity() (src/compiler/codegen_identity.rs) — a keccak-256 fingerprint over every input that affects emitted bytecode: the codegen-relevant config fields plus the compile-time feature set of the compiling binary. Compilers agreeing on the value compile any given wasm input to identical bytes; disagreeing ones may not. Hosts that address modules by hash can pin this next to the bytecode and reject foreign modules with a clean error instead of executing them under different semantics.

Preimage details: every variable-length element is length-prefixed so distinct configs cannot collide; import-linker entries are hashed in sorted name order because the linker is backed by a hash map with unstable iteration order; state-router entries are hashed in declaration order because that order is part of the emitted routing code. CODEGEN_FEATURE_FPU / codegen_feature_set() expose the feature bitmask; features that only change the host-side surface (std, serde, wasmtime, …) are deliberately excluded since they do not affect the bytes.

fpu documentation (per the Linear comment) — floating point is not officially supported; the feature exists only for the e2e suite and the fuzzer. Documented at impl_fpu_opcode!, at the executor's dispatch split (src/vm/executor.rs), and in the Cargo.toml feature list, each noting that the feature changes emitted bytecode rather than just runtime behaviour.

docs/module-format.md — new "Codegen determinism" section listing every input that affects emitted bytecode and explaining that the header version identifies the format, not the configuration.

Not included

Embedding the identity in the module header and checking it in verify_module (suggestion 1 of the issue) would require a RWASM_VERSION_V2 bump and would change the bytes — and therefore the hash — of every module ever compiled. That is a consensus-level decision, so this PR delivers the identity as a host-checkable value and leaves the format untouched. Worth a follow-up decision.

Also note the runtime never sees a CompilationConfig (RwasmInstance::new takes only store/engine/module), so a load-time check has to be driven by the host comparing identities either way.

Testing

cargo test --lib (80 passed) and cargo test --lib --features fpu; cargo clippy --all-targets --all-features clean; cargo check --no-default-features clean.

New tests cover: stability across equal configs; a changed identity for each codegen-relevant flag; independence from import-linker insertion order; sensitivity to state-router ordering; and a pinned digest of the default config per feature set, which both locks the preimage layout against silent drift and demonstrates that an fpu build and a default build carry different identities.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@dmitry123, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b72323c3-0aab-495a-b551-9e5a39f2eaff

📥 Commits

Reviewing files that changed from the base of the PR and between b8f6091 and 7669c5a.

📒 Files selected for processing (6)
  • Cargo.toml
  • docs/module-format.md
  • src/compiler/codegen_identity.rs
  • src/compiler/mod.rs
  • src/isa/mod.rs
  • src/vm/executor.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Criterion results (vs baseline)


running 81 tests
test compiler::codegen_identity::tests::default_identity_is_pinned ... ignored
test compiler::codegen_identity::tests::feature_set_reports_fpu ... ignored
test compiler::codegen_identity::tests::identity_ignores_import_linker_insertion_order ... ignored
test compiler::codegen_identity::tests::identity_is_stable_for_equal_configs ... ignored
test compiler::codegen_identity::tests::identity_separates_state_router_ordering ... ignored
test compiler::codegen_identity::tests::identity_tracks_codegen_relevant_flags ... ignored
test compiler::compiled_expr::tests::compiledexpr_eval_const_returns_none_for_global_or_funcref ... ignored
test compiler::compiled_expr::tests::compiledexpr_from_const_roundtrips ... ignored
test compiler::compiled_expr::tests::compiledexpr_funcref_and_global_introspection ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_global_get_uses_context ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_add_mixed_const_and_global ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_add_mixed_global_and_funcref ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_add_wraps ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_const ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i32_sub_wraps ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i64_const ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_i64_mul_wraps ... ignored
test compiler::compiled_expr::tests::compiledexpr_new_ref_func_uses_context ... ignored
test compiler::compiled_expr::tests::compiledexpr_zero_is_zero ... ignored
test compiler::compiled_expr::tests::constop_eval_returns_value ... ignored
test compiler::compiled_expr::tests::empty_eval_context_always_none ... ignored
test compiler::compiled_expr::tests::eval_with_context_reads_globals_and_funcs ... ignored
test compiler::compiled_expr::tests::expr_op_combines_operands_and_propagates_none ... ignored
test compiler::compiled_expr::tests::funcrefop_reads_from_context ... ignored
test compiler::compiled_expr::tests::globalop_maps_value_kinds_correctly ... ignored
test compiler::compiled_expr::tests::op_clone_panics_for_expr_variant - should panic ... ignored
test compiler::compiled_expr::tests::op_clone_works_for_non_expr_variants ... ignored
test compiler::compiled_expr::tests::op_constant_encodes_f32_f64_bits ... ignored
test compiler::compiled_expr::tests::op_constant_encodes_funcref_externref_ids ... ignored
test compiler::compiled_expr::tests::op_constant_encodes_i32_i64 ... ignored
test compiler::drop_keep::tests::test_drop_keep_translation ... ignored
test compiler::func_type_registry::tests::deduplicates_matching_signatures ... ignored
test compiler::func_type_registry::tests::index_lookup_is_stable ... ignored
test compiler::func_type_registry::tests::resolves_unique_signatures_correctly ... ignored
test compiler::parser::tests::unsupported_component_model_returns_error ... ignored
test module::tests::test_decode_exact_rejects_trailing_garbage ... ignored
test module::tests::test_decode_module_wo_source_pc ... ignored
test module::tests::test_decode_rejects_partial_source_pc ... ignored
test module::tests::test_endianness ... ignored
test module::tests::test_module_encoding ... ignored
test module::verification::tests::accepts_verified_encoded_module ... ignored
test module::verification::tests::regular_construction_does_not_verify ... ignored
test module::verification::tests::regular_decode_does_not_verify ... ignored
test module::verification::tests::rejects_branch_target_outside_code_section ... ignored
test module::verification::tests::rejects_call_target_outside_code_section ... ignored
test module::verification::tests::rejects_missing_table_index_payload ... ignored
test module::verification::tests::rejects_section_index_outside_limits ... ignored
test module::verification::tests::rejects_source_pc_outside_code_section ... ignored
test module::verification::tests::rejects_zero_local_depth ... ignored
test strategy::types::tests::checked_memory_range_end_rejects_overflow ... ignored
test types::nan_preserving_float::tests::test_neg_nan_f32 ... ignored
test types::nan_preserving_float::tests::test_neg_nan_f64 ... ignored
test types::nan_preserving_float::tests::test_ops_f32 ... ignored
test types::nan_preserving_float::tests::test_ops_f64 ... ignored
test types::opcode::tests::test_fpu_opcode_encoding_uses_offset ... ignored
test types::opcode::tests::test_opcode_code_values ... ignored
test types::opcode::tests::test_opcode_encoding ... ignored
test types::opcode::tests::test_opcode_encoding_uses_explicit_code ... ignored
test types::opcode::tests::test_opcode_size ... ignored
test types::units::tests::bytes_new16 ... ignored
test types::units::tests::bytes_new32 ... ignored
test types::units::tests::bytes_new64 ... ignored
test types::units::tests::pages_checked_add ... ignored
test types::units::tests::pages_checked_sub ... ignored
test types::units::tests::pages_max ... ignored
test types::units::tests::pages_new ... ignored
test types::units::tests::pages_to_bytes ... ignored
test types::value::copysign_regression_works ... ignored
test types::value::wasm_float_max_regression_works ... ignored
test types::value::wasm_float_min_regression_works ... ignored
test vm::store::tests::clamps_runtime_memory_limit_to_global_maximum ... ignored
test wasmtime::tests::test_call_with_charging_linear_wasmtime ... ignored
test wasmtime::tests::test_call_with_charging_param_overflow_wasmtime ... ignored
test wasmtime::tests::test_call_with_charging_quadratic_wasmtime ... ignored
test wasmtime::tests::test_wasmtime_caller_memory_read_into_vec_checks_bounds_before_allocating ... ignored
test wasmtime::tests::test_wasmtime_caller_missing_memory_returns_trap ... ignored
test wasmtime::tests::test_wasmtime_executor_memory_read_into_vec_checks_bounds_before_allocating ... ignored
test wasmtime::tests::test_wasmtime_executor_missing_entrypoint_returns_trap ... ignored
test wasmtime::tests::test_wasmtime_snapshot_missing_memory_returns_trap ... ignored
test wasmtime::types::tests::maps_unknown_wasmtime_error_to_illegal_opcode ... ignored
test wasmtime::types::tests::maps_wasmtime_traps_to_rwasm_traps ... ignored

test result: ok. 0 passed; 0 failed; 81 ignored; 0 measured; 0 filtered out; finished in 0.00s

Comparisons/bench_native
                        time:   [5.2536 ns 5.2769 ns 5.3097 ns]
Found 61 outliers among 1000 measurements (6.10%)
  27 (2.70%) high mild
  34 (3.40%) high severe
Comparisons/bench_strategy_wasmtime
                        time:   [15.673 µs 15.976 µs 16.288 µs]
Found 223 outliers among 1000 measurements (22.30%)
  11 (1.10%) high mild
  212 (21.20%) high severe
Comparisons/bench_strategy_rwasm
                        time:   [13.268 µs 13.338 µs 13.412 µs]
Found 128 outliers among 1000 measurements (12.80%)
  15 (1.50%) high mild
  113 (11.30%) high severe

Heads-up: runner perf is noisy; treat deltas as a smoke check.

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.99552% with 29 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/compiler/codegen_identity.rs 86.99% 29 Missing ⚠️

📢 Thoughts on this report? Let us know!

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