diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 5c2be29006f..f776f81700d 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -901,16 +901,57 @@ Result<> IRBuilder::pushScope(ScopeCtx&& scope) { scope.startPos = lastBinaryPos; lastBinaryPos = *binaryPos; } + // Tunnel catch pops directly into unnamed blocks. Allow this even when the + // block has no input type so we can round-trip WAT without the printer + // needing to synthesize block input types, as well as when the block's input + // type matches the pop's type. + bool isUnnamedBlock = scope.getBlock() && !scope.getBlock()->name; + bool hasPop = + getScope().exprStack.size() == 1 && getScope().exprStack[0]->is(); + if (isUnnamedBlock && hasPop && + (scope.inputType == Type::none || + scope.inputType == getScope().exprStack[0]->type)) { + // Move the pop into the new scope. + auto* pop = getScope().exprStack.back(); + getScope().exprStack.pop_back(); + scopeStack.emplace_back(std::move(scope)); + pushSynthetic(pop); + // If this unnamed block later receives a name from a branch targeting it, + // the nested pop will need to be fixed up. + scopeStack[0].noteSyntheticBlock(); + return Ok{}; + } + bool hasInput = scope.inputType != Type::none; if (hasInput && !scope.isDelimiter()) { auto inputs = ChildPopper{*this}.popInputs(scope.inputType); CHECK_ERR(inputs); + const auto& parentLocals = getScope().inputLocals; + // The new scope may only receive a suffix of the outer scope's parameters, + // so match inputs starting from the remaining size of the outer stack + // rather than from the beginning. + size_t baseIndex = getScope().exprStack.size(); scope.inputLocals.reserve(inputs->size()); for (size_t i = 0; i < inputs->size(); ++i) { - auto scratch = addScratchLocal(scope.inputType[i]); - CHECK_ERR(scratch); - scope.inputLocals.push_back(*scratch); - pushSynthetic(builder.makeLocalSet(*scratch, (*inputs)[i])); + // Reuse the enclosing scope's scratch local rather than allocating a new + // one when an input comes directly from an enclosing scope's input. + // Earlier inputs that allocate new scratch locals (e.g. due to type + // widening) are copied before this scope starts, which safely reorders + // scratch `local.get`s past any subsequent reused inputs. + auto* input = (*inputs)[i]; + size_t parentIndex = baseIndex + i; + bool isInputGet = + parentIndex < parentLocals.size() && input->is() && + input->cast()->index == parentLocals[parentIndex] && + input->type == scope.inputType[i]; + if (isInputGet) { + scope.inputLocals.push_back(parentLocals[parentIndex]); + } else { + auto scratch = addScratchLocal(scope.inputType[i]); + CHECK_ERR(scratch); + scope.inputLocals.push_back(*scratch); + pushSynthetic(builder.makeLocalSet(*scratch, input)); + } } } scopeStack.emplace_back(std::move(scope)); @@ -2022,38 +2063,22 @@ Result<> IRBuilder::makeUnreachable() { Result<> IRBuilder::makePop(Type type) { // We don't actually want to create a new Pop expression here because we // already create them automatically when starting a legacy catch block that - // needs one. Just verify that the Pop we are being asked to make is the same - // type as the Pop we have already made, and move it into the current scope if - // we have entered unnamed blocks since the catch began. (Note that the block - // may receive a name later, if we encounter a break to it, if we process - // that after the pop. In that case, the fixups that run later will have work - // to do.) - ScopeCtx* catchScope = nullptr; - for (int i = int(scopeStack.size()) - 1; i >= 0; --i) { - auto& scope = scopeStack[i]; - if (scope.getCatch()) { - catchScope = &scope; - break; - } - auto* block = scope.getBlock(); - if (!block || block->name || !scope.exprStack.empty()) { - break; + // needs one (and move them into nested unnamed blocks in pushScope). Just + // verify that the Pop we are being asked to make is the same type as the Pop + // we have already made. + auto& scope = getScope(); + if (scope.exprStack.size() != 1 || !scope.exprStack[0]->is()) { + if (auto* tryy = scope.getCatch(); + tryy && wasm.getTag(tryy->catchTags.back())->params() == Type::none) { + return Err{"cannot pop from empty tag"}; } - } - if (!catchScope || catchScope->exprStack.size() != 1 || - !catchScope->exprStack[0]->is()) { return Err{ "pop instructions may only appear at the beginning of catch blocks"}; } - auto* pop = catchScope->exprStack[0]; - auto expectedType = pop->type; - if (!Type::isSubType(expectedType, type)) { + auto expectedType = scope.exprStack[0]->type; + if (type != expectedType) { return Err{std::string("Expected pop of type ") + expectedType.toString()}; } - if (catchScope != &getScope()) { - catchScope->exprStack.pop_back(); - pushSynthetic(pop); - } return Ok{}; } diff --git a/test/lit/basic/exception-handling-legacy.wast b/test/lit/basic/exception-handling-legacy.wast index 11bdb4ca77c..a47932538cc 100644 --- a/test/lit/basic/exception-handling-legacy.wast +++ b/test/lit/basic/exception-handling-legacy.wast @@ -1181,39 +1181,6 @@ ) ) - ;; CHECK-TEXT: (func $pop-can-be-supertype (type $0) - ;; CHECK-TEXT-NEXT: (try - ;; CHECK-TEXT-NEXT: (do - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: (catch $e-eqref - ;; CHECK-TEXT-NEXT: (drop - ;; CHECK-TEXT-NEXT: (pop eqref) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $pop-can-be-supertype (type $0) - ;; CHECK-BIN-NEXT: (try - ;; CHECK-BIN-NEXT: (do - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: (catch $e-eqref - ;; CHECK-BIN-NEXT: (drop - ;; CHECK-BIN-NEXT: (pop eqref) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - ;; CHECK-BIN-NEXT: ) - (func $pop-can-be-supertype - (try - (do) - (catch $e-eqref - (drop - (pop anyref) ;; pop can be supertype - ) - ) - ) - ) - ;; CHECK-TEXT: (func $catchless-try-with-inner-delegate (type $0) ;; CHECK-TEXT-NEXT: (try $label$0 ;; CHECK-TEXT-NEXT: (do @@ -1780,18 +1747,6 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG: (func $22 (type $0) -;; CHECK-BIN-NODEBUG-NEXT: (try -;; CHECK-BIN-NODEBUG-NEXT: (do -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (catch $tag$3 -;; CHECK-BIN-NODEBUG-NEXT: (drop -;; CHECK-BIN-NODEBUG-NEXT: (pop eqref) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: ) - -;; CHECK-BIN-NODEBUG: (func $23 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (try $label ;; CHECK-BIN-NODEBUG-NEXT: (do ;; CHECK-BIN-NODEBUG-NEXT: (try @@ -1808,7 +1763,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (unreachable) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $24 (type $0) +;; CHECK-BIN-NODEBUG: (func $23 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (block ;; CHECK-BIN-NODEBUG-NEXT: (block ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1821,7 +1776,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $25 (type $0) +;; CHECK-BIN-NODEBUG: (func $24 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (if ;; CHECK-BIN-NODEBUG-NEXT: (try (result i32) ;; CHECK-BIN-NODEBUG-NEXT: (do diff --git a/test/lit/basic/pop-fixup.wast b/test/lit/basic/pop-fixup.wast index 5ba40f46924..5c6ec1d84d2 100644 --- a/test/lit/basic/pop-fixup.wast +++ b/test/lit/basic/pop-fixup.wast @@ -184,23 +184,17 @@ ;; CHECK: (func $stacky-block-param (type $0) ;; CHECK-NEXT: (local $scratch i32) - ;; CHECK-NEXT: (local $scratch_1 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e - ;; CHECK-NEXT: (local.set $scratch - ;; CHECK-NEXT: (pop i32) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch_1 - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -209,8 +203,8 @@ (func $stacky-block-param try catch $e - ;; Add a block with an input param, forcing us to use a local to capture the - ;; pop. + ;; An unnamed block with an input param moves the pop directly into the + ;; block rather than using an extra scratch local to capture it. block (param i32) nop drop @@ -290,4 +284,179 @@ end end ) + + ;; CHECK: (func $named-block (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $block) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $named-block + try + catch $e + ;; As in $stacky-named-block, but without the nop so no synthetic block is + ;; created for stacky code. The block still receives a name from the br, so + ;; the pop still needs to be fixed up. + block + pop i32 + drop + br 0 + end + end + ) + + ;; CHECK: (func $stacky-block-param-tuple (type $0) + ;; CHECK-NEXT: (local $scratch (tuple i32 i32)) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (pop (tuple i32 i32)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $stacky-block-param-tuple + try + catch $e2 + ;; An unnamed block with matching multivalue input params moves the + ;; multivalue pop directly into the block. + block (param i32 i32) + nop + drop + drop + end + end + ) + + ;; CHECK: (func $stacky-block-param-tuple-partial (type $0) + ;; CHECK-NEXT: (local $scratch (tuple i32 i32)) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (pop (tuple i32 i32)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $stacky-block-param-tuple-partial + try + catch $e2 + ;; An unnamed block whose input param only consumes part of a multivalue pop + ;; extracts the tuple elements before the block rather than moving the pop + ;; into the block. + block (param i32) + nop + drop + end + drop + end + ) + + ;; CHECK: (func $stacky-unnamed-block-tuple (type $0) + ;; CHECK-NEXT: (local $scratch (tuple i32 i32)) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e2 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (pop (tuple i32 i32)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $stacky-unnamed-block-tuple + try + catch $e2 + ;; An unnamed block without explicit input params moves a multivalue pop + ;; directly into the block. + block + pop (tuple i32 i32) + nop + drop + drop + end + end + ) ) diff --git a/test/lit/control-flow-input.wast b/test/lit/control-flow-input.wast index babf5293d47..52bf5cb0017 100644 --- a/test/lit/control-flow-input.wast +++ b/test/lit/control-flow-input.wast @@ -9,21 +9,28 @@ ;; RUN: wasm-opt -all %s.wasm -S -o - | filecheck %s (module - (type $id (func (param i32) (result i32))) - ;; CHECK: (type $0 (func (result i32 i64))) ;; CHECK: (type $1 (func (result i32))) ;; CHECK: (type $2 (func)) - ;; CHECK: (type $3 (func (param i32))) + ;; CHECK: (type $3 (func (result funcref i32))) + + ;; CHECK: (type $id (func (param i32) (result i32))) + (type $id (func (param i32) (result i32))) + + ;; CHECK: (type $5 (func (param i32))) + + ;; CHECK: (type $6 (func (param i64) (result i32 i64))) - ;; CHECK: (type $4 (func (result i64))) + ;; CHECK: (type $7 (func (result funcref))) - ;; CHECK: (type $5 (func (result f32))) + ;; CHECK: (type $8 (func (result i64))) - ;; CHECK: (tag $e (type $3) (param i32)) + ;; CHECK: (type $9 (func (result f32))) + + ;; CHECK: (tag $e (type $5) (param i32)) (tag $e (param i32)) ;; CHECK: (func $block (type $1) (result i32) @@ -247,6 +254,240 @@ end ) + ;; CHECK: (func $block-tunnel (type $1) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-tunnel (result i32) + ;; A parameter tunneled through nested blocks should reuse a single scratch + ;; local. + i32.const 0 + block (param i32) (result i32) + block (param i32) (result i32) + end + end + ) + + ;; CHECK: (func $block-double-tunnel (type $1) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-double-tunnel (result i32) + ;; A parameter tunneled through multiple nested blocks should still reuse a + ;; single scratch local. + i32.const 0 + block (param i32) (result i32) + block (param i32) (result i32) + block (param i32) (result i32) + end + end + end + ) + + ;; CHECK: (func $block-tunnel-multivalue (type $0) (result i32 i64) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_1 i64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $0) (result i32 i64) + ;; CHECK-NEXT: (block (type $0) (result i32 i64) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-tunnel-multivalue (result i32 i64) + ;; Multiple parameters tunneled through nested blocks should reuse the same + ;; scratch locals. + i32.const 0 + i64.const 1 + block (param i32 i64) (result i32 i64) + block (param i32 i64) (result i32 i64) + end + end + ) + + ;; CHECK: (func $block-tunnel-suffix (type $0) (result i32 i64) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_1 i64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $0) (result i32 i64) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-tunnel-suffix (result i32 i64) + ;; An inner block that only consumes a suffix of the enclosing block's + ;; parameters should still reuse the corresponding scratch local. + i32.const 0 + i64.const 1 + block (param i32 i64) (result i32 i64) + block (param i64) (result i64) + end + end + ) + + ;; CHECK: (func $block-tunnel-partial (type $6) (param $x i64) (result i32 i64) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_2 i64) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $0) (result i32 i64) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $0) (result i32 i64) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-tunnel-partial (param $x i64) (result i32 i64) + ;; A parameter tunneled alongside a newly pushed parameter should still + ;; reuse the enclosing block's scratch local. + i32.const 0 + block (param i32) (result i32 i64) + local.get $x + block (param i32 i64) (result i32 i64) + end + end + ) + + ;; CHECK: (func $block-tunnel-other-local (type $id) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local $scratch_3 i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-tunnel-other-local (param $x i32) (result i32) + ;; A local.get of a different local at a stack position corresponding to an + ;; enclosing block's input should not be mistaken for that input's scratch + ;; local. + i32.const 0 + i32.const 1 + block (param i32 i32) (result i32) + i32.add + drop + local.get $x + block (param i32) (result i32) + ;; $x should not be treated like a scratch input local here. + end + end + ) + + ;; CHECK: (func $block-tunnel-subtype (type $7) (result funcref) + ;; CHECK-NEXT: (local $scratch (ref null $id)) + ;; CHECK-NEXT: (local $scratch_1 funcref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result funcref) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result funcref) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-tunnel-subtype (result funcref) + ;; A parameter whose type is widened in a nested block cannot reuse the + ;; narrower scratch local because keeping the narrower type on the stack + ;; would allow us to parse invalid WAT that requires the narrower type. + ref.null $id + block (param (ref null $id)) (result funcref) + block (param funcref) (result funcref) + end + end + ) + + ;; CHECK: (func $block-tunnel-subtype-mixed (type $3) (result funcref i32) + ;; CHECK-NEXT: (local $scratch (ref null $id)) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (local $scratch_2 funcref) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $3) (result funcref i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (type $3) (result funcref i32) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-tunnel-subtype-mixed (result funcref i32) + ;; If an earlier parameter is widened and allocates a new scratch local, a + ;; subsequent parameter whose type matches can still reuse its scratch + ;; local. + ref.null $id + i32.const 0 + block (param (ref null $id) i32) (result funcref i32) + block (param funcref i32) (result funcref i32) + end + end + ) + ;; CHECK: (func $loop (type $1) (result i32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local.set $scratch @@ -348,7 +589,7 @@ end ) - ;; CHECK: (func $loop-branch-cond-new-val (type $4) (result i64) + ;; CHECK: (func $loop-branch-cond-new-val (type $8) (result i64) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (i32.const 0) @@ -387,10 +628,8 @@ ;; CHECK: (func $nested-loops-multivalue (type $0) (result i32 i64) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local $scratch_1 i64) - ;; CHECK-NEXT: (local $scratch_2 i32) - ;; CHECK-NEXT: (local $scratch_3 i64) - ;; CHECK-NEXT: (local $scratch_4 (tuple i32 i64)) - ;; CHECK-NEXT: (local $scratch_5 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_2 (tuple i32 i64)) + ;; CHECK-NEXT: (local $scratch_3 (tuple i32 i64)) ;; CHECK-NEXT: (block $label2 (type $0) (result i32 i64) ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (i32.const 0) @@ -400,36 +639,30 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (loop $label10 ;; CHECK-NEXT: (block $label11 - ;; CHECK-NEXT: (local.set $scratch_5 + ;; CHECK-NEXT: (local.set $scratch_3 ;; CHECK-NEXT: (block $label1 (type $0) (result i32 i64) - ;; CHECK-NEXT: (local.set $scratch_2 - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $scratch_3 - ;; CHECK-NEXT: (local.get $scratch_1) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (loop $label0 ;; CHECK-NEXT: (block $label3 - ;; CHECK-NEXT: (local.set $scratch_4 + ;; CHECK-NEXT: (local.set $scratch_2 ;; CHECK-NEXT: (block $label (type $0) (result i32 i64) ;; CHECK-NEXT: (br_table $label $label1 $label2 ;; CHECK-NEXT: (tuple.make 2 - ;; CHECK-NEXT: (local.get $scratch_2) - ;; CHECK-NEXT: (local.get $scratch_3) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (local.get $scratch_1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $label3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $scratch_3 + ;; CHECK-NEXT: (local.set $scratch_1 ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $scratch_4) + ;; CHECK-NEXT: (local.get $scratch_2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $label0) @@ -440,12 +673,12 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (tuple.extract 2 0 - ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: (local.get $scratch_3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $scratch_1 ;; CHECK-NEXT: (tuple.extract 2 1 - ;; CHECK-NEXT: (local.get $scratch_5) + ;; CHECK-NEXT: (local.get $scratch_3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $label10) @@ -466,6 +699,41 @@ unreachable ) + ;; CHECK: (func $block-loop-tunnel (type $1) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (loop $label0 (result i32) + ;; CHECK-NEXT: (block $label1 (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $label (result i32) + ;; CHECK-NEXT: (br $label1 + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $block-loop-tunnel (result i32) + ;; A parameter tunneled from a block into a loop should reuse the same + ;; scratch local. + i32.const 0 + block (param i32) (result i32) + loop (param i32) (result i32) + i32.const 1 + br_if 0 + end + end + ) + ;; CHECK: (func $if (type $1) (result i32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local.set $scratch @@ -600,7 +868,7 @@ end ) - ;; CHECK: (func $if-else-multivalue (type $5) (result f32) + ;; CHECK: (func $if-else-multivalue (type $9) (result f32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local $scratch_1 i64) ;; CHECK-NEXT: (local $scratch_2 i32) @@ -658,6 +926,84 @@ end ) + ;; CHECK: (func $if-tunnel (type $1) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-tunnel (result i32) + ;; A parameter tunneled through nested ifs and blocks should reuse the same + ;; scratch local in both arms. + i32.const 0 + i32.const 1 + if (param i32) (result i32) + i32.const 2 + if (param i32) (result i32) + end + else + block (param i32) (result i32) + end + end + ) + + ;; CHECK: (func $if-tunnel-nop (type $1) (result i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $scratch_1 i32) + ;; CHECK-NEXT: (local $scratch_2 i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.get $scratch_2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $if-tunnel-nop (result i32) + ;; A none-typed instruction before an if wraps the outer input in a block + ;; when popping the if input, requiring a new scratch local for the if. + i32.const 0 + block (param i32) (result i32) + nop + i32.const 1 + if (param i32) (result i32) + end + end + ) + ;; CHECK: (func $try (type $1) (result i32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local.set $scratch diff --git a/test/lit/control-flow-input.wast.wasm b/test/lit/control-flow-input.wast.wasm index 9c8408ca0a7..03f308713f7 100644 Binary files a/test/lit/control-flow-input.wast.wasm and b/test/lit/control-flow-input.wast.wasm differ diff --git a/test/lit/parse-bad-pop.wast b/test/lit/parse-bad-pop.wast new file mode 100644 index 00000000000..da99219ae9d --- /dev/null +++ b/test/lit/parse-bad-pop.wast @@ -0,0 +1,184 @@ +;; Test that misplaced pop instructions result in the expected parse errors + +;; RUN: foreach %s %t not wasm-opt -all 2>&1 | filecheck %s +;; RUN: foreach %s %t not wasm-shell 2>&1 | filecheck %s + +;; CHECK: 3:5: error: pop instructions may only appear at the beginning of catch blocks +(module + (func $empty-stack + (pop i32) + ) +) + +;; CHECK: 4:5: error: pop instructions may only appear at the beginning of catch blocks +(module + (func $non-pop-on-stack + (i32.const 0) + (pop i32) + ) +) + +;; CHECK: 5:9: error: pop instructions may only appear at the beginning of catch blocks +(module + (func $in-try-body + (try + (do + (pop i32) + ) + (catch_all) + ) + ) +) + +;; CHECK: 6:9: error: pop instructions may only appear at the beginning of catch blocks +(module + (func $in-catch-all + (try + (do) + (catch_all + (pop i32) + ) + ) + ) +) + +;; CHECK: 7:9: error: cannot pop from empty tag +(module + (tag $e) + (func $empty-tag-catch + (try + (do) + (catch $e + (pop i32) + ) + ) + ) +) + +;; CHECK: 8:9: error: pop instructions may only appear at the beginning of catch blocks +(module + (tag $e (param i32)) + (func $after-inst-in-catch + (try + (do) + (catch $e + (nop) + (pop i32) + ) + ) + ) +) + +;; CHECK: 8:11: error: pop instructions may only appear at the beginning of catch blocks +(module + (tag $e (param i32)) + (func $in-named-block + (try + (do) + (catch $e + (block $b + (pop i32) + ) + ) + ) + ) +) + +;; CHECK: 8:11: error: pop instructions may only appear at the beginning of catch blocks +(module + (tag $e (param i32)) + (func $in-loop + (try + (do) + (catch $e + (loop + (pop i32) + ) + ) + ) + ) +) + +;; CHECK: 7:9: error: Expected pop of type i32 +(module + (tag $e (param i32)) + (func $wrong-type + (try + (do) + (catch $e + (pop i64) + ) + ) + ) +) + +;; CHECK: 7:9: error: Expected pop of type anyref +(module + (tag $e (param anyref)) + (func $subtype + (try + (do) + (catch $e + (pop eqref) + ) + ) + ) +) + +;; CHECK: 7:9: error: Expected pop of type eqref +(module + (tag $e (param eqref)) + (func $supertype + (try + (do) + (catch $e + (pop anyref) + ) + ) + ) +) + +;; CHECK: 8:11: error: pop instructions may only appear at the beginning of catch blocks +(module + (tag $e (param i64)) + (func $in-mismatched-param-block + (try + (do) + (catch $e + (block (param i32) + (pop i64) + ) + ) + ) + ) +) + +;; CHECK: local.set's value type must be correct +(module + (tag $e (param i64)) + (func $implicit-mismatched-param-block + (try + (do) + (catch $e + (block (param i32) + (drop) + ) + ) + ) + ) +) + +;; CHECK: block with value and last element with value must match types +(module + (tag $e (param eqref)) + (func $implicit-supertype-param-block (result eqref) + (try (result eqref) + (do + (ref.null eq) + ) + (catch $e + (block (param anyref) (result eqref)) + ) + ) + ) +)