diff options
-rw-r--r-- | src/wasm-ir-builder.h | 51 | ||||
-rw-r--r-- | src/wasm/wasm-ir-builder.cpp | 454 | ||||
-rw-r--r-- | src/wasm/wat-parser.cpp | 82 | ||||
-rw-r--r-- | test/lit/wat-kitchen-sink.wast | 223 |
4 files changed, 475 insertions, 335 deletions
diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 6a1b3f18a..3805a3e38 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -42,9 +42,7 @@ public: // Get the valid Binaryen IR expression representing the sequence of visited // instructions. The IRBuilder is reset and can be used with a fresh sequence // of instructions after this is called. - Expression* build(); - - [[nodiscard]] Result<std::vector<Expression*>> finishInstrs(); + [[nodiscard]] Result<Expression*> build(); // Call visit() on an existing Expression with its non-child fields // initialized to initialize the child fields and refinalize it. The specific @@ -56,11 +54,13 @@ public: [[nodiscard]] Result<> visitStructNew(StructNew*); [[nodiscard]] Result<> visitArrayNew(ArrayNew*); + [[nodiscard]] Result<> visitEnd(); + // Alternatively, call makeXYZ to have the IRBuilder allocate the nodes. This // is generally safer than calling `visit` because the function signatures // ensure that there are no missing fields. [[nodiscard]] Result<> makeNop(); - [[nodiscard]] Result<> makeBlock(); + [[nodiscard]] Result<> makeBlock(Name label, Type type); // [[nodiscard]] Result<> makeIf(); // [[nodiscard]] Result<> makeLoop(); // [[nodiscard]] Result<> makeBreak(); @@ -168,9 +168,6 @@ public: // [[nodiscard]] Result<> makeStringSliceWTF(); // [[nodiscard]] Result<> makeStringSliceIter(); - // TODO: make this private. - void pushScope(Type type) { scopeStack.push_back({{}, type}); } - void setFunction(Function* func) { this->func = func; } private: @@ -183,24 +180,42 @@ private: // to have. struct BlockCtx { std::vector<Expression*> exprStack; - Type type; + Block* block; + // Whether we have seen an unreachable instruction and are in + // stack-polymorphic unreachable mode. + bool unreachable = false; }; // The stack of block contexts currently being parsed. std::vector<BlockCtx> scopeStack; - std::vector<Expression*>& getExprStack(); - Type getResultType() { - assert(!scopeStack.empty()); - return scopeStack.back().type; + + BlockCtx& getScope() { + if (scopeStack.empty()) { + // We are not in a block context, so push a dummy scope. + scopeStack.push_back({{}, nullptr}); + } + return scopeStack.back(); } - // Whether we have seen an unreachable instruction and are in - // stack-polymorphic unreachable mode. - bool unreachable = false; + [[nodiscard]] Result<Index> addScratchLocal(Type); + [[nodiscard]] Result<Expression*> pop(); + void push(Expression*); + + struct HoistedVal { + // The index in the stack of the original value-producing expression. + Index valIndex; + // The local.get placed on the stack, if any. + LocalGet* get; + }; - Result<Index> addScratchLocal(Type); - [[nodiscard]] Result<> push(Expression*); - Result<Expression*> pop(); + // Find the last value-producing expression, if any, and hoist its value to + // the top of the stack using a scratch local if necessary. + [[nodiscard]] MaybeResult<HoistedVal> hoistLastValue(); + // Transform the stack as necessary such that the original producer of the + // hoisted value will be popped along with the final expression that produces + // the value, if they are different. May only be called directly after + // hoistLastValue(). + [[nodiscard]] Result<> packageHoistedValue(const HoistedVal&); }; } // namespace wasm diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 6786816f3..7e163fe4e 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -39,14 +39,6 @@ Result<> validateTypeAnnotation(HeapType type, Expression* child) { } // anonymous namespace -std::vector<Expression*>& IRBuilder::getExprStack() { - if (scopeStack.empty()) { - // We are not in a function, so push a dummy scope. - scopeStack.push_back({{}, Type::none}); - } - return scopeStack.back().exprStack; -} - Result<Index> IRBuilder::addScratchLocal(Type type) { if (!func) { return Err{"scratch local required, but there is no function context"}; @@ -55,125 +47,141 @@ Result<Index> IRBuilder::addScratchLocal(Type type) { return Builder::addVar(func, name, type); } -Result<> IRBuilder::push(Expression* expr) { - auto& exprStack = getExprStack(); +MaybeResult<IRBuilder::HoistedVal> IRBuilder::hoistLastValue() { + auto& stack = getScope().exprStack; + int index = stack.size() - 1; + for (; index >= 0; --index) { + if (stack[index]->type != Type::none) { + break; + } + } + if (index < 0) { + // There is no value-producing or unreachable expression. + return {}; + } + if (unsigned(index) == stack.size() - 1) { + // Value-producing expression already on top of the stack. + return HoistedVal{Index(index), nullptr}; + } + auto*& expr = stack[index]; + auto type = expr->type; + if (type == Type::unreachable) { + // Make sure the top of the stack also has an unreachable expression. + if (stack.back()->type != Type::unreachable) { + push(builder.makeUnreachable()); + } + return HoistedVal{Index(index), nullptr}; + } + // Hoist with a scratch local. + auto scratchIdx = addScratchLocal(type); + CHECK_ERR(scratchIdx); + expr = builder.makeLocalSet(*scratchIdx, expr); + auto* get = builder.makeLocalGet(*scratchIdx, type); + push(get); + return HoistedVal{Index(index), get}; +} + +Result<> IRBuilder::packageHoistedValue(const HoistedVal& hoisted) { + auto& scope = getScope(); + assert(!scope.exprStack.empty()); + + auto packageAsBlock = [&](Type type) { + // Create a block containing the producer of the hoisted value, the final + // get of the hoisted value, and everything in between. + std::vector<Expression*> exprs(scope.exprStack.begin() + hoisted.valIndex, + scope.exprStack.end()); + auto* block = builder.makeBlock(exprs, type); + scope.exprStack.resize(hoisted.valIndex); + push(block); + }; + + auto type = scope.exprStack.back()->type; + + if (!type.isTuple()) { + if (hoisted.get) { + packageAsBlock(type); + } + return Ok{}; + } + + // We need to break up the hoisted tuple. Create and push a block setting the + // tuple to a local and returning its first element, then push additional gets + // of each of its subsequent elements. Reuse the scratch local we used for + // hoisting, if it exists. + Index scratchIdx; + if (hoisted.get) { + // Update the get on top of the stack to just return the first element. + scope.exprStack.back() = builder.makeTupleExtract(hoisted.get, 0); + packageAsBlock(type[0]); + scratchIdx = hoisted.get->index; + } else { + auto scratch = addScratchLocal(type); + CHECK_ERR(scratch); + auto* block = builder.makeSequence( + builder.makeLocalSet(*scratch, scope.exprStack.back()), + builder.makeTupleExtract(builder.makeLocalGet(*scratch, type), 0), + type[0]); + scope.exprStack.pop_back(); + push(block); + scratchIdx = *scratch; + } + for (Index i = 1, size = type.size(); i < size; ++i) { + push(builder.makeTupleExtract(builder.makeLocalGet(scratchIdx, type), i)); + } + return Ok{}; +} + +void IRBuilder::push(Expression* expr) { + auto& scope = getScope(); if (expr->type == Type::unreachable) { // We want to avoid popping back past this most recent unreachable // instruction. Drop all prior instructions so they won't be consumed by // later instructions but will still be emitted for their side effects, if // any. - for (auto& expr : exprStack) { + for (auto& expr : scope.exprStack) { expr = builder.dropIfConcretelyTyped(expr); } - unreachable = true; - exprStack.push_back(expr); - } else if (expr->type.isTuple()) { - auto scratchIdx = addScratchLocal(expr->type); - CHECK_ERR(scratchIdx); - CHECK_ERR(push(builder.makeLocalSet(*scratchIdx, expr))); - for (Index i = 0; i < expr->type.size(); ++i) { - CHECK_ERR(push(builder.makeTupleExtract( - builder.makeLocalGet(*scratchIdx, expr->type), i))); - } - } else { - exprStack.push_back(expr); + scope.unreachable = true; } - return Ok{}; + scope.exprStack.push_back(expr); } Result<Expression*> IRBuilder::pop() { - auto& exprStack = getExprStack(); + auto& scope = getScope(); // Find the suffix of expressions that do not produce values. - auto firstNone = exprStack.size(); - for (; firstNone > 0; --firstNone) { - auto* expr = exprStack[firstNone - 1]; - if (expr->type != Type::none) { - break; - } - } + auto hoisted = hoistLastValue(); + CHECK_ERR(hoisted); - if (firstNone == 0) { + if (!hoisted) { // There are no expressions that produce values. - if (unreachable) { + if (scope.unreachable) { return builder.makeUnreachable(); } return Err{"popping from empty stack"}; } - if (firstNone == exprStack.size()) { - // The last expression produced a value. - auto expr = exprStack.back(); - exprStack.pop_back(); - return expr; - } + CHECK_ERR(packageHoistedValue(*hoisted)); - // We need to assemble a block of expressions that returns the value of the - // first one using a scratch local (unless it's unreachable, in which case - // we can throw the following expressions away). - auto* expr = exprStack[firstNone - 1]; - if (expr->type == Type::unreachable) { - exprStack.resize(firstNone - 1); - return expr; + auto* ret = scope.exprStack.back(); + scope.exprStack.pop_back(); + return ret; +} + +Result<Expression*> IRBuilder::build() { + if (scopeStack.empty()) { + return builder.makeNop(); } - auto scratchIdx = addScratchLocal(expr->type); - CHECK_ERR(scratchIdx); - std::vector<Expression*> exprs; - exprs.reserve(exprStack.size() - firstNone + 2); - exprs.push_back(builder.makeLocalSet(*scratchIdx, expr)); - exprs.insert(exprs.end(), exprStack.begin() + firstNone, exprStack.end()); - exprs.push_back(builder.makeLocalGet(*scratchIdx, expr->type)); - - exprStack.resize(firstNone - 1); - return builder.makeBlock(exprs, expr->type); -} - -Expression* IRBuilder::build() { - auto& exprStack = getExprStack(); - assert(scopeStack.size() == 1); - assert(exprStack.size() == 1); - - auto e = exprStack.back(); - exprStack.clear(); - unreachable = false; - return e; -} - -Result<std::vector<Expression*>> IRBuilder::finishInstrs() { - auto& exprStack = getExprStack(); - auto type = getResultType(); - - // We have finished parsing a sequence of instructions. Fix up the parsed - // instructions and reset the context for the next sequence. - if (type.isTuple()) { - std::vector<Expression*> elems(type.size()); - bool hadUnreachableElem = false; - for (size_t i = 0; i < elems.size(); ++i) { - auto elem = pop(); - CHECK_ERR(elem); - elems[elems.size() - 1 - i] = *elem; - if ((*elem)->type == Type::unreachable) { - // We don't want to pop back past an unreachable here. Push the - // unreachable back and throw away any post-unreachable values we have - // popped. - exprStack.push_back(*elem); - hadUnreachableElem = true; - break; - } - } - if (!hadUnreachableElem) { - exprStack.push_back(builder.makeTupleMake(std::move(elems))); - } - } else if (type != Type::none) { - // Ensure the last expression produces the value. - auto expr = pop(); - CHECK_ERR(expr); - exprStack.push_back(*expr); + if (scopeStack.size() > 1 || scopeStack.back().block != nullptr) { + return Err{"unfinished block context"}; } - unreachable = false; - auto ret = std::move(exprStack); - scopeStack.pop_back(); - return ret; + if (scopeStack.back().exprStack.size() > 1) { + return Err{"unused expressions without block context"}; + } + assert(scopeStack.back().exprStack.size() == 1); + auto* expr = scopeStack.back().exprStack.back(); + scopeStack.clear(); + return expr; } Result<> IRBuilder::visit(Expression* curr) { @@ -185,7 +193,8 @@ Result<> IRBuilder::visit(Expression* curr) { // for other kinds of nodes as well, as done above. ReFinalizeNode{}.visit(curr); } - return push(curr); + push(curr); + return Ok{}; } // Handle the common case of instructions with a constant number of children @@ -224,7 +233,7 @@ Result<> IRBuilder::visitExpression(Expression* curr) { } Result<> IRBuilder::visitBlock(Block* curr) { - // TODO: Handle popping scope and filling block here instead of externally. + scopeStack.push_back({{}, curr}); return Ok{}; } @@ -272,9 +281,75 @@ Result<> IRBuilder::visitArrayNew(ArrayNew* curr) { return Ok{}; } -Result<> IRBuilder::makeNop() { return push(builder.makeNop()); } +Result<> IRBuilder::visitEnd() { + if (scopeStack.empty() || !scopeStack.back().block) { + return Err{"unexpected end"}; + } -Result<> IRBuilder::makeBlock() { return push(builder.makeBlock()); } + auto& scope = scopeStack.back(); + Block* block = scope.block; + if (block->type.isTuple()) { + if (scope.unreachable) { + // We may not have enough concrete values on the stack to construct the + // full tuple, and if we tried to fill out the beginning of a tuple.make + // with additional popped `unreachable`s, that could cause a trap to + // happen before important side effects. Instead, just drop everything on + // the stack and finish with a single unreachable. + // + // TODO: Validate that the available expressions are a correct suffix of + // the expected type, since this will no longer be caught by normal + // validation? + for (auto& expr : scope.exprStack) { + expr = builder.dropIfConcretelyTyped(expr); + } + if (scope.exprStack.back()->type != Type::unreachable) { + scope.exprStack.push_back(builder.makeUnreachable()); + } + } else { + auto hoisted = hoistLastValue(); + CHECK_ERR(hoisted); + auto hoistedType = scope.exprStack.back()->type; + if (hoistedType.size() != block->type.size()) { + // We cannot propagate the hoisted value directly because it does not + // have the correct number of elements. Break it up if necessary and + // construct our returned tuple from parts. + CHECK_ERR(packageHoistedValue(*hoisted)); + std::vector<Expression*> elems(block->type.size()); + for (size_t i = 0; i < elems.size(); ++i) { + auto elem = pop(); + CHECK_ERR(elem); + elems[elems.size() - 1 - i] = *elem; + } + scope.exprStack.push_back(builder.makeTupleMake(std::move(elems))); + } + } + } else if (block->type.isConcrete()) { + // If the value is buried in none-typed expressions, we have to bring it to + // the top. + auto hoisted = hoistLastValue(); + CHECK_ERR(hoisted); + } + block->list.set(scope.exprStack); + // TODO: Track branches so we can know whether this block is a target and + // finalize more efficiently. + block->finalize(block->type); + scopeStack.pop_back(); + push(block); + return Ok{}; +} + +Result<> IRBuilder::makeNop() { + push(builder.makeNop()); + return Ok{}; +} + +Result<> IRBuilder::makeBlock(Name label, Type type) { + auto* block = wasm.allocator.alloc<Block>(); + block->name = label; + block->type = type; + scopeStack.push_back({{}, block}); + return Ok{}; +} // Result<> IRBuilder::makeIf() {} @@ -289,30 +364,34 @@ Result<> IRBuilder::makeBlock() { return push(builder.makeBlock()); } // Result<> IRBuilder::makeCallIndirect() {} Result<> IRBuilder::makeLocalGet(Index local) { - return push(builder.makeLocalGet(local, func->getLocalType(local))); + push(builder.makeLocalGet(local, func->getLocalType(local))); + return Ok{}; } Result<> IRBuilder::makeLocalSet(Index local) { LocalSet curr; CHECK_ERR(visitLocalSet(&curr)); - return push(builder.makeLocalSet(local, curr.value)); + push(builder.makeLocalSet(local, curr.value)); + return Ok{}; } Result<> IRBuilder::makeLocalTee(Index local) { LocalSet curr; CHECK_ERR(visitLocalSet(&curr)); - return push( - builder.makeLocalTee(local, curr.value, func->getLocalType(local))); + push(builder.makeLocalTee(local, curr.value, func->getLocalType(local))); + return Ok{}; } Result<> IRBuilder::makeGlobalGet(Name global) { - return push(builder.makeGlobalGet(global, wasm.getGlobal(global)->type)); + push(builder.makeGlobalGet(global, wasm.getGlobal(global)->type)); + return Ok{}; } Result<> IRBuilder::makeGlobalSet(Name global) { GlobalSet curr; CHECK_ERR(visitGlobalSet(&curr)); - return push(builder.makeGlobalSet(global, curr.value)); + push(builder.makeGlobalSet(global, curr.value)); + return Ok{}; } Result<> IRBuilder::makeLoad(unsigned bytes, @@ -323,23 +402,25 @@ Result<> IRBuilder::makeLoad(unsigned bytes, Name mem) { Load curr; CHECK_ERR(visitLoad(&curr)); - return push( - builder.makeLoad(bytes, signed_, offset, align, curr.ptr, type, mem)); + push(builder.makeLoad(bytes, signed_, offset, align, curr.ptr, type, mem)); + return Ok{}; } Result<> IRBuilder::makeStore( unsigned bytes, Address offset, unsigned align, Type type, Name mem) { Store curr; CHECK_ERR(visitStore(&curr)); - return push( + push( builder.makeStore(bytes, offset, align, curr.ptr, curr.value, type, mem)); + return Ok{}; } Result<> IRBuilder::makeAtomicLoad(unsigned bytes, Address offset, Type type, Name mem) { Load curr; CHECK_ERR(visitLoad(&curr)); - return push(builder.makeAtomicLoad(bytes, offset, curr.ptr, type, mem)); + push(builder.makeAtomicLoad(bytes, offset, curr.ptr, type, mem)); + return Ok{}; } Result<> IRBuilder::makeAtomicStore(unsigned bytes, @@ -348,16 +429,17 @@ Result<> IRBuilder::makeAtomicStore(unsigned bytes, Name mem) { Store curr; CHECK_ERR(visitStore(&curr)); - return push( - builder.makeAtomicStore(bytes, offset, curr.ptr, curr.value, type, mem)); + push(builder.makeAtomicStore(bytes, offset, curr.ptr, curr.value, type, mem)); + return Ok{}; } Result<> IRBuilder::makeAtomicRMW( AtomicRMWOp op, unsigned bytes, Address offset, Type type, Name mem) { AtomicRMW curr; CHECK_ERR(visitAtomicRMW(&curr)); - return push( + push( builder.makeAtomicRMW(op, bytes, offset, curr.ptr, curr.value, type, mem)); + return Ok{}; } Result<> IRBuilder::makeAtomicCmpxchg(unsigned bytes, @@ -366,56 +448,64 @@ Result<> IRBuilder::makeAtomicCmpxchg(unsigned bytes, Name mem) { AtomicCmpxchg curr; CHECK_ERR(visitAtomicCmpxchg(&curr)); - return push(builder.makeAtomicCmpxchg( + push(builder.makeAtomicCmpxchg( bytes, offset, curr.ptr, curr.expected, curr.replacement, type, mem)); + return Ok{}; } Result<> IRBuilder::makeAtomicWait(Type type, Address offset, Name mem) { AtomicWait curr; CHECK_ERR(visitAtomicWait(&curr)); - return push(builder.makeAtomicWait( + push(builder.makeAtomicWait( curr.ptr, curr.expected, curr.timeout, type, offset, mem)); + return Ok{}; } Result<> IRBuilder::makeAtomicNotify(Address offset, Name mem) { AtomicNotify curr; CHECK_ERR(visitAtomicNotify(&curr)); - return push( - builder.makeAtomicNotify(curr.ptr, curr.notifyCount, offset, mem)); + push(builder.makeAtomicNotify(curr.ptr, curr.notifyCount, offset, mem)); + return Ok{}; } Result<> IRBuilder::makeAtomicFence() { - return push(builder.makeAtomicFence()); + push(builder.makeAtomicFence()); + return Ok{}; } Result<> IRBuilder::makeSIMDExtract(SIMDExtractOp op, uint8_t lane) { SIMDExtract curr; CHECK_ERR(visitSIMDExtract(&curr)); - return push(builder.makeSIMDExtract(op, curr.vec, lane)); + push(builder.makeSIMDExtract(op, curr.vec, lane)); + return Ok{}; } Result<> IRBuilder::makeSIMDReplace(SIMDReplaceOp op, uint8_t lane) { SIMDReplace curr; CHECK_ERR(visitSIMDReplace(&curr)); - return push(builder.makeSIMDReplace(op, curr.vec, lane, curr.value)); + push(builder.makeSIMDReplace(op, curr.vec, lane, curr.value)); + return Ok{}; } Result<> IRBuilder::makeSIMDShuffle(const std::array<uint8_t, 16>& lanes) { SIMDShuffle curr; CHECK_ERR(visitSIMDShuffle(&curr)); - return push(builder.makeSIMDShuffle(curr.left, curr.right, lanes)); + push(builder.makeSIMDShuffle(curr.left, curr.right, lanes)); + return Ok{}; } Result<> IRBuilder::makeSIMDTernary(SIMDTernaryOp op) { SIMDTernary curr; CHECK_ERR(visitSIMDTernary(&curr)); - return push(builder.makeSIMDTernary(op, curr.a, curr.b, curr.c)); + push(builder.makeSIMDTernary(op, curr.a, curr.b, curr.c)); + return Ok{}; } Result<> IRBuilder::makeSIMDShift(SIMDShiftOp op) { SIMDShift curr; CHECK_ERR(visitSIMDShift(&curr)); - return push(builder.makeSIMDShift(op, curr.vec, curr.shift)); + push(builder.makeSIMDShift(op, curr.vec, curr.shift)); + return Ok{}; } Result<> IRBuilder::makeSIMDLoad(SIMDLoadOp op, @@ -424,7 +514,8 @@ Result<> IRBuilder::makeSIMDLoad(SIMDLoadOp op, Name mem) { SIMDLoad curr; CHECK_ERR(visitSIMDLoad(&curr)); - return push(builder.makeSIMDLoad(op, offset, align, curr.ptr, mem)); + push(builder.makeSIMDLoad(op, offset, align, curr.ptr, mem)); + return Ok{}; } Result<> IRBuilder::makeSIMDLoadStoreLane(SIMDLoadStoreLaneOp op, @@ -434,48 +525,55 @@ Result<> IRBuilder::makeSIMDLoadStoreLane(SIMDLoadStoreLaneOp op, Name mem) { SIMDLoadStoreLane curr; CHECK_ERR(visitSIMDLoadStoreLane(&curr)); - return push(builder.makeSIMDLoadStoreLane( + push(builder.makeSIMDLoadStoreLane( op, offset, align, lane, curr.ptr, curr.vec, mem)); + return Ok{}; } Result<> IRBuilder::makeMemoryInit(Name data, Name mem) { MemoryInit curr; CHECK_ERR(visitMemoryInit(&curr)); - return push( - builder.makeMemoryInit(data, curr.dest, curr.offset, curr.size, mem)); + push(builder.makeMemoryInit(data, curr.dest, curr.offset, curr.size, mem)); + return Ok{}; } Result<> IRBuilder::makeDataDrop(Name data) { - return push(builder.makeDataDrop(data)); + push(builder.makeDataDrop(data)); + return Ok{}; } Result<> IRBuilder::makeMemoryCopy(Name destMem, Name srcMem) { MemoryCopy curr; CHECK_ERR(visitMemoryCopy(&curr)); - return push( + push( builder.makeMemoryCopy(curr.dest, curr.source, curr.size, destMem, srcMem)); + return Ok{}; } Result<> IRBuilder::makeMemoryFill(Name mem) { MemoryFill curr; CHECK_ERR(visitMemoryFill(&curr)); - return push(builder.makeMemoryFill(curr.dest, curr.value, curr.size, mem)); + push(builder.makeMemoryFill(curr.dest, curr.value, curr.size, mem)); + return Ok{}; } Result<> IRBuilder::makeConst(Literal val) { - return push(builder.makeConst(val)); + push(builder.makeConst(val)); + return Ok{}; } Result<> IRBuilder::makeUnary(UnaryOp op) { Unary curr; CHECK_ERR(visitUnary(&curr)); - return push(builder.makeUnary(op, curr.value)); + push(builder.makeUnary(op, curr.value)); + return Ok{}; } Result<> IRBuilder::makeBinary(BinaryOp op) { Binary curr; CHECK_ERR(visitBinary(&curr)); - return push(builder.makeBinary(op, curr.left, curr.right)); + push(builder.makeBinary(op, curr.left, curr.right)); + return Ok{}; } Result<> IRBuilder::makeSelect(std::optional<Type> type) { @@ -487,45 +585,53 @@ Result<> IRBuilder::makeSelect(std::optional<Type> type) { if (type && !Type::isSubType(built->type, *type)) { return Err{"select type does not match expected type"}; } - return push(built); + push(built); + return Ok{}; } Result<> IRBuilder::makeDrop() { Drop curr; CHECK_ERR(visitDrop(&curr)); - return push(builder.makeDrop(curr.value)); + push(builder.makeDrop(curr.value)); + return Ok{}; } Result<> IRBuilder::makeReturn() { Return curr; CHECK_ERR(visitReturn(&curr)); - return push(builder.makeReturn(curr.value)); + push(builder.makeReturn(curr.value)); + return Ok{}; } Result<> IRBuilder::makeMemorySize(Name mem) { - return push(builder.makeMemorySize(mem)); + push(builder.makeMemorySize(mem)); + return Ok{}; } Result<> IRBuilder::makeMemoryGrow(Name mem) { MemoryGrow curr; CHECK_ERR(visitMemoryGrow(&curr)); - return push(builder.makeMemoryGrow(curr.delta, mem)); + push(builder.makeMemoryGrow(curr.delta, mem)); + return Ok{}; } Result<> IRBuilder::makeUnreachable() { - return push(builder.makeUnreachable()); + push(builder.makeUnreachable()); + return Ok{}; } // Result<> IRBuilder::makePop() {} Result<> IRBuilder::makeRefNull(HeapType type) { - return push(builder.makeRefNull(type)); + push(builder.makeRefNull(type)); + return Ok{}; } Result<> IRBuilder::makeRefIsNull() { RefIsNull curr; CHECK_ERR(visitRefIsNull(&curr)); - return push(builder.makeRefIsNull(curr.value)); + push(builder.makeRefIsNull(curr.value)); + return Ok{}; } // Result<> IRBuilder::makeRefFunc() {} @@ -533,7 +639,8 @@ Result<> IRBuilder::makeRefIsNull() { Result<> IRBuilder::makeRefEq() { RefEq curr; CHECK_ERR(visitRefEq(&curr)); - return push(builder.makeRefEq(curr.left, curr.right)); + push(builder.makeRefEq(curr.left, curr.right)); + return Ok{}; } // Result<> IRBuilder::makeTableGet() {} @@ -557,13 +664,15 @@ Result<> IRBuilder::makeRefEq() { Result<> IRBuilder::makeI31New() { I31New curr; CHECK_ERR(visitI31New(&curr)); - return push(builder.makeI31New(curr.value)); + push(builder.makeI31New(curr.value)); + return Ok{}; } Result<> IRBuilder::makeI31Get(bool signed_) { I31Get curr; CHECK_ERR(visitI31Get(&curr)); - return push(builder.makeI31Get(curr.i31, signed_)); + push(builder.makeI31Get(curr.i31, signed_)); + return Ok{}; } // Result<> IRBuilder::makeCallRef() {} @@ -579,11 +688,13 @@ Result<> IRBuilder::makeStructNew(HeapType type) { // Differentiate from struct.new_default with a non-empty expression list. curr.operands.resize(type.getStruct().fields.size()); CHECK_ERR(visitStructNew(&curr)); - return push(builder.makeStructNew(type, std::move(curr.operands))); + push(builder.makeStructNew(type, std::move(curr.operands))); + return Ok{}; } Result<> IRBuilder::makeStructNewDefault(HeapType type) { - return push(builder.makeStructNew(type, {})); + push(builder.makeStructNew(type, {})); + return Ok{}; } Result<> IRBuilder::makeStructGet(HeapType type, Index field, bool signed_) { @@ -591,15 +702,16 @@ Result<> IRBuilder::makeStructGet(HeapType type, Index field, bool signed_) { StructGet curr; CHECK_ERR(visitStructGet(&curr)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - return push( - builder.makeStructGet(field, curr.ref, fields[field].type, signed_)); + push(builder.makeStructGet(field, curr.ref, fields[field].type, signed_)); + return Ok{}; } Result<> IRBuilder::makeStructSet(HeapType type, Index field) { StructSet curr; CHECK_ERR(visitStructSet(&curr)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - return push(builder.makeStructSet(field, curr.ref, curr.value)); + push(builder.makeStructSet(field, curr.ref, curr.value)); + return Ok{}; } Result<> IRBuilder::makeArrayNew(HeapType type) { @@ -607,25 +719,29 @@ Result<> IRBuilder::makeArrayNew(HeapType type) { // Differentiate from array.new_default with dummy initializer. curr.init = (Expression*)0x01; CHECK_ERR(visitArrayNew(&curr)); - return push(builder.makeArrayNew(type, curr.size, curr.init)); + push(builder.makeArrayNew(type, curr.size, curr.init)); + return Ok{}; } Result<> IRBuilder::makeArrayNewDefault(HeapType type) { ArrayNew curr; CHECK_ERR(visitArrayNew(&curr)); - return push(builder.makeArrayNew(type, curr.size)); + push(builder.makeArrayNew(type, curr.size)); + return Ok{}; } Result<> IRBuilder::makeArrayNewData(HeapType type, Name data) { ArrayNewData curr; CHECK_ERR(visitArrayNewData(&curr)); - return push(builder.makeArrayNewData(type, data, curr.offset, curr.size)); + push(builder.makeArrayNewData(type, data, curr.offset, curr.size)); + return Ok{}; } Result<> IRBuilder::makeArrayNewElem(HeapType type, Name elem) { ArrayNewElem curr; CHECK_ERR(visitArrayNewElem(&curr)); - return push(builder.makeArrayNewElem(type, elem, curr.offset, curr.size)); + push(builder.makeArrayNewElem(type, elem, curr.offset, curr.size)); + return Ok{}; } // Result<> IRBuilder::makeArrayNewFixed() {} @@ -634,21 +750,24 @@ Result<> IRBuilder::makeArrayGet(HeapType type, bool signed_) { ArrayGet curr; CHECK_ERR(visitArrayGet(&curr)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - return push(builder.makeArrayGet( + push(builder.makeArrayGet( curr.ref, curr.index, type.getArray().element.type, signed_)); + return Ok{}; } Result<> IRBuilder::makeArraySet(HeapType type) { ArraySet curr; CHECK_ERR(visitArraySet(&curr)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - return push(builder.makeArraySet(curr.ref, curr.index, curr.value)); + push(builder.makeArraySet(curr.ref, curr.index, curr.value)); + return Ok{}; } Result<> IRBuilder::makeArrayLen() { ArrayLen curr; CHECK_ERR(visitArrayLen(&curr)); - return push(builder.makeArrayLen(curr.ref)); + push(builder.makeArrayLen(curr.ref)); + return Ok{}; } Result<> IRBuilder::makeArrayCopy(HeapType destType, HeapType srcType) { @@ -656,16 +775,17 @@ Result<> IRBuilder::makeArrayCopy(HeapType destType, HeapType srcType) { CHECK_ERR(visitArrayCopy(&curr)); CHECK_ERR(validateTypeAnnotation(destType, curr.destRef)); CHECK_ERR(validateTypeAnnotation(srcType, curr.srcRef)); - return push(builder.makeArrayCopy( + push(builder.makeArrayCopy( curr.destRef, curr.destIndex, curr.srcRef, curr.srcIndex, curr.length)); + return Ok{}; } Result<> IRBuilder::makeArrayFill(HeapType type) { ArrayFill curr; CHECK_ERR(visitArrayFill(&curr)); CHECK_ERR(validateTypeAnnotation(type, curr.ref)); - return push( - builder.makeArrayFill(curr.ref, curr.index, curr.value, curr.size)); + push(builder.makeArrayFill(curr.ref, curr.index, curr.value, curr.size)); + return Ok{}; } // Result<> IRBuilder::makeArrayInitData() {} diff --git a/src/wasm/wat-parser.cpp b/src/wasm/wat-parser.cpp index bf438ae68..3961cbb7f 100644 --- a/src/wasm/wat-parser.cpp +++ b/src/wasm/wat-parser.cpp @@ -676,7 +676,7 @@ struct NullInstrParserCtx { InstrsT finishInstrs(InstrsT&) { return Ok{}; } ExprT makeExpr(InstrsT) { return Ok{}; } - ExprT instrToExpr(InstrT) { return Ok{}; } + Result<ExprT> instrToExpr(InstrT) { return Ok{}; } template<typename HeapTypeT> FieldIdxT getFieldFromIdx(HeapTypeT, uint32_t) { return Ok{}; @@ -696,9 +696,11 @@ struct NullInstrParserCtx { MemargT getMemarg(uint64_t, uint32_t) { return Ok{}; } template<typename BlockTypeT> - InstrT makeBlock(Index, std::optional<Name>, BlockTypeT, InstrsT) { + InstrT makeBlock(Index, std::optional<Name>, BlockTypeT) { return Ok{}; } + InstrT finishBlock(Index, InstrsT) { return Ok{}; } + InstrT makeUnreachable(Index) { return Ok{}; } InstrT makeNop(Index) { return Ok{}; } InstrT makeBinary(Index, BinaryOp) { return Ok{}; } @@ -1280,7 +1282,7 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { // Keep track of instructions internally rather than letting the general // parser collect them. using InstrT = Ok; - using InstrsT = std::vector<Expression*>; + using InstrsT = Ok; using ExprT = Expression*; using FieldIdxT = Index; @@ -1334,12 +1336,10 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { HeapType getBlockTypeFromResult(const std::vector<Type> results) { assert(results.size() == 1); - irBuilder.pushScope(results[0]); return HeapType(Signature(Type::none, results[0])); } Result<HeapType> getBlockTypeFromTypeUse(Index pos, HeapType type) { - irBuilder.pushScope(type.getSignature().results); return type; } @@ -1347,11 +1347,9 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { void appendInstr(Ok&, InstrT instr) {} - Result<InstrsT> finishInstrs(Ok&) { - return withLoc(irBuilder.finishInstrs()); - } + Result<InstrsT> finishInstrs(Ok&) { return Ok{}; } - Expression* instrToExpr(Ok&) { return irBuilder.build(); } + Result<Expression*> instrToExpr(Ok&) { return irBuilder.build(); } GlobalTypeT makeGlobalType(Mutability, TypeT) { return Ok{}; } @@ -1475,25 +1473,12 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { ImportNames*, TypeUseT, std::optional<LocalsT>, - std::optional<InstrsT> insts, - Index) { - Expression* body; - if (insts) { - switch (insts->size()) { - case 0: - body = builder.makeNop(); - break; - case 1: - body = insts->back(); - break; - default: - body = builder.makeBlock(*insts, wasm.functions[index]->getResults()); - break; - } - } else { - body = builder.makeNop(); - } - wasm.functions[index]->body = body; + std::optional<InstrsT>, + Index pos) { + CHECK_ERR(withLoc(pos, irBuilder.visitEnd())); + auto body = irBuilder.build(); + CHECK_ERR(withLoc(pos, body)); + wasm.functions[index]->body = *body; return Ok{}; } @@ -1537,16 +1522,7 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { return Builder::addVar(func, name, type); } - Expression* makeExpr(InstrsT& instrs) { - switch (instrs.size()) { - case 0: - return builder.makeNop(); - case 1: - return instrs.front(); - default: - return builder.makeBlock(instrs); - } - } + Result<Expression*> makeExpr(InstrsT& instrs) { return irBuilder.build(); } Memarg getMemarg(uint64_t offset, uint32_t align) { return {offset, align}; } @@ -1560,20 +1536,16 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { return wasm.memories[0]->name; } - Result<> makeBlock(Index pos, - std::optional<Name> label, - HeapType type, - const std::vector<Expression*>& instrs) { + Result<> makeBlock(Index pos, std::optional<Name> label, HeapType type) { // TODO: validate labels? // TODO: Move error on input types to here? - auto results = type.getSignature().results; - Block* block = wasm.allocator.alloc<Block>(); - block->type = results; - if (label) { - block->name = *label; - } - block->list.set(instrs); - return withLoc(pos, irBuilder.visit(block)); + return withLoc(pos, + irBuilder.makeBlock(label ? *label : Name{}, + type.getSignature().results)); + } + + Result<> finishBlock(Index pos, InstrsT) { + return withLoc(pos, irBuilder.visitEnd()); } Result<> makeUnreachable(Index pos) { @@ -2595,6 +2567,8 @@ MaybeResult<typename Ctx::InstrT> block(Ctx& ctx, bool folded) { auto type = blocktype(ctx); CHECK_ERR(type); + ctx.makeBlock(pos, label, *type); + auto insts = instrs(ctx); CHECK_ERR(insts); @@ -2612,7 +2586,7 @@ MaybeResult<typename Ctx::InstrT> block(Ctx& ctx, bool folded) { } } - return ctx.makeBlock(pos, label, *type, std::move(*insts)); + return ctx.finishBlock(pos, std::move(*insts)); } template<typename Ctx> @@ -3745,7 +3719,9 @@ template<typename Ctx> MaybeResult<> data(Ctx& ctx) { } else if (ctx.in.takeLParen()) { auto inst = instr(ctx); CHECK_ERR(inst); - offset = ctx.instrToExpr(*inst); + auto offsetExpr = ctx.instrToExpr(*inst); + CHECK_ERR(offsetExpr); + offset = *offsetExpr; if (!ctx.in.takeRParen()) { return ctx.in.err("expected end of offset instruction"); } @@ -3893,7 +3869,7 @@ Result<> parseModule(Module& wasm, std::string_view input) { for (Index i = 0; i < decls.funcDefs.size(); ++i) { ctx.index = i; ctx.setFunction(wasm.functions[i].get()); - ctx.irBuilder.pushScope(ctx.func->getResults()); + CHECK_ERR(ctx.irBuilder.makeBlock(Name{}, ctx.func->getResults())); WithPosition with(ctx, decls.funcDefs[i].pos); auto parsed = func(ctx); CHECK_ERR(parsed); diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast index ff48024f7..ddf35edcf 100644 --- a/test/lit/wat-kitchen-sink.wast +++ b/test/lit/wat-kitchen-sink.wast @@ -5,27 +5,29 @@ (module $parse ;; types - ;; CHECK: (type $void (func)) - - ;; CHECK: (type $pair (struct (field (mut i32)) (field (mut i64)))) - ;; CHECK: (type $ret2 (func (result i32 i32))) (type $ret2 (func (result i32 i32))) (rec - ;; CHECK: (type $3 (func (result i32))) + ;; CHECK: (type $void (func)) - ;; CHECK: (type $4 (func (param i32 i64))) + ;; CHECK: (type $pair (struct (field (mut i32)) (field (mut i64)))) ;; CHECK: (type $a1 (array i64)) + ;; CHECK: (type $4 (func (result i32))) + + ;; CHECK: (type $5 (func (param i32 i64))) + ;; CHECK: (type $a2 (array (mut f32))) - ;; CHECK: (type $7 (func (param i32))) + ;; CHECK: (type $7 (func (result i32 i64))) - ;; CHECK: (type $8 (func (param i32 i32 i32))) + ;; CHECK: (type $8 (func (param i32))) - ;; CHECK: (type $9 (func (param v128 i32) (result v128))) + ;; CHECK: (type $9 (func (param i32 i32 i32))) + + ;; CHECK: (type $10 (func (param v128 i32) (result v128))) ;; CHECK: (type $packed-i8 (array (mut i8))) @@ -33,31 +35,29 @@ ;; CHECK: (type $many (func (param i32 i64 f32 f64) (result anyref (ref func)))) - ;; CHECK: (type $13 (func (param i32 i32))) - - ;; CHECK: (type $14 (func (param i32 i32 f64 f64))) + ;; CHECK: (type $14 (func (param i32 i32))) - ;; CHECK: (type $15 (func (param i64))) + ;; CHECK: (type $15 (func (param i32 i32 f64 f64))) - ;; CHECK: (type $16 (func (param v128) (result i32))) + ;; CHECK: (type $16 (func (param i64))) - ;; CHECK: (type $17 (func (param v128 v128) (result v128))) + ;; CHECK: (type $17 (func (param v128) (result i32))) - ;; CHECK: (type $18 (func (param v128 v128 v128) (result v128))) + ;; CHECK: (type $18 (func (param v128 v128) (result v128))) - ;; CHECK: (type $19 (func (param i32 i64 v128))) + ;; CHECK: (type $19 (func (param v128 v128 v128) (result v128))) - ;; CHECK: (type $20 (func (param i32 i32 i64 i64))) + ;; CHECK: (type $20 (func (param i32 i64 v128))) - ;; CHECK: (type $21 (func (param i32) (result i32))) + ;; CHECK: (type $21 (func (param i32 i32 i64 i64))) - ;; CHECK: (type $22 (func (param i32 i64) (result i32 i64))) + ;; CHECK: (type $22 (func (param i32) (result i32))) - ;; CHECK: (type $23 (func (param i64) (result i32 i64))) + ;; CHECK: (type $23 (func (param i32 i64) (result i32 i64))) - ;; CHECK: (type $24 (func (param i32) (result i32 i64))) + ;; CHECK: (type $24 (func (param i64) (result i32 i64))) - ;; CHECK: (type $25 (func (result i32 i64))) + ;; CHECK: (type $25 (func (param i32) (result i32 i64))) ;; CHECK: (type $26 (func (param anyref) (result i32))) @@ -225,18 +225,15 @@ ;; CHECK: (export "f5.1" (func $fimport$1)) ;; CHECK: (func $0 (type $void) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) - ;; CHECK: (func $f1 (type $7) (param $0 i32) - ;; CHECK-NEXT: (nop) + ;; CHECK: (func $f1 (type $8) (param $0 i32) ;; CHECK-NEXT: ) (func $f1 (param i32)) - ;; CHECK: (func $f2 (type $7) (param $x i32) - ;; CHECK-NEXT: (nop) + ;; CHECK: (func $f2 (type $8) (param $x i32) ;; CHECK-NEXT: ) (func $f2 (param $x i32)) - ;; CHECK: (func $f3 (type $3) (result i32) + ;; CHECK: (func $f3 (type $4) (result i32) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $f3 (result i32) @@ -246,7 +243,6 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $l f32) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $f4 (type 17) (local i32 i64) (local $l f32)) (func (export "f5.0") (export "f5.1") (import "mod" "f5")) @@ -311,7 +307,7 @@ nop ) - ;; CHECK: (func $add (type $3) (result i32) + ;; CHECK: (func $add (type $4) (result i32) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) @@ -323,7 +319,7 @@ i32.add ) - ;; CHECK: (func $add-folded (type $3) (result i32) + ;; CHECK: (func $add-folded (type $4) (result i32) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) @@ -336,7 +332,7 @@ ) ) - ;; CHECK: (func $add-stacky (type $3) (result i32) + ;; CHECK: (func $add-stacky (type $4) (result i32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (block (result i32) @@ -356,7 +352,7 @@ i32.add ) - ;; CHECK: (func $add-stacky-2 (type $3) (result i32) + ;; CHECK: (func $add-stacky-2 (type $4) (result i32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (i32.const 1) @@ -376,7 +372,7 @@ i32.add ) - ;; CHECK: (func $add-stacky-3 (type $3) (result i32) + ;; CHECK: (func $add-stacky-3 (type $4) (result i32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local.set $scratch ;; CHECK-NEXT: (i32.add @@ -394,7 +390,7 @@ nop ) - ;; CHECK: (func $add-stacky-4 (type $3) (result i32) + ;; CHECK: (func $add-stacky-4 (type $4) (result i32) ;; CHECK-NEXT: (local $scratch i32) ;; CHECK-NEXT: (local $scratch_1 i32) ;; CHECK-NEXT: (local $scratch_2 i32) @@ -428,7 +424,7 @@ nop ) - ;; CHECK: (func $add-unreachable (type $3) (result i32) + ;; CHECK: (func $add-unreachable (type $4) (result i32) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (i32.const 1) @@ -440,7 +436,7 @@ i32.add ) - ;; CHECK: (func $add-unreachable-2 (type $3) (result i32) + ;; CHECK: (func $add-unreachable-2 (type $4) (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -455,7 +451,7 @@ i32.add ) - ;; CHECK: (func $add-unreachable-3 (type $3) (result i32) + ;; CHECK: (func $add-unreachable-3 (type $4) (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -554,6 +550,13 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $add-twice-unreachable (type $ret2) unreachable @@ -572,6 +575,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $add-twice-unreachable-2 (type $ret2) i32.const 1 @@ -662,7 +672,7 @@ drop ) - ;; CHECK: (func $locals (type $13) (param $0 i32) (param $x i32) + ;; CHECK: (func $locals (type $14) (param $0 i32) (param $x i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $y i32) ;; CHECK-NEXT: (drop @@ -722,25 +732,14 @@ ) ;; CHECK: (func $block-folded (type $void) - ;; CHECK-NEXT: (local $scratch (i32 i32)) ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $l (result i32 i32) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (tuple.extract 0 - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (tuple.extract 1 - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $block-folded @@ -758,29 +757,29 @@ ;; CHECK-NEXT: (local $scratch_1 (i32 i32)) ;; CHECK-NEXT: (local $scratch_2 i32) ;; CHECK-NEXT: (block $0 - ;; CHECK-NEXT: (local.set $scratch_1 - ;; CHECK-NEXT: (block $1 (result i32 i32) - ;; CHECK-NEXT: (tuple.make - ;; CHECK-NEXT: (block $2 (result i32) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch - ;; CHECK-NEXT: (block $3 (result i32) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (local.set $scratch_2 - ;; CHECK-NEXT: (tuple.extract 0 - ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch_1 + ;; CHECK-NEXT: (block $1 (result i32 i32) + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (block $2 (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block $3 (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 0 + ;; CHECK-NEXT: (local.get $scratch_1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -811,7 +810,38 @@ nop ) - ;; CHECK: (func $binary (type $14) (param $0 i32) (param $1 i32) (param $2 f64) (param $3 f64) + ;; CHECK: (func $multivalue-nested (type $ret2) (result i32 i32) + ;; CHECK-NEXT: (local $scratch (i32 i32)) + ;; CHECK-NEXT: (block (result i32 i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (block (result i32 i32) + ;; CHECK-NEXT: (block (result i32 i32) + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multivalue-nested (type $ret2) + block (type $ret2) + nop + block (type $ret2) + block (type $ret2) + i32.const 0 + i32.const 1 + end + end + nop + end + ) + + ;; CHECK: (func $binary (type $15) (param $0 i32) (param $1 i32) (param $2 f64) (param $3 f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (local.get $0) @@ -836,7 +866,7 @@ drop ) - ;; CHECK: (func $unary (type $15) (param $0 i64) + ;; CHECK: (func $unary (type $16) (param $0 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i64.eqz ;; CHECK-NEXT: (local.get $0) @@ -849,7 +879,7 @@ drop ) - ;; CHECK: (func $select (type $8) (param $0 i32) (param $1 i32) (param $2 i32) + ;; CHECK: (func $select (type $9) (param $0 i32) (param $1 i32) (param $2 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (select ;; CHECK-NEXT: (local.get $0) @@ -922,7 +952,7 @@ drop ) - ;; CHECK: (func $memory-grow (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $memory-grow (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (memory.grow $mem ;; CHECK-NEXT: (local.get $0) @@ -961,7 +991,7 @@ global.set 4 ) - ;; CHECK: (func $load (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $load (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.load $mem offset=42 ;; CHECK-NEXT: (local.get $0) @@ -990,7 +1020,7 @@ drop ) - ;; CHECK: (func $store (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $store (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (i32.store $mem offset=42 align=1 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) @@ -1016,7 +1046,7 @@ f32.store $mem-i64 ) - ;; CHECK: (func $atomic-rmw (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $atomic-rmw (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.atomic.rmw16.add_u $mem ;; CHECK-NEXT: (local.get $0) @@ -1041,7 +1071,7 @@ drop ) - ;; CHECK: (func $atomic-cmpxchg (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $atomic-cmpxchg (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.atomic.rmw8.cmpxchg_u $mem ;; CHECK-NEXT: (local.get $0) @@ -1070,7 +1100,7 @@ drop ) - ;; CHECK: (func $atomic-wait (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $atomic-wait (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (memory.atomic.wait32 $mem ;; CHECK-NEXT: (local.get $0) @@ -1099,7 +1129,7 @@ drop ) - ;; CHECK: (func $atomic-notify (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $atomic-notify (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (memory.atomic.notify $mem offset=8 ;; CHECK-NEXT: (local.get $0) @@ -1131,7 +1161,7 @@ atomic.fence ) - ;; CHECK: (func $simd-extract (type $16) (param $0 v128) (result i32) + ;; CHECK: (func $simd-extract (type $17) (param $0 v128) (result i32) ;; CHECK-NEXT: (i32x4.extract_lane 3 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -1141,7 +1171,7 @@ i32x4.extract_lane 3 ) - ;; CHECK: (func $simd-replace (type $9) (param $0 v128) (param $1 i32) (result v128) + ;; CHECK: (func $simd-replace (type $10) (param $0 v128) (param $1 i32) (result v128) ;; CHECK-NEXT: (i32x4.replace_lane 2 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -1153,7 +1183,7 @@ i32x4.replace_lane 2 ) - ;; CHECK: (func $simd-shuffle (type $17) (param $0 v128) (param $1 v128) (result v128) + ;; CHECK: (func $simd-shuffle (type $18) (param $0 v128) (param $1 v128) (result v128) ;; CHECK-NEXT: (i8x16.shuffle 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -1165,7 +1195,7 @@ i8x16.shuffle 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23 ) - ;; CHECK: (func $simd-ternary (type $18) (param $0 v128) (param $1 v128) (param $2 v128) (result v128) + ;; CHECK: (func $simd-ternary (type $19) (param $0 v128) (param $1 v128) (param $2 v128) (result v128) ;; CHECK-NEXT: (v128.bitselect ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -1179,7 +1209,7 @@ v128.bitselect ) - ;; CHECK: (func $simd-shift (type $9) (param $0 v128) (param $1 i32) (result v128) + ;; CHECK: (func $simd-shift (type $10) (param $0 v128) (param $1 i32) (result v128) ;; CHECK-NEXT: (i8x16.shl ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -1191,7 +1221,7 @@ i8x16.shl ) - ;; CHECK: (func $simd-load (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $simd-load (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (v128.load8x8_s $mem offset=8 ;; CHECK-NEXT: (local.get $0) @@ -1212,7 +1242,7 @@ drop ) - ;; CHECK: (func $simd-load-store-lane (type $19) (param $0 i32) (param $1 i64) (param $2 v128) + ;; CHECK: (func $simd-load-store-lane (type $20) (param $0 i32) (param $1 i64) (param $2 v128) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (v128.load16_lane $mem 7 ;; CHECK-NEXT: (local.get $0) @@ -1234,7 +1264,7 @@ v128.store64_lane 3 align=4 0 ) - ;; CHECK: (func $memory-init (type $8) (param $0 i32) (param $1 i32) (param $2 i32) + ;; CHECK: (func $memory-init (type $9) (param $0 i32) (param $1 i32) (param $2 i32) ;; CHECK-NEXT: (memory.init $mem-i32 $passive ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -1275,7 +1305,7 @@ data.drop $passive ) - ;; CHECK: (func $memory-copy (type $20) (param $0 i32) (param $1 i32) (param $2 i64) (param $3 i64) + ;; CHECK: (func $memory-copy (type $21) (param $0 i32) (param $1 i32) (param $2 i64) (param $3 i64) ;; CHECK-NEXT: (memory.copy $mem $mem ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -1307,7 +1337,7 @@ memory.copy $mem-i64 3 ) - ;; CHECK: (func $memory-fill (type $4) (param $0 i32) (param $1 i64) + ;; CHECK: (func $memory-fill (type $5) (param $0 i32) (param $1 i64) ;; CHECK-NEXT: (memory.fill $mem ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 1) @@ -1346,7 +1376,7 @@ return ) - ;; CHECK: (func $return-one (type $21) (param $0 i32) (result i32) + ;; CHECK: (func $return-one (type $22) (param $0 i32) (result i32) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -1356,7 +1386,7 @@ return ) - ;; CHECK: (func $return-two (type $22) (param $0 i32) (param $1 i64) (result i32 i64) + ;; CHECK: (func $return-two (type $23) (param $0 i32) (param $1 i64) (result i32 i64) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (tuple.make ;; CHECK-NEXT: (local.get $0) @@ -1370,7 +1400,7 @@ return ) - ;; CHECK: (func $return-two-first-unreachable (type $23) (param $0 i64) (result i32 i64) + ;; CHECK: (func $return-two-first-unreachable (type $24) (param $0 i64) (result i32 i64) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (tuple.make ;; CHECK-NEXT: (unreachable) @@ -1384,7 +1414,7 @@ return ) - ;; CHECK: (func $return-two-second-unreachable (type $24) (param $0 i32) (result i32 i64) + ;; CHECK: (func $return-two-second-unreachable (type $25) (param $0 i32) (result i32 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -1646,7 +1676,6 @@ ) ;; CHECK: (func $use-types (type $59) (param $0 (ref $s0)) (param $1 (ref $s1)) (param $2 (ref $s2)) (param $3 (ref $s3)) (param $4 (ref $s4)) (param $5 (ref $s5)) (param $6 (ref $s6)) (param $7 (ref $s7)) (param $8 (ref $s8)) (param $9 (ref $a0)) (param $10 (ref $a1)) (param $11 (ref $a2)) (param $12 (ref $a3)) (param $13 (ref $subvoid)) (param $14 (ref $submany)) - ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $use-types (param (ref $s0)) |