Skip to content

call_indirect into a HETEROGENEOUS funcref table (mixed signatures) loud-declines — needs a runtime type-id check; the terminal layer of falcon's dispatch (20 funcs) #676

Description

@avrabe

Continuing the falcon dispatch story (index #650 ✓, null-slot #664 ✓ — thank you, both confirmed fixed against the real build). v0.36.0's note said "falcon's dispatch story complete", but verifying synth v0.37.0 on the actual fused falcon core shows the same 20 functions decline one layer deeper — the closed-world check now finds a type-signature mismatch:

call_indirect (expected type 3, table 1): closed-world type check failed — table 1 entry
(function 93, type 7) has a different signature than expected type 3 — a runtime type check
is not implementable on the raw code-pointer table; refusing to emit an unchecked indirect branch

Root cause: heterogeneous funcref table

falcon's fused (table 1 41) is a single heterogeneous elem segment of 40 functions spanning multiple signatures (type 3 = (i32 i32)→i32, type 7 = (i32 i32 i32 i32)→i32, …). Each call_indirect site expects one specific type; at runtime the index always selects a correctly-typed entry (falcon passes wasmtime SIL), but statically the table also holds other-typed entries, so the closed-world verifier can't prove type-safety and — lacking a runtime type check — declines all 20.

This is valid wasm: heterogeneous tables are legal, and a call_indirect type mismatch is a runtime trap (WASM 4.4.8), not a validation error. So the sound lowering is the runtime type check itself.

Minimal repro

(module
  (type $t2 (func (param i32 i32) (result i32)))
  (type $t4 (func (param i32 i32 i32 i32) (result i32)))
  (table $tab 2 2 funcref)
  (func $add2 (type $t2) (i32.add (local.get 0) (local.get 1)))
  (func $sum4 (type $t4) (i32.const 4))
  (elem (table $tab) (i32.const 0) func $add2 $sum4)   ;; slot0=t2, slot1=t4 — heterogeneous
  (func (export "dispatch") (param $sel i32) (result i32)
    (call_indirect $tab (type $t2) (i32.const 7) (i32.const 8) (local.get $sel))))
synth compile -t cortex-m7dp → skips 'dispatch': table 0 entry (function 1, type 1)
has a different signature than expected type 0

Suggested fix (subsumes the null-slot case)

A per-slot type-id sidecar: alongside the code-pointer table, emit a parallel u32 type_id[] (structural-type hash / dense type index). At each call_indirect: bounds-check; load type_id[idx]; CMP expected_type_id; BNE → UDF (trap); BLX. This is exactly the WASM-mandated runtime check, and a reserved type_id = 0 for null slots generalizes #664's null-trap in the same mechanism.

jess impact

The same 20 falcon funcs (the whole table-1 dispatch cluster). Inventory on v0.37.0 unchanged at 46/146 = 24 #369 float + 20 heterogeneous-table CI (this) + 1 #518 + 1 #503. All still behind #369, so no urgency — flagging with the repro so the "dispatch complete" milestone is accurate. (Minor aside: meld emits 31 type decls for 25 distinct signatures — 6 structural dups — harmless here since your check is structural, noting in case it matters for the type-id densification.)

Thanks again for the sustained turnaround on this cluster.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions