blob: 863bbbf5145dad607d311b5e94a39e2a5a1a46bb [file] [log] [blame] [edit]
/*
* Copyright 2018 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_stack_h
#define wasm_stack_h
#include "ir/branch-utils.h"
#include "pass.h"
#include "wasm-binary.h"
#include "wasm-traversal.h"
#include "wasm.h"
namespace wasm {
// Stack IR: an IR that represents code at the wasm binary format level,
// that is, a stack machine. Binaryen IR is *almost* identical to this,
// but as documented in README.md, there are a few differences, intended
// to make Binaryen IR fast and flexible for maximal optimization. Stack
// IR, on the other hand, is designed to optimize a few final things that
// can only really be done when modeling the stack machine format precisely.
// Currently the benefits of Stack IR are minor, less than 1% reduction in
// code size. For that reason it is just a secondary IR, run optionally
// after the main IR has been optimized. However, if we improve Stack IR
// optimizations to a point where they have a significant impact, it's
// possible that could motivate investigating replacing the main IR with Stack
// IR (so that we have just a single IR).
// A StackIR instance (see wasm.h) contains a linear sequence of
// stack instructions. This representation is very simple: just a single vector
// of all instructions, in order.
// * nullptr is allowed in the vector, representing something to skip.
// This is useful as a common thing optimizations do is remove instructions,
// so this way we can do so without compacting the vector all the time.
// Direct writing binaryen IR to binary is fast. Otherwise, StackIRGenerator
// lets you optimize the Stack IR before emitting stack IR to binary (but the
// cost is that the extra IR in the middle makes things 20% slower than emitting
// binaryen IR to binary directly).
// A Stack IR instruction. Most just directly reflect a Binaryen IR node,
// but we need extra ones for certain things.
class StackInst {
public:
StackInst(MixedArena&) {}
enum Op {
Basic, // an instruction directly corresponding to a non-control-flow
// Binaryen IR node
BlockBegin, // the beginning of a block
BlockEnd, // the ending of a block
IfBegin, // the beginning of a if
IfElse, // the else of a if
IfEnd, // the ending of a if
LoopBegin, // the beginning of a loop
LoopEnd, // the ending of a loop
TryBegin, // the beginning of a try
Catch, // the catch within a try
TryEnd // the ending of a try
} op;
Expression* origin; // the expression this originates from
// the type - usually identical to the origin type, but e.g. wasm has no
// unreachable blocks, they must be none
Type type;
};
class BinaryInstWriter : public OverriddenVisitor<BinaryInstWriter> {
public:
BinaryInstWriter(WasmBinaryWriter& parent,
BufferWithRandomAccess& o,
Function* func,
bool sourceMap)
: parent(parent), o(o), func(func), sourceMap(sourceMap) {}
void visit(Expression* curr) {
if (func && !sourceMap) {
parent.writeDebugLocation(curr, func);
}
OverriddenVisitor<BinaryInstWriter>::visit(curr);
if (func && !sourceMap) {
parent.writeDebugLocationEnd(curr, func);
}
}
void visitBlock(Block* curr);
void visitIf(If* curr);
void visitLoop(Loop* curr);
void visitBreak(Break* curr);
void visitSwitch(Switch* curr);
void visitCall(Call* curr);
void visitCallIndirect(CallIndirect* curr);
void visitLocalGet(LocalGet* curr);
void visitLocalSet(LocalSet* curr);
void visitGlobalGet(GlobalGet* curr);
void visitGlobalSet(GlobalSet* curr);
void visitLoad(Load* curr);
void visitStore(Store* curr);
void visitAtomicRMW(AtomicRMW* curr);
void visitAtomicCmpxchg(AtomicCmpxchg* curr);
void visitAtomicWait(AtomicWait* curr);
void visitAtomicNotify(AtomicNotify* curr);
void visitAtomicFence(AtomicFence* curr);
void visitSIMDExtract(SIMDExtract* curr);
void visitSIMDReplace(SIMDReplace* curr);
void visitSIMDShuffle(SIMDShuffle* curr);
void visitSIMDTernary(SIMDTernary* curr);
void visitSIMDShift(SIMDShift* curr);
void visitSIMDLoad(SIMDLoad* curr);
void visitMemoryInit(MemoryInit* curr);
void visitDataDrop(DataDrop* curr);
void visitMemoryCopy(MemoryCopy* curr);
void visitMemoryFill(MemoryFill* curr);
void visitConst(Const* curr);
void visitUnary(Unary* curr);
void visitBinary(Binary* curr);
void visitSelect(Select* curr);
void visitReturn(Return* curr);
void visitHost(Host* curr);
void visitRefNull(RefNull* curr);
void visitRefIsNull(RefIsNull* curr);
void visitRefFunc(RefFunc* curr);
void visitTry(Try* curr);
void visitThrow(Throw* curr);
void visitRethrow(Rethrow* curr);
void visitBrOnExn(BrOnExn* curr);
void visitNop(Nop* curr);
void visitUnreachable(Unreachable* curr);
void visitDrop(Drop* curr);
void visitPush(Push* curr);
void visitPop(Pop* curr);
void emitIfElse(If* curr);
void emitCatch(Try* curr);
// emit an end at the end of a block/loop/if/try
void emitScopeEnd(Expression* curr);
// emit an end at the end of a function
void emitFunctionEnd();
void emitUnreachable();
void mapLocalsAndEmitHeader();
private:
void emitMemoryAccess(size_t alignment, size_t bytes, uint32_t offset);
int32_t getBreakIndex(Name name);
WasmBinaryWriter& parent;
BufferWithRandomAccess& o;
Function* func = nullptr;
bool sourceMap;
std::vector<Name> breakStack;
// type => number of locals of that type in the compact form
std::map<Type, size_t> numLocalsByType;
// local index => index in compact form of [all int32s][all int64s]etc
std::map<Index, size_t> mappedLocals;
};
// Takes binaryen IR and converts it to something else (binary or stack IR)
template<typename SubType>
class BinaryenIRWriter : public OverriddenVisitor<BinaryenIRWriter<SubType>> {
public:
BinaryenIRWriter(Function* func) : func(func) {}
void write();
// visits a node, emitting the proper code for it
void visit(Expression* curr);
void visitBlock(Block* curr);
void visitIf(If* curr);
void visitLoop(Loop* curr);
void visitBreak(Break* curr);
void visitSwitch(Switch* curr);
void visitCall(Call* curr);
void visitCallIndirect(CallIndirect* curr);
void visitLocalGet(LocalGet* curr);
void visitLocalSet(LocalSet* curr);
void visitGlobalGet(GlobalGet* curr);
void visitGlobalSet(GlobalSet* curr);
void visitLoad(Load* curr);
void visitStore(Store* curr);
void visitAtomicRMW(AtomicRMW* curr);
void visitAtomicCmpxchg(AtomicCmpxchg* curr);
void visitAtomicWait(AtomicWait* curr);
void visitAtomicNotify(AtomicNotify* curr);
void visitAtomicFence(AtomicFence* curr);
void visitSIMDExtract(SIMDExtract* curr);
void visitSIMDReplace(SIMDReplace* curr);
void visitSIMDShuffle(SIMDShuffle* curr);
void visitSIMDTernary(SIMDTernary* curr);
void visitSIMDShift(SIMDShift* curr);
void visitSIMDLoad(SIMDLoad* curr);
void visitMemoryInit(MemoryInit* curr);
void visitDataDrop(DataDrop* curr);
void visitMemoryCopy(MemoryCopy* curr);
void visitMemoryFill(MemoryFill* curr);
void visitConst(Const* curr);
void visitUnary(Unary* curr);
void visitBinary(Binary* curr);
void visitSelect(Select* curr);
void visitReturn(Return* curr);
void visitHost(Host* curr);
void visitRefNull(RefNull* curr);
void visitRefIsNull(RefIsNull* curr);
void visitRefFunc(RefFunc* curr);
void visitTry(Try* curr);
void visitThrow(Throw* curr);
void visitRethrow(Rethrow* curr);
void visitBrOnExn(BrOnExn* curr);
void visitNop(Nop* curr);
void visitUnreachable(Unreachable* curr);
void visitDrop(Drop* curr);
void visitPush(Push* curr);
void visitPop(Pop* curr);
protected:
Function* func = nullptr;
private:
void emit(Expression* curr) { static_cast<SubType*>(this)->emit(curr); }
void emitHeader() { static_cast<SubType*>(this)->emitHeader(); }
void emitIfElse(If* curr) { static_cast<SubType*>(this)->emitIfElse(curr); }
void emitCatch(Try* curr) { static_cast<SubType*>(this)->emitCatch(curr); }
void emitScopeEnd(Expression* curr) {
static_cast<SubType*>(this)->emitScopeEnd(curr);
}
void emitFunctionEnd() { static_cast<SubType*>(this)->emitFunctionEnd(); }
void emitUnreachable() { static_cast<SubType*>(this)->emitUnreachable(); }
void emitDebugLocation(Expression* curr) {
static_cast<SubType*>(this)->emitDebugLocation(curr);
}
void visitPossibleBlockContents(Expression* curr);
};
template<typename SubType> void BinaryenIRWriter<SubType>::write() {
assert(func && "BinaryenIRWriter: function is not set");
emitHeader();
visitPossibleBlockContents(func->body);
emitFunctionEnd();
}
// emits a node, but if it is a block with no name, emit a list of its contents
template<typename SubType>
void BinaryenIRWriter<SubType>::visitPossibleBlockContents(Expression* curr) {
auto* block = curr->dynCast<Block>();
if (!block || BranchUtils::BranchSeeker::has(block, block->name)) {
visit(curr);
return;
}
for (auto* child : block->list) {
visit(child);
}
if (block->type == Type::unreachable &&
block->list.back()->type != Type::unreachable) {
// similar to in visitBlock, here we could skip emitting the block itself,
// but must still end the 'block' (the contents, really) with an unreachable
emitUnreachable();
}
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visit(Expression* curr) {
emitDebugLocation(curr);
OverriddenVisitor<BinaryenIRWriter>::visit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitBlock(Block* curr) {
auto visitChildren = [this](Block* curr, Index from) {
auto& list = curr->list;
while (from < list.size()) {
visit(list[from++]);
}
};
auto afterChildren = [this](Block* curr) {
if (curr->type == Type::unreachable) {
// an unreachable block is one that cannot be exited. We cannot encode
// this directly in wasm, where blocks must be none,i32,i64,f32,f64. Since
// the block cannot be exited, we can emit an unreachable at the end, and
// that will always be valid, and then the block is ok as a none
emitUnreachable();
}
emitScopeEnd(curr);
if (curr->type == Type::unreachable) {
// and emit an unreachable *outside* the block too, so later things can
// pop anything
emitUnreachable();
}
};
// Handle very deeply nested blocks in the first position efficiently,
// avoiding heavy recursion. We only start to do this if we see it will help
// us (to avoid allocation of the vector).
if (!curr->list.empty() && curr->list[0]->is<Block>()) {
std::vector<Block*> parents;
Block* child;
while (!curr->list.empty() && (child = curr->list[0]->dynCast<Block>())) {
parents.push_back(curr);
emit(curr);
curr = child;
}
// Emit the current block, which does not have a block as a child in the
// first position.
emit(curr);
visitChildren(curr, 0);
afterChildren(curr);
// Finish the later parts of all the parent blocks.
while (!parents.empty()) {
auto* parent = parents.back();
parents.pop_back();
visitChildren(parent, 1);
afterChildren(parent);
}
return;
}
// Simple case of not having a nested block in the first position.
emit(curr);
visitChildren(curr, 0);
afterChildren(curr);
}
template<typename SubType> void BinaryenIRWriter<SubType>::visitIf(If* curr) {
visit(curr->condition);
if (curr->condition->type == Type::unreachable) {
// this if-else is unreachable because of the condition, i.e., the condition
// does not exit. So don't emit the if (but do consume the condition)
emitUnreachable();
return;
}
emit(curr);
visitPossibleBlockContents(curr->ifTrue);
if (curr->ifFalse) {
emitIfElse(curr);
visitPossibleBlockContents(curr->ifFalse);
}
emitScopeEnd(curr);
if (curr->type == Type::unreachable) {
// we already handled the case of the condition being unreachable.
// otherwise, we may still be unreachable, if we are an if-else with both
// sides unreachable. wasm does not allow this to be emitted directly, so we
// must do something more. we could do better, but for now we emit an extra
// unreachable instruction after the if, so it is not consumed itself,
assert(curr->ifFalse);
emitUnreachable();
}
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitLoop(Loop* curr) {
emit(curr);
visitPossibleBlockContents(curr->body);
if (curr->type == Type::unreachable) {
// we emitted a loop without a return type, and the body might be block
// contents, so ensure it is not consumed
emitUnreachable();
}
emitScopeEnd(curr);
if (curr->type == Type::unreachable) {
// we emitted a loop without a return type, so it must not be consumed
emitUnreachable();
}
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitBreak(Break* curr) {
if (curr->value) {
visit(curr->value);
}
if (curr->condition) {
visit(curr->condition);
}
emit(curr);
if (curr->condition && curr->type == Type::unreachable) {
// a br_if is normally none or emits a value. if it is unreachable, then
// either the condition or the value is unreachable, which is extremely
// rare, and may require us to make the stack polymorphic (if the block we
// branch to has a value, we may lack one as we are not a reachable branch;
// the wasm spec on the other hand does presume the br_if emits a value of
// the right type, even if it popped unreachable)
emitUnreachable();
}
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitSwitch(Switch* curr) {
if (curr->value) {
visit(curr->value);
}
visit(curr->condition);
if (!BranchUtils::isBranchReachable(curr)) {
// if the branch is not reachable, then it's dangerous to emit it, as wasm
// type checking rules are different, especially in unreachable code. so
// just don't emit that unreachable code.
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitCall(Call* curr) {
for (auto* operand : curr->operands) {
visit(operand);
}
// For non-control-flow value-returning instructions, if the type of an
// expression is unreachable, we emit an unreachable and don't emit the
// instruction itself. If we don't emit an unreachable, instructions that
// follow can have a validation failure in wasm binary format. For example:
// [unreachable] (f32.add
// [unreachable] (i32.eqz
// [unreachable] (unreachable)
// )
// ...
// )
// This is a valid prgram in binaryen IR, because the unreachable type
// propagates out of an expression, making both i32.eqz and f32.add
// unreachable. But in binary format, this becomes:
// unreachable
// i32.eqz
// f32.add ;; validation failure; it takes an i32!
// And here f32.add causes validation failure in wasm validation. So in this
// case we add an unreachable to prevent following instructions to consume
// the current value (here i32.eqz).
//
// The same applies for other expressions.
if (curr->type == Type::unreachable && !curr->isReturn) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitCallIndirect(CallIndirect* curr) {
for (auto* operand : curr->operands) {
visit(operand);
}
visit(curr->target);
if (curr->type == Type::unreachable && !curr->isReturn) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitLocalGet(LocalGet* curr) {
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitLocalSet(LocalSet* curr) {
visit(curr->value);
if (curr->isTee() && curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitGlobalGet(GlobalGet* curr) {
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitGlobalSet(GlobalSet* curr) {
visit(curr->value);
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitLoad(Load* curr) {
visit(curr->ptr);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitStore(Store* curr) {
visit(curr->ptr);
visit(curr->value);
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicRMW(AtomicRMW* curr) {
visit(curr->ptr);
visit(curr->value);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicCmpxchg(AtomicCmpxchg* curr) {
visit(curr->ptr);
visit(curr->expected);
visit(curr->replacement);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicWait(AtomicWait* curr) {
visit(curr->ptr);
visit(curr->expected);
visit(curr->timeout);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicNotify(AtomicNotify* curr) {
visit(curr->ptr);
visit(curr->notifyCount);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicFence(AtomicFence* curr) {
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDExtract(SIMDExtract* curr) {
visit(curr->vec);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDReplace(SIMDReplace* curr) {
visit(curr->vec);
visit(curr->value);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDShuffle(SIMDShuffle* curr) {
visit(curr->left);
visit(curr->right);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDTernary(SIMDTernary* curr) {
visit(curr->a);
visit(curr->b);
visit(curr->c);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDShift(SIMDShift* curr) {
visit(curr->vec);
visit(curr->shift);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDLoad(SIMDLoad* curr) {
visit(curr->ptr);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitMemoryInit(MemoryInit* curr) {
visit(curr->dest);
visit(curr->offset);
visit(curr->size);
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitDataDrop(DataDrop* curr) {
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitMemoryCopy(MemoryCopy* curr) {
visit(curr->dest);
visit(curr->source);
visit(curr->size);
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitMemoryFill(MemoryFill* curr) {
visit(curr->dest);
visit(curr->value);
visit(curr->size);
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitConst(Const* curr) {
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitUnary(Unary* curr) {
visit(curr->value);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitBinary(Binary* curr) {
visit(curr->left);
visit(curr->right);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitSelect(Select* curr) {
visit(curr->ifTrue);
visit(curr->ifFalse);
visit(curr->condition);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitReturn(Return* curr) {
if (curr->value) {
visit(curr->value);
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitHost(Host* curr) {
switch (curr->op) {
case MemorySize: {
break;
}
case MemoryGrow: {
visit(curr->operands[0]);
break;
}
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitRefNull(RefNull* curr) {
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitRefIsNull(RefIsNull* curr) {
visit(curr->value);
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitRefFunc(RefFunc* curr) {
if (curr->type == Type::unreachable) {
emitUnreachable();
return;
}
emit(curr);
}
template<typename SubType> void BinaryenIRWriter<SubType>::visitTry(Try* curr) {
emit(curr);
visitPossibleBlockContents(curr->body);
emitCatch(curr);
visitPossibleBlockContents(curr->catchBody);
emitScopeEnd(curr);
if (curr->type == Type::unreachable) {
emitUnreachable();
}
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitThrow(Throw* curr) {
for (auto* operand : curr->operands) {
visit(operand);
}
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitRethrow(Rethrow* curr) {
visit(curr->exnref);
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitBrOnExn(BrOnExn* curr) {
visit(curr->exnref);
emit(curr);
if (curr->type == Type::unreachable) {
emitUnreachable();
}
}
template<typename SubType> void BinaryenIRWriter<SubType>::visitNop(Nop* curr) {
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitUnreachable(Unreachable* curr) {
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitDrop(Drop* curr) {
visit(curr->value);
emit(curr);
}
template<typename SubType>
void BinaryenIRWriter<SubType>::visitPush(Push* curr) {
visit(curr->value);
emit(curr);
}
template<typename SubType> void BinaryenIRWriter<SubType>::visitPop(Pop* curr) {
emit(curr);
}
// Binaryen IR to binary writer
class BinaryenIRToBinaryWriter
: public BinaryenIRWriter<BinaryenIRToBinaryWriter> {
public:
BinaryenIRToBinaryWriter(WasmBinaryWriter& parent,
BufferWithRandomAccess& o,
Function* func = nullptr,
bool sourceMap = false)
: BinaryenIRWriter<BinaryenIRToBinaryWriter>(func), parent(parent),
writer(parent, o, func, sourceMap), sourceMap(sourceMap) {}
void visit(Expression* curr) {
BinaryenIRWriter<BinaryenIRToBinaryWriter>::visit(curr);
}
void emit(Expression* curr) { writer.visit(curr); }
void emitHeader() {
if (func->prologLocation.size()) {
parent.writeDebugLocation(*func->prologLocation.begin());
}
writer.mapLocalsAndEmitHeader();
}
void emitIfElse(If* curr) { writer.emitIfElse(curr); }
void emitCatch(Try* curr) { writer.emitCatch(curr); }
void emitScopeEnd(Expression* curr) { writer.emitScopeEnd(curr); }
void emitFunctionEnd() {
if (func->epilogLocation.size()) {
parent.writeDebugLocation(*func->epilogLocation.begin());
}
writer.emitFunctionEnd();
}
void emitUnreachable() { writer.emitUnreachable(); }
void emitDebugLocation(Expression* curr) {
if (sourceMap) {
parent.writeDebugLocation(curr, func);
}
}
private:
WasmBinaryWriter& parent;
BinaryInstWriter writer;
bool sourceMap = false;
};
// Binaryen IR to stack IR converter
// Queues the expressions linearly in Stack IR (SIR)
class StackIRGenerator : public BinaryenIRWriter<StackIRGenerator> {
public:
StackIRGenerator(MixedArena& allocator, Function* func)
: BinaryenIRWriter<StackIRGenerator>(func), allocator(allocator) {}
void emit(Expression* curr);
void emitScopeEnd(Expression* curr);
void emitHeader() {}
void emitIfElse(If* curr) {
stackIR.push_back(makeStackInst(StackInst::IfElse, curr));
}
void emitCatch(Try* curr) {
stackIR.push_back(makeStackInst(StackInst::Catch, curr));
}
void emitFunctionEnd() {}
void emitUnreachable() {
stackIR.push_back(makeStackInst(Builder(allocator).makeUnreachable()));
}
void emitDebugLocation(Expression* curr) {}
StackIR& getStackIR() { return stackIR; }
private:
StackInst* makeStackInst(StackInst::Op op, Expression* origin);
StackInst* makeStackInst(Expression* origin) {
return makeStackInst(StackInst::Basic, origin);
}
MixedArena& allocator;
StackIR stackIR; // filled in write()
};
// Stack IR to binary writer
class StackIRToBinaryWriter {
public:
StackIRToBinaryWriter(WasmBinaryWriter& parent,
BufferWithRandomAccess& o,
Function* func)
: writer(parent, o, func, false /* sourceMap */), func(func) {}
void write();
private:
BinaryInstWriter writer;
Function* func;
};
} // namespace wasm
#endif // wasm_stack_h