Skip to content

fix: disambiguate import resolution by module+name (closes #97)#100

Merged
avrabe merged 2 commits intomainfrom
fix/issue-97-import-name-collision
Apr 22, 2026
Merged

fix: disambiguate import resolution by module+name (closes #97)#100
avrabe merged 2 commits intomainfrom
fix/issue-97-import-name-collision

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented Apr 22, 2026

Summary

Fixes #97. The intra-component import resolution lookup matched on import field name only, conflating two now imports from wasi:clocks/wall-clock and wasi:clocks/monotonic-clock. The losing caller's call site was rewritten to point at a void-returning shim where its callers expected an i64 return — producing fused bytecode that failed wasm-tools validate with type mismatch: expected i32 but nothing on stack.

Changes

  • meld-core/src/resolver.rs adds from_import_module: String to ModuleResolution and populates it at the three call sites that push resolutions.
  • meld-core/src/merger.rs requires res.from_import_module == imp.module (in addition to the existing name match) at the four lookup sites. Empty from_import_module is treated as a wildcard for backward compat with synthesized resolutions.
  • New meld-core/tests/file_ops_validate.rs regression test that fuses the upstream fixture and asserts wasmparser::Validator passes.

Test plan

  • cargo test --package meld-core --lib — 175/175 pass
  • cargo test --package meld-core — ~287 tests across 14 suites pass
  • cargo test --test file_ops_validate — passes locally with fixture present
  • Original reproduction (meld fuse --validate /tmp/file_ops.wasm) now reports Validation passed

Known follow-up

The fixture is gitignored (*.wasm); the test no-ops if missing, so this PR is CI-safe but the regression is only caught locally. A follow-up will replace the fixture with a synthetic minimal component built via wasm-encoder.

🤖 Generated with Claude Code

avrabe and others added 2 commits April 22, 2026 07:57
When a core module imports two functions with the same field name from
different host modules — e.g.,
  (import "wasi:clocks/wall-clock@0.2.0"      "now" (func ...))
  (import "wasi:clocks/monotonic-clock@0.2.0" "now" (func ...))
— the merger's intra-component resolution lookup matched on
`import.name` only and would route both `now` callers to whichever
ModuleResolution happened to be first in the list. The losing caller
ended up pointing at a void-returning shim where its callers expected
an i64 return, producing fused bytecode that fails wasmparser
validation with "type mismatch: expected i32 but nothing on stack".

Found via `meld fuse --validate` on TinyGo's `file_ops_component.wasm`
(reported as issue #97). The TinyGo runtime calls both clock `now`s,
and the wall-clock-now site was rewritten as `call <monotonic-now-shim>`
which is `(param i32) -> ()` instead of `(result i64)`, so the call
expected an i32 on the stack and the subsequent `local.set` had
nothing to consume.

Fix:
  * resolver.rs adds `from_import_module` to ModuleResolution and
    populates it at the three call sites that push resolutions.
  * merger.rs gates the four lookup sites on both module and name
    (empty `from_import_module` is treated as a wildcard for backward
    compat with synthesized resolutions).

Adds tests/file_ops_validate.rs as a regression test that fuses the
upstream fixture and asserts wasmparser::Validator passes. The
fixture is currently gitignored (*.wasm); the test no-ops if missing
so this commit is CI-safe but the test only catches regressions
locally. Follow-up: replace with a synthetic minimal fixture.

Verified:
  * cargo test --package meld-core --lib: 175/175 pass
  * cargo test --package meld-core: ~287 tests across 14 suites pass
  * `meld fuse --validate /tmp/file_ops.wasm`: now passes (was failing
    with "type mismatch" at offset 0x1e449 in func 131)

Closes #97.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first cut depended on a 844K file_ops_component.wasm under
tests/issue_97/, which is gitignored by *.wasm so the test silently
skipped in CI.

Replace with a synthetic minimal P2 component built inline via
wasm-encoder:
- Provider module 0 exports `now: () -> (i64, i32)` (wall-clock-style)
- Provider module 1 exports `now: () -> i64` (monotonic-clock-style)
- Consumer module 2 imports both `now`s under different host module
  names (wasi:clocks/wall-clock and wasi:clocks/monotonic-clock)

Wired through an InstanceSection so both imports resolve to distinct
providers inside the same component.

Verified the test catches the regression: temporarily reverting the
four from_import_module filter clauses in merger.rs makes the test
fail with `type mismatch: values remaining on stack at end of block`.

Drops the FIXTURE constant, fixture_exists() helper, and silent skip
path. The test now runs in CI without external assets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avrabe avrabe merged commit 950f315 into main Apr 22, 2026
4 checks passed
@avrabe avrabe deleted the fix/issue-97-import-name-collision branch April 22, 2026 17:28
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.

Adapter generator produces invalid bytecode for file_ops_component (stack mismatch at func 131)

1 participant