From 112efb9449d0378a33d9885fde4fc68727296ed1 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 24 Jun 2026 06:22:09 +0200 Subject: [PATCH 1/2] chore(rivet): land REQ_GC_SUBTYPING with measured WAST oracle + root-cause (#149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feature-loop step 3 for the GC-subtyping conformance fix. Records the measure-first oracle (type-subtyping.wast = 90 pass / 27 fail, failure modes characterised) and the root-cause (rec-group type_canonical_ids canonicalization collapsing distinct types; consumed by wast_execution import-linking + wast_validator subtype checks). The fix gates on driving 27 down without regressing the full suite (mandatory full-suite gate — ~400 historical regressions when subtyping is touched naively). derives-from REQ_FUNC_022. rivet validate: 0 errors. --- .../requirements/functional-requirements.yaml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/safety/requirements/functional-requirements.yaml b/safety/requirements/functional-requirements.yaml index df242690..924f82eb 100644 --- a/safety/requirements/functional-requirements.yaml +++ b/safety/requirements/functional-requirements.yaml @@ -116,6 +116,46 @@ artifacts: implementation: kiln-build-core/src/wast_execution.rs note: "Test runner exists; ~1,600 assertion failures across Issues 146-149" + - id: REQ_GC_SUBTYPING + type: requirement + title: GC reference-type subtyping is spec-correct (decode → validate → link) + description: > + The interpreter's WebAssembly-GC reference-type subtyping relation shall + match the spec across decode, validation, and cross-module linking: a + module that declares an invalid `sub` chain must be rejected + (assert_invalid), an import whose supplied type is not a subtype of the + expected type must fail to link (assert_unlinkable), and a valid subtype + relation must be accepted. The relation is currently wrong in BOTH + directions (too permissive AND too strict). Issue #149. + status: proposed + tags: [wasm-gc, reference-types, subtyping, spec-conformance, release-v0.3.5] + links: + - type: derives-from + target: REQ_FUNC_022 + fields: + upstream-ref: "https://github.com/pulseengine/kiln/issues/149" + oracle: > + WAST conformance is the mechanical oracle (measure-first baseline, + 2026-06, testsuite @external/testsuite): type-subtyping.wast = 90 passed / + 27 failed. Failure modes characterised: ~7 assert_unlinkable that wrongly + LINK (too permissive cross-module import check), 4 assert_invalid 'sub + type' that wrongly VALIDATE (too permissive subtype-declaration check), + and several `(ref $N) vs (ref $M)` import mismatches that wrongly REJECT + (too strict). Run: `cargo-kiln testsuite --run-wast --wast-dir + external/testsuite --wast-filter type-subtyping`. The fix must drive 27 + down WITHOUT regressing the full suite (historically ~400 regressions + when subtyping is touched naively — full-suite gate is mandatory). + root-cause: > + Root-caused earlier to rec-group canonicalization: `type_canonical_ids` + (kiln-decoder/src/{decoder,streaming_decoder}.rs) collapses distinct + types so the subtype check returns true on equal canonical ids even when + the structural relation should not hold (observed val_idx=6 target=4 + true because canon[6]==canon[4]==4). Consumed by the linking import-type + check (kiln-build-core/src/wast_execution.rs) and the subtype-declaration + validator (kiln-build-core/src/wast_validator.rs). The subtype *walker* + reads correct for sub-chains; the canonical-id assignment is the locus. + Fix one cluster at a time, full-suite-regression-gated each step. + # ========================================================================= # Component Model # ========================================================================= From eb6cfbc62fcea727f7be7f1c4947c81aca225fb5 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Wed, 24 Jun 2026 06:43:36 +0200 Subject: [PATCH 2/2] fix(decoder): accept typed-funcref tables in call_indirect validation (#149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WebAssembly-GC type-subtyping conformance: the wast validator rejected `call_indirect` / `return_call_indirect` whenever the table's element type was not *exactly* `funcref`. Per the spec the table type need only be a subtype of `funcref`, so a typed function-reference table such as `(ref null $t)` (where `$t` is a function type) is valid. The equality check `elem_type != RefType::Funcref` was too strict and rejected the valid `(ref null $t2)` table in type-subtyping.wast, cascading the exported `run` function to "not exported" because the module failed to instantiate. Empirical root-cause (instrumented, then removed): the failing module was the call_indirect-over-typed-table case (type-subtyping.wast lines ~320), not a rec-group canonical-id collapse — the canonicalization walker read correct for the sub-chains. Fix is the element-type check only; replaced the equality with the existing `is_ref_type_subtype(elem, funcref)` helper in both call_indirect (0x11) and return_call_indirect (0x13). Also removed two pre-existing /tmp-file debug blocks that violated the tracing rule. Oracle (measured from source, not the installed binary): type-subtyping cluster: 2 failing assertions -> 0 (file now passes, 117/117 assertions). Full WAST suite: 261 -> 262 files passing, 19 -> 18 failing; 65776 -> 65778 assertions passing, 531 -> 529 failing. Per-file failure diff: only type-subtyping.wast changed (2 -> 0); every other file byte-identical. Zero regressions. Co-Authored-By: Claude Opus 4.8 (1M context) Trace: AD-QUALITY-001 --- kiln-build-core/src/wast_validator.rs | 29 ++++++++++----------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/kiln-build-core/src/wast_validator.rs b/kiln-build-core/src/wast_validator.rs index 799023b9..dae7a4b6 100644 --- a/kiln-build-core/src/wast_validator.rs +++ b/kiln-build-core/src/wast_validator.rs @@ -291,12 +291,6 @@ const WASM_MAX_MEMORY_PAGES: u32 = 65536; impl WastModuleValidator { /// Validate a module pub fn validate(module: &Module) -> Result<()> { - { - use std::io::Write; - if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/kiln_debug.log") { - writeln!(f, "[VALIDATE] called with {} functions, {} types", module.functions.len(), module.types.len()).ok(); - } - } // Validate memory, table, and tag limits Self::validate_memory_limits(module)?; Self::validate_table_limits(module)?; @@ -1451,14 +1445,6 @@ impl WastModuleValidator { return Err(anyhow!("type mismatch")); } for &expected in frame.output_types.iter().rev() { - { - use std::io::Write; - if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/kiln_block_end_debug.log") { - let actual_top = stack.last(); - writeln!(f, "[BLOCK_END] reachable: expected={:?}, actual_top={:?}, stack_len={}, frame_height={}", - expected, actual_top, stack.len(), frame_height).ok(); - } - } if !Self::pop_type_with_module(&mut stack, expected, frame_height, false, Some(module)) { return Err(anyhow!("type mismatch")); } @@ -1696,9 +1682,13 @@ impl WastModuleValidator { return Err(anyhow!("unknown table")); } - // Validate table element type is funcref (not externref) + // The table's element type must be a subtype of `funcref` + // (i.e. `ref null func`). A plain `funcref` table qualifies, but + // so does any typed function-reference table such as + // `(ref null $t)` where `$t` is a function type. Only non-func + // reference tables (externref, anyref hierarchy, …) are rejected. if let Some(elem_type) = Self::get_table_element_type(module, table_idx) { - if elem_type != kiln_foundation::RefType::Funcref { + if !Self::is_ref_type_subtype(&elem_type, &kiln_foundation::RefType::Funcref, module) { return Err(anyhow!("type mismatch")); } } @@ -1802,9 +1792,12 @@ impl WastModuleValidator { return Err(anyhow!("unknown table")); } - // Validate table element type is funcref (not externref) + // The table's element type must be a subtype of `funcref`. + // (See call_indirect above — typed function-reference tables + // such as `(ref null $t)` are valid; only non-func reference + // tables are rejected.) if let Some(elem_type) = Self::get_table_element_type(module, table_idx) { - if elem_type != kiln_foundation::RefType::Funcref { + if !Self::is_ref_type_subtype(&elem_type, &kiln_foundation::RefType::Funcref, module) { return Err(anyhow!("type mismatch")); } }