From 2586422b75ec3a4caad0ad4771c3bbe8a6a7266d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 16 May 2025 11:22:20 -0700 Subject: [PATCH 1/4] Fix TupleMake typo in wasm-delegations-fields.def Fixes #7560. --- src/wasm-delegations-fields.def | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 7566573beff..e180855c467 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -601,7 +601,7 @@ DELEGATE_FIELD_CASE_START(Pop) DELEGATE_FIELD_CASE_END(Pop) DELEGATE_FIELD_CASE_START(TupleMake) -DELEGATE_FIELD_CHILD_VECTOR(Tuple, operands) +DELEGATE_FIELD_CHILD_VECTOR(TupleMake, operands) DELEGATE_FIELD_CASE_END(TupleMake) DELEGATE_FIELD_CASE_START(TupleExtract) From dcd4c51f66ff0828cc6ee39f529ca61e75142bbb Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 16 May 2025 11:00:12 -0700 Subject: [PATCH 2/4] Handle stack switching in Unsubtyping Add the rather complicated subtyping requirements to SubtypingDiscoverer and test that unsubtyping preserves the necessary subtype relationships. --- scripts/test/fuzzing.py | 1 + src/ir/subtype-exprs.h | 124 ++++- src/wasm/wasm-ir-builder.cpp | 1 + .../passes/unsubtyping-stack-switching.wast | 478 ++++++++++++++++++ 4 files changed, 598 insertions(+), 6 deletions(-) create mode 100644 test/lit/passes/unsubtyping-stack-switching.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index b074106b271..b057f84545b 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -109,6 +109,7 @@ 'coalesce-locals-stack-switching.wast', 'dce-stack-switching.wast', 'precompute-stack-switching.wast', + 'unsubtyping-stack-switching.wast', 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 7d9f30b6778..e689db981e4 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -412,15 +412,127 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStringWTF16Get(StringWTF16Get* curr) {} void visitStringSliceWTF(StringSliceWTF* curr) {} - void visitContNew(ContNew* curr) { WASM_UNREACHABLE("not implemented"); } - void visitContBind(ContBind* curr) { WASM_UNREACHABLE("not implemented"); } - void visitSuspend(Suspend* curr) { WASM_UNREACHABLE("not implemented"); } - void visitResume(Resume* curr) { WASM_UNREACHABLE("not implemented"); } + void visitContNew(ContNew* curr) { + if (!curr->type.isContinuation()) { + return; + } + // The type of the function reference must remain a subtype of the function + // type expected by the continuation. + self()->noteSubtype(curr->func->type.getHeapType(), + curr->type.getHeapType().getContinuation().type); + } + void visitContBind(ContBind* curr) { + if (!curr->cont->type.isContinuation()) { + return; + } + // Each of the bound arguments must remain subtypes of their expected + // parameters. + auto params = curr->cont->type.getHeapType() + .getContinuation() + .type.getSignature() + .params; + assert(curr->operands.size() <= params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], params[i]); + } + } + void visitSuspend(Suspend* curr) { + // The operands must remain subtypes of the parameters given by the tag. + auto params = + self()->getModule()->getTag(curr->tag)->type.getSignature().params; + assert(curr->operands.size() == params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], params[i]); + } + } + void processResumeHandlers(Type contType, + const ArenaVector& handlerTags, + const ArenaVector& handlerBlocks) { + auto contSig = contType.getHeapType().getContinuation().type.getSignature(); + assert(handlerTags.size() == handlerBlocks.size()); + auto& wasm = *self()->getModule(); + // Process each handler in turn. + for (Index i = 0; i < handlerTags.size(); ++i) { + if (!handlerBlocks[i]) { + // Switch handlers do not constrain types in any way. + continue; + } + auto tagSig = wasm.getTag(handlerTags[i])->type.getSignature(); + // The types sent on suspensions with this tag must remain subtypes of the + // types expected at the target block. + auto expected = self()->findBreakTarget(handlerBlocks[i])->type; + assert(tagSig.params.size() + 1 == expected.size()); + for (Index j = 0; j < tagSig.params.size(); ++j) { + self()->noteSubtype(tagSig.params[i], expected[i]); + } + auto nextSig = expected[expected.size() - 1] + .getHeapType() + .getContinuation() + .type.getSignature(); + // The types we send to the next continuation must remain subtypes of the + // types the continuation is expecting based on the tag results. + self()->noteSubtype(nextSig.params, tagSig.results); + // The types returned by the current continuation must remain subtypes of + // the types returned by the next continuation. + self()->noteSubtype(contSig.results, nextSig.results); + } + } + void visitResume(Resume* curr) { + if (!curr->cont->type.isContinuation()) { + return; + } + processResumeHandlers( + curr->cont->type, curr->handlerTags, curr->handlerBlocks); + // The types we send to the resumed continuation must remain subtypes of the + // types expected by the continuation. + auto params = curr->cont->type.getHeapType() + .getContinuation() + .type.getSignature() + .params; + assert(curr->operands.size() == params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], params[i]); + } + } void visitResumeThrow(ResumeThrow* curr) { - WASM_UNREACHABLE("not implemented"); + if (!curr->cont->type.isContinuation()) { + return; + } + processResumeHandlers( + curr->cont->type, curr->handlerTags, curr->handlerBlocks); + // The types we use to create the exception package must remain subtypes of + // the types expected by the exception tag. + auto params = + self()->getModule()->getTag(curr->tag)->type.getSignature().params; + assert(curr->operands.size() == params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], params[i]); + } } void visitStackSwitch(StackSwitch* curr) { - WASM_UNREACHABLE("not implemented"); + if (!curr->cont->type.isContinuation()) { + return; + } + // The types sent when switching must remain subtypes of the types expected + // by the target continuation. + auto contSig = + curr->cont->type.getHeapType().getContinuation().type.getSignature(); + assert(curr->operands.size() + 1 == contSig.params.size()); + for (Index i = 0; i < curr->operands.size(); ++i) { + self()->noteSubtype(curr->operands[i], contSig.params[i]); + } + // The type returned by the target continuation must remain a subtype of the + // type the current continuation returns as indicated by the tag result. + auto currResult = + self()->getModule()->getTag(curr->tag)->type.getSignature().results; + self()->noteSubtype(contSig.results, currResult); + // The type returned by the current continuation must remain a subtype of + // the type returned by the return continuation. + auto retSig = contSig.params[contSig.params.size() - 1] + .getHeapType() + .getContinuation() + .type.getSignature(); + self()->noteSubtype(currResult, retSig.results); } }; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index d738df0c404..0a7debcea37 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2471,6 +2471,7 @@ IRBuilder::makeResume(HeapType ct, return Err{"the sizes of tags and labels must be equal"}; } if (!ct.isContinuation()) { + std::cout << ct.toString() << "\n"; return Err{"expected continuation type"}; } diff --git a/test/lit/passes/unsubtyping-stack-switching.wast b/test/lit/passes/unsubtyping-stack-switching.wast new file mode 100644 index 00000000000..e7b8fb6b64f --- /dev/null +++ b/test/lit/passes/unsubtyping-stack-switching.wast @@ -0,0 +1,478 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --unsubtyping --remove-unused-types -all -S -o - | filecheck %s + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (func))) + (type $super (sub (func))) + ;; CHECK: (type $sub (sub $super (func))) + (type $sub (sub $super (func))) + ;; CHECK: (type $cont (cont $super)) + (type $cont (cont $super)) + + ;; CHECK: (elem declare func $cont-new) + + ;; CHECK: (func $cont-new (type $sub) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (cont.new $cont + ;; CHECK-NEXT: (ref.func $cont-new) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-new (type $sub) + (drop + ;; This requires $sub <: $super. + (cont.new $cont + (ref.func $cont-new) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $one (func (param (ref $super)))) + (type $one (func (param (ref $super)))) + ;; CHECK: (type $none (func)) + (type $none (func)) + + ;; CHECK: (type $cont-one (cont $one)) + (type $cont-one (cont $one)) + ;; CHECK: (type $cont-none (cont $none)) + (type $cont-none (cont $none)) + + ;; CHECK: (func $cont-bind (type $none) + ;; CHECK-NEXT: (local $one (ref null $cont-one)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (cont.bind $cont-one $cont-none + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (local.get $one) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-bind + (local $one (ref null $cont-one)) + (drop + ;; This requires $sub <: $super. + (cont.bind $cont-one $cont-none + (struct.new $sub) + (local.get $one) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $2 (func (param (ref null $super)))) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (tag $e (type $2) (param (ref null $super))) + (tag $e (param (ref null $super))) + + ;; CHECK: (func $suspend (type $3) + ;; CHECK-NEXT: (suspend $e + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $suspend + ;; This requires $sub <: $super. + (suspend $e + (struct.new $sub) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func (param (ref null $super)))) + (type $f (func (param (ref null $super)))) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $4 (func)) + + ;; CHECK: (func $resume-param (type $4) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (resume $cont + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-param + (local $cont (ref null $cont)) + ;; This requires $sub <: $super. + (resume $cont + (struct.new $sub) + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $4 (func (param (ref null $sub)))) + + ;; CHECK: (type $5 (func (result (ref null $super) (ref null $cont)))) + + ;; CHECK: (tag $e (type $4) (param (ref null $sub))) + (tag $e (param (ref null $sub))) + + ;; CHECK: (func $resume-label (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $l (type $5) (result (ref null $super) (ref null $cont)) + ;; CHECK-NEXT: (resume $cont (on $e $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-label + (local $cont (ref null $cont)) + (tuple.drop 2 + (block $l (result (ref null $super) (ref null $cont)) + ;; Sending the tag parameter to the block requires $sub <: $super. + (resume $cont (on $e $l) + (local.get $cont) + ) + (unreachable) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $next-f (func (param (ref null $sub)))) + (type $next-f (func (param (ref null $sub)))) + + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + ;; CHECK: (type $next-cont (cont $next-f)) + (type $next-cont (cont $next-f)) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (tag $e (type $6) (result (ref null $super))) + (tag $e (result (ref null $super))) + + ;; CHECK: (func $resume-tag (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $next-cont)) + ;; CHECK-NEXT: (resume $cont (on $e $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-tag + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $next-cont)) + ;; Based on the tag, the continuation expects a $super back. Based on + ;; the type we give the next continuation, we will send it a $sub back. + ;; This requires $sub <: $super. + (resume $cont (on $e $l) + (local.get $cont) + ) + (ref.null nocont) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func (result (ref null $sub)))) + (type $f (func (result (ref null $sub)))) + ;; CHECK: (type $next-f (func (result (ref null $super)))) + (type $next-f (func (result (ref null $super)))) + + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + ;; CHECK: (type $next-cont (cont $next-f)) + (type $next-cont (cont $next-f)) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (tag $e (type $6)) + (tag $e) + + ;; CHECK: (func $resume-result (type $6) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $next-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (resume $cont (on $e $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-result + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $next-cont)) + (drop + ;; The continuation we're resuming returns a $sub. In the next type we + ;; give it, it returns a $super. This requires $sub <: $super. + (resume $cont (on $e $l) + (local.get $cont) + ) + ) + (ref.null nocont) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $4 (func (param (ref null $super)))) + + ;; CHECK: (tag $e (type $4) (param (ref null $super))) + (tag $e (param (ref null $super))) + + ;; CHECK: (func $resume-throw-param (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (resume_throw $cont $e + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-param + (local $cont (ref null $cont)) + ;; This requires $sub <: $super + (resume_throw $cont $e + (struct.new $sub) + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $4 (func (param (ref null $sub)))) + + ;; CHECK: (type $5 (func (result (ref null $super) (ref null $cont)))) + + ;; CHECK: (tag $e (type $4) (param (ref null $sub))) + (tag $e (param (ref null $sub))) + + ;; CHECK: (func $resume-throw-label (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $l (type $5) (result (ref null $super) (ref null $cont)) + ;; CHECK-NEXT: (resume_throw $cont $e (on $e $l) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-label + (local $cont (ref null $cont)) + (tuple.drop 2 + (block $l (result (ref null $super) (ref null $cont)) + ;; Sending the tag parameter to the block requires $sub <: $super. + (resume_throw $cont $e (on $e $l) + (ref.null none) + (local.get $cont) + ) + (unreachable) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + (rec + ;; CHECK: (type $f (func (param (ref null $super) (ref null $ret-cont)))) + (type $f (func (param (ref null $super) (ref null $ret-cont)))) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $ret-f (func)) + (type $ret-f (func)) + ;; CHECK: (type $ret-cont (cont $ret-f)) + (type $ret-cont (cont $ret-f)) + ) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (tag $e (type $6)) + (tag $e) + + ;; CHECK: (func $switch-param (type $6) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (switch $cont $e + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch-param + (local $cont (ref null $cont)) + (switch $cont $e + (struct.new $sub) + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + (rec + ;; CHECK: (type $f (func (param (ref null $ret-cont)) (result (ref null $sub)))) + (type $f (func (param (ref null $ret-cont)) (result (ref null $sub)))) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $ret-f (func (result (ref null $super)))) + (type $ret-f (func (result (ref null $super)))) + ;; CHECK: (type $ret-cont (cont $ret-f)) + (type $ret-cont (cont $ret-f)) + ) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (type $7 (func)) + + ;; CHECK: (tag $e (type $6) (result (ref null $super))) + (tag $e (result (ref null $super))) + + ;; CHECK: (func $switch-target-result (type $7) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (switch $cont $e + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch-target-result + (local $cont (ref null $cont)) + ;; The current continuation returns a $super (as indicated in the tag) and + ;; the target continuation will return a $sub. This requires $sub <: $super. + (switch $cont $e + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + (rec + ;; CHECK: (type $f (func (param (ref null $ret-cont)) (result (ref null $sub)))) + (type $f (func (param (ref null $ret-cont)) (result (ref null $sub)))) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $ret-f (func (result (ref null $super)))) + (type $ret-f (func (result (ref null $super)))) + ;; CHECK: (type $ret-cont (cont $ret-f)) + (type $ret-cont (cont $ret-f)) + ) + + ;; CHECK: (type $6 (func (result (ref null $sub)))) + + ;; CHECK: (type $7 (func)) + + ;; CHECK: (tag $e (type $6) (result (ref null $sub))) + (tag $e (result (ref null $sub))) + + ;; CHECK: (func $switch-return-result (type $7) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (switch $cont $e + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch-return-result + (local $cont (ref null $cont)) + ;; The current continuation returns a $sub (as indicated in the tag) and + ;; after the switch it will be given the return continuation type returning + ;; $super. This requires $sub <: $super. + (switch $cont $e + (local.get $cont) + ) + ) +) From 5b87a9648d30c8a50cd431de04c8cc461aaffa6e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 19 May 2025 12:58:38 -0700 Subject: [PATCH 3/4] remove debugging print --- src/wasm/wasm-ir-builder.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 0a7debcea37..d738df0c404 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2471,7 +2471,6 @@ IRBuilder::makeResume(HeapType ct, return Err{"the sizes of tags and labels must be equal"}; } if (!ct.isContinuation()) { - std::cout << ct.toString() << "\n"; return Err{"expected continuation type"}; } From e3df27101a377604dc7037b0f704cda35ac96cae Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 19 May 2025 13:00:50 -0700 Subject: [PATCH 4/4] add comment to test --- test/lit/passes/unsubtyping-stack-switching.wast | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/lit/passes/unsubtyping-stack-switching.wast b/test/lit/passes/unsubtyping-stack-switching.wast index e7b8fb6b64f..0d6e204cd66 100644 --- a/test/lit/passes/unsubtyping-stack-switching.wast +++ b/test/lit/passes/unsubtyping-stack-switching.wast @@ -385,6 +385,8 @@ ;; CHECK-NEXT: ) (func $switch-param (local $cont (ref null $cont)) + ;; The continuation expects a $super, but we send it a $sub. This requires + ;; $sub <: $super. (switch $cont $e (struct.new $sub) (local.get $cont)