Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 11 additions & 18 deletions kiln-build-core/src/wast_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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"));
}
Expand Down Expand Up @@ -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"));
}
}
Expand Down Expand Up @@ -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"));
}
}
Expand Down
40 changes: 40 additions & 0 deletions safety/requirements/functional-requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
# =========================================================================
Expand Down
Loading