/* * 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 #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& 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 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 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 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> 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 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>::visit(curr); if (auto* block = curr->dynCast()) { 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(); #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 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& 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) { 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