Stream: general

Topic: Understanding rec groups


view this post on Zulip Nathaniel Cook (Nov 14 2025 at 23:47):

I have a component that has two core modules where one depends on the other. The dependent module also imports a function from the outer component via canon lower.

Here is my simplified reproducing WAT

(component
  (import "f" (func $f (param "s" string)))

  (core rec
      (type $s (array i8))
      (type $ty (func (param (ref $s))))
  )

  (core func $f (canon lower (func $f) gc (core-type $ty)))

  (core module $m
    (rec
      (type $s (array i8))
      (type $ty (func (param (ref $s))))
    )
    (type $ty2 (func (param (ref struct) (ref $s))))
    (import "a" "b" (func $f (type $ty)))
    (export "f"  (func $fc))
    (func $fc (type $ty2) (param (ref struct) (ref $s))
      local.get 1
      call 0
      return
    )
  )

  (core instance $i (instantiate $m (with "a" (instance (export "b" (func $f))))))

  (core module $n
    (rec
      (type $s (array i8))
      (type $ty (func (param (ref struct) (ref $s))))
    )
    (import "c" "f" (func $f (type $ty)))
  )
  (core instance (instantiate $n (with "c" (instance $i))))
)

When I run this WAT I get the following:

$ wasmtime run -W gc=y,function-references=y,component-model-gc=y test.wat
Error: failed to parse WebAssembly module

Caused by:
    type mismatch for export `f` of module instantiation argument `c`
    expected: (func (param (ref struct) (ref (id 3))))
    found:    (func (param (ref struct) (ref (id 0)))) (at offset 0xe2)

Notice the dependent module is a small wrapper around the function that simply drops the (ref struct) arg and calls the inner function. The function body is not important, its real purpose has been simplified away. However this wrapping seems to trigger this issue. Without this slight change to the function signature the parent module can be instantiated successfully.

If I remove the (rec ...) groupings entirely it also works.

Somehow a combination of rec groups, nested modules, and changing the function signature breaks this case. Any ideas? I am working to get some features of cm-gc working in wasmtime and ran into this issue.

view this post on Zulip Alex Crichton (Nov 14 2025 at 23:57):

I believe what's happening here is that the rec group in the component and in module $m are the same, but the rec group in module $n is different (e.g. the addition of (ref struct)). That makes the type $s different across rec groups despite it being structurally the same

view this post on Zulip Alex Crichton (Nov 14 2025 at 23:58):

or at least that's my understanding of rec groups, which is that an (array i8) type defined inside and outside a rec group are different

view this post on Zulip Nathaniel Cook (Nov 15 2025 at 00:04):

I had a similar thought. Ok so if I create identical rec groups even if I don't need the extra function type in the parent rec group that should make the array type inside each rec group the same. So its the whole set of types in the rec group that define the structure. I'll try that thanks.

view this post on Zulip Alex Crichton (Nov 15 2025 at 00:07):

Nick would know better than I if this is intended behavior of wasm gc (unrelated to component-model-gc), and he should be back next week I think

view this post on Zulip fitzgen (he/him) (Nov 18 2025 at 17:20):

Nathaniel Cook said:

So its the whole set of types in the rec group that define the structure.

This is correct. Rec groups as a whole are structural and deduplicated with one another. Types within the same rec group, however, are not deduplicated, and also the "same" type in two otherwise different rec groups (e.g. have more or less types, have types in a different order, have different sets of types) are also different types. Types are effectively nominal within the context of their rec group.

view this post on Zulip Nathaniel Cook (Nov 18 2025 at 17:35):

Thanks this help me understand.

Follow up question, as you might remember I am attempting to add cm+gc support to wasmtime. I have a few bits working but ran into an issue regarding rec groups and canon lower. The current implementation expects all types of the core type for lowering to be in the same rec group. Meaning the core type itself and any referenced types must be in the same rec group otherwise wasmtime crashes during compilation.

After understanding rec groups it seems the correct solution is to change the compilation process to allow for the core type to reference types across rec groups so long as they are all in scope. Otherwise it's quite restrictive on compilers generating wasm to know all the ways that types are grouped across compilation units. For example with the string type above we would need to create a different string type for each of the different contexts in which it is used as the core type for lowering and all parts of the generated wasm need to know this grouping.

Does this sound correct, that the core type in lowering contexts should support multiple rec groups?

view this post on Zulip fitzgen (he/him) (Nov 18 2025 at 18:03):

Nathaniel Cook said:

otherwise wasmtime crashes during compilation

Crashes, panics, or returns a compilation error?

Nathaniel Cook said:

The current implementation expects all types of the core type for lowering to be in the same rec group. Meaning the core type itself and any referenced types must be in the same rec group

Is this in your WIP branch? That is not what is sketched in https://github.com/WebAssembly/component-model/issues/525 or implemented in wasmparser's validation, unless I am misunderstanding things.

Pre-Proposal: Wasm GC Support in the Canonical ABI This issue proposes extensions to the Component Model's Canonical ABI for Wasm GC support and describes some of the motivation for particular choi...

view this post on Zulip Nathaniel Cook (Nov 18 2025 at 18:15):

otherwise wasmtime crashes during compilation

Crashes, panics, or returns a compilation error?

Panics indexing into a map of wasmparser type ids to wasmtime types. I believe this is because the map is only populated with types from the rec group containing the core-type for lowering.

I can reproduce the panic outside of my branch:

(component
  (import "f" (func $f (param "s" string)))
  (core type $s (array i8))
  (core type $ty (func (param (ref $s))))
  (core func $f (canon lower (func $f) gc (core-type $ty))))

This parses fine but when attempting to execute in wasmtime hits the indexing panic mentioned above.

wasmtime -W component-model-gc=yes,gc=yes test.wat

view this post on Zulip Nathaniel Cook (Nov 18 2025 at 18:17):

If however I place both the array type and the func type into a single rec group it doesn't fail to compile

view this post on Zulip fitzgen (he/him) (Nov 18 2025 at 18:20):

hm yeah I think this is the same issue that @Andrew Brown ran into with spawn_ref and shared-everything-threads. I think we need to refactor the way that the component translation juggles types, but I forget the details

view this post on Zulip Nathaniel Cook (Nov 18 2025 at 18:31):

Ok glad to know I am on the right track. I might hack something on my WIP branch and see what I come up with. If it proves interesting I'll share it.

view this post on Zulip Nathaniel Cook (Nov 19 2025 at 15:00):

I did get a simple fix working in my branch. I just added a pass over the core-type checking for reference types and tracking the unique rec group ids found. Then I explicitly interned those rec groups before interning the rec group containing the core-type itself. This prevents the crash but doesn't seem like a efficient long term solution.


Last updated: Dec 06 2025 at 05:03 UTC