I have a DSL that compiles to WASM and I am attempting to get linking with other wasm components working. High level is all the code from my DSL gets compiled into a set of core modules and then all linked using core semantics into a single component. Then I link against the external wasm components.
The issue I am having is I get a trap for a failed cast when I try and run my component. The error comes from the linked shared everything core modules. Here is a the relevant code in the main function.
(core module (;1;)
(type (;0;) (func (param (ref struct) f64) (result f64)))
(type (;1;) (sub (struct (field (ref 0)))))
(type (;2;) (func (param (ref struct) f64) (result (ref 1))))
(type (;3;) (sub (struct (field (ref 2)))))
(type (;4;) (func (param (ref struct) i64) (result (ref 3))))
(type (;5;) (func (param i64) (result f64)))
(type (;6;) (sub (struct (field (ref 4)))))
(type (;7;) (sub final 6 (struct (field (ref 4)))))
(import "std::math" "compute" (func (;0;) (type 4)))
(export "remote1" (func 0))
(export "main" (func 1))
(func (;1;) (type 5) (param i64) (result f64)
(local (ref 6) (ref 3) (ref 1))
ref.func 0
struct.new 7
ref.cast (ref 6)
local.tee 1
i64.const 1
local.get 1
struct.get 6 0
call_ref 4
local.tee 2
f64.const 0x1p+1 (;=2;)
local.get 2
struct.get 3 0
call_ref 2
local.tee 3
f64.const 0x1.8p+1 (;=3;)
local.get 3
struct.get 1 0
call_ref 0
return
)
)
The trap occurs on the call_ref 4 instruction. My DSL has curried closures so we are seeing a pattern of creating a struct (GC proposal) with a function reference as the first field and a its closed over env as the remaining fields.
Reading this code we create function ref to function 0 which is an imported function. Then after getting it out of the struct we call it with call_ref 4. We can easily see that the struct's field is of type 4 so this should be valid however I get the trap.
If I instead change the ref.func 0 to a locally defined function instead of an imported one it I do not get the trap.
(core module (;1;)
(type (;0;) (func (param (ref struct) f64) (result f64)))
(type (;1;) (sub (struct (field (ref 0)))))
(type (;2;) (func (param (ref struct) f64) (result (ref 1))))
(type (;3;) (sub (struct (field (ref 2)))))
(type (;4;) (func (param (ref struct) i64) (result (ref 3))))
(type (;5;) (func (param i64) (result f64)))
(type (;6;) (sub (struct (field (ref 4)))))
(type (;7;) (sub final 6 (struct (field (ref 4)))))
(import "std::math" "compute" (func (;0;) (type 4)))
(export "remote1" (func 0))
(export "main" (func 1))
(export "foo" (func 2))
(export "foo1" (func 3))
(export "foo2" (func 4))
(func (;1;) (type 5) (param i64) (result f64)
(local (ref 6) (ref 3) (ref 1))
ref.func 2
struct.new 7
ref.cast (ref 6)
local.tee 1
i64.const 1
local.get 1
struct.get 6 0
call_ref 4
local.tee 2
f64.const 0x1p+1 (;=2;)
local.get 2
struct.get 3 0
call_ref 2
local.tee 3
f64.const 0x1.8p+1 (;=3;)
local.get 3
struct.get 1 0
call_ref 0
return
)
(func (;2;) (type 4) (param (ref struct) i64) (result (ref 3))
ref.func 3
struct.new 3
return
)
(func (;3;) (type 2) (param (ref struct) f64) (result (ref 1))
ref.func 4
struct.new 1
return
)
(func (;4;) (type 0) (param (ref struct) f64) (result f64)
f64.const 0x1p+1 (;=2;)
return
)
)
Again lots of currying so I need multiple local functions to make this type check.
Is there something wrong about creating a function reference (using GC proposal semantics) to an imported function? Why does the code execute without a trap when its all local but not if a function is imported?
As far as I have been able to read in the various specs a function reference can be shared between core modules.
Here is the complete component if its helpful
blr.wasm
I can invoke it with
wasmtime run -W gc=y,function-references=y,component-model-gc=y --invoke 'main(1)' blr.wasm
(I still need to update my compiler to emit code compatible with wasi/cli so I have a plain main function for now)
This looks like a bug in wasmtime to me, thanks for the thorough writeup! Mind opening an issue on the wasmtime repo for this? Basically a copy paste of your message here should be sufficient.
Also @fitzgen (he/him) you're likely interested in this
Good to know my workflow is expected, issue here https://github.com/bytecodealliance/wasmtime/issues/11650
Do you see any workaround in the meantime?
hm actually, are you sure that the trapping instruction is right?
Running that component locally I get the same two stack frames as the issue you opened, notably 0x17e517 called by 0x17e626
The failing instruction 0x17e517, however, is ref.cast (ref 6), not a call_ref (although 0x17e626 is indeed call_ref). Is this perhaps a situation where the order of the backtrace was confused?
basically the issue and your post here look like they're describing 0x17e626, the caller of the trapping function, not the trapping function at 0x17e517
Hmm, yeah I may have confused the order of the backtrace.
Yeah I can confirm I read the backtrace wrong and inspecting the ref.cast (ref 6) from the other core module its clear that the struct passed in is incorrect. Looks like a bug on my side. Appreciate the help and apologies for the noise. Kinda glads it my bug :) easier to fix.
Last updated: Dec 06 2025 at 05:03 UTC