From c84ae3b306cad5b7836992ee6b2e00e51463641a Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Sat, 25 Jul 2026 00:33:23 +0000 Subject: [PATCH 1/8] (WIP, gemini) Allow struct.wait and any equality-comparable type --- src/ir/child-typer.h | 11 +++++- src/wasm-interpreter.h | 2 +- src/wasm/wasm-validator.cpp | 33 ++++++++++++++-- test/lit/waitqueue.wast | 76 +++++++++++++++++++++++++++++++++++++ test/spec/waitqueue.wast | 28 +++++++++++++- 5 files changed, 143 insertions(+), 7 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 148cb7219ba..a7789ba5f33 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1036,9 +1036,18 @@ template struct ChildTyper : OverriddenVisitor { ht = curr->ref->type.getHeapType(); } + const auto& fields = ht->getStruct().fields; + assert(curr->index < fields.size()); + auto type = fields[curr->index].type; + auto expectedType = type; + if (expectedType.isRef()) { + expectedType = + Type(HeapTypes::eq.getBasic(type.getHeapType().getShared()), Nullable); + } + note(&curr->ref, Type(*ht, Nullable)); note(&curr->waitqueue, Type(HeapTypes::sharedWaitqueue, Nullable)); - note(&curr->expected, Type(Type::BasicType::i32)); + note(&curr->expected, expectedType); note(&curr->timeout, Type(Type::BasicType::i64)); } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6e046152252..2be6b0e0057 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2336,7 +2336,7 @@ class ExpressionRunner : public OverriddenVisitor { trap("null ref"); } auto& field = data->values[curr->index]; - if (field.geti32() != expected.getSingleValue().geti32()) { + if (field != expected.getSingleValue()) { return Literal(int32_t{1}); // not equal } // TODO: Add threads support. For now, report a host limit here, as there diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 3193e6539e4..6f8e9a6cdec 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3652,10 +3652,35 @@ void FunctionValidator::visitStructWait(StructWait* curr) { Type(HeapTypes::sharedWaitqueue, Nullable), curr, "struct.wait waitqueue must be a shared waitqueue reference"); - shouldBeEqual(curr->expected->type, - Type(Type::BasicType::i32), - curr, - "struct.wait expected must be an i32"); + + if (curr->ref->type != Type::unreachable && curr->ref->type.isRef() && + !curr->ref->type.getHeapType().isMaybeShared(HeapType::none) && + curr->ref->type.getHeapType().isStruct()) { + auto type = curr->ref->type.getHeapType(); + const auto& fields = type.getStruct().fields; + if (curr->index < fields.size()) { + auto& field = fields[curr->index]; + Type expectedExpectedType; + if (field.type == Type::i32) { + expectedExpectedType = Type::i32; + } else if (field.type == Type::i64) { + expectedExpectedType = Type::i64; + } else if (field.type.isRef()) { + expectedExpectedType = + Type(HeapTypes::eq.getBasic(field.type.getHeapType().getShared()), + Nullable); + } else { + shouldBeTrue( + false, curr, "struct.wait field type invalid for operation"); + return; + } + shouldBeSubType(curr->expected->type, + expectedExpectedType, + curr, + "struct.wait expected value must have the proper type"); + } + } + shouldBeEqual(curr->timeout->type, Type(Type::BasicType::i64), curr, diff --git a/test/lit/waitqueue.wast b/test/lit/waitqueue.wast index 9bc1b40220d..c66a5be4261 100644 --- a/test/lit/waitqueue.wast +++ b/test/lit/waitqueue.wast @@ -6,9 +6,17 @@ ;; RTRIP: (type $t (shared (struct (field (mut i32))))) (type $t (shared (struct (field (mut i32))))) + ;; CHECK: (type $t64 (shared (struct (field (mut i64))))) + + ;; CHECK: (type $tref (shared (struct (field (mut (ref null (shared eq))))))) + ;; CHECK: (global $g (ref $t) (struct.new $t ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: )) + ;; RTRIP: (type $t64 (shared (struct (field (mut i64))))) + + ;; RTRIP: (type $tref (shared (struct (field (mut (ref null (shared eq))))))) + ;; RTRIP: (global $g (ref $t) (struct.new $t ;; RTRIP-NEXT: (i32.const 0) ;; RTRIP-NEXT: )) @@ -22,6 +30,14 @@ ;; RTRIP: (global $nwq (mut (ref null (shared nowaitqueue))) (ref.null (shared nowaitqueue))) (global $nwq (mut (ref null (shared nowaitqueue))) (ref.null (shared nowaitqueue))) + ;; CHECK: (global $g64 (ref $t64) (struct.new $t64 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $gref (ref $tref) (struct.new $tref + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: )) + ;; CHECK: (func $wait (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.wait $t 0 @@ -32,6 +48,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; RTRIP: (global $g64 (ref $t64) (struct.new $t64 + ;; RTRIP-NEXT: (i64.const 0) + ;; RTRIP-NEXT: )) + + ;; RTRIP: (global $gref (ref $tref) (struct.new $tref + ;; RTRIP-NEXT: (ref.null (shared none)) + ;; RTRIP-NEXT: )) + ;; RTRIP: (func $wait (type $0) ;; RTRIP-NEXT: (drop ;; RTRIP-NEXT: (struct.wait $t 0 @@ -46,6 +70,58 @@ (drop (struct.wait $t 0 (global.get $g) (global.get $wq) (i32.const 0) (i64.const 0))) ) + (type $t64 (shared (struct (field (mut i64))))) + (global $g64 (ref $t64) (struct.new $t64 (i64.const 0))) + ;; CHECK: (func $wait_i64 (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.wait $t64 0 + ;; CHECK-NEXT: (global.get $g64) + ;; CHECK-NEXT: (global.get $wq) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $wait_i64 (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (struct.wait $t64 0 + ;; RTRIP-NEXT: (global.get $g64) + ;; RTRIP-NEXT: (global.get $wq) + ;; RTRIP-NEXT: (i64.const 0) + ;; RTRIP-NEXT: (i64.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $wait_i64 + (drop (struct.wait $t64 0 (global.get $g64) (global.get $wq) (i64.const 0) (i64.const 0))) + ) + + (type $tref (shared (struct (field (mut (ref null (shared eq))))))) + (global $gref (ref $tref) (struct.new $tref (ref.null (shared eq)))) + ;; CHECK: (func $wait_ref (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.wait $tref 0 + ;; CHECK-NEXT: (global.get $gref) + ;; CHECK-NEXT: (global.get $wq) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $wait_ref (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (struct.wait $tref 0 + ;; RTRIP-NEXT: (global.get $gref) + ;; RTRIP-NEXT: (global.get $wq) + ;; RTRIP-NEXT: (ref.null (shared none)) + ;; RTRIP-NEXT: (i64.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $wait_ref + (drop (struct.wait $tref 0 (global.get $gref) (global.get $wq) (ref.null (shared eq)) (i64.const 0))) + ) + ;; CHECK: (func $notify (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (waitqueue.notify diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index 6317a39d970..3b90bf4f7b2 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -25,7 +25,7 @@ (func (param $expected i32) (param $timeout i64) (result i32) (struct.wait $t 0 (global.get $g) (global.get $wq) (i64.const 0) (local.get $timeout)) ) - ) "struct.wait expected must be an i32" + ) "struct.wait expected value must have the proper type" ) (assert_invalid @@ -76,12 +76,18 @@ (module (type $t (shared (struct (field (mut i32))))) + (type $t64 (shared (struct (field (mut i64))))) + (type $tref (shared (struct (field (mut (ref null (shared eq))))))) (global $g (mut (ref null $t)) (struct.new $t (i32.const 0))) + (global $g64 (mut (ref null $t64)) (struct.new $t64 (i64.const 0))) + (global $gref (mut (ref null $tref)) (struct.new $tref (ref.null (shared eq)))) (global $wq (mut (ref null (shared waitqueue))) (waitqueue.new)) (func (export "setToNull") (global.set $g (ref.null $t)) + (global.set $g64 (ref.null $t64)) + (global.set $gref (ref.null $tref)) (global.set $wq (ref.null (shared waitqueue))) ) @@ -89,6 +95,18 @@ (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (local.get $timeout)) ) + (func (export "struct.wait_i64") (param $expected i64) (param $timeout i64) (result i32) + (struct.wait $t64 0 (global.get $g64) (global.get $wq) (local.get $expected) (local.get $timeout)) + ) + + (func (export "struct.wait_ref") (param $expected (ref null (shared eq))) (param $timeout i64) (result i32) + (struct.wait $tref 0 (global.get $gref) (global.get $wq) (local.get $expected) (local.get $timeout)) + ) + + (func (export "struct.wait_ref_i31") (param $expected i32) (param $timeout i64) (result i32) + (struct.wait $tref 0 (global.get $gref) (global.get $wq) (ref.i31_shared (local.get $expected)) (local.get $timeout)) + ) + (func (export "waitqueue.notify") (param $count i32) (result i32) (waitqueue.notify (global.get $wq) (local.get $count)) ) @@ -111,12 +129,20 @@ ;; Control word matched, wait 0ns and return 2. (assert_return (invoke "struct.wait" (i32.const 1) (i64.const 0)) (i32.const 2)) +(assert_return (invoke "struct.wait_i64" (i64.const 1) (i64.const 100)) (i32.const 1)) +(assert_return (invoke "struct.wait_i64" (i64.const 0) (i64.const 0)) (i32.const 2)) + +(assert_return (invoke "struct.wait_ref_i31" (i32.const 0) (i64.const 100)) (i32.const 1)) +(assert_return (invoke "struct.wait_ref" (ref.null (shared eq)) (i64.const 0)) (i32.const 2)) + ;; Try to wake up 1 thread, but no-one was waiting. (assert_return (invoke "waitqueue.notify" (i32.const 1)) (i32.const 0)) (invoke "setToNull") (assert_trap (invoke "struct.wait" (i32.const 0) (i64.const 0)) "null ref") +(assert_trap (invoke "struct.wait_i64" (i64.const 0) (i64.const 0)) "null ref") +(assert_trap (invoke "struct.wait_ref" (ref.null (shared eq)) (i64.const 0)) "null ref") (assert_trap (invoke "waitqueue.notify" (i32.const 0)) "null ref") ;; Binary format test for waitqueue and nowaitqueue. From 96ea5644ce5a490fa3c441eb58380dcaf3bf9254 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Mon, 27 Jul 2026 16:54:56 +0000 Subject: [PATCH 2/8] Make ref.i31 a constant expression --- src/ir/properties.h | 2 ++ test/spec/waitqueue.wast | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index 4b4126d750d..2926c5037f0 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -86,6 +86,8 @@ inline bool isSingleConstantExpression(const Expression* curr) { if (refAs->op == ExternConvertAny || refAs->op == AnyConvertExtern) { return isSingleConstantExpression(refAs->value); } + } else if (auto* i31 = curr->dynCast()) { + return isSingleConstantExpression(i31->value); } return curr->is() || curr->is() || curr->is() || curr->is(); diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index 3b90bf4f7b2..b28183ec032 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -103,10 +103,6 @@ (struct.wait $tref 0 (global.get $gref) (global.get $wq) (local.get $expected) (local.get $timeout)) ) - (func (export "struct.wait_ref_i31") (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $tref 0 (global.get $gref) (global.get $wq) (ref.i31_shared (local.get $expected)) (local.get $timeout)) - ) - (func (export "waitqueue.notify") (param $count i32) (result i32) (waitqueue.notify (global.get $wq) (local.get $count)) ) @@ -132,7 +128,7 @@ (assert_return (invoke "struct.wait_i64" (i64.const 1) (i64.const 100)) (i32.const 1)) (assert_return (invoke "struct.wait_i64" (i64.const 0) (i64.const 0)) (i32.const 2)) -(assert_return (invoke "struct.wait_ref_i31" (i32.const 0) (i64.const 100)) (i32.const 1)) +(assert_return (invoke "struct.wait_ref" (ref.i31 (i32.const 0)) (i64.const 100)) (i32.const 1)) (assert_return (invoke "struct.wait_ref" (ref.null (shared eq)) (i64.const 0)) (i32.const 2)) ;; Try to wake up 1 thread, but no-one was waiting. From ff41b362fdb885655cc556df60c8e967f1670d72 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 28 Jul 2026 18:40:04 +0000 Subject: [PATCH 3/8] Fix test --- src/ir/child-typer.h | 1 + src/wasm/wasm-ir-builder.cpp | 1 + test/spec/waitqueue.wast | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index a7789ba5f33..f0ccc6369cc 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1018,6 +1018,7 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->ref, Type(*ht, Nullable)); auto type = fields[curr->index].type; auto expectedType = type; + // TODO: assertion for f32 / f64? if (expectedType.isRef()) { expectedType = Type(HeapTypes::eq.getBasic(type.getHeapType().getShared()), Nullable); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 4e35d8ce0c3..aa7906ff8ee 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2352,6 +2352,7 @@ Result<> IRBuilder::makeStructWait(HeapType type, Index index) { } StructWait curr(wasm.allocator); + curr.index = index; CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); push(builder.makeStructWait( diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index b28183ec032..5e769f33c80 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -128,7 +128,7 @@ (assert_return (invoke "struct.wait_i64" (i64.const 1) (i64.const 100)) (i32.const 1)) (assert_return (invoke "struct.wait_i64" (i64.const 0) (i64.const 0)) (i32.const 2)) -(assert_return (invoke "struct.wait_ref" (ref.i31 (i32.const 0)) (i64.const 100)) (i32.const 1)) +(assert_return (invoke "struct.wait_ref" (ref.i31_shared (i32.const 0)) (i64.const 100)) (i32.const 1)) (assert_return (invoke "struct.wait_ref" (ref.null (shared eq)) (i64.const 0)) (i32.const 2)) ;; Try to wake up 1 thread, but no-one was waiting. From d51565d0e4d817a2963ca3c8cccce1f09a66f4f3 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 28 Jul 2026 20:37:01 +0000 Subject: [PATCH 4/8] Simplify the test a bit --- src/wasm/wasm-validator.cpp | 4 ++-- test/spec/waitqueue.wast | 36 +++++++++++++++++++----------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 6f8e9a6cdec..42ce7ff7e32 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3658,7 +3658,7 @@ void FunctionValidator::visitStructWait(StructWait* curr) { curr->ref->type.getHeapType().isStruct()) { auto type = curr->ref->type.getHeapType(); const auto& fields = type.getStruct().fields; - if (curr->index < fields.size()) { + if (shouldBeTrue(curr->index < fields.size(), curr, "struct.wait field index immediate must be within bounds")) { auto& field = fields[curr->index]; Type expectedExpectedType; if (field.type == Type::i32) { @@ -3671,7 +3671,7 @@ void FunctionValidator::visitStructWait(StructWait* curr) { Nullable); } else { shouldBeTrue( - false, curr, "struct.wait field type invalid for operation"); + false, curr, "struct.wait field type must be i32, i64 of a subtype of (ref null (shared eq))"); return; } shouldBeSubType(curr->expected->type, diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index 5e769f33c80..47b2e4e0a9d 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -1,8 +1,8 @@ (assert_invalid (module (type $t (shared (struct (field i32)))) - (func (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 0 (ref.null $t) (ref.null waitqueue) (local.get $expected) (local.get $timeout)) + (func (result i32) + (struct.wait $t 0 (ref.null $t) (ref.null waitqueue) (i32.const 0) (i64.const 0)) ) ) "struct.wait waitqueue must be a shared waitqueue reference" ) @@ -10,9 +10,8 @@ (assert_invalid (module (type $t (shared (struct (field i32)))) - (global $wq (ref (shared waitqueue)) (waitqueue.new)) - (func (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 2 (ref.null $t) (global.get $wq) (local.get $expected) (local.get $timeout)) + (func (result i32) + (struct.wait $t 1 (ref.null $t) (waitqueue.new) (i32.const 0) (i64.const 0)) ) ) "struct index out of bounds" ) @@ -20,21 +19,27 @@ (assert_invalid (module (type $t (shared (struct (field i32)))) - (global $g (ref $t) (struct.new $t (i32.const 0))) - (global $wq (ref (shared waitqueue)) (waitqueue.new)) - (func (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 0 (global.get $g) (global.get $wq) (i64.const 0) (local.get $timeout)) + (func (result i32) + (struct.wait $t 0 (ref.null $t) (waitqueue.new) (i64.const 0) (i64.const 0)) ) ) "struct.wait expected value must have the proper type" ) +(assert_invalid + (module + (type $t (shared (struct (field f32)))) + (func (result i32) + (struct.wait $t 0 (ref.null $t) (waitqueue.new) (f32.const 0) (i64.const 0)) + ) + ) + "struct.wait field type must be i32, i64 of a subtype of (ref null (shared eq))" +) + (assert_invalid (module (type $t (shared (struct (field i32)))) - (global $g (ref $t) (struct.new $t (i32.const 0))) - (global $wq (ref (shared waitqueue)) (waitqueue.new)) - (func (param $expected i32) (param $timeout i64) (result i32) - (struct.wait $t 0 (global.get $g) (global.get $wq) (local.get $expected) (i32.const 0)) + (func (result i32) + (struct.wait $t 0 (ref.null $t) (waitqueue.new) (i32.const 0) (i32.const 0)) ) ) "struct.wait timeout must be an i64" ) @@ -42,7 +47,6 @@ (assert_invalid (module (type $t (shared (struct (field i32)))) - (global $wq (ref (shared waitqueue)) (waitqueue.new)) (func (param $count i32) (result i32) (waitqueue.notify (ref.null waitqueue) (local.get $count)) ) @@ -51,10 +55,8 @@ (assert_invalid (module - (type $t (shared (struct (field i32)))) - (global $wq (ref (shared waitqueue)) (waitqueue.new)) (func (param $count i32) (result i32) - (waitqueue.notify (global.get $wq) (i64.const 0)) + (waitqueue.notify (waitqueue.new) (i64.const 0)) ) ) "waitqueue.notify count must be an i32" ) From e88453cc9289543a48e8f7b9d7dcb481ad3b6de3 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 28 Jul 2026 20:48:02 +0000 Subject: [PATCH 5/8] Gemini: try adding validations to irbuilder --- src/wasm/wasm-ir-builder.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index aa7906ff8ee..b536e666b29 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2355,6 +2355,27 @@ Result<> IRBuilder::makeStructWait(HeapType type, Index index) { curr.index = index; CHECK_ERR(ChildPopper{*this}.visitStructWait(&curr, type)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); + + if (curr.expected->type != Type::unreachable) { + auto& field = type.getStruct().fields[index]; + Type expectedExpectedType; + if (field.type == Type::i32) { + expectedExpectedType = Type::i32; + } else if (field.type == Type::i64) { + expectedExpectedType = Type::i64; + } else if (field.type.isRef()) { + expectedExpectedType = + Type(HeapTypes::eq.getBasic(field.type.getHeapType().getShared()), + Nullable); + } else { + return Err{ + "struct.wait field type must be i32, i64 of a subtype of (ref null (shared eq))"}; + } + if (!Type::isSubType(curr.expected->type, expectedExpectedType)) { + return Err{"struct.wait expected value must have the proper type"}; + } + } + push(builder.makeStructWait( index, curr.ref, curr.waitqueue, curr.expected, curr.timeout)); return Ok{}; From 7975c44ddde860e8342393680831f089d60b11f1 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 28 Jul 2026 21:14:43 +0000 Subject: [PATCH 6/8] nit changes --- src/ir/child-typer.h | 2 +- src/wasm/wasm-ir-builder.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index f0ccc6369cc..26cb5a25805 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1018,7 +1018,6 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->ref, Type(*ht, Nullable)); auto type = fields[curr->index].type; auto expectedType = type; - // TODO: assertion for f32 / f64? if (expectedType.isRef()) { expectedType = Type(HeapTypes::eq.getBasic(type.getHeapType().getShared()), Nullable); @@ -1041,6 +1040,7 @@ template struct ChildTyper : OverriddenVisitor { assert(curr->index < fields.size()); auto type = fields[curr->index].type; auto expectedType = type; + // TODO: assertion for f32 / f64? if (expectedType.isRef()) { expectedType = Type(HeapTypes::eq.getBasic(type.getHeapType().getShared()), Nullable); diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index b536e666b29..5a6db125e4e 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2357,7 +2357,7 @@ Result<> IRBuilder::makeStructWait(HeapType type, Index index) { CHECK_ERR(validateTypeAnnotation(type, curr.ref)); if (curr.expected->type != Type::unreachable) { - auto& field = type.getStruct().fields[index]; + const auto& field = type.getStruct().fields[index]; Type expectedExpectedType; if (field.type == Type::i32) { expectedExpectedType = Type::i32; From d4839be3411be0a4340709e5f47efb05752c940d Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 28 Jul 2026 21:27:37 +0000 Subject: [PATCH 7/8] Update lit tests --- test/lit/passes/dae-gc.wast | 21 ++++++++++++++------- test/lit/passes/dae_all-features.wast | 5 +++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index bdccf601edf..ec0136ae84a 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -17,15 +17,22 @@ ) ;; CHECK: (func $bar (type $0) ;; CHECK-NEXT: (local $0 i31ref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $0 - ;; CHECK-NEXT: (ref.i31 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $bar (param $0 i31ref) diff --git a/test/lit/passes/dae_all-features.wast b/test/lit/passes/dae_all-features.wast index 145ae1ff032..41bcb73b53e 100644 --- a/test/lit/passes/dae_all-features.wast +++ b/test/lit/passes/dae_all-features.wast @@ -537,6 +537,11 @@ ;; CHECK: (func $0 (type $0) ;; CHECK-NEXT: (local $0 i31ref) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $0 (param $x i31ref) From 5a4d346900e600594147edc351ed60714fe32126 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 28 Jul 2026 21:40:44 +0000 Subject: [PATCH 8/8] Try fixing DAE --- src/passes/param-utils.cpp | 13 ++++ test/lit/passes/dae-gc.wast | 31 +++------- test/lit/passes/dae-typessa-repeat-types.wast | 21 +++---- test/lit/passes/dae_all-features.wast | 60 +++---------------- test/lit/passes/dae_tnh.wast | 3 - test/lit/passes/signature-pruning.wast | 3 - 6 files changed, 38 insertions(+), 93 deletions(-) diff --git a/src/passes/param-utils.cpp b/src/passes/param-utils.cpp index 6861756c475..f4381a09428 100644 --- a/src/passes/param-utils.cpp +++ b/src/passes/param-utils.cpp @@ -262,6 +262,19 @@ SortedVector applyConstantValues(const std::vector& funcs, continue; } + // If the parameter is not used in any of the functions, writing the + // constant value to it is redundant and creates a dead local set. + bool used = false; + for (auto* func : funcs) { + if (getUsedParams(func, module).count(i)) { + used = true; + break; + } + } + if (!used) { + continue; + } + // Optimize: write the constant value in the function bodies, making them // ignore the parameter's value. Builder builder(*module); diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index ec0136ae84a..b4abdd4bf3f 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -17,22 +17,15 @@ ) ;; CHECK: (func $bar (type $0) ;; CHECK-NEXT: (local $0 i31ref) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.i31 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.tee $0 - ;; CHECK-NEXT: (ref.i31 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $bar (param $0 i31ref) @@ -186,15 +179,7 @@ ;; CHECK: (func $1 (type $0) ;; CHECK-NEXT: (local $0 (ref string)) ;; CHECK-NEXT: (local $1 (ref string)) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (string.const "929") - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (string.const "310") - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $1 (param $0 (ref string)) (param $1 (ref string)) ;; The parameters here will be removed, and the constant values placed in the diff --git a/test/lit/passes/dae-typessa-repeat-types.wast b/test/lit/passes/dae-typessa-repeat-types.wast index 7072d215cac..a50300f1dfe 100644 --- a/test/lit/passes/dae-typessa-repeat-types.wast +++ b/test/lit/passes/dae-typessa-repeat-types.wast @@ -11,11 +11,11 @@ ;; Trigger TypeSSA ;; CHECK: (type $2 (func)) - ;; CHECK: (type $3 (func (result i32 (ref (exact $struct))))) - ;; CHECK: (type $array_1 (sub $array (array (mut i32)))) - ;; CHECK: (type $5 (func (result i32 (ref $struct)))) + ;; CHECK: (type $4 (func (result i32 (ref $struct)))) + + ;; CHECK: (type $5 (func (result i32 (ref (exact $struct))))) ;; CHECK: (global $array (ref $array) (array.new $array_1 ;; CHECK-NEXT: (i32.const 0) @@ -43,15 +43,10 @@ ;; CHECK: (func $callee (type $2) ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (tuple.drop 2 - ;; CHECK-NEXT: (block (type $3) (result i32 (ref (exact $struct))) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (type $3) (result i32 (ref (exact $struct))) - ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (struct.new_default $struct) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $5) (result i32 (ref (exact $struct))) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -67,7 +62,7 @@ ) ) - ;; CHECK: (func $other (type $5) (result i32 (ref $struct)) + ;; CHECK: (func $other (type $4) (result i32 (ref $struct)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $other (result i32 (ref $struct)) diff --git a/test/lit/passes/dae_all-features.wast b/test/lit/passes/dae_all-features.wast index 41bcb73b53e..5cb0f032cdb 100644 --- a/test/lit/passes/dae_all-features.wast +++ b/test/lit/passes/dae_all-features.wast @@ -30,11 +30,6 @@ (elem (i32.const 0) $a9 $c8) ;; CHECK: (func $a (type $0) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $a (param $x i32)) ;; CHECK: (func $b (type $0) @@ -45,9 +40,6 @@ ) ;; CHECK: (func $a1 (type $0) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $a1 (param $x i32) @@ -112,11 +104,6 @@ ) ;; CHECK: (func $a4 (type $0) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $a4 (param $x i32) ;; This function is called with one constant and one unreachable. We can @@ -244,13 +231,8 @@ ) ;; CHECK: (func $a10 (type $0) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (call $a10) - ;; CHECK-NEXT: (call $a10) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $a10) + ;; CHECK-NEXT: (call $a10) ;; CHECK-NEXT: ) (func $a10 (param $x i32) ;; recursion (call $a10 (i32.const 1)) @@ -417,9 +399,6 @@ ) ;; CHECK: (func $bar (type $1) (result i32) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 7) ;; CHECK-NEXT: ) (func $bar (param $x i32) (result i32) @@ -436,9 +415,6 @@ ;; CHECK: (func $foo (type $T) (result i32) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (return_call_indirect $0 (type $T) ;; CHECK-NEXT: (i32.const 0) @@ -537,11 +513,6 @@ ;; CHECK: (func $0 (type $0) ;; CHECK-NEXT: (local $0 i31ref) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.i31 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $0 (param $x i31ref) @@ -674,22 +645,17 @@ ;; CHECK: (func $0 (type $0) ;; CHECK-NEXT: (local $0 i32) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) (func $0 (param $0 i32) (result i32) ;; The returns here are nested in each other, and one is a recursive call to @@ -840,15 +806,7 @@ ;; CHECK-NEXT: (local $0 i64) ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 v128) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (i64.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $target (param $0 i64) (param $1 v128) (param $2 i64) (result f32) ;; All parameters here should vanish. diff --git a/test/lit/passes/dae_tnh.wast b/test/lit/passes/dae_tnh.wast index 6e78283fdce..b1f9d911b39 100644 --- a/test/lit/passes/dae_tnh.wast +++ b/test/lit/passes/dae_tnh.wast @@ -116,9 +116,6 @@ ;; CHECK: (func $target (type $0) (param $0 i32) ;; CHECK-NEXT: (local $1 f64) - ;; CHECK-NEXT: (local.set $1 - ;; CHECK-NEXT: (f64.const 4.2) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast index d86e855dbe8..c2ffc51f90b 100644 --- a/test/lit/passes/signature-pruning.wast +++ b/test/lit/passes/signature-pruning.wast @@ -1309,9 +1309,6 @@ ;; CHECK: (func $other (type $other) ;; CHECK-NEXT: (local $0 anyref) - ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $other (type $other) (param anyref)