From 8e0a1d0e58096a3d17eac1b8c3bafa6b519c02a3 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Mar 2026 20:11:19 -0700 Subject: [PATCH 1/2] More unsubtyping for cont.bind We were missing a few type constraints for cont.bind. Add them, and also add more thorough tests for all the stack switching instructions, including the cases where the continuations are null. As a drive-by to get these tests working, fix the parsing of resume and resume_throw* to set the proper type arity when the input is null. --- src/ir/subtype-exprs.h | 34 +- src/wasm-builder.h | 26 +- src/wasm/wasm-ir-builder.cpp | 6 +- src/wasm/wasm-validator.cpp | 44 +- .../passes/unsubtyping-stack-switching.wast | 1539 ++++++++++++++--- 5 files changed, 1403 insertions(+), 246 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 25c3e842adc..801adcb37d4 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -494,32 +494,42 @@ struct SubtypingDiscoverer : public OverriddenVisitor { curr->type.getHeapType().getContinuation().type); } void visitContBind(ContBind* curr) { - if (!curr->cont->type.isContinuation()) { + if (!curr->cont->type.isContinuation() || !curr->type.isContinuation()) { return; } + auto inType = curr->cont->type.getHeapType(); + auto outType = curr->type.getHeapType(); + auto sigIn = inType.getContinuation().type.getSignature(); + auto sigOut = outType.getContinuation().type.getSignature(); + // 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]); + size_t numBound = curr->operands.size(); + for (Index i = 0; i < numBound; ++i) { + self()->noteSubtype(curr->operands[i], sigIn.params[i]); + } + // Each of the unbound output parameters must remain subtypes of their + // corresponding input parameters. + size_t numRemaining = sigIn.params.size() - numBound; + for (Index i = 0; i < numRemaining; ++i) { + self()->noteSubtype(sigOut.params[i], sigIn.params[numBound + i]); } + // The original input results must remain a subtype of the new output + // results. + self()->noteSubtype(sigIn.results, sigOut.results); } void visitSuspend(Suspend* curr) { + auto sig = self()->getModule()->getTag(curr->tag)->type.getSignature(); // 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()); + assert(curr->operands.size() == sig.params.size()); for (Index i = 0; i < curr->operands.size(); ++i) { - self()->noteSubtype(curr->operands[i], params[i]); + self()->noteSubtype(curr->operands[i], sig.params[i]); } } void processResumeHandlers(Type contType, const ArenaVector& handlerTags, const ArenaVector& handlerBlocks) { + assert(contType.isContinuation()); auto contSig = contType.getHeapType().getContinuation().type.getSignature(); assert(handlerTags.size() == handlerBlocks.size()); auto& wasm = *self()->getModule(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 8340ce8e860..3614eb21e5a 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1352,37 +1352,27 @@ class Builder { const std::vector& handlerBlocks, const std::vector& sentTypes, ExpressionList&& operands, - Expression* cont) { + Expression* cont, + HeapType contType) { auto* ret = wasm.allocator.alloc(); ret->handlerTags.set(handlerTags); ret->handlerBlocks.set(handlerBlocks); ret->sentTypes.set(sentTypes); ret->operands = std::move(operands); ret->cont = cont; + assert(contType.isContinuation()); + ret->type = contType.getContinuation().type.getSignature().results; ret->finalize(); return ret; } - template - Resume* makeResume(const std::vector& handlerTags, - const std::vector& handlerBlocks, - const std::vector& sentTypes, - ExpressionList& operands, - Expression* cont) { - auto* ret = wasm.allocator.alloc(); - ret->handlerTags.set(handlerTags); - ret->handlerBlocks.set(handlerBlocks); - ret->sentTypes.set(sentTypes); - ret->operands.set(operands); - ret->cont = cont; - ret->finalize(); - return ret; - } + ResumeThrow* makeResumeThrow(Name tag, const std::vector& handlerTags, const std::vector& handlerBlocks, const std::vector& sentTypes, ExpressionList&& operands, - Expression* cont) { + Expression* cont, + HeapType contType) { auto* ret = wasm.allocator.alloc(); ret->tag = tag; ret->handlerTags.set(handlerTags); @@ -1390,6 +1380,8 @@ class Builder { ret->sentTypes.set(sentTypes); ret->operands = std::move(operands); ret->cont = cont; + assert(contType.isContinuation()); + ret->type = contType.getContinuation().type.getSignature().results; ret->finalize(); return ret; } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index b635f989502..762c1dafd39 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2675,7 +2675,8 @@ IRBuilder::makeResume(HeapType ct, resumetable->targets, resumetable->sentTypes, std::move(curr.operands), - curr.cont)); + curr.cont, + ct)); return Ok{}; } @@ -2715,7 +2716,8 @@ IRBuilder::makeResumeThrow(HeapType ct, resumetable->targets, resumetable->sentTypes, std::move(curr.operands), - curr.cont)); + curr.cont, + ct)); return Ok{}; } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index f6185e2f448..81762c47853 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4510,6 +4510,28 @@ void FunctionValidator::visitResumeThrow(ResumeThrow* curr) { return; } + if (curr->cont->type == Type::unreachable) { + return; + } + + if (!shouldBeTrue(curr->cont->type.isRef(), + curr, + "resume_throw continuation must be a reference")) { + return; + } + + auto type = curr->cont->type.getHeapType(); + if (type.isMaybeShared(HeapType::nocont)) { + return; + } + + if (!shouldBeTrue( + type.isContinuation(), + curr, + "resume_throw continuation must have a defined continuation type")) { + return; + } + if (curr->tag) { // Normal resume_throw auto* tag = getModule()->getTagOrNull(curr->tag); @@ -4551,28 +4573,6 @@ void FunctionValidator::visitResumeThrow(ResumeThrow* curr) { } } - if (curr->cont->type == Type::unreachable) { - return; - } - - if (!shouldBeTrue(curr->cont->type.isRef(), - curr, - "resume_throw continuation must be a reference")) { - return; - } - - auto type = curr->cont->type.getHeapType(); - if (type.isMaybeShared(HeapType::nocont)) { - return; - } - - if (!shouldBeTrue( - type.isContinuation(), - curr, - "resume_throw continuation must have a defined continuation type")) { - return; - } - auto sig = type.getContinuation().type.getSignature(); shouldBeEqualOrFirstIsUnreachable( diff --git a/test/lit/passes/unsubtyping-stack-switching.wast b/test/lit/passes/unsubtyping-stack-switching.wast index fd11ecfe92a..2bd95096208 100644 --- a/test/lit/passes/unsubtyping-stack-switching.wast +++ b/test/lit/passes/unsubtyping-stack-switching.wast @@ -23,7 +23,8 @@ ;; CHECK-NEXT: ) (func $cont-new (type $sub) (drop - ;; This requires $sub <: $super. + ;; This requires $sub <: $super because the operand is expected to be a + ;; $super but is actually a $sub. (cont.new $cont (ref.func $cont-new) ) @@ -48,7 +49,7 @@ ;; CHECK: (type $cont-none (cont $none)) (type $cont-none (cont $none)) - ;; CHECK: (func $cont-bind (type $none) + ;; CHECK: (func $cont-bind-operand (type $none) ;; CHECK-NEXT: (local $one (ref null $cont-one)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (cont.bind $cont-one $cont-none @@ -57,10 +58,11 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cont-bind + (func $cont-bind-operand (local $one (ref null $cont-one)) (drop - ;; This requires $sub <: $super. + ;; This requires $sub <: $super because the operand is expected to be a + ;; $super. (cont.bind $cont-one $cont-none (struct.new $sub) (local.get $one) @@ -69,6 +71,256 @@ ) ) +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (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)) + (type $cont-none (cont $none)) + + ;; CHECK: (func $cont-bind-operand (type $none) + ;; CHECK-NEXT: (local $one (ref null $cont-one)) + ;; CHECK-NEXT: (local $use-sub (ref null $sub)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ContBind we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $one) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-bind-operand + (local $one (ref null $cont-one)) + (local $use-sub (ref null $sub)) + (drop + ;; Same as above, but now the operand is unreachable. We can optimize and + ;; should not crash. + (cont.bind $cont-one $cont-none + (unreachable) + (local.get $one) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (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-operand (type $none) + ;; CHECK-NEXT: (local $one (ref null $cont-one)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ContBind we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-bind-operand + (local $one (ref null $cont-one)) + (drop + ;; Same as above, but now the continuation is null so we can optimize. + (cont.bind $cont-one $cont-none + (struct.new $sub) + (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 $in (func (param (ref $super)))) + (type $in (func (param (ref $super)))) + ;; CHECK: (type $out (func (param (ref $sub)))) + (type $out (func (param (ref $sub)))) + + ;; CHECK: (type $cont-in (cont $in)) + (type $cont-in (cont $in)) + ;; CHECK: (type $cont-out (cont $out)) + (type $cont-out (cont $out)) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (func $cont-bind-param (type $6) + ;; CHECK-NEXT: (local $in (ref null $cont-in)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (cont.bind $cont-in $cont-out + ;; CHECK-NEXT: (local.get $in) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-bind-param + (local $in (ref null $cont-in)) + (drop + ;; This requires $sub <: $super because the output continuation parameters + ;; must be subtypes of the unbound input continuation parameters. + (cont.bind $cont-in $cont-out + (local.get $in) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $in (func (param (ref $super)))) + (type $in (func (param (ref $super)))) + ;; CHECK: (type $out (func (param (ref $sub)))) + (type $out (func (param (ref $sub)))) + + ;; CHECK: (type $cont-in (cont $in)) + (type $cont-in (cont $in)) + ;; CHECK: (type $cont-out (cont $out)) + (type $cont-out (cont $out)) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (func $cont-bind-param (type $6) + ;; CHECK-NEXT: (local $in (ref null $cont-in)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ContBind we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-bind-param + (local $in (ref null $cont-in)) + (drop + ;; Same as above, but now the continuation is null so we can optimize. + (cont.bind $cont-in $cont-out + (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 $in (func (result (ref null $sub)))) + (type $in (func (result (ref null $sub)))) + ;; CHECK: (type $out (func (result (ref null $super)))) + (type $out (func (result (ref null $super)))) + + ;; CHECK: (type $cont-in (cont $in)) + (type $cont-in (cont $in)) + ;; CHECK: (type $cont-out (cont $out)) + (type $cont-out (cont $out)) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (func $cont-bind-result (type $6) + ;; CHECK-NEXT: (local $in (ref null $cont-in)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (cont.bind $cont-in $cont-out + ;; CHECK-NEXT: (local.get $in) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-bind-result + (local $in (ref null $cont-in)) + (drop + ;; This requires $sub <: $super because the result of the input + ;; continuation must be a subtype of the result of the output + ;; continuation. + (cont.bind $cont-in $cont-out + (local.get $in) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $in (func (result (ref null $sub)))) + (type $in (func (result (ref null $sub)))) + ;; CHECK: (type $out (func (result (ref null $super)))) + (type $out (func (result (ref null $super)))) + + ;; CHECK: (type $cont-in (cont $in)) + (type $cont-in (cont $in)) + ;; CHECK: (type $cont-out (cont $out)) + (type $cont-out (cont $out)) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (func $cont-bind-result (type $6) + ;; CHECK-NEXT: (local $in (ref null $cont-in)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ContBind we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-bind-result + (local $in (ref null $cont-in)) + (drop + ;; Same as above, but now the continuation is null so we can optimize. + (cont.bind $cont-in $cont-out + (ref.null nocont) + ) + ) + ) +) + + (module ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) @@ -80,17 +332,17 @@ ;; CHECK: (type $3 (func)) - ;; CHECK: (tag $e (type $2) (param (ref null $super))) - (tag $e (param (ref null $super))) + ;; CHECK: (tag $take-super (type $2) (param (ref null $super))) + (tag $take-super (param (ref null $super))) - ;; CHECK: (func $suspend (type $3) - ;; CHECK-NEXT: (suspend $e + ;; CHECK: (func $suspend-operand (type $3) + ;; CHECK-NEXT: (suspend $take-super ;; CHECK-NEXT: (struct.new_default $sub) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $suspend + (func $suspend-operand ;; This requires $sub <: $super. - (suspend $e + (suspend $take-super (struct.new $sub) ) ) @@ -131,7 +383,818 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) - ;; CHECK: (type $sub (sub $super (struct))) + ;; CHECK: (type $sub (sub (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: (block ;; (replaces unreachable Resume we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-param + (local $cont (ref null $cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (resume $cont + (struct.new $sub) + (ref.null nocont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $f (func)) + (type $f (func)) + + (type $super (sub (cont $f))) + ;; CHECK: (type $sub (sub (cont $f))) + (type $sub (sub $super (cont $f))) + + ;; CHECK: (func $resume-ref (type $f) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (resume $sub + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-ref + (local $sub (ref null $sub)) + ;; This does NOT require $sub <: $super because we turn this into a + ;; `resume $sub`. + (resume $super + (local.get $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)) + (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 $send-sub (type $4) (param (ref null $sub))) + (tag $send-sub (param (ref null $sub))) + + ;; CHECK: (func $resume-tag-param (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 $send-sub $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-tag-param + (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 $send-sub $l) + (local.get $cont) + ) + (unreachable) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (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 $send-sub (type $4) (param (ref null $sub))) + (tag $send-sub (param (ref null $sub))) + + ;; CHECK: (func $resume-tag-param (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: (block ;; (replaces unreachable Resume we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-tag-param + (local $cont (ref null $cont)) + (tuple.drop 2 + (block $l (result (ref null $super) (ref null $cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (resume $cont (on $send-sub $l) + (ref.null nocont) + ) + (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 $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $receive-sub (func (param (ref null $sub)))) + (type $receive-sub (func (param (ref null $sub)))) + ;; CHECK: (type $receive-sub-cont (cont $receive-sub)) + (type $receive-sub-cont (cont $receive-sub)) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (tag $expect-super (type $6) (result (ref null $super))) + (tag $expect-super (result (ref null $super))) + + ;; CHECK: (func $resume-tag-result (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $receive-sub-cont)) + ;; CHECK-NEXT: (resume $cont (on $expect-super $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-tag-result + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $receive-sub-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 $expect-super $l) + (local.get $cont) + ) + (ref.null nocont) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $receive-sub (func (param (ref null $sub)))) + (type $receive-sub (func (param (ref null $sub)))) + ;; CHECK: (type $receive-sub-cont (cont $receive-sub)) + (type $receive-sub-cont (cont $receive-sub)) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (tag $expect-super (type $6) (result (ref null $super))) + (tag $expect-super (result (ref null $super))) + + ;; CHECK: (func $resume-tag-result (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $receive-sub-cont)) + ;; CHECK-NEXT: (block ;; (replaces unreachable Resume we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-tag-result + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $receive-sub-cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (resume $cont (on $expect-super $l) + (ref.null nocont) + ) + (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 $return-sub (func (result (ref null $sub)))) + (type $return-sub (func (result (ref null $sub)))) + ;; CHECK: (type $return-super (func (result (ref null $super)))) + (type $return-super (func (result (ref null $super)))) + + ;; CHECK: (type $return-sub-cont (cont $return-sub)) + (type $return-sub-cont (cont $return-sub)) + ;; CHECK: (type $return-super-cont (cont $return-super)) + (type $return-super-cont (cont $return-super)) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (tag $e (type $6)) + (tag $e) + + ;; CHECK: (func $resume-cont-result (type $6) + ;; CHECK-NEXT: (local $cont (ref null $return-sub-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $return-super-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (resume $return-sub-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-cont-result + (local $cont (ref null $return-sub-cont)) + (drop + (block $l (result (ref null $return-super-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 $return-sub-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 (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $return-sub (func (result (ref null $sub)))) + (type $return-sub (func (result (ref null $sub)))) + ;; CHECK: (type $return-super (func (result (ref null $super)))) + (type $return-super (func (result (ref null $super)))) + + ;; CHECK: (type $return-sub-cont (cont $return-sub)) + (type $return-sub-cont (cont $return-sub)) + ;; CHECK: (type $return-super-cont (cont $return-super)) + (type $return-super-cont (cont $return-super)) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (tag $e (type $6)) + (tag $e) + + ;; CHECK: (func $resume-cont-result (type $6) + ;; CHECK-NEXT: (local $cont (ref null $return-sub-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $return-super-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable Resume we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-cont-result + (local $cont (ref null $return-sub-cont)) + (drop + (block $l (result (ref null $return-super-cont)) + (drop + ;; Same as above, but now the continuation is null so we can optimize. + (resume $return-sub-cont (on $e $l) + (ref.null nocont) + ) + ) + (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 $throw-super (type $4) (param (ref null $super))) + (tag $throw-super (param (ref null $super))) + + ;; CHECK: (func $resume-throw-param (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (resume_throw $cont $throw-super + ;; 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 $throw-super + (struct.new $sub) + (local.get $cont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (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 $throw-super (type $4) (param (ref null $super))) + (tag $throw-super (param (ref null $super))) + + ;; CHECK: (func $resume-throw-param (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (block ;; (replaces unreachable ResumeThrow we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-param + (local $cont (ref null $cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (resume_throw $cont $throw-super + (struct.new $sub) + (ref.null nocont) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $f (func)) + (type $f (func)) + + (type $super (sub (cont $f))) + ;; CHECK: (type $sub (sub (cont $f))) + (type $sub (sub $super (cont $f))) + + ;; CHECK: (tag $e (type $f)) + (tag $e) + + ;; CHECK: (func $resume-throw-ref (type $f) + ;; CHECK-NEXT: (local $sub (ref null $sub)) + ;; CHECK-NEXT: (resume_throw $sub $e + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-ref + (local $sub (ref null $sub)) + ;; This does NOT require $sub <: $super because we turn this into a + ;; `resume $sub`. + (resume_throw $super $e + (local.get $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)) + (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 $send-sub (type $4) (param (ref null $sub))) + (tag $send-sub (param (ref null $sub))) + + ;; CHECK: (tag $e (type $f)) + (tag $e) + + ;; CHECK: (func $resume-throw-tag-param (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 $send-sub $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-tag-param + (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 $send-sub $l) + (local.get $cont) + ) + (unreachable) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (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 $send-sub (type $4) (param (ref null $sub))) + (tag $send-sub (param (ref null $sub))) + + ;; CHECK: (tag $e (type $f)) + (tag $e) + + ;; CHECK: (func $resume-throw-tag-param (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: (block ;; (replaces unreachable ResumeThrow we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-tag-param + (local $cont (ref null $cont)) + (tuple.drop 2 + (block $l (result (ref null $super) (ref null $cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (resume_throw $cont $e (on $send-sub $l) + (ref.null nocont) + ) + (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 $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 $send-sub (type $4) (param (ref null $sub))) + (tag $send-sub (param (ref null $sub))) + + ;; CHECK: (func $resume-throw-ref-tag-param (type $f) + ;; CHECK-NEXT: (local $exn exnref) + ;; 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_ref $cont (on $send-sub $l) + ;; CHECK-NEXT: (local.get $exn) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-ref-tag-param + (local $exn exnref) + (local $cont (ref null $cont)) + (tuple.drop 2 + (block $l (result (ref null $super) (ref null $cont)) + ;; Now with a resume_throw_ref. This requies $sub <: $super. + (resume_throw_ref $cont (on $send-sub $l) + (local.get $exn) + (local.get $cont) + ) + (unreachable) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (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 $send-sub (type $4) (param (ref null $sub))) + (tag $send-sub (param (ref null $sub))) + + ;; CHECK: (func $resume-throw-ref-tag-param (type $f) + ;; CHECK-NEXT: (local $exn exnref) + ;; 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: (block ;; (replaces unreachable ResumeThrow we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $exn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-ref-tag-param + (local $exn exnref) + (local $cont (ref null $cont)) + (tuple.drop 2 + (block $l (result (ref null $super) (ref null $cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (resume_throw_ref $cont (on $send-sub $l) + (local.get $exn) + (ref.null nocont) + ) + (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 $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $receive-sub (func (param (ref null $sub)))) + (type $receive-sub (func (param (ref null $sub)))) + ;; CHECK: (type $receive-sub-cont (cont $receive-sub)) + (type $receive-sub-cont (cont $receive-sub)) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (tag $expect-super (type $6) (result (ref null $super))) + (tag $expect-super (result (ref null $super))) + + ;; CHECK: (tag $e (type $f)) + (tag $e) + + ;; CHECK: (func $resume-throw-tag-result (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $receive-sub-cont)) + ;; CHECK-NEXT: (resume_throw $cont $e (on $expect-super $l) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-tag-result + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $receive-sub-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_throw $cont $e (on $expect-super $l) + (local.get $cont) + ) + (ref.null nocont) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $cont (cont $f)) + (type $cont (cont $f)) + + ;; CHECK: (type $receive-sub (func (param (ref null $sub)))) + (type $receive-sub (func (param (ref null $sub)))) + ;; CHECK: (type $receive-sub-cont (cont $receive-sub)) + (type $receive-sub-cont (cont $receive-sub)) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (tag $expect-super (type $6) (result (ref null $super))) + (tag $expect-super (result (ref null $super))) + + ;; CHECK: (tag $e (type $f)) + (tag $e) + + ;; CHECK: (func $resume-throw-tag-result (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $receive-sub-cont)) + ;; CHECK-NEXT: (block ;; (replaces unreachable ResumeThrow we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-tag-result + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $receive-sub-cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (resume_throw $cont $e (on $expect-super $l) + (ref.null nocont) + ) + (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 $receive-sub (func (param (ref null $sub)))) + (type $receive-sub (func (param (ref null $sub)))) + ;; CHECK: (type $receive-sub-cont (cont $receive-sub)) + (type $receive-sub-cont (cont $receive-sub)) + + ;; CHECK: (type $6 (func (result (ref null $super)))) + + ;; CHECK: (tag $expect-super (type $6) (result (ref null $super))) + (tag $expect-super (result (ref null $super))) + + ;; CHECK: (func $resume-throw-ref-tag-result (type $f) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $receive-sub-cont)) + ;; CHECK-NEXT: (resume_throw_ref $cont (on $expect-super $l) + ;; CHECK-NEXT: (ref.null noexn) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-ref-tag-result + (local $cont (ref null $cont)) + (drop + (block $l (result (ref null $receive-sub-cont)) + ;; Now with resume_throw_ref. This requirest $sub <: $super. + (resume_throw_ref $cont (on $expect-super $l) + (ref.null noexn) + (local.get $cont) + ) + (ref.null nocont) + ) + ) + ) +) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (struct))) (type $sub (sub $super (struct))) ;; CHECK: (type $f (func)) @@ -139,33 +1202,43 @@ ;; CHECK: (type $cont (cont $f)) (type $cont (cont $f)) - ;; CHECK: (type $4 (func (param (ref null $sub)))) + ;; CHECK: (type $receive-sub (func (param (ref null $sub)))) + (type $receive-sub (func (param (ref null $sub)))) + ;; CHECK: (type $receive-sub-cont (cont $receive-sub)) + (type $receive-sub-cont (cont $receive-sub)) - ;; CHECK: (type $5 (func (result (ref null $super) (ref null $cont)))) + ;; CHECK: (type $6 (func (result (ref null $super)))) - ;; CHECK: (tag $e (type $4) (param (ref null $sub))) - (tag $e (param (ref null $sub))) + ;; CHECK: (tag $expect-super (type $6) (result (ref null $super))) + (tag $expect-super (result (ref null $super))) - ;; CHECK: (func $resume-label (type $f) + ;; CHECK: (func $resume-throw-ref-tag-result (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: (drop + ;; CHECK-NEXT: (block $l (result (ref null $receive-sub-cont)) + ;; CHECK-NEXT: (block ;; (replaces unreachable ResumeThrow we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null noexn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.null nocont) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $resume-label + (func $resume-throw-ref-tag-result (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) + (drop + (block $l (result (ref null $receive-sub-cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (resume_throw_ref $cont (on $expect-super $l) + (ref.null noexn) + (ref.null nocont) ) - (unreachable) + (ref.null nocont) ) ) ) @@ -178,41 +1251,44 @@ ;; 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 $return-sub (func (result (ref null $sub)))) + (type $return-sub (func (result (ref null $sub)))) + ;; CHECK: (type $return-super (func (result (ref null $super)))) + (type $return-super (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 $return-sub-cont (cont $return-sub)) + (type $return-sub-cont (cont $return-sub)) + ;; CHECK: (type $return-super-cont (cont $return-super)) + (type $return-super-cont (cont $return-super)) - ;; CHECK: (type $6 (func (result (ref null $super)))) + ;; CHECK: (type $6 (func)) - ;; CHECK: (tag $e (type $6) (result (ref null $super))) - (tag $e (result (ref null $super))) + ;; CHECK: (tag $e (type $6)) + (tag $e) - ;; CHECK: (func $resume-tag (type $f) - ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK: (func $resume-throw-cont-result (type $6) + ;; CHECK-NEXT: (local $cont (ref null $return-sub-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: (block $l (result (ref null $return-super-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (resume_throw $return-sub-cont $e (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-tag - (local $cont (ref null $cont)) + (func $resume-throw-cont-result + (local $cont (ref null $return-sub-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) + (block $l (result (ref null $return-super-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_throw $return-sub-cont $e (on $e $l) + (local.get $cont) + ) ) (ref.null nocont) ) @@ -224,46 +1300,48 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) - ;; CHECK: (type $sub (sub $super (struct))) + ;; CHECK: (type $sub (sub (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 $return-sub (func (result (ref null $sub)))) + (type $return-sub (func (result (ref null $sub)))) + ;; CHECK: (type $return-super (func (result (ref null $super)))) + (type $return-super (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 $return-sub-cont (cont $return-sub)) + (type $return-sub-cont (cont $return-sub)) + ;; CHECK: (type $return-super-cont (cont $return-super)) + (type $return-super-cont (cont $return-super)) ;; 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: (func $resume-throw-cont-result (type $6) + ;; CHECK-NEXT: (local $cont (ref null $return-sub-cont)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $l (result (ref null $next-cont)) + ;; CHECK-NEXT: (block $l (result (ref null $return-super-cont)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (resume $cont (on $e $l) - ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: (block ;; (replaces unreachable ResumeThrow we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null nocont) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $resume-result - (local $cont (ref null $cont)) + (func $resume-throw-cont-result + (local $cont (ref null $return-sub-cont)) (drop - (block $l (result (ref null $next-cont)) + (block $l (result (ref null $return-super-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) + ;; Same as above, but now the continuation is null so we can optimize. + (resume_throw $return-sub-cont $e (on $e $l) + (ref.null nocont) ) ) (ref.null nocont) @@ -279,29 +1357,49 @@ ;; 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 $return-sub (func (result (ref null $sub)))) + (type $return-sub (func (result (ref null $sub)))) + ;; CHECK: (type $return-super (func (result (ref null $super)))) + (type $return-super (func (result (ref null $super)))) - ;; CHECK: (type $4 (func (param (ref null $super)))) + ;; CHECK: (type $return-sub-cont (cont $return-sub)) + (type $return-sub-cont (cont $return-sub)) + ;; CHECK: (type $return-super-cont (cont $return-super)) + (type $return-super-cont (cont $return-super)) - ;; CHECK: (tag $e (type $4) (param (ref null $super))) - (tag $e (param (ref null $super))) + ;; CHECK: (type $6 (func)) - ;; 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: (tag $e (type $6)) + (tag $e) + + ;; CHECK: (func $resume-throw-ref-cont-result (type $6) + ;; CHECK-NEXT: (local $cont (ref null $return-sub-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $return-super-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (resume_throw_ref $return-sub-cont (on $e $l) + ;; CHECK-NEXT: (ref.null noexn) + ;; CHECK-NEXT: (local.get $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) ;; 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) + (func $resume-throw-ref-cont-result + (local $cont (ref null $return-sub-cont)) + (drop + (block $l (result (ref null $return-super-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_throw_ref $return-sub-cont (on $e $l) + (ref.null noexn) + (local.get $cont) + ) + ) + (ref.null nocont) + ) ) ) ) @@ -310,43 +1408,55 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) (type $super (sub (struct))) - ;; CHECK: (type $sub (sub $super (struct))) + ;; CHECK: (type $sub (sub (struct))) (type $sub (sub $super (struct))) - ;; CHECK: (type $f (func)) - (type $f (func)) - ;; CHECK: (type $cont (cont $f)) - (type $cont (cont $f)) + ;; CHECK: (type $return-sub (func (result (ref null $sub)))) + (type $return-sub (func (result (ref null $sub)))) + ;; CHECK: (type $return-super (func (result (ref null $super)))) + (type $return-super (func (result (ref null $super)))) - ;; CHECK: (type $4 (func (param (ref null $sub)))) + ;; CHECK: (type $return-sub-cont (cont $return-sub)) + (type $return-sub-cont (cont $return-sub)) + ;; CHECK: (type $return-super-cont (cont $return-super)) + (type $return-super-cont (cont $return-super)) - ;; CHECK: (type $5 (func (result (ref null $super) (ref null $cont)))) + ;; CHECK: (type $6 (func)) - ;; CHECK: (tag $e (type $4) (param (ref null $sub))) - (tag $e (param (ref null $sub))) + ;; CHECK: (tag $e (type $6)) + (tag $e) - ;; 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: (func $resume-throw-ref-cont-result (type $6) + ;; CHECK-NEXT: (local $cont (ref null $return-sub-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $return-super-cont)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ResumeThrow we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null noexn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.null nocont) ;; 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) + (func $resume-throw-ref-cont-result + (local $cont (ref null $return-sub-cont)) + (drop + (block $l (result (ref null $return-super-cont)) + (drop + ;; Same as above, but now the continuation is null so we can optimize. + (resume_throw_ref $return-sub-cont (on $e $l) + (ref.null noexn) + (ref.null nocont) + ) ) - (unreachable) + (ref.null nocont) ) ) ) @@ -394,6 +1504,52 @@ ) ) +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (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: (block ;; (replaces unreachable StackSwitch we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch-param + (local $cont (ref null $cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (switch $cont $e + (struct.new $sub) + (ref.null nocont) + ) + ) +) + (module ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) @@ -436,6 +1592,50 @@ ) ) +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (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: (block ;; (replaces unreachable StackSwitch we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $switch-target-result + (local $cont (ref null $cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (switch $cont $e + (ref.null nocont) + ) + ) +) + (module ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) @@ -479,48 +1679,47 @@ ) ) -;; Test we do not error on resume_throw_ref. It adds no subtyping constraints -;; between declared types besides those from the handlers. (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $f (func)) + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub (struct))) + (type $sub (sub $super (struct))) - ;; CHECK: (type $k (cont $f)) + (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: (elem declare func $no_handler) + ;; 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: (tag $e0 (type $f)) - (tag $e0) + ;; CHECK: (type $6 (func (result (ref null $sub)))) - (type $f (func)) - (type $k (cont $f)) + ;; CHECK: (type $7 (func)) - ;; CHECK: (func $no_handler (type $f) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - (func $no_handler - (unreachable) - ) + ;; CHECK: (tag $e (type $6) (result (ref null $sub))) + (tag $e (result (ref null $sub))) - ;; CHECK: (func $throw_unhandled_ref (type $f) - ;; CHECK-NEXT: (resume_throw_ref $k - ;; CHECK-NEXT: (block $h (result (ref exn)) - ;; CHECK-NEXT: (try_table (catch_ref $e0 $h) - ;; CHECK-NEXT: (throw $e0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (cont.new $k - ;; CHECK-NEXT: (ref.func $no_handler) + ;; CHECK: (func $switch-return-result (type $7) + ;; CHECK-NEXT: (local $cont (ref null $cont)) + ;; CHECK-NEXT: (block ;; (replaces unreachable StackSwitch we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null nocont) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $throw_unhandled_ref - (block $h (result exnref) - (try_table (catch_ref $e0 $h) (throw $e0)) - (unreachable) + (func $switch-return-result + (local $cont (ref null $cont)) + ;; Same as above, but now the continuation is null so we can optimize. + (switch $cont $e + (ref.null nocont) ) - (resume_throw_ref $k (cont.new $k (ref.func $no_handler))) ) ) @@ -591,50 +1790,4 @@ ) ) -(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-super (sub (cont $super))) - (type $cont-super (sub (cont $super))) - ;; CHECK: (type $cont-sub (sub $cont-super (cont $sub))) - (type $cont-sub (sub $cont-super (cont $sub))) - - ;; CHECK: (type $4 (func (param (ref $cont-sub)) (result (ref $cont-super)))) - - ;; CHECK: (func $cont-new (type $4) (param $sub (ref $cont-sub)) (result (ref $cont-super)) - ;; CHECK-NEXT: (local.get $sub) - ;; CHECK-NEXT: ) - (func $cont-new (param $sub (ref $cont-sub)) (result (ref $cont-super)) - ;; This requires $cont-sub <: $cont-super and $sub <: $super. - (local.get $sub) - ) -) - -(module - ;; CHECK: (rec - ;; CHECK-NEXT: (type $super (sub (func))) - (type $super (sub (func))) - ;; CHECK: (type $sub (sub (func))) - (type $sub (sub $super (func))) - ;; CHECK: (type $cont-super (sub (cont $super))) - (type $cont-super (sub (cont $super))) - ;; CHECK: (type $cont-sub (sub (cont $sub))) - (type $cont-sub (sub $cont-super (cont $sub))) - - ;; CHECK: (type $4 (func (param (ref $cont-sub)) (result (ref $cont-sub)))) - - ;; CHECK: (func $cont-new (type $4) (param $sub (ref $cont-sub)) (result (ref $cont-sub)) - ;; CHECK-NEXT: (local $keepalive (ref $cont-super)) - ;; CHECK-NEXT: (local.get $sub) - ;; CHECK-NEXT: ) - (func $cont-new (param $sub (ref $cont-sub)) (result (ref $cont-sub)) - (local $keepalive (ref $cont-super)) - ;; As above, but now we return the subtype, so there is no constraint. We - ;; can unsubtype both the continuations and the funcs. - (local.get $sub) - ) -) From 9694cc25603caacb65028ceec8c781fb49c5faba Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 20 Mar 2026 20:22:11 -0700 Subject: [PATCH 2/2] restore deleted tests --- .../passes/unsubtyping-stack-switching.wast | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/lit/passes/unsubtyping-stack-switching.wast b/test/lit/passes/unsubtyping-stack-switching.wast index 2bd95096208..5703dd4ff7e 100644 --- a/test/lit/passes/unsubtyping-stack-switching.wast +++ b/test/lit/passes/unsubtyping-stack-switching.wast @@ -1790,4 +1790,49 @@ ) ) +(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-super (sub (cont $super))) + (type $cont-super (sub (cont $super))) + ;; CHECK: (type $cont-sub (sub $cont-super (cont $sub))) + (type $cont-sub (sub $cont-super (cont $sub))) + + ;; CHECK: (type $4 (func (param (ref $cont-sub)) (result (ref $cont-super)))) + + ;; CHECK: (func $cont-subtyping (type $4) (param $sub (ref $cont-sub)) (result (ref $cont-super)) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + (func $cont-subtyping (param $sub (ref $cont-sub)) (result (ref $cont-super)) + ;; This requires $cont-sub <: $cont-super and $sub <: $super. + (local.get $sub) + ) +) +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (func))) + (type $super (sub (func))) + ;; CHECK: (type $sub (sub (func))) + (type $sub (sub $super (func))) + ;; CHECK: (type $cont-super (sub (cont $super))) + (type $cont-super (sub (cont $super))) + ;; CHECK: (type $cont-sub (sub (cont $sub))) + (type $cont-sub (sub $cont-super (cont $sub))) + + ;; CHECK: (type $4 (func (param (ref $cont-sub)) (result (ref $cont-sub)))) + + ;; CHECK: (func $cont-unsubtyping (type $4) (param $sub (ref $cont-sub)) (result (ref $cont-sub)) + ;; CHECK-NEXT: (local $keepalive (ref $cont-super)) + ;; CHECK-NEXT: (local.get $sub) + ;; CHECK-NEXT: ) + (func $cont-unsubtyping (param $sub (ref $cont-sub)) (result (ref $cont-sub)) + (local $keepalive (ref $cont-super)) + ;; As above, but now we return the subtype, so there is no constraint. We + ;; can unsubtype both the continuations and the funcs. + (local.get $sub) + ) +)