| /* |
| * Copyright 2016 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_builder_h |
| #define wasm_wasm_builder_h |
| |
| #include "ir/manipulation.h" |
| #include "parsing.h" |
| #include "wasm.h" |
| |
| namespace wasm { |
| |
| // Useful data structures |
| |
| struct NameType { |
| Name name; |
| Type type; |
| NameType() : name(nullptr), type(Type::none) {} |
| NameType(Name name, Type type) : name(name), type(type) {} |
| }; |
| |
| // General AST node builder |
| |
| class Builder { |
| Module& wasm; |
| |
| public: |
| Builder(Module& wasm) : wasm(wasm) {} |
| |
| // make* functions create an expression instance. |
| |
| static std::unique_ptr<Function> makeFunction(Name name, |
| HeapType type, |
| std::vector<Type>&& vars, |
| Expression* body = nullptr) { |
| assert(type.isSignature()); |
| auto func = std::make_unique<Function>(); |
| func->name = name; |
| func->type = type; |
| func->body = body; |
| func->vars.swap(vars); |
| return func; |
| } |
| |
| static std::unique_ptr<Function> makeFunction(Name name, |
| std::vector<NameType>&& params, |
| HeapType type, |
| std::vector<NameType>&& vars, |
| Expression* body = nullptr) { |
| assert(type.isSignature()); |
| auto func = std::make_unique<Function>(); |
| func->name = name; |
| func->type = type; |
| func->body = body; |
| for (size_t i = 0; i < params.size(); ++i) { |
| NameType& param = params[i]; |
| assert(func->getParams()[i] == param.type); |
| Index index = func->localNames.size(); |
| func->localIndices[param.name] = index; |
| func->localNames[index] = param.name; |
| } |
| for (auto& var : vars) { |
| func->vars.push_back(var.type); |
| Index index = func->localNames.size(); |
| func->localIndices[var.name] = index; |
| func->localNames[index] = var.name; |
| } |
| return func; |
| } |
| |
| static std::unique_ptr<Table> makeTable(Name name, |
| Type type = Type::funcref, |
| Address initial = 0, |
| Address max = Table::kMaxSize) { |
| auto table = std::make_unique<Table>(); |
| table->name = name; |
| table->type = type; |
| table->initial = initial; |
| table->max = max; |
| return table; |
| } |
| |
| static std::unique_ptr<ElementSegment> |
| makeElementSegment(Name name, |
| Name table, |
| Expression* offset = nullptr, |
| Type type = Type::funcref) { |
| auto seg = std::make_unique<ElementSegment>(); |
| seg->name = name; |
| seg->table = table; |
| seg->offset = offset; |
| seg->type = type; |
| return seg; |
| } |
| |
| static std::unique_ptr<Export> |
| makeExport(Name name, Name value, ExternalKind kind) { |
| auto export_ = std::make_unique<Export>(); |
| export_->name = name; |
| export_->value = value; |
| export_->kind = kind; |
| return export_; |
| } |
| |
| enum Mutability { Mutable, Immutable }; |
| |
| static std::unique_ptr<Global> |
| makeGlobal(Name name, Type type, Expression* init, Mutability mutable_) { |
| auto glob = std::make_unique<Global>(); |
| glob->name = name; |
| glob->type = type; |
| glob->init = init; |
| glob->mutable_ = mutable_ == Mutable; |
| return glob; |
| } |
| |
| static std::unique_ptr<Tag> makeTag(Name name, Signature sig) { |
| auto tag = std::make_unique<Tag>(); |
| tag->name = name; |
| tag->sig = sig; |
| return tag; |
| } |
| |
| // IR nodes |
| |
| Nop* makeNop() { return wasm.allocator.alloc<Nop>(); } |
| Block* makeBlock(Expression* first = nullptr) { |
| auto* ret = wasm.allocator.alloc<Block>(); |
| if (first) { |
| ret->list.push_back(first); |
| ret->finalize(); |
| } |
| return ret; |
| } |
| Block* makeBlock(Name name, Expression* first = nullptr) { |
| auto* ret = makeBlock(first); |
| ret->name = name; |
| ret->finalize(); |
| return ret; |
| } |
| Block* makeBlock(const std::vector<Expression*>& items) { |
| auto* ret = wasm.allocator.alloc<Block>(); |
| ret->list.set(items); |
| ret->finalize(); |
| return ret; |
| } |
| Block* makeBlock(const std::vector<Expression*>& items, Type type) { |
| auto* ret = wasm.allocator.alloc<Block>(); |
| ret->list.set(items); |
| ret->finalize(type); |
| return ret; |
| } |
| Block* makeBlock(const ExpressionList& items) { |
| auto* ret = wasm.allocator.alloc<Block>(); |
| ret->list.set(items); |
| ret->finalize(); |
| return ret; |
| } |
| Block* makeBlock(const ExpressionList& items, Type type) { |
| auto* ret = wasm.allocator.alloc<Block>(); |
| ret->list.set(items); |
| ret->finalize(type); |
| return ret; |
| } |
| Block* makeBlock(Name name, const ExpressionList& items) { |
| auto* ret = wasm.allocator.alloc<Block>(); |
| ret->name = name; |
| ret->list.set(items); |
| ret->finalize(); |
| return ret; |
| } |
| Block* makeBlock(Name name, const ExpressionList& items, Type type) { |
| auto* ret = wasm.allocator.alloc<Block>(); |
| ret->name = name; |
| ret->list.set(items); |
| ret->finalize(type); |
| return ret; |
| } |
| If* makeIf(Expression* condition, |
| Expression* ifTrue, |
| Expression* ifFalse = nullptr) { |
| auto* ret = wasm.allocator.alloc<If>(); |
| ret->condition = condition; |
| ret->ifTrue = ifTrue; |
| ret->ifFalse = ifFalse; |
| ret->finalize(); |
| return ret; |
| } |
| If* makeIf(Expression* condition, |
| Expression* ifTrue, |
| Expression* ifFalse, |
| Type type) { |
| auto* ret = wasm.allocator.alloc<If>(); |
| ret->condition = condition; |
| ret->ifTrue = ifTrue; |
| ret->ifFalse = ifFalse; |
| ret->finalize(type); |
| return ret; |
| } |
| Loop* makeLoop(Name name, Expression* body) { |
| auto* ret = wasm.allocator.alloc<Loop>(); |
| ret->name = name; |
| ret->body = body; |
| ret->finalize(); |
| return ret; |
| } |
| Loop* makeLoop(Name name, Expression* body, Type type) { |
| auto* ret = wasm.allocator.alloc<Loop>(); |
| ret->name = name; |
| ret->body = body; |
| ret->finalize(type); |
| return ret; |
| } |
| Break* makeBreak(Name name, |
| Expression* value = nullptr, |
| Expression* condition = nullptr) { |
| auto* ret = wasm.allocator.alloc<Break>(); |
| ret->name = name; |
| ret->value = value; |
| ret->condition = condition; |
| ret->finalize(); |
| return ret; |
| } |
| template<typename T> |
| Switch* makeSwitch(T& list, |
| Name default_, |
| Expression* condition, |
| Expression* value = nullptr) { |
| auto* ret = wasm.allocator.alloc<Switch>(); |
| ret->targets.set(list); |
| ret->default_ = default_; |
| ret->value = value; |
| ret->condition = condition; |
| return ret; |
| } |
| Call* makeCall(Name target, |
| const std::vector<Expression*>& args, |
| Type type, |
| bool isReturn = false) { |
| auto* call = wasm.allocator.alloc<Call>(); |
| // not all functions may exist yet, so type must be provided |
| call->type = type; |
| call->target = target; |
| call->operands.set(args); |
| call->isReturn = isReturn; |
| return call; |
| } |
| template<typename T> |
| Call* makeCall(Name target, const T& args, Type type, bool isReturn = false) { |
| auto* call = wasm.allocator.alloc<Call>(); |
| // not all functions may exist yet, so type must be provided |
| call->type = type; |
| call->target = target; |
| call->operands.set(args); |
| call->isReturn = isReturn; |
| call->finalize(); |
| return call; |
| } |
| template<typename T> |
| CallIndirect* makeCallIndirect(const Name table, |
| Expression* target, |
| const T& args, |
| HeapType heapType, |
| bool isReturn = false) { |
| assert(heapType.isSignature()); |
| auto* call = wasm.allocator.alloc<CallIndirect>(); |
| call->table = table; |
| call->heapType = heapType; |
| call->type = heapType.getSignature().results; |
| call->target = target; |
| call->operands.set(args); |
| call->isReturn = isReturn; |
| call->finalize(); |
| return call; |
| } |
| template<typename T> |
| CallRef* makeCallRef(Expression* target, |
| const T& args, |
| Type type, |
| bool isReturn = false) { |
| auto* call = wasm.allocator.alloc<CallRef>(); |
| call->type = type; |
| call->target = target; |
| call->operands.set(args); |
| call->isReturn = isReturn; |
| call->finalize(); |
| return call; |
| } |
| LocalGet* makeLocalGet(Index index, Type type) { |
| auto* ret = wasm.allocator.alloc<LocalGet>(); |
| ret->index = index; |
| ret->type = type; |
| return ret; |
| } |
| LocalSet* makeLocalSet(Index index, Expression* value) { |
| auto* ret = wasm.allocator.alloc<LocalSet>(); |
| ret->index = index; |
| ret->value = value; |
| ret->makeSet(); |
| ret->finalize(); |
| return ret; |
| } |
| LocalSet* makeLocalTee(Index index, Expression* value, Type type) { |
| auto* ret = wasm.allocator.alloc<LocalSet>(); |
| ret->index = index; |
| ret->value = value; |
| ret->makeTee(type); |
| return ret; |
| } |
| GlobalGet* makeGlobalGet(Name name, Type type) { |
| auto* ret = wasm.allocator.alloc<GlobalGet>(); |
| ret->name = name; |
| ret->type = type; |
| return ret; |
| } |
| GlobalSet* makeGlobalSet(Name name, Expression* value) { |
| auto* ret = wasm.allocator.alloc<GlobalSet>(); |
| ret->name = name; |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| Load* makeLoad(unsigned bytes, |
| bool signed_, |
| uint32_t offset, |
| unsigned align, |
| Expression* ptr, |
| Type type) { |
| auto* ret = wasm.allocator.alloc<Load>(); |
| ret->isAtomic = false; |
| ret->bytes = bytes; |
| ret->signed_ = signed_; |
| ret->offset = offset; |
| ret->align = align; |
| ret->ptr = ptr; |
| ret->type = type; |
| return ret; |
| } |
| Load* |
| makeAtomicLoad(unsigned bytes, uint32_t offset, Expression* ptr, Type type) { |
| Load* load = makeLoad(bytes, false, offset, bytes, ptr, type); |
| load->isAtomic = true; |
| return load; |
| } |
| AtomicWait* makeAtomicWait(Expression* ptr, |
| Expression* expected, |
| Expression* timeout, |
| Type expectedType, |
| Address offset) { |
| auto* wait = wasm.allocator.alloc<AtomicWait>(); |
| wait->offset = offset; |
| wait->ptr = ptr; |
| wait->expected = expected; |
| wait->timeout = timeout; |
| wait->expectedType = expectedType; |
| wait->finalize(); |
| return wait; |
| } |
| AtomicNotify* |
| makeAtomicNotify(Expression* ptr, Expression* notifyCount, Address offset) { |
| auto* notify = wasm.allocator.alloc<AtomicNotify>(); |
| notify->offset = offset; |
| notify->ptr = ptr; |
| notify->notifyCount = notifyCount; |
| notify->finalize(); |
| return notify; |
| } |
| AtomicFence* makeAtomicFence() { return wasm.allocator.alloc<AtomicFence>(); } |
| Store* makeStore(unsigned bytes, |
| uint32_t offset, |
| unsigned align, |
| Expression* ptr, |
| Expression* value, |
| Type type) { |
| auto* ret = wasm.allocator.alloc<Store>(); |
| ret->isAtomic = false; |
| ret->bytes = bytes; |
| ret->offset = offset; |
| ret->align = align; |
| ret->ptr = ptr; |
| ret->value = value; |
| ret->valueType = type; |
| ret->finalize(); |
| assert(ret->value->type.isConcrete() ? ret->value->type == type : true); |
| return ret; |
| } |
| Store* makeAtomicStore(unsigned bytes, |
| uint32_t offset, |
| Expression* ptr, |
| Expression* value, |
| Type type) { |
| Store* store = makeStore(bytes, offset, bytes, ptr, value, type); |
| store->isAtomic = true; |
| return store; |
| } |
| AtomicRMW* makeAtomicRMW(AtomicRMWOp op, |
| unsigned bytes, |
| uint32_t offset, |
| Expression* ptr, |
| Expression* value, |
| Type type) { |
| auto* ret = wasm.allocator.alloc<AtomicRMW>(); |
| ret->op = op; |
| ret->bytes = bytes; |
| ret->offset = offset; |
| ret->ptr = ptr; |
| ret->value = value; |
| ret->type = type; |
| ret->finalize(); |
| return ret; |
| } |
| AtomicCmpxchg* makeAtomicCmpxchg(unsigned bytes, |
| uint32_t offset, |
| Expression* ptr, |
| Expression* expected, |
| Expression* replacement, |
| Type type) { |
| auto* ret = wasm.allocator.alloc<AtomicCmpxchg>(); |
| ret->bytes = bytes; |
| ret->offset = offset; |
| ret->ptr = ptr; |
| ret->expected = expected; |
| ret->replacement = replacement; |
| ret->type = type; |
| ret->finalize(); |
| return ret; |
| } |
| SIMDExtract* |
| makeSIMDExtract(SIMDExtractOp op, Expression* vec, uint8_t index) { |
| auto* ret = wasm.allocator.alloc<SIMDExtract>(); |
| ret->op = op; |
| ret->vec = vec; |
| ret->index = index; |
| ret->finalize(); |
| return ret; |
| } |
| SIMDReplace* makeSIMDReplace(SIMDReplaceOp op, |
| Expression* vec, |
| uint8_t index, |
| Expression* value) { |
| auto* ret = wasm.allocator.alloc<SIMDReplace>(); |
| ret->op = op; |
| ret->vec = vec; |
| ret->index = index; |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| SIMDShuffle* makeSIMDShuffle(Expression* left, |
| Expression* right, |
| const std::array<uint8_t, 16>& mask) { |
| auto* ret = wasm.allocator.alloc<SIMDShuffle>(); |
| ret->left = left; |
| ret->right = right; |
| ret->mask = mask; |
| ret->finalize(); |
| return ret; |
| } |
| SIMDTernary* makeSIMDTernary(SIMDTernaryOp op, |
| Expression* a, |
| Expression* b, |
| Expression* c) { |
| auto* ret = wasm.allocator.alloc<SIMDTernary>(); |
| ret->op = op; |
| ret->a = a; |
| ret->b = b; |
| ret->c = c; |
| ret->finalize(); |
| return ret; |
| } |
| SIMDShift* makeSIMDShift(SIMDShiftOp op, Expression* vec, Expression* shift) { |
| auto* ret = wasm.allocator.alloc<SIMDShift>(); |
| ret->op = op; |
| ret->vec = vec; |
| ret->shift = shift; |
| ret->finalize(); |
| return ret; |
| } |
| SIMDLoad* |
| makeSIMDLoad(SIMDLoadOp op, Address offset, Address align, Expression* ptr) { |
| auto* ret = wasm.allocator.alloc<SIMDLoad>(); |
| ret->op = op; |
| ret->offset = offset; |
| ret->align = align; |
| ret->ptr = ptr; |
| ret->finalize(); |
| return ret; |
| } |
| SIMDLoadStoreLane* makeSIMDLoadStoreLane(SIMDLoadStoreLaneOp op, |
| Address offset, |
| Address align, |
| uint8_t index, |
| Expression* ptr, |
| Expression* vec) { |
| auto* ret = wasm.allocator.alloc<SIMDLoadStoreLane>(); |
| ret->op = op; |
| ret->offset = offset; |
| ret->align = align; |
| ret->index = index; |
| ret->ptr = ptr; |
| ret->vec = vec; |
| ret->finalize(); |
| return ret; |
| } |
| MemoryInit* makeMemoryInit(uint32_t segment, |
| Expression* dest, |
| Expression* offset, |
| Expression* size) { |
| auto* ret = wasm.allocator.alloc<MemoryInit>(); |
| ret->segment = segment; |
| ret->dest = dest; |
| ret->offset = offset; |
| ret->size = size; |
| ret->finalize(); |
| return ret; |
| } |
| DataDrop* makeDataDrop(uint32_t segment) { |
| auto* ret = wasm.allocator.alloc<DataDrop>(); |
| ret->segment = segment; |
| ret->finalize(); |
| return ret; |
| } |
| MemoryCopy* |
| makeMemoryCopy(Expression* dest, Expression* source, Expression* size) { |
| auto* ret = wasm.allocator.alloc<MemoryCopy>(); |
| ret->dest = dest; |
| ret->source = source; |
| ret->size = size; |
| ret->finalize(); |
| return ret; |
| } |
| MemoryFill* |
| makeMemoryFill(Expression* dest, Expression* value, Expression* size) { |
| auto* ret = wasm.allocator.alloc<MemoryFill>(); |
| ret->dest = dest; |
| ret->value = value; |
| ret->size = size; |
| ret->finalize(); |
| return ret; |
| } |
| Const* makeConst(Literal value) { |
| assert(value.type.isNumber()); |
| auto* ret = wasm.allocator.alloc<Const>(); |
| ret->value = value; |
| ret->type = value.type; |
| return ret; |
| } |
| template<typename T> Const* makeConst(T x) { return makeConst(Literal(x)); } |
| Unary* makeUnary(UnaryOp op, Expression* value) { |
| auto* ret = wasm.allocator.alloc<Unary>(); |
| ret->op = op; |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| Const* makeConstPtr(uint64_t val) { |
| return makeConst(Literal::makeFromInt64(val, wasm.memory.indexType)); |
| } |
| Binary* makeBinary(BinaryOp op, Expression* left, Expression* right) { |
| auto* ret = wasm.allocator.alloc<Binary>(); |
| ret->op = op; |
| ret->left = left; |
| ret->right = right; |
| ret->finalize(); |
| return ret; |
| } |
| Select* |
| makeSelect(Expression* condition, Expression* ifTrue, Expression* ifFalse) { |
| auto* ret = wasm.allocator.alloc<Select>(); |
| ret->condition = condition; |
| ret->ifTrue = ifTrue; |
| ret->ifFalse = ifFalse; |
| ret->finalize(); |
| return ret; |
| } |
| Select* makeSelect(Expression* condition, |
| Expression* ifTrue, |
| Expression* ifFalse, |
| Type type) { |
| auto* ret = wasm.allocator.alloc<Select>(); |
| ret->condition = condition; |
| ret->ifTrue = ifTrue; |
| ret->ifFalse = ifFalse; |
| ret->finalize(type); |
| return ret; |
| } |
| Return* makeReturn(Expression* value = nullptr) { |
| auto* ret = wasm.allocator.alloc<Return>(); |
| ret->value = value; |
| return ret; |
| } |
| MemorySize* makeMemorySize() { |
| auto* ret = wasm.allocator.alloc<MemorySize>(); |
| if (wasm.memory.is64()) { |
| ret->make64(); |
| } |
| ret->finalize(); |
| return ret; |
| } |
| MemoryGrow* makeMemoryGrow(Expression* delta) { |
| auto* ret = wasm.allocator.alloc<MemoryGrow>(); |
| if (wasm.memory.is64()) { |
| ret->make64(); |
| } |
| ret->delta = delta; |
| ret->finalize(); |
| return ret; |
| } |
| RefNull* makeRefNull(HeapType type) { |
| auto* ret = wasm.allocator.alloc<RefNull>(); |
| ret->finalize(Type(type, Nullable)); |
| return ret; |
| } |
| RefNull* makeRefNull(Type type) { |
| auto* ret = wasm.allocator.alloc<RefNull>(); |
| ret->finalize(type); |
| return ret; |
| } |
| RefIs* makeRefIs(RefIsOp op, Expression* value) { |
| auto* ret = wasm.allocator.alloc<RefIs>(); |
| ret->op = op; |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| RefFunc* makeRefFunc(Name func, HeapType heapType) { |
| auto* ret = wasm.allocator.alloc<RefFunc>(); |
| ret->func = func; |
| ret->finalize(Type(heapType, NonNullable)); |
| return ret; |
| } |
| RefEq* makeRefEq(Expression* left, Expression* right) { |
| auto* ret = wasm.allocator.alloc<RefEq>(); |
| ret->left = left; |
| ret->right = right; |
| ret->finalize(); |
| return ret; |
| } |
| TableGet* makeTableGet(Name table, Expression* index, Type type) { |
| auto* ret = wasm.allocator.alloc<TableGet>(); |
| ret->table = table; |
| ret->index = index; |
| ret->type = type; |
| ret->finalize(); |
| return ret; |
| } |
| TableSet* makeTableSet(Name table, Expression* index, Expression* value) { |
| auto* ret = wasm.allocator.alloc<TableSet>(); |
| ret->table = table; |
| ret->index = index; |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| TableSize* makeTableSize(Name table) { |
| auto* ret = wasm.allocator.alloc<TableSize>(); |
| ret->table = table; |
| ret->finalize(); |
| return ret; |
| } |
| TableGrow* makeTableGrow(Name table, Expression* value, Expression* delta) { |
| auto* ret = wasm.allocator.alloc<TableGrow>(); |
| ret->table = table; |
| ret->value = value; |
| ret->delta = delta; |
| ret->finalize(); |
| return ret; |
| } |
| |
| private: |
| Try* makeTry(Name name, |
| Expression* body, |
| const std::vector<Name>& catchTags, |
| const std::vector<Expression*>& catchBodies, |
| Name delegateTarget, |
| Type type, |
| bool hasType) { // differentiate whether a type was passed in |
| auto* ret = wasm.allocator.alloc<Try>(); |
| ret->name = name; |
| ret->body = body; |
| ret->catchTags.set(catchTags); |
| ret->catchBodies.set(catchBodies); |
| if (hasType) { |
| ret->finalize(type); |
| } else { |
| ret->finalize(); |
| } |
| return ret; |
| } |
| |
| public: |
| Try* makeTry(Expression* body, |
| const std::vector<Name>& catchTags, |
| const std::vector<Expression*>& catchBodies) { |
| return makeTry( |
| Name(), body, catchTags, catchBodies, Name(), Type::none, false); |
| } |
| Try* makeTry(Expression* body, |
| const std::vector<Name>& catchTags, |
| const std::vector<Expression*>& catchBodies, |
| Type type) { |
| return makeTry(Name(), body, catchTags, catchBodies, Name(), type, true); |
| } |
| Try* makeTry(Name name, |
| Expression* body, |
| const std::vector<Name>& catchTags, |
| const std::vector<Expression*>& catchBodies) { |
| return makeTry( |
| name, body, catchTags, catchBodies, Name(), Type::none, false); |
| } |
| Try* makeTry(Name name, |
| Expression* body, |
| const std::vector<Name>& catchTags, |
| const std::vector<Expression*>& catchBodies, |
| Type type) { |
| return makeTry(name, body, catchTags, catchBodies, Name(), type, true); |
| } |
| Try* makeTry(Expression* body, Name delegateTarget) { |
| return makeTry(Name(), body, {}, {}, delegateTarget, Type::none, false); |
| } |
| Try* makeTry(Expression* body, Name delegateTarget, Type type) { |
| return makeTry(Name(), body, {}, {}, delegateTarget, type, true); |
| } |
| Try* makeTry(Name name, Expression* body, Name delegateTarget) { |
| return makeTry(name, body, {}, {}, delegateTarget, Type::none, false); |
| } |
| Try* makeTry(Name name, Expression* body, Name delegateTarget, Type type) { |
| return makeTry(name, body, {}, {}, delegateTarget, type, true); |
| } |
| Throw* makeThrow(Tag* tag, const std::vector<Expression*>& args) { |
| return makeThrow(tag->name, args); |
| } |
| Throw* makeThrow(Name tag, const std::vector<Expression*>& args) { |
| auto* ret = wasm.allocator.alloc<Throw>(); |
| ret->tag = tag; |
| ret->operands.set(args); |
| ret->finalize(); |
| return ret; |
| } |
| Rethrow* makeRethrow(Name target) { |
| auto* ret = wasm.allocator.alloc<Rethrow>(); |
| ret->target = target; |
| ret->finalize(); |
| return ret; |
| } |
| Unreachable* makeUnreachable() { return wasm.allocator.alloc<Unreachable>(); } |
| Pop* makePop(Type type) { |
| auto* ret = wasm.allocator.alloc<Pop>(); |
| ret->type = type; |
| ret->finalize(); |
| return ret; |
| } |
| template<typename ListType> TupleMake* makeTupleMake(ListType&& operands) { |
| auto* ret = wasm.allocator.alloc<TupleMake>(); |
| ret->operands.set(operands); |
| ret->finalize(); |
| return ret; |
| } |
| TupleExtract* makeTupleExtract(Expression* tuple, Index index) { |
| auto* ret = wasm.allocator.alloc<TupleExtract>(); |
| ret->tuple = tuple; |
| ret->index = index; |
| ret->finalize(); |
| return ret; |
| } |
| I31New* makeI31New(Expression* value) { |
| auto* ret = wasm.allocator.alloc<I31New>(); |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| I31Get* makeI31Get(Expression* i31, bool signed_) { |
| auto* ret = wasm.allocator.alloc<I31Get>(); |
| ret->i31 = i31; |
| ret->signed_ = signed_; |
| ret->finalize(); |
| return ret; |
| } |
| RefTest* makeRefTest(Expression* ref, Expression* rtt) { |
| auto* ret = wasm.allocator.alloc<RefTest>(); |
| ret->ref = ref; |
| ret->rtt = rtt; |
| ret->finalize(); |
| return ret; |
| } |
| RefTest* makeRefTest(Expression* ref, HeapType intendedType) { |
| auto* ret = wasm.allocator.alloc<RefTest>(); |
| ret->ref = ref; |
| ret->intendedType = intendedType; |
| ret->finalize(); |
| return ret; |
| } |
| RefCast* makeRefCast(Expression* ref, Expression* rtt) { |
| auto* ret = wasm.allocator.alloc<RefCast>(); |
| ret->ref = ref; |
| ret->rtt = rtt; |
| ret->finalize(); |
| return ret; |
| } |
| |
| RefCast* |
| makeRefCast(Expression* ref, HeapType intendedType, RefCast::Safety safety) { |
| auto* ret = wasm.allocator.alloc<RefCast>(); |
| ret->ref = ref; |
| ret->intendedType = intendedType; |
| ret->safety = safety; |
| ret->finalize(); |
| return ret; |
| } |
| BrOn* |
| makeBrOn(BrOnOp op, Name name, Expression* ref, Expression* rtt = nullptr) { |
| auto* ret = wasm.allocator.alloc<BrOn>(); |
| ret->op = op; |
| ret->name = name; |
| ret->ref = ref; |
| ret->rtt = rtt; |
| ret->finalize(); |
| return ret; |
| } |
| BrOn* makeBrOn(BrOnOp op, Name name, Expression* ref, HeapType intendedType) { |
| auto* ret = wasm.allocator.alloc<BrOn>(); |
| ret->op = op; |
| ret->name = name; |
| ret->ref = ref; |
| ret->intendedType = intendedType; |
| ret->finalize(); |
| return ret; |
| } |
| RttCanon* makeRttCanon(HeapType heapType) { |
| auto* ret = wasm.allocator.alloc<RttCanon>(); |
| ret->type = Type(Rtt(heapType.getDepth(), heapType)); |
| ret->finalize(); |
| return ret; |
| } |
| RttSub* makeRttSub(HeapType heapType, Expression* parent) { |
| auto* ret = wasm.allocator.alloc<RttSub>(); |
| ret->parent = parent; |
| auto parentRtt = parent->type.getRtt(); |
| if (parentRtt.hasDepth()) { |
| ret->type = Type(Rtt(parentRtt.depth + 1, heapType)); |
| } else { |
| ret->type = Type(Rtt(heapType)); |
| } |
| ret->finalize(); |
| return ret; |
| } |
| RttSub* makeRttFreshSub(HeapType heapType, Expression* parent) { |
| auto* ret = makeRttSub(heapType, parent); |
| ret->fresh = true; |
| return ret; |
| } |
| template<typename T> |
| StructNew* makeStructNew(Expression* rtt, const T& args) { |
| auto* ret = wasm.allocator.alloc<StructNew>(); |
| ret->rtt = rtt; |
| ret->operands.set(args); |
| ret->finalize(); |
| return ret; |
| } |
| template<typename T> StructNew* makeStructNew(HeapType type, const T& args) { |
| auto* ret = wasm.allocator.alloc<StructNew>(); |
| ret->operands.set(args); |
| ret->type = Type(type, NonNullable); |
| ret->finalize(); |
| return ret; |
| } |
| StructGet* |
| makeStructGet(Index index, Expression* ref, Type type, bool signed_ = false) { |
| auto* ret = wasm.allocator.alloc<StructGet>(); |
| ret->index = index; |
| ret->ref = ref; |
| ret->type = type; |
| ret->signed_ = signed_; |
| ret->finalize(); |
| return ret; |
| } |
| StructSet* makeStructSet(Index index, Expression* ref, Expression* value) { |
| auto* ret = wasm.allocator.alloc<StructSet>(); |
| ret->index = index; |
| ret->ref = ref; |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| ArrayNew* |
| makeArrayNew(Expression* rtt, Expression* size, Expression* init = nullptr) { |
| auto* ret = wasm.allocator.alloc<ArrayNew>(); |
| ret->rtt = rtt; |
| ret->size = size; |
| ret->init = init; |
| ret->finalize(); |
| return ret; |
| } |
| ArrayNew* |
| makeArrayNew(HeapType type, Expression* size, Expression* init = nullptr) { |
| auto* ret = wasm.allocator.alloc<ArrayNew>(); |
| ret->size = size; |
| ret->init = init; |
| ret->type = Type(type, NonNullable); |
| ret->finalize(); |
| return ret; |
| } |
| ArrayInit* makeArrayInit(Expression* rtt, |
| const std::vector<Expression*>& values) { |
| auto* ret = wasm.allocator.alloc<ArrayInit>(); |
| ret->rtt = rtt; |
| ret->values.set(values); |
| ret->finalize(); |
| return ret; |
| } |
| ArrayInit* makeArrayInit(HeapType type, |
| const std::vector<Expression*>& values) { |
| auto* ret = wasm.allocator.alloc<ArrayInit>(); |
| ret->values.set(values); |
| ret->type = Type(type, NonNullable); |
| ret->finalize(); |
| return ret; |
| } |
| ArrayGet* |
| makeArrayGet(Expression* ref, Expression* index, bool signed_ = false) { |
| auto* ret = wasm.allocator.alloc<ArrayGet>(); |
| ret->ref = ref; |
| ret->index = index; |
| ret->signed_ = signed_; |
| ret->finalize(); |
| return ret; |
| } |
| ArraySet* |
| makeArraySet(Expression* ref, Expression* index, Expression* value) { |
| auto* ret = wasm.allocator.alloc<ArraySet>(); |
| ret->ref = ref; |
| ret->index = index; |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| ArrayLen* makeArrayLen(Expression* ref) { |
| auto* ret = wasm.allocator.alloc<ArrayLen>(); |
| ret->ref = ref; |
| ret->finalize(); |
| return ret; |
| } |
| ArrayCopy* makeArrayCopy(Expression* destRef, |
| Expression* destIndex, |
| Expression* srcRef, |
| Expression* srcIndex, |
| Expression* length) { |
| auto* ret = wasm.allocator.alloc<ArrayCopy>(); |
| ret->destRef = destRef; |
| ret->destIndex = destIndex; |
| ret->srcRef = srcRef; |
| ret->srcIndex = srcIndex; |
| ret->length = length; |
| ret->finalize(); |
| return ret; |
| } |
| RefAs* makeRefAs(RefAsOp op, Expression* value) { |
| auto* ret = wasm.allocator.alloc<RefAs>(); |
| ret->op = op; |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| |
| // Additional helpers |
| |
| Drop* makeDrop(Expression* value) { |
| auto* ret = wasm.allocator.alloc<Drop>(); |
| ret->value = value; |
| ret->finalize(); |
| return ret; |
| } |
| |
| // Make a constant expression. This might be a wasm Const, or something |
| // else of constant value like ref.null. |
| Expression* makeConstantExpression(Literal value) { |
| auto type = value.type; |
| if (type.isNumber()) { |
| return makeConst(value); |
| } |
| if (value.isNull()) { |
| return makeRefNull(type); |
| } |
| if (type.isFunction()) { |
| return makeRefFunc(value.getFunc(), type.getHeapType()); |
| } |
| if (type.isRtt()) { |
| return makeRtt(value.type); |
| } |
| TODO_SINGLE_COMPOUND(type); |
| switch (type.getBasic()) { |
| case Type::anyref: |
| case Type::eqref: |
| assert(value.isNull() && "unexpected non-null reference type literal"); |
| return makeRefNull(type); |
| case Type::i31ref: |
| return makeI31New(makeConst(value.geti31())); |
| default: |
| WASM_UNREACHABLE("invalid constant expression"); |
| } |
| } |
| |
| Expression* makeConstantExpression(Literals values) { |
| assert(values.size() > 0); |
| if (values.size() == 1) { |
| return makeConstantExpression(values[0]); |
| } else { |
| std::vector<Expression*> consts; |
| for (auto value : values) { |
| consts.push_back(makeConstantExpression(value)); |
| } |
| return makeTupleMake(consts); |
| } |
| } |
| |
| // Given a type, creates an RTT expression of that type, using a combination |
| // of rtt.canon and rtt.subs. |
| Expression* makeRtt(Type type) { |
| Expression* ret = makeRttCanon(type.getHeapType()); |
| if (type.getRtt().hasDepth()) { |
| for (Index i = 0; i < type.getRtt().depth; i++) { |
| ret = makeRttSub(type.getHeapType(), ret); |
| } |
| } |
| return ret; |
| } |
| |
| // Additional utility functions for building on top of nodes |
| // Convenient to have these on Builder, as it has allocation built in |
| |
| static Index addParam(Function* func, Name name, Type type) { |
| // only ok to add a param if no vars, otherwise indices are invalidated |
| assert(func->localIndices.size() == func->getParams().size()); |
| assert(name.is()); |
| Signature sig = func->getSig(); |
| std::vector<Type> params(sig.params.begin(), sig.params.end()); |
| params.push_back(type); |
| func->type = Signature(Type(params), sig.results); |
| Index index = func->localNames.size(); |
| func->localIndices[name] = index; |
| func->localNames[index] = name; |
| return index; |
| } |
| |
| static Index addVar(Function* func, Name name, Type type) { |
| // always ok to add a var, it does not affect other indices |
| assert(type.isConcrete()); |
| Index index = func->getNumLocals(); |
| if (name.is()) { |
| func->localIndices[name] = index; |
| func->localNames[index] = name; |
| } |
| func->vars.emplace_back(type); |
| return index; |
| } |
| |
| static Index addVar(Function* func, Type type) { |
| return addVar(func, Name(), type); |
| } |
| |
| static void clearLocalNames(Function* func) { |
| func->localNames.clear(); |
| func->localIndices.clear(); |
| } |
| |
| // ensure a node is a block, if it isn't already, and optionally append to the |
| // block |
| Block* blockify(Expression* any, Expression* append = nullptr) { |
| Block* block = nullptr; |
| if (any) { |
| block = any->dynCast<Block>(); |
| } |
| if (!block) { |
| block = makeBlock(any); |
| } |
| if (append) { |
| block->list.push_back(append); |
| block->finalize(); |
| } |
| return block; |
| } |
| |
| template<typename... Ts> |
| Block* blockify(Expression* any, Expression* append, Ts... args) { |
| return blockify(blockify(any, append), args...); |
| } |
| |
| // ensure a node is a block, if it isn't already, and optionally append to the |
| // block this variant sets a name for the block, so it will not reuse a block |
| // already named |
| Block* |
| blockifyWithName(Expression* any, Name name, Expression* append = nullptr) { |
| Block* block = nullptr; |
| if (any) { |
| block = any->dynCast<Block>(); |
| } |
| if (!block || block->name.is()) { |
| block = makeBlock(any); |
| } |
| block->name = name; |
| if (append) { |
| block->list.push_back(append); |
| block->finalize(); |
| } |
| return block; |
| } |
| |
| // a helper for the common pattern of a sequence of two expressions. Similar |
| // to blockify, but does *not* reuse a block if the first is one. |
| Block* makeSequence(Expression* left, Expression* right) { |
| auto* block = makeBlock(left); |
| block->list.push_back(right); |
| block->finalize(); |
| return block; |
| } |
| |
| Block* makeSequence(Expression* left, Expression* right, Type type) { |
| auto* block = makeBlock(left); |
| block->list.push_back(right); |
| block->finalize(type); |
| return block; |
| } |
| |
| // Drop an expression if it has a concrete type |
| Expression* dropIfConcretelyTyped(Expression* curr) { |
| if (!curr->type.isConcrete()) { |
| return curr; |
| } |
| return makeDrop(curr); |
| } |
| |
| void flip(If* iff) { |
| std::swap(iff->ifTrue, iff->ifFalse); |
| iff->condition = makeUnary(EqZInt32, iff->condition); |
| } |
| |
| // Returns a replacement with the precise same type, and with minimal contents |
| // as best we can. As a replacement, this may reuse the input node. |
| template<typename T> Expression* replaceWithIdenticalType(T* curr) { |
| if (curr->type.isTuple() && curr->type.isDefaultable()) { |
| return makeConstantExpression(Literal::makeZeros(curr->type)); |
| } |
| if (curr->type.isNullable()) { |
| return ExpressionManipulator::refNull(curr, curr->type); |
| } |
| if (curr->type.isFunction() || !curr->type.isBasic()) { |
| // We can't do any better, keep the original. |
| return curr; |
| } |
| Literal value; |
| // TODO: reuse node conditionally when possible for literals |
| switch (curr->type.getBasic()) { |
| case Type::i32: |
| value = Literal(int32_t(0)); |
| break; |
| case Type::i64: |
| value = Literal(int64_t(0)); |
| break; |
| case Type::f32: |
| value = Literal(float(0)); |
| break; |
| case Type::f64: |
| value = Literal(double(0)); |
| break; |
| case Type::v128: { |
| std::array<uint8_t, 16> bytes; |
| bytes.fill(0); |
| value = Literal(bytes.data()); |
| break; |
| } |
| case Type::funcref: |
| WASM_UNREACHABLE("handled above"); |
| case Type::anyref: |
| case Type::eqref: |
| return ExpressionManipulator::refNull(curr, curr->type); |
| case Type::i31ref: |
| return makeI31New(makeConst(0)); |
| case Type::dataref: |
| return curr; |
| case Type::none: |
| return ExpressionManipulator::nop(curr); |
| case Type::unreachable: |
| return ExpressionManipulator::unreachable(curr); |
| } |
| return makeConst(value); |
| } |
| }; |
| |
| // This class adds methods that first inspect the input. They may not have fully |
| // comprehensive error checking, when that can be left to the validator; the |
| // benefit of the validate* methods is that they can share code between the |
| // text and binary format parsers, for handling certain situations in the |
| // input which preclude even creating valid IR, which the validator depends |
| // on. |
| class ValidatingBuilder : public Builder { |
| size_t line = -1, col = -1; |
| |
| public: |
| ValidatingBuilder(Module& wasm, size_t line) : Builder(wasm), line(line) {} |
| ValidatingBuilder(Module& wasm, size_t line, size_t col) |
| : Builder(wasm), line(line), col(col) {} |
| |
| Expression* validateAndMakeBrOn(BrOnOp op, |
| Name name, |
| Expression* ref, |
| Expression* rtt = nullptr) { |
| if (op == BrOnCast) { |
| if (rtt->type == Type::unreachable) { |
| // An unreachable rtt is not supported: the text and binary formats do |
| // not provide the type, so if it's unreachable we should not even |
| // create a br_on_cast in such a case, as we'd have no idea what it |
| // casts to. |
| return makeSequence(makeDrop(ref), rtt); |
| } |
| } |
| if (op == BrOnNull) { |
| if (!ref->type.isRef() && ref->type != Type::unreachable) { |
| throw ParseException("Invalid ref for br_on_null", line, col); |
| } |
| } |
| return makeBrOn(op, name, ref, rtt); |
| } |
| |
| template<typename T> |
| Expression* validateAndMakeCallRef(Expression* target, |
| const T& args, |
| bool isReturn = false) { |
| if (!target->type.isRef()) { |
| if (target->type == Type::unreachable) { |
| // An unreachable target is not supported. Similiar to br_on_cast, just |
| // emit an unreachable sequence, since we don't have enough information |
| // to create a full call_ref. |
| auto* block = makeBlock(args); |
| block->list.push_back(target); |
| block->finalize(Type::unreachable); |
| return block; |
| } |
| throw ParseException("Non-reference type for a call_ref", line, col); |
| } |
| auto heapType = target->type.getHeapType(); |
| if (!heapType.isSignature()) { |
| throw ParseException("Invalid reference type for a call_ref", line, col); |
| } |
| return makeCallRef(target, args, heapType.getSignature().results, isReturn); |
| } |
| }; |
| |
| } // namespace wasm |
| |
| #endif // wasm_wasm_builder_h |