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/test/lit/passes/unsubtyping-stack-switching.wast b/test/lit/passes/unsubtyping-stack-switching.wast new file mode 100644 index 00000000000..0d6e204cd66 --- /dev/null +++ b/test/lit/passes/unsubtyping-stack-switching.wast @@ -0,0 +1,480 @@ +;; 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)) + ;; The continuation expects a $super, but we send it a $sub. This requires + ;; $sub <: $super. + (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) + ) + ) +)