diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mixed_arena.h | 10 | ||||
-rw-r--r-- | src/wasm-builder.h | 15 | ||||
-rw-r--r-- | src/wasm-ir-builder.h | 208 | ||||
-rw-r--r-- | src/wasm.h | 14 | ||||
-rw-r--r-- | src/wasm/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/wasm/wasm-ir-builder.cpp | 703 | ||||
-rw-r--r-- | src/wasm/wat-parser.cpp | 586 |
7 files changed, 1046 insertions, 491 deletions
diff --git a/src/mixed_arena.h b/src/mixed_arena.h index 4d8cda0ac..530fa9daa 100644 --- a/src/mixed_arena.h +++ b/src/mixed_arena.h @@ -403,7 +403,15 @@ public: ArenaVector(MixedArena& allocator) : allocator(allocator) {} ArenaVector(ArenaVector<T>&& other) : allocator(other.allocator) { - *this = other; + swap(other); + } + + ArenaVector<T>& operator=(ArenaVector<T>&& other) { + if (this != &other) { + this->clear(); + this->swap(other); + } + return *this; } void allocate(size_t size) { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index cb7c5e745..2093b9419 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -893,6 +893,21 @@ public: ret->finalize(); return ret; } + StructNew* makeStructNew(HeapType type, + std::initializer_list<Expression*> args) { + auto* ret = wasm.allocator.alloc<StructNew>(); + ret->operands.set(args); + ret->type = Type(type, NonNullable); + ret->finalize(); + return ret; + } + StructNew* makeStructNew(HeapType type, ExpressionList&& args) { + auto* ret = wasm.allocator.alloc<StructNew>(); + ret->operands = std::move(args); + ret->type = Type(type, NonNullable); + ret->finalize(); + return ret; + } template<typename T> StructNew* makeStructNew(HeapType type, const T& args) { auto* ret = wasm.allocator.alloc<StructNew>(); ret->operands.set(args); diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h new file mode 100644 index 000000000..6a1b3f18a --- /dev/null +++ b/src/wasm-ir-builder.h @@ -0,0 +1,208 @@ +/* + * Copyright 2023 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_wasm_ir_builder_h +#define wasm_wasm_ir_builder_h + +#include <vector> + +#include "support/result.h" +#include "wasm-builder.h" +#include "wasm-traversal.h" +#include "wasm-type.h" +#include "wasm.h" + +namespace wasm { + +// A utility for constructing valid Binaryen IR from arbitrary valid sequences +// of WebAssembly instructions. The user is responsible for providing Expression +// nodes with all of their non-child fields already filled out, and IRBuilder is +// responsible for setting child fields and finalizing nodes. +// +// To use, call CHECK_ERR(visit(...)) or CHECK_ERR(makeXYZ(...)) on each +// expression in the sequence, then call build(). +class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { +public: + IRBuilder(Module& wasm, Function* func = nullptr) + : wasm(wasm), func(func), builder(wasm) {} + + // 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(); + + // Call visit() on an existing Expression with its non-child fields + // initialized to initialize the child fields and refinalize it. The specific + // visitors are internal implementation details. + [[nodiscard]] Result<> visit(Expression*); + [[nodiscard]] Result<> visitExpression(Expression*); + [[nodiscard]] Result<> visitBlock(Block*); + [[nodiscard]] Result<> visitReturn(Return*); + [[nodiscard]] Result<> visitStructNew(StructNew*); + [[nodiscard]] Result<> visitArrayNew(ArrayNew*); + + // 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<> makeIf(); + // [[nodiscard]] Result<> makeLoop(); + // [[nodiscard]] Result<> makeBreak(); + // [[nodiscard]] Result<> makeSwitch(); + // [[nodiscard]] Result<> makeCall(); + // [[nodiscard]] Result<> makeCallIndirect(); + [[nodiscard]] Result<> makeLocalGet(Index local); + [[nodiscard]] Result<> makeLocalSet(Index local); + [[nodiscard]] Result<> makeLocalTee(Index local); + [[nodiscard]] Result<> makeGlobalGet(Name global); + [[nodiscard]] Result<> makeGlobalSet(Name global); + [[nodiscard]] Result<> makeLoad(unsigned bytes, + bool signed_, + Address offset, + unsigned align, + Type type, + Name mem); + [[nodiscard]] Result<> makeStore( + unsigned bytes, Address offset, unsigned align, Type type, Name mem); + [[nodiscard]] Result<> + makeAtomicLoad(unsigned bytes, Address offset, Type type, Name mem); + [[nodiscard]] Result<> + makeAtomicStore(unsigned bytes, Address offset, Type type, Name mem); + [[nodiscard]] Result<> makeAtomicRMW( + AtomicRMWOp op, unsigned bytes, Address offset, Type type, Name mem); + [[nodiscard]] Result<> + makeAtomicCmpxchg(unsigned bytes, Address offset, Type type, Name mem); + [[nodiscard]] Result<> makeAtomicWait(Type type, Address offset, Name mem); + [[nodiscard]] Result<> makeAtomicNotify(Address offset, Name mem); + [[nodiscard]] Result<> makeAtomicFence(); + [[nodiscard]] Result<> makeSIMDExtract(SIMDExtractOp op, uint8_t lane); + [[nodiscard]] Result<> makeSIMDReplace(SIMDReplaceOp op, uint8_t lane); + [[nodiscard]] Result<> makeSIMDShuffle(const std::array<uint8_t, 16>& lanes); + [[nodiscard]] Result<> makeSIMDTernary(SIMDTernaryOp op); + [[nodiscard]] Result<> makeSIMDShift(SIMDShiftOp op); + [[nodiscard]] Result<> + makeSIMDLoad(SIMDLoadOp op, Address offset, unsigned align, Name mem); + [[nodiscard]] Result<> makeSIMDLoadStoreLane(SIMDLoadStoreLaneOp op, + Address offset, + unsigned align, + uint8_t lane, + Name mem); + [[nodiscard]] Result<> makeMemoryInit(Name data, Name mem); + [[nodiscard]] Result<> makeDataDrop(Name data); + [[nodiscard]] Result<> makeMemoryCopy(Name destMem, Name srcMem); + [[nodiscard]] Result<> makeMemoryFill(Name mem); + [[nodiscard]] Result<> makeConst(Literal val); + [[nodiscard]] Result<> makeUnary(UnaryOp op); + [[nodiscard]] Result<> makeBinary(BinaryOp op); + [[nodiscard]] Result<> makeSelect(std::optional<Type> type = std::nullopt); + [[nodiscard]] Result<> makeDrop(); + [[nodiscard]] Result<> makeReturn(); + [[nodiscard]] Result<> makeMemorySize(Name mem); + [[nodiscard]] Result<> makeMemoryGrow(Name mem); + [[nodiscard]] Result<> makeUnreachable(); + // [[nodiscard]] Result<> makePop(); + [[nodiscard]] Result<> makeRefNull(HeapType type); + [[nodiscard]] Result<> makeRefIsNull(); + // [[nodiscard]] Result<> makeRefFunc(); + [[nodiscard]] Result<> makeRefEq(); + // [[nodiscard]] Result<> makeTableGet(); + // [[nodiscard]] Result<> makeTableSet(); + // [[nodiscard]] Result<> makeTableSize(); + // [[nodiscard]] Result<> makeTableGrow(); + // [[nodiscard]] Result<> makeTry(); + // [[nodiscard]] Result<> makeThrow(); + // [[nodiscard]] Result<> makeRethrow(); + // [[nodiscard]] Result<> makeTupleMake(); + // [[nodiscard]] Result<> makeTupleExtract(); + [[nodiscard]] Result<> makeI31New(); + [[nodiscard]] Result<> makeI31Get(bool signed_); + // [[nodiscard]] Result<> makeCallRef(); + // [[nodiscard]] Result<> makeRefTest(); + // [[nodiscard]] Result<> makeRefCast(); + // [[nodiscard]] Result<> makeBrOn(); + [[nodiscard]] Result<> makeStructNew(HeapType type); + [[nodiscard]] Result<> makeStructNewDefault(HeapType type); + [[nodiscard]] Result<> + makeStructGet(HeapType type, Index field, bool signed_); + [[nodiscard]] Result<> makeStructSet(HeapType type, Index field); + [[nodiscard]] Result<> makeArrayNew(HeapType type); + [[nodiscard]] Result<> makeArrayNewDefault(HeapType type); + [[nodiscard]] Result<> makeArrayNewData(HeapType type, Name data); + [[nodiscard]] Result<> makeArrayNewElem(HeapType type, Name elem); + // [[nodiscard]] Result<> makeArrayNewFixed(); + [[nodiscard]] Result<> makeArrayGet(HeapType type, bool signed_); + [[nodiscard]] Result<> makeArraySet(HeapType type); + [[nodiscard]] Result<> makeArrayLen(); + [[nodiscard]] Result<> makeArrayCopy(HeapType destType, HeapType srcType); + [[nodiscard]] Result<> makeArrayFill(HeapType type); + // [[nodiscard]] Result<> makeArrayInitData(); + // [[nodiscard]] Result<> makeArrayInitElem(); + // [[nodiscard]] Result<> makeRefAs(); + // [[nodiscard]] Result<> makeStringNew(); + // [[nodiscard]] Result<> makeStringConst(); + // [[nodiscard]] Result<> makeStringMeasure(); + // [[nodiscard]] Result<> makeStringEncode(); + // [[nodiscard]] Result<> makeStringConcat(); + // [[nodiscard]] Result<> makeStringEq(); + // [[nodiscard]] Result<> makeStringAs(); + // [[nodiscard]] Result<> makeStringWTF8Advance(); + // [[nodiscard]] Result<> makeStringWTF16Get(); + // [[nodiscard]] Result<> makeStringIterNext(); + // [[nodiscard]] Result<> makeStringIterMove(); + // [[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: + Module& wasm; + Function* func; + Builder builder; + + // The context for a single block scope, including the instructions parsed + // inside that scope so far and the ultimate result type we expect this block + // to have. + struct BlockCtx { + std::vector<Expression*> exprStack; + Type type; + }; + + // 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; + } + + // Whether we have seen an unreachable instruction and are in + // stack-polymorphic unreachable mode. + bool unreachable = false; + + Result<Index> addScratchLocal(Type); + [[nodiscard]] Result<> push(Expression*); + Result<Expression*> pop(); +}; + +} // namespace wasm + +#endif // wasm_wasm_ir_builder_h diff --git a/src/wasm.h b/src/wasm.h index 90628c5bf..c2c2db79e 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1347,6 +1347,7 @@ public: class RefIsNull : public SpecificExpression<Expression::RefIsNullId> { public: + RefIsNull() = default; RefIsNull(MixedArena& allocator) {} Expression* value; @@ -1366,6 +1367,7 @@ public: class RefEq : public SpecificExpression<Expression::RefEqId> { public: + RefEq() = default; RefEq(MixedArena& allocator) {} Expression* left; @@ -1478,6 +1480,7 @@ public: class I31New : public SpecificExpression<Expression::I31NewId> { public: + I31New() = default; I31New(MixedArena& allocator) {} Expression* value; @@ -1487,6 +1490,7 @@ public: class I31Get : public SpecificExpression<Expression::I31GetId> { public: + I31Get() = default; I31Get(MixedArena& allocator) {} Expression* i31; @@ -1562,6 +1566,7 @@ public: class StructGet : public SpecificExpression<Expression::StructGetId> { public: + StructGet() = default; StructGet(MixedArena& allocator) {} Index index; @@ -1574,6 +1579,7 @@ public: class StructSet : public SpecificExpression<Expression::StructSetId> { public: + StructSet() = default; StructSet(MixedArena& allocator) {} Index index; @@ -1585,6 +1591,7 @@ public: class ArrayNew : public SpecificExpression<Expression::ArrayNewId> { public: + ArrayNew() = default; ArrayNew(MixedArena& allocator) {} // If set, then the initial value is assigned to all entries in the array. If @@ -1600,6 +1607,7 @@ public: class ArrayNewData : public SpecificExpression<Expression::ArrayNewDataId> { public: + ArrayNewData() = default; ArrayNewData(MixedArena& allocator) {} Name segment; @@ -1611,6 +1619,7 @@ public: class ArrayNewElem : public SpecificExpression<Expression::ArrayNewElemId> { public: + ArrayNewElem() = default; ArrayNewElem(MixedArena& allocator) {} Name segment; @@ -1631,6 +1640,7 @@ public: class ArrayGet : public SpecificExpression<Expression::ArrayGetId> { public: + ArrayGet() = default; ArrayGet(MixedArena& allocator) {} Expression* ref; @@ -1643,6 +1653,7 @@ public: class ArraySet : public SpecificExpression<Expression::ArraySetId> { public: + ArraySet() = default; ArraySet(MixedArena& allocator) {} Expression* ref; @@ -1654,6 +1665,7 @@ public: class ArrayLen : public SpecificExpression<Expression::ArrayLenId> { public: + ArrayLen() = default; ArrayLen(MixedArena& allocator) {} Expression* ref; @@ -1663,6 +1675,7 @@ public: class ArrayCopy : public SpecificExpression<Expression::ArrayCopyId> { public: + ArrayCopy() = default; ArrayCopy(MixedArena& allocator) {} Expression* destRef; @@ -1676,6 +1689,7 @@ public: class ArrayFill : public SpecificExpression<Expression::ArrayFillId> { public: + ArrayFill() = default; ArrayFill(MixedArena& allocator) {} Expression* ref; diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt index 7b7f85698..30f52f9ee 100644 --- a/src/wasm/CMakeLists.txt +++ b/src/wasm/CMakeLists.txt @@ -8,6 +8,7 @@ set(wasm_SOURCES wasm-emscripten.cpp wasm-interpreter.cpp wasm-io.cpp + wasm-ir-builder.cpp wasm-s-parser.cpp wasm-stack.cpp wasm-type.cpp diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp new file mode 100644 index 000000000..6786816f3 --- /dev/null +++ b/src/wasm/wasm-ir-builder.cpp @@ -0,0 +1,703 @@ +/* + * Copyright 2023 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cassert> + +#include "ir/names.h" +#include "ir/utils.h" +#include "wasm-ir-builder.h" + +using namespace std::string_literals; + +namespace wasm { + +namespace { + +Result<> validateTypeAnnotation(HeapType type, Expression* child) { + if (child->type == Type::unreachable) { + return Ok{}; + } + if (!child->type.isRef() || + !HeapType::isSubType(child->type.getHeapType(), type)) { + return Err{"invalid reference type on stack"}; + } + return Ok{}; +} + +} // 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"}; + } + Name name = Names::getValidLocalName(*func, "scratch"); + return Builder::addVar(func, name, type); +} + +Result<> IRBuilder::push(Expression* expr) { + auto& exprStack = getExprStack(); + 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) { + 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); + } + return Ok{}; +} + +Result<Expression*> IRBuilder::pop() { + auto& exprStack = getExprStack(); + + // 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; + } + } + + if (firstNone == 0) { + // There are no expressions that produce values. + if (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; + } + + // 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 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); + } + unreachable = false; + auto ret = std::move(exprStack); + scopeStack.pop_back(); + return ret; +} + +Result<> IRBuilder::visit(Expression* curr) { + UnifiedExpressionVisitor<IRBuilder, Result<>>::visit(curr); + if (auto* block = curr->dynCast<Block>()) { + block->finalize(block->type); + } else { + // TODO: Call more efficient versions of finalize() that take the known type + // for other kinds of nodes as well, as done above. + ReFinalizeNode{}.visit(curr); + } + return push(curr); +} + +// Handle the common case of instructions with a constant number of children +// uniformly. +Result<> IRBuilder::visitExpression(Expression* curr) { +#define DELEGATE_ID curr->_id +#define DELEGATE_START(id) [[maybe_unused]] auto* expr = curr->cast<id>(); +#define DELEGATE_FIELD_CHILD(id, field) \ + auto field = pop(); \ + CHECK_ERR(field); \ + expr->field = *field; +#define DELEGATE_END(id) + +#define DELEGATE_FIELD_OPTIONAL_CHILD(id, field) \ + WASM_UNREACHABLE("should have called visit" #id " because " #id \ + " has optional child " #field); +#define DELEGATE_FIELD_CHILD_VECTOR(id, field) \ + WASM_UNREACHABLE("should have called visit" #id " because " #id \ + " has child vector " #field); + +#define DELEGATE_FIELD_INT(id, field) +#define DELEGATE_FIELD_INT_ARRAY(id, field) +#define DELEGATE_FIELD_LITERAL(id, field) +#define DELEGATE_FIELD_NAME(id, field) +#define DELEGATE_FIELD_NAME_VECTOR(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_DEF(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(id, field) +#define DELEGATE_FIELD_TYPE(id, field) +#define DELEGATE_FIELD_HEAPTYPE(id, field) +#define DELEGATE_FIELD_ADDRESS(id, field) + +#include "wasm-delegations-fields.def" + + return Ok{}; +} + +Result<> IRBuilder::visitBlock(Block* curr) { + // TODO: Handle popping scope and filling block here instead of externally. + return Ok{}; +} + +Result<> IRBuilder::visitReturn(Return* curr) { + if (!func) { + return Err{"cannot return outside of a function"}; + } + size_t n = func->getResults().size(); + if (n == 0) { + curr->value = nullptr; + } else if (n == 1) { + auto val = pop(); + CHECK_ERR(val); + curr->value = *val; + } else { + std::vector<Expression*> vals(n); + for (size_t i = 0; i < n; ++i) { + auto val = pop(); + CHECK_ERR(val); + vals[n - i - 1] = *val; + } + curr->value = builder.makeTupleMake(vals); + } + return Ok{}; +} + +Result<> IRBuilder::visitStructNew(StructNew* curr) { + for (size_t i = 0, n = curr->operands.size(); i < n; ++i) { + auto val = pop(); + CHECK_ERR(val); + curr->operands[n - 1 - i] = *val; + } + return Ok{}; +} + +Result<> IRBuilder::visitArrayNew(ArrayNew* curr) { + auto size = pop(); + CHECK_ERR(size); + curr->size = *size; + if (!curr->isWithDefault()) { + auto init = pop(); + CHECK_ERR(init); + curr->init = *init; + } + return Ok{}; +} + +Result<> IRBuilder::makeNop() { return push(builder.makeNop()); } + +Result<> IRBuilder::makeBlock() { return push(builder.makeBlock()); } + +// Result<> IRBuilder::makeIf() {} + +// Result<> IRBuilder::makeLoop() {} + +// Result<> IRBuilder::makeBreak() {} + +// Result<> IRBuilder::makeSwitch() {} + +// Result<> IRBuilder::makeCall() {} + +// Result<> IRBuilder::makeCallIndirect() {} + +Result<> IRBuilder::makeLocalGet(Index local) { + return push(builder.makeLocalGet(local, func->getLocalType(local))); +} + +Result<> IRBuilder::makeLocalSet(Index local) { + LocalSet curr; + CHECK_ERR(visitLocalSet(&curr)); + return push(builder.makeLocalSet(local, curr.value)); +} + +Result<> IRBuilder::makeLocalTee(Index local) { + LocalSet curr; + CHECK_ERR(visitLocalSet(&curr)); + return push( + builder.makeLocalTee(local, curr.value, func->getLocalType(local))); +} + +Result<> IRBuilder::makeGlobalGet(Name global) { + return push(builder.makeGlobalGet(global, wasm.getGlobal(global)->type)); +} + +Result<> IRBuilder::makeGlobalSet(Name global) { + GlobalSet curr; + CHECK_ERR(visitGlobalSet(&curr)); + return push(builder.makeGlobalSet(global, curr.value)); +} + +Result<> IRBuilder::makeLoad(unsigned bytes, + bool signed_, + Address offset, + unsigned align, + Type type, + Name mem) { + Load curr; + CHECK_ERR(visitLoad(&curr)); + return push( + builder.makeLoad(bytes, signed_, offset, align, curr.ptr, type, mem)); +} + +Result<> IRBuilder::makeStore( + unsigned bytes, Address offset, unsigned align, Type type, Name mem) { + Store curr; + CHECK_ERR(visitStore(&curr)); + return push( + builder.makeStore(bytes, offset, align, curr.ptr, curr.value, type, mem)); +} + +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)); +} + +Result<> IRBuilder::makeAtomicStore(unsigned bytes, + Address offset, + Type type, + Name mem) { + Store curr; + CHECK_ERR(visitStore(&curr)); + return push( + builder.makeAtomicStore(bytes, offset, curr.ptr, curr.value, type, mem)); +} + +Result<> IRBuilder::makeAtomicRMW( + AtomicRMWOp op, unsigned bytes, Address offset, Type type, Name mem) { + AtomicRMW curr; + CHECK_ERR(visitAtomicRMW(&curr)); + return push( + builder.makeAtomicRMW(op, bytes, offset, curr.ptr, curr.value, type, mem)); +} + +Result<> IRBuilder::makeAtomicCmpxchg(unsigned bytes, + Address offset, + Type type, + Name mem) { + AtomicCmpxchg curr; + CHECK_ERR(visitAtomicCmpxchg(&curr)); + return push(builder.makeAtomicCmpxchg( + bytes, offset, curr.ptr, curr.expected, curr.replacement, type, mem)); +} + +Result<> IRBuilder::makeAtomicWait(Type type, Address offset, Name mem) { + AtomicWait curr; + CHECK_ERR(visitAtomicWait(&curr)); + return push(builder.makeAtomicWait( + curr.ptr, curr.expected, curr.timeout, type, offset, mem)); +} + +Result<> IRBuilder::makeAtomicNotify(Address offset, Name mem) { + AtomicNotify curr; + CHECK_ERR(visitAtomicNotify(&curr)); + return push( + builder.makeAtomicNotify(curr.ptr, curr.notifyCount, offset, mem)); +} + +Result<> IRBuilder::makeAtomicFence() { + return push(builder.makeAtomicFence()); +} + +Result<> IRBuilder::makeSIMDExtract(SIMDExtractOp op, uint8_t lane) { + SIMDExtract curr; + CHECK_ERR(visitSIMDExtract(&curr)); + return push(builder.makeSIMDExtract(op, curr.vec, lane)); +} + +Result<> IRBuilder::makeSIMDReplace(SIMDReplaceOp op, uint8_t lane) { + SIMDReplace curr; + CHECK_ERR(visitSIMDReplace(&curr)); + return push(builder.makeSIMDReplace(op, curr.vec, lane, curr.value)); +} + +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)); +} + +Result<> IRBuilder::makeSIMDTernary(SIMDTernaryOp op) { + SIMDTernary curr; + CHECK_ERR(visitSIMDTernary(&curr)); + return push(builder.makeSIMDTernary(op, curr.a, curr.b, curr.c)); +} + +Result<> IRBuilder::makeSIMDShift(SIMDShiftOp op) { + SIMDShift curr; + CHECK_ERR(visitSIMDShift(&curr)); + return push(builder.makeSIMDShift(op, curr.vec, curr.shift)); +} + +Result<> IRBuilder::makeSIMDLoad(SIMDLoadOp op, + Address offset, + unsigned align, + Name mem) { + SIMDLoad curr; + CHECK_ERR(visitSIMDLoad(&curr)); + return push(builder.makeSIMDLoad(op, offset, align, curr.ptr, mem)); +} + +Result<> IRBuilder::makeSIMDLoadStoreLane(SIMDLoadStoreLaneOp op, + Address offset, + unsigned align, + uint8_t lane, + Name mem) { + SIMDLoadStoreLane curr; + CHECK_ERR(visitSIMDLoadStoreLane(&curr)); + return push(builder.makeSIMDLoadStoreLane( + op, offset, align, lane, curr.ptr, curr.vec, mem)); +} + +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)); +} + +Result<> IRBuilder::makeDataDrop(Name data) { + return push(builder.makeDataDrop(data)); +} + +Result<> IRBuilder::makeMemoryCopy(Name destMem, Name srcMem) { + MemoryCopy curr; + CHECK_ERR(visitMemoryCopy(&curr)); + return push( + builder.makeMemoryCopy(curr.dest, curr.source, curr.size, destMem, srcMem)); +} + +Result<> IRBuilder::makeMemoryFill(Name mem) { + MemoryFill curr; + CHECK_ERR(visitMemoryFill(&curr)); + return push(builder.makeMemoryFill(curr.dest, curr.value, curr.size, mem)); +} + +Result<> IRBuilder::makeConst(Literal val) { + return push(builder.makeConst(val)); +} + +Result<> IRBuilder::makeUnary(UnaryOp op) { + Unary curr; + CHECK_ERR(visitUnary(&curr)); + return push(builder.makeUnary(op, curr.value)); +} + +Result<> IRBuilder::makeBinary(BinaryOp op) { + Binary curr; + CHECK_ERR(visitBinary(&curr)); + return push(builder.makeBinary(op, curr.left, curr.right)); +} + +Result<> IRBuilder::makeSelect(std::optional<Type> type) { + Select curr; + CHECK_ERR(visitSelect(&curr)); + auto* built = + type ? builder.makeSelect(curr.condition, curr.ifTrue, curr.ifFalse, *type) + : builder.makeSelect(curr.condition, curr.ifTrue, curr.ifFalse); + if (type && !Type::isSubType(built->type, *type)) { + return Err{"select type does not match expected type"}; + } + return push(built); +} + +Result<> IRBuilder::makeDrop() { + Drop curr; + CHECK_ERR(visitDrop(&curr)); + return push(builder.makeDrop(curr.value)); +} + +Result<> IRBuilder::makeReturn() { + Return curr; + CHECK_ERR(visitReturn(&curr)); + return push(builder.makeReturn(curr.value)); +} + +Result<> IRBuilder::makeMemorySize(Name mem) { + return push(builder.makeMemorySize(mem)); +} + +Result<> IRBuilder::makeMemoryGrow(Name mem) { + MemoryGrow curr; + CHECK_ERR(visitMemoryGrow(&curr)); + return push(builder.makeMemoryGrow(curr.delta, mem)); +} + +Result<> IRBuilder::makeUnreachable() { + return push(builder.makeUnreachable()); +} + +// Result<> IRBuilder::makePop() {} + +Result<> IRBuilder::makeRefNull(HeapType type) { + return push(builder.makeRefNull(type)); +} + +Result<> IRBuilder::makeRefIsNull() { + RefIsNull curr; + CHECK_ERR(visitRefIsNull(&curr)); + return push(builder.makeRefIsNull(curr.value)); +} + +// Result<> IRBuilder::makeRefFunc() {} + +Result<> IRBuilder::makeRefEq() { + RefEq curr; + CHECK_ERR(visitRefEq(&curr)); + return push(builder.makeRefEq(curr.left, curr.right)); +} + +// Result<> IRBuilder::makeTableGet() {} + +// Result<> IRBuilder::makeTableSet() {} + +// Result<> IRBuilder::makeTableSize() {} + +// Result<> IRBuilder::makeTableGrow() {} + +// Result<> IRBuilder::makeTry() {} + +// Result<> IRBuilder::makeThrow() {} + +// Result<> IRBuilder::makeRethrow() {} + +// Result<> IRBuilder::makeTupleMake() {} + +// Result<> IRBuilder::makeTupleExtract() {} + +Result<> IRBuilder::makeI31New() { + I31New curr; + CHECK_ERR(visitI31New(&curr)); + return push(builder.makeI31New(curr.value)); +} + +Result<> IRBuilder::makeI31Get(bool signed_) { + I31Get curr; + CHECK_ERR(visitI31Get(&curr)); + return push(builder.makeI31Get(curr.i31, signed_)); +} + +// Result<> IRBuilder::makeCallRef() {} + +// Result<> IRBuilder::makeRefTest() {} + +// Result<> IRBuilder::makeRefCast() {} + +// Result<> IRBuilder::makeBrOn() {} + +Result<> IRBuilder::makeStructNew(HeapType type) { + StructNew curr(wasm.allocator); + // 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))); +} + +Result<> IRBuilder::makeStructNewDefault(HeapType type) { + return push(builder.makeStructNew(type, {})); +} + +Result<> IRBuilder::makeStructGet(HeapType type, Index field, bool signed_) { + const auto& fields = type.getStruct().fields; + StructGet curr; + CHECK_ERR(visitStructGet(&curr)); + CHECK_ERR(validateTypeAnnotation(type, curr.ref)); + return push( + builder.makeStructGet(field, curr.ref, fields[field].type, signed_)); +} + +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)); +} + +Result<> IRBuilder::makeArrayNew(HeapType type) { + ArrayNew curr; + // 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)); +} + +Result<> IRBuilder::makeArrayNewDefault(HeapType type) { + ArrayNew curr; + CHECK_ERR(visitArrayNew(&curr)); + return push(builder.makeArrayNew(type, curr.size)); +} + +Result<> IRBuilder::makeArrayNewData(HeapType type, Name data) { + ArrayNewData curr; + CHECK_ERR(visitArrayNewData(&curr)); + return push(builder.makeArrayNewData(type, data, curr.offset, curr.size)); +} + +Result<> IRBuilder::makeArrayNewElem(HeapType type, Name elem) { + ArrayNewElem curr; + CHECK_ERR(visitArrayNewElem(&curr)); + return push(builder.makeArrayNewElem(type, elem, curr.offset, curr.size)); +} + +// Result<> IRBuilder::makeArrayNewFixed() {} + +Result<> IRBuilder::makeArrayGet(HeapType type, bool signed_) { + ArrayGet curr; + CHECK_ERR(visitArrayGet(&curr)); + CHECK_ERR(validateTypeAnnotation(type, curr.ref)); + return push(builder.makeArrayGet( + curr.ref, curr.index, type.getArray().element.type, signed_)); +} + +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)); +} + +Result<> IRBuilder::makeArrayLen() { + ArrayLen curr; + CHECK_ERR(visitArrayLen(&curr)); + return push(builder.makeArrayLen(curr.ref)); +} + +Result<> IRBuilder::makeArrayCopy(HeapType destType, HeapType srcType) { + ArrayCopy curr; + CHECK_ERR(visitArrayCopy(&curr)); + CHECK_ERR(validateTypeAnnotation(destType, curr.destRef)); + CHECK_ERR(validateTypeAnnotation(srcType, curr.srcRef)); + return push(builder.makeArrayCopy( + curr.destRef, curr.destIndex, curr.srcRef, curr.srcIndex, curr.length)); +} + +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)); +} + +// Result<> IRBuilder::makeArrayInitData() {} + +// Result<> IRBuilder::makeArrayInitElem() {} + +// Result<> IRBuilder::makeRefAs() {} + +// Result<> IRBuilder::makeStringNew() {} + +// Result<> IRBuilder::makeStringConst() {} + +// Result<> IRBuilder::makeStringMeasure() {} + +// Result<> IRBuilder::makeStringEncode() {} + +// Result<> IRBuilder::makeStringConcat() {} + +// Result<> IRBuilder::makeStringEq() {} + +// Result<> IRBuilder::makeStringAs() {} + +// Result<> IRBuilder::makeStringWTF8Advance() {} + +// Result<> IRBuilder::makeStringWTF16Get() {} + +// Result<> IRBuilder::makeStringIterNext() {} + +// Result<> IRBuilder::makeStringIterMove() {} + +// Result<> IRBuilder::makeStringSliceWTF() {} + +// Result<> IRBuilder::makeStringSliceIter() {} + +} // namespace wasm diff --git a/src/wasm/wat-parser.cpp b/src/wasm/wat-parser.cpp index 970901409..bf438ae68 100644 --- a/src/wasm/wat-parser.cpp +++ b/src/wasm/wat-parser.cpp @@ -18,6 +18,7 @@ #include "ir/names.h" #include "support/name.h" #include "wasm-builder.h" +#include "wasm-ir-builder.h" #include "wasm-type.h" #include "wasm.h" #include "wat-lexer.h" @@ -1305,20 +1306,12 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { // local.get, etc. Function* func = nullptr; - // The context for a single block scope, including the instructions parsed - // inside that scope so far and the ultimate result type we expect this block - // to have. - struct BlockCtx { - std::vector<Expression*> exprStack; - Type type; - }; - - // The stack of block contexts currently being parsed. - std::vector<BlockCtx> scopeStack; + IRBuilder irBuilder; - // Whether we have seen an unreachable instruction and are in - // stack-polymorphic unreachable mode. - bool unreachable = false; + void setFunction(Function* func) { + this->func = func; + irBuilder.setFunction(func); + } ParseDefsCtx(std::string_view in, Module& wasm, @@ -1326,105 +1319,27 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { const std::unordered_map<Index, HeapType>& implicitTypes, const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), wasm(wasm), builder(wasm), - types(types), implicitTypes(implicitTypes) {} + types(types), implicitTypes(implicitTypes), irBuilder(wasm) {} - std::vector<Expression*>& getExprStack() { - if (scopeStack.empty()) { - // We are not in a function, so push a dummy scope. - scopeStack.push_back({{}, Type::none}); + template<typename T> Result<T> withLoc(Index pos, Result<T> res) { + if (auto err = res.getErr()) { + return in.err(pos, err->msg); } - return scopeStack.back().exprStack; + return res; } - void pushScope(Type type) { scopeStack.push_back({{}, type}); } - - Type getResultType() { - assert(!scopeStack.empty()); - return scopeStack.back().type; - } - - Result<> push(Index pos, Expression* expr) { - auto& exprStack = getExprStack(); - 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) { - expr = builder.dropIfConcretelyTyped(expr); - } - unreachable = true; - exprStack.push_back(expr); - } else if (expr->type.isTuple()) { - auto scratchIdx = addScratchLocal(pos, expr->type); - CHECK_ERR(scratchIdx); - CHECK_ERR(push(pos, builder.makeLocalSet(*scratchIdx, expr))); - for (Index i = 0; i < expr->type.size(); ++i) { - CHECK_ERR(push(pos, - builder.makeTupleExtract( - builder.makeLocalGet(*scratchIdx, expr->type), i))); - } - } else { - exprStack.push_back(expr); - } - return Ok{}; - } - - Result<Expression*> pop(Index pos) { - auto& exprStack = getExprStack(); - - // 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; - } - } - - if (firstNone == 0) { - // There are no expressions that produce values. - if (unreachable) { - return builder.makeUnreachable(); - } - return in.err(pos, "popping from empty stack"); - } - - if (firstNone == exprStack.size()) { - // The last expression produced a value. - auto expr = exprStack.back(); - exprStack.pop_back(); - return expr; - } - - // 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 scratchIdx = addScratchLocal(pos, 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); + template<typename T> Result<T> withLoc(Result<T> res) { + return withLoc(in.getPos(), res); } HeapType getBlockTypeFromResult(const std::vector<Type> results) { assert(results.size() == 1); - pushScope(results[0]); + irBuilder.pushScope(results[0]); return HeapType(Signature(Type::none, results[0])); } Result<HeapType> getBlockTypeFromTypeUse(Index pos, HeapType type) { - pushScope(type.getSignature().results); + irBuilder.pushScope(type.getSignature().results); return type; } @@ -1433,52 +1348,10 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { void appendInstr(Ok&, InstrT instr) {} Result<InstrsT> finishInstrs(Ok&) { - 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(self().in.getPos()); - 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(self().in.getPos()); - CHECK_ERR(expr); - exprStack.push_back(*expr); - } - unreachable = false; - auto ret = std::move(exprStack); - scopeStack.pop_back(); - return ret; + return withLoc(irBuilder.finishInstrs()); } - Expression* instrToExpr(Ok&) { - auto& exprStack = getExprStack(); - assert(scopeStack.size() == 1); - assert(exprStack.size() == 1); - - auto e = exprStack.back(); - exprStack.clear(); - unreachable = false; - return e; - } + Expression* instrToExpr(Ok&) { return irBuilder.build(); } GlobalTypeT makeGlobalType(Mutability, TypeT) { return Ok{}; } @@ -1677,17 +1550,6 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { Memarg getMemarg(uint64_t offset, uint32_t align) { return {offset, align}; } - Result<> validateTypeAnnotation(Index pos, HeapType type, Expression* child) { - if (child->type == Type::unreachable) { - return Ok{}; - } - if (!child->type.isRef() || - !HeapType::isSubType(child->type.getHeapType(), type)) { - return in.err(pos, "invalid reference type on stack"); - } - return Ok{}; - } - Result<Name> getMemory(Index pos, Name* mem) { if (mem) { return *mem; @@ -1705,126 +1567,88 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { // 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) { - return push(pos, builder.makeBlock(*label, instrs, results)); - } else { - return push(pos, builder.makeBlock(instrs, results)); + block->name = *label; } + block->list.set(instrs); + return withLoc(pos, irBuilder.visit(block)); } Result<> makeUnreachable(Index pos) { - return push(pos, builder.makeUnreachable()); + return withLoc(pos, irBuilder.makeUnreachable()); } - Result<> makeNop(Index pos) { return push(pos, builder.makeNop()); } + Result<> makeNop(Index pos) { return withLoc(pos, irBuilder.makeNop()); } Result<> makeBinary(Index pos, BinaryOp op) { - auto rhs = pop(pos); - CHECK_ERR(rhs); - auto lhs = pop(pos); - CHECK_ERR(lhs); - return push(pos, builder.makeBinary(op, *lhs, *rhs)); + return withLoc(pos, irBuilder.makeBinary(op)); } Result<> makeUnary(Index pos, UnaryOp op) { - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeUnary(op, *val)); + return withLoc(pos, irBuilder.makeUnary(op)); } Result<> makeSelect(Index pos, std::vector<Type>* res) { - if (res && res->size() > 1) { - return in.err(pos, "select may not have more than one result type"); - } - auto cond = pop(pos); - CHECK_ERR(cond); - auto ifFalse = pop(pos); - CHECK_ERR(ifFalse); - auto ifTrue = pop(pos); - CHECK_ERR(ifTrue); - auto select = builder.makeSelect(*cond, *ifTrue, *ifFalse); - if (res && !res->empty() && !Type::isSubType(select->type, res->front())) { - return in.err(pos, "select type annotation is incorrect"); + if (res && res->size()) { + if (res->size() > 1) { + return in.err(pos, "select may not have more than one result type"); + } + return withLoc(pos, irBuilder.makeSelect((*res)[0])); } - return push(pos, builder.makeSelect(*cond, *ifTrue, *ifFalse)); + return withLoc(pos, irBuilder.makeSelect()); } - Result<> makeDrop(Index pos) { - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeDrop(*val)); - } + Result<> makeDrop(Index pos) { return withLoc(pos, irBuilder.makeDrop()); } Result<> makeMemorySize(Index pos, Name* mem) { auto m = getMemory(pos, mem); CHECK_ERR(m); - return push(pos, builder.makeMemorySize(*m)); + return withLoc(pos, irBuilder.makeMemorySize(*m)); } Result<> makeMemoryGrow(Index pos, Name* mem) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeMemoryGrow(*val, *m)); + return withLoc(pos, irBuilder.makeMemoryGrow(*m)); } Result<> makeLocalGet(Index pos, Index local) { - if (!func) { - return in.err(pos, "local.get must be inside a function"); - } - assert(local < func->getNumLocals()); - return push(pos, builder.makeLocalGet(local, func->getLocalType(local))); + return withLoc(pos, irBuilder.makeLocalGet(local)); } Result<> makeLocalTee(Index pos, Index local) { - if (!func) { - return in.err(pos, "local.tee must be inside a function"); - } - assert(local < func->getNumLocals()); - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, - builder.makeLocalTee(local, *val, func->getLocalType(local))); + return withLoc(pos, irBuilder.makeLocalTee(local)); } Result<> makeLocalSet(Index pos, Index local) { - if (!func) { - return in.err(pos, "local.set must be inside a function"); - } - assert(local < func->getNumLocals()); - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeLocalSet(local, *val)); + return withLoc(pos, irBuilder.makeLocalSet(local)); } Result<> makeGlobalGet(Index pos, Name global) { - assert(wasm.getGlobalOrNull(global)); - auto type = wasm.getGlobal(global)->type; - return push(pos, builder.makeGlobalGet(global, type)); + return withLoc(pos, irBuilder.makeGlobalGet(global)); } Result<> makeGlobalSet(Index pos, Name global) { assert(wasm.getGlobalOrNull(global)); - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeGlobalSet(global, *val)); + return withLoc(pos, irBuilder.makeGlobalSet(global)); } Result<> makeI32Const(Index pos, uint32_t c) { - return push(pos, builder.makeConst(Literal(c))); + return withLoc(pos, irBuilder.makeConst(Literal(c))); } Result<> makeI64Const(Index pos, uint64_t c) { - return push(pos, builder.makeConst(Literal(c))); + return withLoc(pos, irBuilder.makeConst(Literal(c))); } Result<> makeF32Const(Index pos, float c) { - return push(pos, builder.makeConst(Literal(c))); + return withLoc(pos, irBuilder.makeConst(Literal(c))); } Result<> makeF64Const(Index pos, double c) { - return push(pos, builder.makeConst(Literal(c))); + return withLoc(pos, irBuilder.makeConst(Literal(c))); } Result<> makeLoad(Index pos, @@ -1836,168 +1660,103 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { Memarg memarg) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto ptr = pop(pos); - CHECK_ERR(ptr); if (isAtomic) { - return push(pos, - builder.makeAtomicLoad(bytes, memarg.offset, *ptr, type, *m)); + return withLoc(pos, + irBuilder.makeAtomicLoad(bytes, memarg.offset, type, *m)); } - return push(pos, - builder.makeLoad( - bytes, signed_, memarg.offset, memarg.align, *ptr, type, *m)); + return withLoc(pos, + irBuilder.makeLoad( + bytes, signed_, memarg.offset, memarg.align, type, *m)); } Result<> makeStore( Index pos, Type type, int bytes, bool isAtomic, Name* mem, Memarg memarg) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto val = pop(pos); - CHECK_ERR(val); - auto ptr = pop(pos); - CHECK_ERR(ptr); if (isAtomic) { - return push( - pos, - builder.makeAtomicStore(bytes, memarg.offset, *ptr, *val, type, *m)); + return withLoc(pos, + irBuilder.makeAtomicStore(bytes, memarg.offset, type, *m)); } - return push(pos, - builder.makeStore( - bytes, memarg.offset, memarg.align, *ptr, *val, type, *m)); + return withLoc( + pos, irBuilder.makeStore(bytes, memarg.offset, memarg.align, type, *m)); } Result<> makeAtomicRMW( Index pos, AtomicRMWOp op, Type type, int bytes, Name* mem, Memarg memarg) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto val = pop(pos); - CHECK_ERR(val); - auto ptr = pop(pos); - CHECK_ERR(ptr); - return push( - pos, - builder.makeAtomicRMW(op, bytes, memarg.offset, *ptr, *val, type, *m)); + return withLoc(pos, + irBuilder.makeAtomicRMW(op, bytes, memarg.offset, type, *m)); } Result<> makeAtomicCmpxchg(Index pos, Type type, int bytes, Name* mem, Memarg memarg) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto replacement = pop(pos); - CHECK_ERR(replacement); - auto expected = pop(pos); - CHECK_ERR(expected); - auto ptr = pop(pos); - CHECK_ERR(ptr); - return push( - pos, - builder.makeAtomicCmpxchg( - bytes, memarg.offset, *ptr, *expected, *replacement, type, *m)); + return withLoc(pos, + irBuilder.makeAtomicCmpxchg(bytes, memarg.offset, type, *m)); } Result<> makeAtomicWait(Index pos, Type type, Name* mem, Memarg memarg) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto timeout = pop(pos); - CHECK_ERR(timeout); - auto expected = pop(pos); - CHECK_ERR(expected); - auto ptr = pop(pos); - CHECK_ERR(ptr); - return push(pos, - builder.makeAtomicWait( - *ptr, *expected, *timeout, type, memarg.offset, *m)); + return withLoc(pos, irBuilder.makeAtomicWait(type, memarg.offset, *m)); } Result<> makeAtomicNotify(Index pos, Name* mem, Memarg memarg) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto count = pop(pos); - CHECK_ERR(count); - auto ptr = pop(pos); - CHECK_ERR(ptr); - return push(pos, builder.makeAtomicNotify(*ptr, *count, memarg.offset, *m)); + return withLoc(pos, irBuilder.makeAtomicNotify(memarg.offset, *m)); } Result<> makeAtomicFence(Index pos) { - return push(pos, builder.makeAtomicFence()); + return withLoc(pos, irBuilder.makeAtomicFence()); } Result<> makeSIMDExtract(Index pos, SIMDExtractOp op, uint8_t lane) { - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeSIMDExtract(op, *val, lane)); + return withLoc(pos, irBuilder.makeSIMDExtract(op, lane)); } Result<> makeSIMDReplace(Index pos, SIMDReplaceOp op, uint8_t lane) { - auto val = pop(pos); - CHECK_ERR(val); - auto vec = pop(pos); - CHECK_ERR(vec); - return push(pos, builder.makeSIMDReplace(op, *vec, lane, *val)); + return withLoc(pos, irBuilder.makeSIMDReplace(op, lane)); } Result<> makeSIMDShuffle(Index pos, const std::array<uint8_t, 16>& lanes) { - auto rhs = pop(pos); - CHECK_ERR(rhs); - auto lhs = pop(pos); - CHECK_ERR(lhs); - return push(pos, builder.makeSIMDShuffle(*lhs, *rhs, lanes)); + return withLoc(pos, irBuilder.makeSIMDShuffle(lanes)); } Result<> makeSIMDTernary(Index pos, SIMDTernaryOp op) { - auto c = pop(pos); - CHECK_ERR(c); - auto b = pop(pos); - CHECK_ERR(b); - auto a = pop(pos); - CHECK_ERR(a); - return push(pos, builder.makeSIMDTernary(op, *a, *b, *c)); + return withLoc(pos, irBuilder.makeSIMDTernary(op)); } Result<> makeSIMDShift(Index pos, SIMDShiftOp op) { - auto shift = pop(pos); - CHECK_ERR(shift); - auto vec = pop(pos); - CHECK_ERR(vec); - return push(pos, builder.makeSIMDShift(op, *vec, *shift)); + return withLoc(pos, irBuilder.makeSIMDShift(op)); } Result<> makeSIMDLoad(Index pos, SIMDLoadOp op, Name* mem, Memarg memarg) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto ptr = pop(pos); - CHECK_ERR(ptr); - return push( - pos, builder.makeSIMDLoad(op, memarg.offset, memarg.align, *ptr, *m)); + return withLoc(pos, + irBuilder.makeSIMDLoad(op, memarg.offset, memarg.align, *m)); } Result<> makeSIMDLoadStoreLane( Index pos, SIMDLoadStoreLaneOp op, Name* mem, Memarg memarg, uint8_t lane) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto vec = pop(pos); - CHECK_ERR(vec); - auto ptr = pop(pos); - CHECK_ERR(ptr); - return push(pos, - builder.makeSIMDLoadStoreLane( - op, memarg.offset, memarg.align, lane, *ptr, *vec, *m)); + return withLoc(pos, + irBuilder.makeSIMDLoadStoreLane( + op, memarg.offset, memarg.align, lane, *m)); } Result<> makeMemoryInit(Index pos, Name* mem, Name data) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto size = pop(pos); - CHECK_ERR(size); - auto offset = pop(pos); - CHECK_ERR(offset); - auto dest = pop(pos); - CHECK_ERR(dest); - return push(pos, builder.makeMemoryInit(data, *dest, *offset, *size, *m)); + return withLoc(pos, irBuilder.makeMemoryInit(data, *m)); } Result<> makeDataDrop(Index pos, Name data) { - return push(pos, builder.makeDataDrop(data)); + return withLoc(pos, irBuilder.makeDataDrop(data)); } Result<> makeMemoryCopy(Index pos, Name* destMem, Name* srcMem) { @@ -2005,240 +1764,87 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> { CHECK_ERR(destMemory); auto srcMemory = getMemory(pos, srcMem); CHECK_ERR(srcMemory); - auto size = pop(pos); - CHECK_ERR(size); - auto src = pop(pos); - CHECK_ERR(src); - auto dest = pop(pos); - CHECK_ERR(dest); - return push( - pos, builder.makeMemoryCopy(*dest, *src, *size, *destMemory, *srcMemory)); + return withLoc(pos, irBuilder.makeMemoryCopy(*destMemory, *srcMemory)); } Result<> makeMemoryFill(Index pos, Name* mem) { auto m = getMemory(pos, mem); CHECK_ERR(m); - auto size = pop(pos); - CHECK_ERR(size); - auto val = pop(pos); - CHECK_ERR(val); - auto dest = pop(pos); - CHECK_ERR(dest); - return push(pos, builder.makeMemoryFill(*dest, *val, *size, *m)); + return withLoc(pos, irBuilder.makeMemoryFill(*m)); } Result<> makeReturn(Index pos) { - if (!func) { - return in.err("cannot return outside of a function"); - } - size_t n = func->getResults().size(); - if (n == 0) { - return push(pos, builder.makeReturn()); - } - if (n == 1) { - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeReturn(*val)); - } - std::vector<Expression*> vals(n); - for (size_t i = 0; i < n; ++i) { - auto val = pop(pos); - CHECK_ERR(val); - vals[n - i - 1] = *val; - } - return push(pos, builder.makeReturn(builder.makeTupleMake(vals))); + return withLoc(pos, irBuilder.makeReturn()); } Result<> makeRefNull(Index pos, HeapType type) { - return push(pos, builder.makeRefNull(type)); + return withLoc(pos, irBuilder.makeRefNull(type)); } Result<> makeRefIsNull(Index pos) { - auto ref = pop(pos); - CHECK_ERR(ref); - return push(pos, builder.makeRefIsNull(*ref)); + return withLoc(pos, irBuilder.makeRefIsNull()); } - Result<> makeRefEq(Index pos) { - auto rhs = pop(pos); - CHECK_ERR(rhs); - auto lhs = pop(pos); - CHECK_ERR(lhs); - return push(pos, builder.makeRefEq(*lhs, *rhs)); - } + Result<> makeRefEq(Index pos) { return withLoc(pos, irBuilder.makeRefEq()); } Result<> makeI31New(Index pos) { - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeI31New(*val)); + return withLoc(pos, irBuilder.makeI31New()); } Result<> makeI31Get(Index pos, bool signed_) { - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeI31Get(*val, signed_)); + return withLoc(pos, irBuilder.makeI31Get(signed_)); } Result<> makeStructNew(Index pos, HeapType type) { - if (!type.isStruct()) { - return in.err(pos, "expected struct type annotation"); - } - size_t numOps = type.getStruct().fields.size(); - std::vector<Expression*> ops(numOps); - for (size_t i = 0; i < numOps; ++i) { - auto op = pop(pos); - CHECK_ERR(op); - ops[numOps - i - 1] = *op; - } - return push(pos, builder.makeStructNew(type, ops)); + return withLoc(pos, irBuilder.makeStructNew(type)); } Result<> makeStructNewDefault(Index pos, HeapType type) { - return push(pos, builder.makeStructNew(type, std::array<Expression*, 0>{})); + return withLoc(pos, irBuilder.makeStructNewDefault(type)); } Result<> makeStructGet(Index pos, HeapType type, Index field, bool signed_) { - if (!type.isStruct()) { - return in.err(pos, "expected struct type annotation"); - } - const auto& fields = type.getStruct().fields; - if (field >= fields.size()) { - return in.err(pos, "struct field index out of bounds"); - } - auto fieldType = fields[field].type; - auto ref = pop(pos); - CHECK_ERR(ref); - CHECK_ERR(validateTypeAnnotation(pos, type, *ref)); - return push(pos, builder.makeStructGet(field, *ref, fieldType, signed_)); + return withLoc(pos, irBuilder.makeStructGet(type, field, signed_)); } Result<> makeStructSet(Index pos, HeapType type, Index field) { - if (!type.isStruct()) { - return in.err(pos, "expected struct type annotation"); - } - if (field >= type.getStruct().fields.size()) { - return in.err(pos, "struct field index out of bounds"); - } - auto val = pop(pos); - CHECK_ERR(val); - auto ref = pop(pos); - CHECK_ERR(ref); - CHECK_ERR(validateTypeAnnotation(pos, type, *ref)); - return push(pos, builder.makeStructSet(field, *ref, *val)); + return withLoc(pos, irBuilder.makeStructSet(type, field)); } Result<> makeArrayNew(Index pos, HeapType type) { - if (!type.isArray()) { - return in.err(pos, "expected array type annotation"); - } - auto size = pop(pos); - CHECK_ERR(size); - auto val = pop(pos); - CHECK_ERR(val); - return push(pos, builder.makeArrayNew(type, *size, *val)); + return withLoc(pos, irBuilder.makeArrayNew(type)); } Result<> makeArrayNewDefault(Index pos, HeapType type) { - if (!type.isArray()) { - return in.err(pos, "expected array type annotation"); - } - auto size = pop(pos); - CHECK_ERR(size); - return push(pos, builder.makeArrayNew(type, *size)); + return withLoc(pos, irBuilder.makeArrayNewDefault(type)); } Result<> makeArrayNewData(Index pos, HeapType type, Name data) { - if (!type.isArray()) { - return in.err(pos, "expected array type annotation"); - } - auto size = pop(pos); - CHECK_ERR(size); - auto offset = pop(pos); - CHECK_ERR(offset); - return push(pos, builder.makeArrayNewData(type, data, *offset, *size)); + return withLoc(pos, irBuilder.makeArrayNewData(type, data)); } - Result<> makeArrayNewElem(Index pos, HeapType type, Name data) { - if (!type.isArray()) { - return in.err(pos, "expected array type annotation"); - } - auto size = pop(pos); - CHECK_ERR(size); - auto offset = pop(pos); - CHECK_ERR(offset); - return push(pos, builder.makeArrayNewElem(type, data, *offset, *size)); + Result<> makeArrayNewElem(Index pos, HeapType type, Name elem) { + return withLoc(pos, irBuilder.makeArrayNewElem(type, elem)); } Result<> makeArrayGet(Index pos, HeapType type, bool signed_) { - if (!type.isArray()) { - return in.err(pos, "expected array type annotation"); - } - auto elemType = type.getArray().element.type; - auto index = pop(pos); - CHECK_ERR(index); - auto ref = pop(pos); - CHECK_ERR(ref); - CHECK_ERR(validateTypeAnnotation(pos, type, *ref)); - return push(pos, builder.makeArrayGet(*ref, *index, elemType, signed_)); + return withLoc(pos, irBuilder.makeArrayGet(type, signed_)); } Result<> makeArraySet(Index pos, HeapType type) { - if (!type.isArray()) { - return in.err(pos, "expected array type annotation"); - } - auto val = pop(pos); - CHECK_ERR(val); - auto index = pop(pos); - CHECK_ERR(index); - auto ref = pop(pos); - CHECK_ERR(ref); - CHECK_ERR(validateTypeAnnotation(pos, type, *ref)); - return push(pos, builder.makeArraySet(*ref, *index, *val)); + return withLoc(pos, irBuilder.makeArraySet(type)); } Result<> makeArrayLen(Index pos) { - auto ref = pop(pos); - CHECK_ERR(ref); - return push(pos, builder.makeArrayLen(*ref)); + return withLoc(pos, irBuilder.makeArrayLen()); } Result<> makeArrayCopy(Index pos, HeapType destType, HeapType srcType) { - if (!destType.isArray()) { - return in.err(pos, "expected array destination type annotation"); - } - if (!srcType.isArray()) { - return in.err(pos, "expected array source type annotation"); - } - auto len = pop(pos); - CHECK_ERR(len); - auto srcIdx = pop(pos); - CHECK_ERR(srcIdx); - auto srcRef = pop(pos); - CHECK_ERR(srcRef); - auto destIdx = pop(pos); - CHECK_ERR(destIdx); - auto destRef = pop(pos); - CHECK_ERR(destRef); - CHECK_ERR(validateTypeAnnotation(pos, srcType, *srcRef)); - CHECK_ERR(validateTypeAnnotation(pos, destType, *destRef)); - return push( - pos, builder.makeArrayCopy(*destRef, *destIdx, *srcRef, *srcIdx, *len)); + return withLoc(pos, irBuilder.makeArrayCopy(destType, srcType)); } Result<> makeArrayFill(Index pos, HeapType type) { - if (!type.isArray()) { - return in.err(pos, "expected array type annotation"); - } - auto size = pop(pos); - CHECK_ERR(size); - auto value = pop(pos); - CHECK_ERR(value); - auto index = pop(pos); - CHECK_ERR(index); - auto ref = pop(pos); - CHECK_ERR(ref); - CHECK_ERR(validateTypeAnnotation(pos, type, *ref)); - return push(pos, builder.makeArrayFill(*ref, *index, *value, *size)); + return withLoc(pos, irBuilder.makeArrayFill(type)); } }; @@ -4286,8 +3892,8 @@ Result<> parseModule(Module& wasm, std::string_view input) { for (Index i = 0; i < decls.funcDefs.size(); ++i) { ctx.index = i; - ctx.func = wasm.functions[i].get(); - ctx.pushScope(ctx.func->getResults()); + ctx.setFunction(wasm.functions[i].get()); + ctx.irBuilder.pushScope(ctx.func->getResults()); WithPosition with(ctx, decls.funcDefs[i].pos); auto parsed = func(ctx); CHECK_ERR(parsed); |