blob: 028e4d4d5c727e0281455bc0c5442a87871b092b [file] [log] [blame]
/*
* Copyright 2015 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.
*/
//
// WebAssembly-to-asm.js translator. Uses the Emscripten optimizer
// infrastructure.
//
#ifndef wasm_wasm2asm_h
#define wasm_wasm2asm_h
#include <cmath>
#include <numeric>
#include "asmjs/shared-constants.h"
#include "wasm.h"
#include "wasm-builder.h"
#include "emscripten-optimizer/optimizer.h"
#include "mixed_arena.h"
#include "asm_v_wasm.h"
#include "ast_utils.h"
#include "passes/passes.h"
namespace wasm {
using namespace cashew;
IString ASM_FUNC("asmFunc"),
ABORT_FUNC("abort"),
FUNCTION_TABLE("FUNCTION_TABLE"),
NO_RESULT("wasm2asm$noresult"), // no result at all
EXPRESSION_RESULT("wasm2asm$expresult"); // result in an expression, no temp var
// Appends extra to block, flattening out if extra is a block as well
void flattenAppend(Ref ast, Ref extra) {
int index;
if (ast[0] == BLOCK || ast[0] == TOPLEVEL) index = 1;
else if (ast[0] == DEFUN) index = 3;
else abort();
if (extra->isArray() && extra[0] == BLOCK) {
for (size_t i = 0; i < extra[1]->size(); i++) {
ast[index]->push_back(extra[1][i]);
}
} else {
ast[index]->push_back(extra);
}
}
//
// Wasm2AsmBuilder - converts a WebAssembly module into asm.js
//
// In general, asm.js => wasm is very straightforward, as can
// be seen in asm2wasm.h. Just a single pass, plus a little
// state bookkeeping (breakStack, etc.), and a few after-the
// fact corrections for imports, etc. However, wasm => asm.js
// is tricky because wasm has statements == expressions, or in
// other words, things like `break` and `if` can show up
// in places where asm.js can't handle them, like inside an
// a loop's condition check.
//
// We therefore need the ability to lower an expression into
// a block of statements, and we keep statementizing until we
// reach a context in which we can emit those statments. This
// requires that we create temp variables to store values
// that would otherwise flow directly into their targets if
// we were an expression (e.g. if a loop's condition check
// is a bunch of statements, we execute those statements,
// then use the computed value in the loop's condition;
// we might also be able to avoid an assign to a temp var
// at the end of those statements, and put just that
// value in the loop's condition).
//
// It is possible to do this in a single pass, if we just
// allocate temp vars freely. However, pathological cases
// can easily show bad behavior here, with many unnecessary
// temp vars. We could rely on optimization passes like
// Emscripten's eliminate/registerize pair, but we want
// wasm2asm to be fairly fast to run, as it might run on
// the client.
//
// The approach taken here therefore performs 2 passes on
// each function. First, it finds which expression will need to
// be statementized. It also sees which labels can receive a break
// with a value. Given that information, in the second pass we can
// allocate // temp vars in an efficient manner, as we know when we
// need them and when their use is finished. They are allocated
// using an RAII class, so that they are automatically freed
// when the scope ends. This means that a node cannot allocate
// its own temp var; instead, the parent - which knows the
// child will return a value in a temp var - allocates it,
// and tells the child what temp var to emit to. The child
// can then pass forward that temp var to its children,
// optimizing away unnecessary forwarding.
class Wasm2AsmBuilder {
MixedArena allocator;
public:
struct Flags {
bool debug = false;
bool pedantic = false;
};
Wasm2AsmBuilder(Flags f) : flags(f) {}
Ref processWasm(Module* wasm);
Ref processFunction(Function* func);
// The first pass on an expression: scan it to see whether it will
// need to be statementized, and note spooky returns of values at
// a distance (aka break with a value).
void scanFunctionBody(Expression* curr);
// The second pass on an expression: process it fully, generating
// asm.js
// @param result Whether the context we are in receives a value,
// and its type, or if not, then we can drop our return,
// if we have one.
Ref processFunctionBody(Function* func, IString result);
Ref processAsserts(Element& e, SExpressionWasmBuilder& sexpBuilder);
// Get a temp var.
IString getTemp(WasmType type, Function* func) {
IString ret;
if (frees[type].size() > 0) {
ret = frees[type].back();
frees[type].pop_back();
} else {
size_t index = temps[type]++;
ret = IString((std::string("wasm2asm_") + printWasmType(type) + "$" +
std::to_string(index)).c_str(), false);
}
if (func->localIndices.find(ret) == func->localIndices.end()) {
Builder::addVar(func, ret, type);
}
return ret;
}
// Free a temp var.
void freeTemp(WasmType type, IString temp) {
frees[type].push_back(temp);
}
static IString fromName(Name name) {
// TODO: more clever name fixing, including checking we do not collide
const char* str = name.str;
// check the various issues, and recurse so we check the others
if (strchr(str, '-')) {
char* mod = strdup(str);
str = mod;
while (*mod) {
if (*mod == '-') *mod = '_';
mod++;
}
IString result = fromName(IString(str, false));
free((void*)str);
return result;
}
if (isdigit(str[0]) || strcmp(str, "if") == 0) {
std::string prefixed = "$$";
prefixed += name.str;
return fromName(IString(prefixed.c_str(), false));
}
return name;
}
void setStatement(Expression* curr) {
willBeStatement.insert(curr);
}
bool isStatement(Expression* curr) {
return curr && willBeStatement.find(curr) != willBeStatement.end();
}
size_t getTableSize() {
return tableSize;
}
private:
Flags flags;
// How many temp vars we need
std::vector<size_t> temps; // type => num temps
// Which are currently free to use
std::vector<std::vector<IString>> frees; // type => list of free names
// Expressions that will be a statement.
std::set<Expression*> willBeStatement;
// All our function tables have the same size TODO: optimize?
size_t tableSize;
void addBasics(Ref ast);
void addImport(Ref ast, Import* import);
void addTables(Ref ast, Module* wasm);
void addExports(Ref ast, Module* wasm);
void addWasmCompatibilityFuncs(Module* wasm);
bool isAssertHandled(Element& e);
Ref makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
Builder& wasmBuilder,
Element& e, Name testFuncName);
Ref makeAssertTrapFunc(SExpressionWasmBuilder& sexpBuilder,
Builder& wasmBuilder,
Element& e, Name testFuncName);
Wasm2AsmBuilder() = delete;
Wasm2AsmBuilder(const Wasm2AsmBuilder &) = delete;
Wasm2AsmBuilder &operator=(const Wasm2AsmBuilder&) = delete;
};
static Function* makeCtzFunc(MixedArena& allocator, UnaryOp op) {
assert(op == CtzInt32 || op == CtzInt64);
Builder b(allocator);
// if eqz(x) then 32 else (32 - clz(x ^ (x - 1)))
bool is32Bit = (op == CtzInt32);
Name funcName = is32Bit ? Name(WASM_CTZ32) : Name(WASM_CTZ64);
BinaryOp subOp = is32Bit ? SubInt32 : SubInt64;
BinaryOp xorOp = is32Bit ? XorInt32 : XorInt64;
UnaryOp clzOp = is32Bit ? ClzInt32 : ClzInt64;
UnaryOp eqzOp = is32Bit ? EqZInt32 : EqZInt64;
WasmType argType = is32Bit ? i32 : i64;
Binary* xorExp = b.makeBinary(
xorOp,
b.makeGetLocal(0, i32),
b.makeBinary(
subOp,
b.makeGetLocal(0, i32),
b.makeConst(is32Bit ? Literal(int32_t(1)) : Literal(int64_t(1)))
)
);
Binary* subExp = b.makeBinary(
subOp,
b.makeConst(is32Bit ? Literal(int32_t(32 - 1)) : Literal(int64_t(64 - 1))),
b.makeUnary(clzOp, xorExp)
);
If* body = b.makeIf(
b.makeUnary(
eqzOp,
b.makeGetLocal(0, i32)
),
b.makeConst(is32Bit ? Literal(int32_t(32)) : Literal(int64_t(64))),
subExp
);
return b.makeFunction(
funcName,
std::vector<NameType>{NameType("x", argType)},
argType,
std::vector<NameType>{},
body
);
}
static Function* makePopcntFunc(MixedArena& allocator, UnaryOp op) {
assert(op == PopcntInt32 || op == PopcntInt64);
Builder b(allocator);
// popcnt implemented as:
// int c; for (c = 0; x != 0; c++) { x = x & (x - 1) }; return c
bool is32Bit = (op == PopcntInt32);
Name funcName = is32Bit ? Name(WASM_POPCNT32) : Name(WASM_POPCNT64);
BinaryOp addOp = is32Bit ? AddInt32 : AddInt64;
BinaryOp subOp = is32Bit ? SubInt32 : SubInt64;
BinaryOp andOp = is32Bit ? AndInt32 : AndInt64;
UnaryOp eqzOp = is32Bit ? EqZInt32 : EqZInt64;
WasmType argType = is32Bit ? i32 : i64;
Name loopName("l");
Name blockName("b");
Break* brIf = b.makeBreak(
blockName,
b.makeGetLocal(1, i32),
b.makeUnary(
eqzOp,
b.makeGetLocal(0, argType)
)
);
SetLocal* update = b.makeSetLocal(
0,
b.makeBinary(
andOp,
b.makeGetLocal(0, argType),
b.makeBinary(
subOp,
b.makeGetLocal(0, argType),
b.makeConst(is32Bit ? Literal(int32_t(1)) : Literal(int64_t(1)))
)
)
);
SetLocal* inc = b.makeSetLocal(
1,
b.makeBinary(
addOp,
b.makeGetLocal(1, argType),
b.makeConst(Literal(1))
)
);
Break* cont = b.makeBreak(loopName);
Loop* loop = b.makeLoop(loopName, b.blockify(brIf, update, inc, cont));
Block* loopBlock = b.blockifyWithName(loop, blockName);
SetLocal* initCount = b.makeSetLocal(1, b.makeConst(Literal(0)));
return b.makeFunction(
funcName,
std::vector<NameType>{NameType("x", argType)},
argType,
std::vector<NameType>{NameType("count", argType)},
b.blockify(initCount, loopBlock)
);
}
Function* makeRotFunc(MixedArena& allocator, BinaryOp op) {
assert(op == RotLInt32 || op == RotRInt32 ||
op == RotLInt64 || op == RotRInt64);
Builder b(allocator);
// left rotate is:
// (((((~0) >>> k) & x) << k) | ((((~0) << (w - k)) & x) >>> (w - k)))
// where k is shift modulo w. reverse shifts for right rotate
bool is32Bit = (op == RotLInt32 || op == RotRInt32);
bool isLRot = (op == RotLInt32 || op == RotLInt64);
static Name names[2][2] = {{Name(WASM_ROTR64), Name(WASM_ROTR32)},
{Name(WASM_ROTL64), Name(WASM_ROTL32)}};
static BinaryOp shifters[2][2] = {{ShrUInt64, ShrUInt32},
{ShlInt64, ShlInt32}};
Name funcName = names[isLRot][is32Bit];
BinaryOp lshift = shifters[isLRot][is32Bit];
BinaryOp rshift = shifters[!isLRot][is32Bit];
BinaryOp orOp = is32Bit ? OrInt32 : OrInt64;
BinaryOp andOp = is32Bit ? AndInt32 : AndInt64;
BinaryOp subOp = is32Bit ? SubInt32 : SubInt64;
WasmType argType = is32Bit ? i32 : i64;
Literal widthMask =
is32Bit ? Literal(int32_t(32 - 1)) : Literal(int64_t(64 - 1));
Literal width =
is32Bit ? Literal(int32_t(32)) : Literal(int64_t(64));
auto shiftVal = [&]() {
return b.makeBinary(
andOp,
b.makeGetLocal(1, argType),
b.makeConst(widthMask)
);
};
auto widthSub = [&]() {
return b.makeBinary(subOp, b.makeConst(width), shiftVal());
};
auto fullMask = [&]() {
return b.makeConst(is32Bit ? Literal(~int32_t(0)) : Literal(~int64_t(0)));
};
Binary* maskRShift = b.makeBinary(rshift, fullMask(), shiftVal());
Binary* lowMask = b.makeBinary(andOp, maskRShift, b.makeGetLocal(0, argType));
Binary* lowShift = b.makeBinary(lshift, lowMask, shiftVal());
Binary* maskLShift = b.makeBinary(lshift, fullMask(), widthSub());
Binary* highMask =
b.makeBinary(andOp, maskLShift, b.makeGetLocal(0, argType));
Binary* highShift = b.makeBinary(rshift, highMask, widthSub());
Binary* body = b.makeBinary(orOp, lowShift, highShift);
return b.makeFunction(
funcName,
std::vector<NameType>{NameType("x", argType),
NameType("k", argType)},
argType,
std::vector<NameType>{},
body
);
}
void Wasm2AsmBuilder::addWasmCompatibilityFuncs(Module* wasm) {
wasm->addFunction(makeCtzFunc(wasm->allocator, CtzInt32));
wasm->addFunction(makePopcntFunc(wasm->allocator, PopcntInt32));
wasm->addFunction(makeRotFunc(wasm->allocator, RotLInt32));
wasm->addFunction(makeRotFunc(wasm->allocator, RotRInt32));
}
Ref Wasm2AsmBuilder::processWasm(Module* wasm) {
addWasmCompatibilityFuncs(wasm);
PassRunner runner(wasm);
runner.add<AutoDrop>();
runner.add("i64-to-i32-lowering");
runner.add("flatten");
runner.add("simplify-locals-notee-nostructure");
runner.add("vacuum");
runner.setDebug(flags.debug);
runner.run();
Ref ret = ValueBuilder::makeToplevel();
Ref asmFunc = ValueBuilder::makeFunction(ASM_FUNC);
ret[1]->push_back(asmFunc);
ValueBuilder::appendArgumentToFunction(asmFunc, GLOBAL);
ValueBuilder::appendArgumentToFunction(asmFunc, ENV);
ValueBuilder::appendArgumentToFunction(asmFunc, BUFFER);
asmFunc[3]->push_back(ValueBuilder::makeStatement(ValueBuilder::makeString(USE_ASM)));
// create heaps, etc
addBasics(asmFunc[3]);
for (auto& import : wasm->imports) {
addImport(asmFunc[3], import.get());
}
// figure out the table size
tableSize = std::accumulate(wasm->table.segments.begin(),
wasm->table.segments.end(),
0, [&](size_t size, Table::Segment seg) -> size_t {
return size + seg.data.size();
});
size_t pow2ed = 1;
while (pow2ed < tableSize) {
pow2ed <<= 1;
}
tableSize = pow2ed;
// functions
for (auto& func : wasm->functions) {
asmFunc[3]->push_back(processFunction(func.get()));
}
addTables(asmFunc[3], wasm);
// memory XXX
addExports(asmFunc[3], wasm);
return ret;
}
void Wasm2AsmBuilder::addBasics(Ref ast) {
// heaps, var HEAP8 = new global.Int8Array(buffer); etc
auto addHeap = [&](IString name, IString view) {
Ref theVar = ValueBuilder::makeVar();
ast->push_back(theVar);
ValueBuilder::appendToVar(theVar,
name,
ValueBuilder::makeNew(
ValueBuilder::makeCall(
ValueBuilder::makeDot(
ValueBuilder::makeName(GLOBAL),
view
),
ValueBuilder::makeName(BUFFER)
)
)
);
};
addHeap(HEAP8, INT8ARRAY);
addHeap(HEAP16, INT16ARRAY);
addHeap(HEAP32, INT32ARRAY);
addHeap(HEAPU8, UINT8ARRAY);
addHeap(HEAPU16, UINT16ARRAY);
addHeap(HEAPU32, UINT32ARRAY);
addHeap(HEAPF32, FLOAT32ARRAY);
addHeap(HEAPF64, FLOAT64ARRAY);
// core asm.js imports
auto addMath = [&](IString name, IString base) {
Ref theVar = ValueBuilder::makeVar();
ast->push_back(theVar);
ValueBuilder::appendToVar(theVar,
name,
ValueBuilder::makeDot(
ValueBuilder::makeDot(
ValueBuilder::makeName(GLOBAL),
MATH
),
base
)
);
};
addMath(MATH_IMUL, IMUL);
addMath(MATH_FROUND, FROUND);
addMath(MATH_ABS, ABS);
addMath(MATH_CLZ32, CLZ32);
}
void Wasm2AsmBuilder::addImport(Ref ast, Import* import) {
Ref theVar = ValueBuilder::makeVar();
ast->push_back(theVar);
Ref module = ValueBuilder::makeName(ENV); // TODO: handle nested module imports
ValueBuilder::appendToVar(theVar,
fromName(import->name),
ValueBuilder::makeDot(
module,
fromName(import->base)
)
);
}
void Wasm2AsmBuilder::addTables(Ref ast, Module* wasm) {
std::map<std::string, std::vector<IString>> tables; // asm.js tables, sig => contents of table
for (Table::Segment& seg : wasm->table.segments) {
for (size_t i = 0; i < seg.data.size(); i++) {
Name name = seg.data[i];
auto func = wasm->getFunction(name);
std::string sig = getSig(func);
auto& table = tables[sig];
if (table.size() == 0) {
// fill it with the first of its type seen. we have to fill with something; and for asm2wasm output, the first is the null anyhow
table.resize(tableSize);
for (size_t j = 0; j < tableSize; j++) {
table[j] = fromName(name);
}
} else {
table[i] = fromName(name);
}
}
}
for (auto& pair : tables) {
auto& sig = pair.first;
auto& table = pair.second;
std::string stable = std::string("FUNCTION_TABLE_") + sig;
IString asmName = IString(stable.c_str(), false);
// add to asm module
Ref theVar = ValueBuilder::makeVar();
ast->push_back(theVar);
Ref theArray = ValueBuilder::makeArray();
ValueBuilder::appendToVar(theVar, asmName, theArray);
for (auto& name : table) {
ValueBuilder::appendToArray(theArray, ValueBuilder::makeName(name));
}
}
}
void Wasm2AsmBuilder::addExports(Ref ast, Module* wasm) {
Ref exports = ValueBuilder::makeObject();
for (auto& export_ : wasm->exports) {
ValueBuilder::appendToObject(
exports,
fromName(export_->name),
ValueBuilder::makeName(fromName(export_->value))
);
}
ast->push_back(ValueBuilder::makeStatement(ValueBuilder::makeReturn(exports)));
}
Ref Wasm2AsmBuilder::processFunction(Function* func) {
if (flags.debug) {
static int fns = 0;
std::cerr << "processFunction " << (fns++) << " " << func->name
<< std::endl;
}
Ref ret = ValueBuilder::makeFunction(fromName(func->name));
frees.clear();
frees.resize(std::max(i32, std::max(f32, f64)) + 1);
temps.clear();
temps.resize(std::max(i32, std::max(f32, f64)) + 1);
temps[i32] = temps[f32] = temps[f64] = 0;
// arguments
for (Index i = 0; i < func->getNumParams(); i++) {
IString name = fromName(func->getLocalNameOrGeneric(i));
ValueBuilder::appendArgumentToFunction(ret, name);
ret[3]->push_back(
ValueBuilder::makeStatement(
ValueBuilder::makeBinary(
ValueBuilder::makeName(name), SET,
makeAsmCoercion(
ValueBuilder::makeName(name),
wasmToAsmType(func->getLocalType(i))
)
)
)
);
}
Ref theVar = ValueBuilder::makeVar();
size_t theVarIndex = ret[3]->size();
ret[3]->push_back(theVar);
// body
auto appendFinalReturn = [&] (Ref retVal) {
flattenAppend(
ret,
ValueBuilder::makeReturn(
makeAsmCoercion(retVal, wasmToAsmType(func->result))
)
);
};
scanFunctionBody(func->body);
bool isBodyBlock = (func->body->_id == Expression::BlockId);
ExpressionList* stats = isBodyBlock ?
&static_cast<Block*>(func->body)->list : nullptr;
bool endsInReturn =
(isBodyBlock && ((*stats)[stats->size()-1]->_id == Expression::ReturnId));
if (endsInReturn) {
// return already taken care of
flattenAppend(ret, processFunctionBody(func, NO_RESULT));
} else if (isStatement(func->body)) {
// store result in variable then return it
IString result =
func->result != none ? getTemp(func->result, func) : NO_RESULT;
flattenAppend(ret, processFunctionBody(func, result));
if (func->result != none) {
appendFinalReturn(ValueBuilder::makeName(result));
freeTemp(func->result, result);
}
} else if (func->result != none) {
// whole thing is an expression, just return body
appendFinalReturn(processFunctionBody(func, EXPRESSION_RESULT));
} else {
// func has no return
flattenAppend(ret, processFunctionBody(func, NO_RESULT));
}
// vars, including new temp vars
for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) {
ValueBuilder::appendToVar(
theVar,
fromName(func->getLocalNameOrGeneric(i)),
makeAsmCoercedZero(wasmToAsmType(func->getLocalType(i)))
);
}
if (theVar[1]->size() == 0) {
ret[3]->splice(theVarIndex, 1);
}
// checks
assert(frees[i32].size() == temps[i32]); // all temp vars should be free at the end
assert(frees[f32].size() == temps[f32]); // all temp vars should be free at the end
assert(frees[f64].size() == temps[f64]); // all temp vars should be free at the end
// cleanups
willBeStatement.clear();
return ret;
}
void Wasm2AsmBuilder::scanFunctionBody(Expression* curr) {
struct ExpressionScanner : public PostWalker<ExpressionScanner> {
Wasm2AsmBuilder* parent;
ExpressionScanner(Wasm2AsmBuilder* parent) : parent(parent) {}
// Visitors
void visitBlock(Block* curr) {
parent->setStatement(curr);
}
void visitIf(If* curr) {
parent->setStatement(curr);
}
void visitLoop(Loop* curr) {
parent->setStatement(curr);
}
void visitBreak(Break* curr) {
parent->setStatement(curr);
}
void visitSwitch(Switch* curr) {
parent->setStatement(curr);
}
void visitCall(Call* curr) {
for (auto item : curr->operands) {
if (parent->isStatement(item)) {
parent->setStatement(curr);
break;
}
}
}
void visitCallImport(CallImport* curr) {
for (auto item : curr->operands) {
if (parent->isStatement(item)) {
parent->setStatement(curr);
break;
}
}
}
void visitCallIndirect(CallIndirect* curr) {
if (parent->isStatement(curr->target)) {
parent->setStatement(curr);
return;
}
for (auto item : curr->operands) {
if (parent->isStatement(item)) {
parent->setStatement(curr);
break;
}
}
}
void visitSetLocal(SetLocal* curr) {
if (parent->isStatement(curr->value)) {
parent->setStatement(curr);
}
}
void visitLoad(Load* curr) {
if (parent->isStatement(curr->ptr)) {
parent->setStatement(curr);
}
}
void visitStore(Store* curr) {
if (parent->isStatement(curr->ptr) || parent->isStatement(curr->value)) {
parent->setStatement(curr);
}
}
void visitUnary(Unary* curr) {
if (parent->isStatement(curr->value)) {
parent->setStatement(curr);
}
}
void visitBinary(Binary* curr) {
if (parent->isStatement(curr->left) || parent->isStatement(curr->right)) {
parent->setStatement(curr);
}
}
void visitSelect(Select* curr) {
if (parent->isStatement(curr->ifTrue) || parent->isStatement(curr->ifFalse) || parent->isStatement(curr->condition)) {
parent->setStatement(curr);
}
}
void visitReturn(Return* curr) {
parent->setStatement(curr);
}
void visitHost(Host* curr) {
for (auto item : curr->operands) {
if (parent->isStatement(item)) {
parent->setStatement(curr);
break;
}
}
}
};
ExpressionScanner(this).walk(curr);
}
Ref Wasm2AsmBuilder::processFunctionBody(Function* func, IString result) {
struct ExpressionProcessor : public Visitor<ExpressionProcessor, Ref> {
Wasm2AsmBuilder* parent;
IString result;
Function* func;
MixedArena allocator;
ExpressionProcessor(Wasm2AsmBuilder* parent, Function* func) : parent(parent), func(func) {}
// A scoped temporary variable.
struct ScopedTemp {
Wasm2AsmBuilder* parent;
WasmType type;
IString temp;
bool needFree;
// @param possible if provided, this is a variable we can use as our temp. it has already been
// allocated in a higher scope, and we can just assign to it as our result is
// going there anyhow.
ScopedTemp(WasmType type, Wasm2AsmBuilder* parent, Function* func,
IString possible = NO_RESULT) : parent(parent), type(type) {
assert(possible != EXPRESSION_RESULT);
if (possible == NO_RESULT) {
temp = parent->getTemp(type, func);
needFree = true;
} else {
temp = possible;
needFree = false;
}
}
~ScopedTemp() {
if (needFree) {
parent->freeTemp(type, temp);
}
}
IString getName() {
return temp;
}
Ref getAstName() {
return ValueBuilder::makeName(temp);
}
};
Ref visit(Expression* curr, IString nextResult) {
IString old = result;
result = nextResult;
Ref ret = Visitor::visit(curr);
result = old; // keep it consistent for the rest of this frame, which may call visit on multiple children
return ret;
}
Ref visit(Expression* curr, ScopedTemp& temp) {
return visit(curr, temp.temp);
}
// this result is for an asm expression slot, but it might be a statement
Ref visitForExpression(Expression* curr, WasmType type, IString& tempName) {
if (isStatement(curr)) {
ScopedTemp temp(type, parent, func);
tempName = temp.temp;
return visit(curr, temp);
} else {
return visit(curr, EXPRESSION_RESULT);
}
}
Ref visitAndAssign(Expression* curr, IString result) {
Ref ret = visit(curr, result);
// if it's not already a statement, then it's an expression, and we need to assign it
// (if it is a statement, it already assigns to the result var)
if (!isStatement(curr) && result != NO_RESULT) {
ret = ValueBuilder::makeStatement(
ValueBuilder::makeBinary(ValueBuilder::makeName(result), SET, ret));
}
return ret;
}
Ref visitAndAssign(Expression* curr, ScopedTemp& temp) {
return visitAndAssign(curr, temp.getName());
}
bool isStatement(Expression* curr) {
return parent->isStatement(curr);
}
// Expressions with control flow turn into a block, which we must
// then handle, even if we are an expression.
bool isBlock(Ref ast) {
return !!ast && ast->isArray() && ast[0] == BLOCK;
}
Ref blockify(Ref ast) {
if (isBlock(ast)) return ast;
Ref ret = ValueBuilder::makeBlock();
ret[1]->push_back(ValueBuilder::makeStatement(ast));
return ret;
}
// For spooky return-at-a-distance/break-with-result, this tells us
// what the result var is for a specific label.
std::map<Name, IString> breakResults;
// Breaks to the top of a loop should be emitted as continues, to that loop's main label
std::unordered_set<Name> continueLabels;
IString fromName(Name name) {
return parent->fromName(name);
}
// Visitors
Ref visitBlock(Block* curr) {
breakResults[curr->name] = result;
Ref ret = ValueBuilder::makeBlock();
size_t size = curr->list.size();
auto noResults = result == NO_RESULT ? size : size-1;
for (size_t i = 0; i < noResults; i++) {
flattenAppend(ret, ValueBuilder::makeStatement(visit(curr->list[i], NO_RESULT)));
}
if (result != NO_RESULT) {
flattenAppend(ret, visitAndAssign(curr->list[size-1], result));
}
if (curr->name.is()) {
ret = ValueBuilder::makeLabel(fromName(curr->name), ret);
}
return ret;
}
Ref visitIf(If* curr) {
IString temp;
Ref condition = visitForExpression(curr->condition, i32, temp);
Ref ifTrue = ValueBuilder::makeStatement(visitAndAssign(curr->ifTrue, result));
Ref ifFalse;
if (curr->ifFalse) {
ifFalse = ValueBuilder::makeStatement(visitAndAssign(curr->ifFalse, result));
}
if (temp.isNull()) {
return ValueBuilder::makeIf(condition, ifTrue, ifFalse); // simple if
}
condition = blockify(condition);
// just add an if to the block
condition[1]->push_back(ValueBuilder::makeIf(ValueBuilder::makeName(temp), ifTrue, ifFalse));
return condition;
}
Ref visitLoop(Loop* curr) {
Name asmLabel = curr->name;
continueLabels.insert(asmLabel);
Ref body = blockify(visit(curr->body, result));
flattenAppend(body, ValueBuilder::makeBreak(fromName(asmLabel)));
Ref ret = ValueBuilder::makeDo(body, ValueBuilder::makeInt(1));
return ValueBuilder::makeLabel(fromName(asmLabel), ret);
}
Ref visitBreak(Break* curr) {
if (curr->condition) {
// we need an equivalent to an if here, so use that code
Break fakeBreak = *curr;
fakeBreak.condition = nullptr;
If fakeIf(allocator);
fakeIf.condition = curr->condition;
fakeIf.ifTrue = &fakeBreak;
return visit(&fakeIf, result);
}
Ref theBreak;
auto iter = continueLabels.find(curr->name);
if (iter == continueLabels.end()) {
theBreak = ValueBuilder::makeBreak(fromName(curr->name));
} else {
theBreak = ValueBuilder::makeContinue(fromName(curr->name));
}
if (!curr->value) return theBreak;
// generate the value, including assigning to the result, and then do the break
Ref ret = visitAndAssign(curr->value, breakResults[curr->name]);
ret = blockify(ret);
ret[1]->push_back(theBreak);
return ret;
}
Expression* defaultBody = nullptr; // default must be last in asm.js
Ref visitSwitch(Switch* curr) {
assert(!curr->value);
Ref ret = ValueBuilder::makeBlock();
Ref condition;
if (isStatement(curr->condition)) {
ScopedTemp temp(i32, parent, func);
flattenAppend(ret[2], visit(curr->condition, temp));
condition = temp.getAstName();
} else {
condition = visit(curr->condition, EXPRESSION_RESULT);
}
Ref theSwitch =
ValueBuilder::makeSwitch(makeAsmCoercion(condition, ASM_INT));
ret[1]->push_back(theSwitch);
for (size_t i = 0; i < curr->targets.size(); i++) {
ValueBuilder::appendCaseToSwitch(theSwitch, ValueBuilder::makeNum(i));
ValueBuilder::appendCodeToSwitch(theSwitch, blockify(ValueBuilder::makeBreak(fromName(curr->targets[i]))), false);
}
ValueBuilder::appendDefaultToSwitch(theSwitch);
ValueBuilder::appendCodeToSwitch(theSwitch, blockify(ValueBuilder::makeBreak(fromName(curr->default_))), false);
return ret;
}
Ref makeStatementizedCall(ExpressionList& operands, Ref ret, Ref theCall, IString result, WasmType type) {
std::vector<ScopedTemp*> temps; // TODO: utility class, with destructor?
for (auto& operand : operands) {
temps.push_back(new ScopedTemp(operand->type, parent, func));
IString temp = temps.back()->temp;
flattenAppend(ret, visitAndAssign(operand, temp));
theCall[2]->push_back(makeAsmCoercion(ValueBuilder::makeName(temp), wasmToAsmType(operand->type)));
}
theCall = makeAsmCoercion(theCall, wasmToAsmType(type));
if (result != NO_RESULT) {
theCall = ValueBuilder::makeStatement(
ValueBuilder::makeBinary(
ValueBuilder::makeName(result), SET, theCall));
}
flattenAppend(ret, theCall);
for (auto temp : temps) {
delete temp;
}
return ret;
}
Ref visitGenericCall(Expression* curr, Name target,
ExpressionList& operands) {
Ref theCall = ValueBuilder::makeCall(fromName(target));
if (!isStatement(curr)) {
// none of our operands is a statement; go right ahead and create a
// simple expression
for (auto operand : operands) {
theCall[2]->push_back(
makeAsmCoercion(visit(operand, EXPRESSION_RESULT),
wasmToAsmType(operand->type)));
}
return makeAsmCoercion(theCall, wasmToAsmType(curr->type));
}
// we must statementize them all
return makeStatementizedCall(operands, ValueBuilder::makeBlock(), theCall,
result, curr->type);
}
Ref visitCall(Call* curr) {
return visitGenericCall(curr, curr->target, curr->operands);
}
Ref visitCallImport(CallImport* curr) {
return visitGenericCall(curr, curr->target, curr->operands);
}
Ref visitCallIndirect(CallIndirect* curr) {
std::string stable = std::string("FUNCTION_TABLE_") + curr->fullType.c_str();
IString table = IString(stable.c_str(), false);
auto makeTableCall = [&](Ref target) {
return ValueBuilder::makeCall(ValueBuilder::makeSub(
ValueBuilder::makeName(table),
ValueBuilder::makeBinary(target, AND, ValueBuilder::makeInt(parent->getTableSize()-1))
));
};
if (!isStatement(curr)) {
// none of our operands is a statement; go right ahead and create a simple expression
Ref theCall = makeTableCall(visit(curr->target, EXPRESSION_RESULT));
for (auto operand : curr->operands) {
theCall[2]->push_back(makeAsmCoercion(visit(operand, EXPRESSION_RESULT), wasmToAsmType(operand->type)));
}
return makeAsmCoercion(theCall, wasmToAsmType(curr->type));
}
// we must statementize them all
Ref ret = ValueBuilder::makeBlock();
ScopedTemp temp(i32, parent, func);
flattenAppend(ret, visit(curr->target, temp));
Ref theCall = makeTableCall(temp.getAstName());
return makeStatementizedCall(curr->operands, ret, theCall, result, curr->type);
}
Ref makeSetVar(Expression* curr, Expression* value, Name name) {
if (!isStatement(curr)) {
return ValueBuilder::makeBinary(
ValueBuilder::makeName(fromName(name)), SET,
visit(value, EXPRESSION_RESULT)
);
}
// if result was provided, our child can just assign there.
// Otherwise, allocate a temp for it to assign to.
ScopedTemp temp(value->type, parent, func, result);
Ref ret = blockify(visit(value, temp));
// the output was assigned to result, so we can just assign it to our target
ret[1]->push_back(
ValueBuilder::makeStatement(
ValueBuilder::makeBinary(
ValueBuilder::makeName(fromName(name)), SET,
temp.getAstName()
)
)
);
return ret;
}
Ref visitGetLocal(GetLocal* curr) {
return ValueBuilder::makeName(
fromName(func->getLocalNameOrGeneric(curr->index))
);
}
Ref visitSetLocal(SetLocal* curr) {
return makeSetVar(curr, curr->value, func->getLocalNameOrGeneric(curr->index));
}
Ref visitGetGlobal(GetGlobal* curr) {
return ValueBuilder::makeName(fromName(curr->name));
}
Ref visitSetGlobal(SetGlobal* curr) {
return makeSetVar(curr, curr->value, curr->name);
}
Ref visitLoad(Load* curr) {
if (isStatement(curr)) {
ScopedTemp temp(i32, parent, func);
GetLocal fakeLocal(allocator);
fakeLocal.index = func->getLocalIndex(temp.getName());
Load fakeLoad = *curr;
fakeLoad.ptr = &fakeLocal;
Ref ret = blockify(visitAndAssign(curr->ptr, temp));
flattenAppend(ret, visitAndAssign(&fakeLoad, result));
return ret;
}
if (curr->align != 0 && curr->align < curr->bytes) {
// set the pointer to a local
ScopedTemp temp(i32, parent, func);
SetLocal set(allocator);
set.index = func->getLocalIndex(temp.getName());
set.value = curr->ptr;
Ref ptrSet = visit(&set, NO_RESULT);
GetLocal get(allocator);
get.index = func->getLocalIndex(temp.getName());
// fake loads
Load load = *curr;
load.ptr = &get;
load.bytes = 1; // do the worst
Ref rest;
switch (curr->type) {
case i32: {
rest = makeAsmCoercion(visit(&load, EXPRESSION_RESULT), ASM_INT);
for (size_t i = 1; i < curr->bytes; i++) {
++load.offset;
Ref add = makeAsmCoercion(visit(&load, EXPRESSION_RESULT), ASM_INT);
add = ValueBuilder::makeBinary(add, LSHIFT, ValueBuilder::makeNum(8*i));
rest = ValueBuilder::makeBinary(rest, OR, add);
}
break;
}
default: {
std::cerr << "Unhandled type in load: " << curr->type << std::endl;
abort();
}
}
return ValueBuilder::makeSeq(ptrSet, rest);
}
// normal load
Ref ptr = visit(curr->ptr, EXPRESSION_RESULT);
if (curr->offset) {
ptr = makeAsmCoercion(
ValueBuilder::makeBinary(ptr, PLUS, ValueBuilder::makeNum(curr->offset)),
ASM_INT);
}
Ref ret;
switch (curr->type) {
case i32: {
switch (curr->bytes) {
case 1:
ret = ValueBuilder::makeSub(
ValueBuilder::makeName(curr->signed_ ? HEAP8 : HEAPU8 ),
ValueBuilder::makePtrShift(ptr, 0));
break;
case 2:
ret = ValueBuilder::makeSub(
ValueBuilder::makeName(curr->signed_ ? HEAP16 : HEAPU16),
ValueBuilder::makePtrShift(ptr, 1));
break;
case 4:
ret = ValueBuilder::makeSub(
ValueBuilder::makeName(curr->signed_ ? HEAP32 : HEAPU32),
ValueBuilder::makePtrShift(ptr, 2));
break;
default: {
std::cerr << "Unhandled number of bytes in i32 load: "
<< curr->bytes << std::endl;
abort();
}
}
break;
}
case f32:
ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32),
ValueBuilder::makePtrShift(ptr, 2));
break;
case f64:
ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64),
ValueBuilder::makePtrShift(ptr, 3));
break;
default: {
std::cerr << "Unhandled type in load: " << curr->type << std::endl;
abort();
}
}
return makeAsmCoercion(ret, wasmToAsmType(curr->type));
}
Ref visitStore(Store* curr) {
if (isStatement(curr)) {
ScopedTemp tempPtr(i32, parent, func);
ScopedTemp tempValue(curr->valueType, parent, func);
GetLocal fakeLocalPtr(allocator);
fakeLocalPtr.index = func->getLocalIndex(tempPtr.getName());
GetLocal fakeLocalValue(allocator);
fakeLocalValue.index = func->getLocalIndex(tempValue.getName());
Store fakeStore = *curr;
fakeStore.ptr = &fakeLocalPtr;
fakeStore.value = &fakeLocalValue;
Ref ret = blockify(visitAndAssign(curr->ptr, tempPtr));
flattenAppend(ret, visitAndAssign(curr->value, tempValue));
flattenAppend(ret, visitAndAssign(&fakeStore, result));
return ret;
}
if (curr->align != 0 && curr->align < curr->bytes) {
// set the pointer to a local
ScopedTemp temp(i32, parent, func);
SetLocal set(allocator);
set.index = func->getLocalIndex(temp.getName());
set.value = curr->ptr;
Ref ptrSet = visit(&set, NO_RESULT);
GetLocal get(allocator);
get.index = func->getLocalIndex(temp.getName());
// set the value to a local
ScopedTemp tempValue(curr->value->type, parent, func);
SetLocal setValue(allocator);
setValue.index = func->getLocalIndex(tempValue.getName());
setValue.value = curr->value;
Ref valueSet = visit(&setValue, NO_RESULT);
GetLocal getValue(allocator);
getValue.index = func->getLocalIndex(tempValue.getName());
// fake stores
Store store = *curr;
store.ptr = &get;
store.bytes = 1; // do the worst
Ref rest;
switch (curr->valueType) {
case i32: {
Const _255(allocator);
_255.value = Literal(int32_t(255));
_255.type = i32;
for (size_t i = 0; i < curr->bytes; i++) {
Const shift(allocator);
shift.value = Literal(int32_t(8*i));
shift.type = i32;
Binary shifted(allocator);
shifted.op = ShrUInt32;
shifted.left = &getValue;
shifted.right = &shift;
shifted.type = i32;
Binary anded(allocator);
anded.op = AndInt32;
anded.left = i > 0 ? static_cast<Expression*>(&shifted) : static_cast<Expression*>(&getValue);
anded.right = &_255;
anded.type = i32;
store.value = &anded;
Ref part = visit(&store, NO_RESULT);
if (i == 0) {
rest = part;
} else {
rest = ValueBuilder::makeSeq(rest, part);
}
++store.offset;
}
break;
}
default: {
std::cerr << "Unhandled type in store: " << curr->valueType
<< std::endl;
abort();
}
}
return ValueBuilder::makeSeq(ValueBuilder::makeSeq(ptrSet, valueSet), rest);
}
// normal store
Ref ptr = visit(curr->ptr, EXPRESSION_RESULT);
if (curr->offset) {
ptr = makeAsmCoercion(ValueBuilder::makeBinary(ptr, PLUS, ValueBuilder::makeNum(curr->offset)), ASM_INT);
}
Ref value = visit(curr->value, EXPRESSION_RESULT);
Ref ret;
switch (curr->valueType) {
case i32: {
switch (curr->bytes) {
case 1: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP8), ValueBuilder::makePtrShift(ptr, 0)); break;
case 2: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP16), ValueBuilder::makePtrShift(ptr, 1)); break;
case 4: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32), ValueBuilder::makePtrShift(ptr, 2)); break;
default: abort();
}
break;
}
case f32: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), ValueBuilder::makePtrShift(ptr, 2)); break;
case f64: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64), ValueBuilder::makePtrShift(ptr, 3)); break;
default: {
std::cerr << "Unhandled type in store: " << curr->valueType
<< std::endl;
abort();
}
}
return ValueBuilder::makeBinary(ret, SET, value);
}
Ref visitDrop(Drop* curr) {
assert(!isStatement(curr));
return visitAndAssign(curr->value, result);
}
Ref visitConst(Const* curr) {
switch (curr->type) {
case i32: return ValueBuilder::makeInt(curr->value.geti32());
case f32: {
Ref ret = ValueBuilder::makeCall(MATH_FROUND);
Const fake(allocator);
fake.value = Literal(double(curr->value.getf32()));
fake.type = f64;
ret[2]->push_back(visitConst(&fake));
return ret;
}
case f64: {
double d = curr->value.getf64();
if (d == 0 && std::signbit(d)) { // negative zero
return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeUnary(MINUS, ValueBuilder::makeDouble(0)));
}
return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeDouble(curr->value.getf64()));
}
default: abort();
}
}
Ref visitUnary(Unary* curr) {
if (isStatement(curr)) {
ScopedTemp temp(curr->value->type, parent, func);
GetLocal fakeLocal(allocator);
fakeLocal.index = func->getLocalIndex(temp.getName());
Unary fakeUnary = *curr;
fakeUnary.value = &fakeLocal;
Ref ret = blockify(visitAndAssign(curr->value, temp));
flattenAppend(ret, visitAndAssign(&fakeUnary, result));
return ret;
}
// normal unary
switch (curr->type) {
case i32: {
switch (curr->op) {
case ClzInt32:
return ValueBuilder::makeCall(
MATH_CLZ32,
visit(curr->value, EXPRESSION_RESULT)
);
case CtzInt32:
return makeSigning(
ValueBuilder::makeCall(
WASM_CTZ32,
visit(curr->value, EXPRESSION_RESULT)
),
ASM_SIGNED
);
case PopcntInt32:
return makeSigning(
ValueBuilder::makeCall(
WASM_POPCNT32,
visit(curr->value, EXPRESSION_RESULT)
),
ASM_SIGNED
);
case EqZInt32:
return ValueBuilder::makeBinary(
makeAsmCoercion(visit(curr->value,
EXPRESSION_RESULT), ASM_INT), EQ,
makeAsmCoercion(ValueBuilder::makeInt(0), ASM_INT));
default: {
std::cerr << "Unhandled unary i32 operator: " << curr
<< std::endl;
abort();
}
}
}
case f32:
case f64: {
Ref ret;
switch (curr->op) {
case NegFloat32:
case NegFloat64:
ret = ValueBuilder::makeUnary(
MINUS,
visit(curr->value, EXPRESSION_RESULT)
);
break;
case AbsFloat32:
case AbsFloat64:
ret = ValueBuilder::makeCall(
MATH_ABS,
visit(curr->value, EXPRESSION_RESULT)
);
break;
case CeilFloat32:
case CeilFloat64:
ret = ValueBuilder::makeCall(
MATH_CEIL,
visit(curr->value, EXPRESSION_RESULT)
);
break;
case FloorFloat32:
case FloorFloat64:
ret = ValueBuilder::makeCall(
MATH_FLOOR,
visit(curr->value, EXPRESSION_RESULT)
);
break;
case TruncFloat32:
case TruncFloat64:
ret = ValueBuilder::makeCall(
MATH_TRUNC,
visit(curr->value, EXPRESSION_RESULT)
);
break;
case NearestFloat32:
case NearestFloat64:
ret = ValueBuilder::makeCall(
MATH_NEAREST,
visit(curr->value,EXPRESSION_RESULT)
);
break;
case SqrtFloat32:
case SqrtFloat64:
ret = ValueBuilder::makeCall(
MATH_SQRT,
visit(curr->value, EXPRESSION_RESULT)
);
break;
// TODO: more complex unary conversions
default:
std::cerr << "Unhandled unary float operator: " << curr
<< std::endl;
abort();
}
if (curr->type == f32) { // doubles need much less coercing
return makeAsmCoercion(ret, ASM_FLOAT);
}
return ret;
}
default: {
std::cerr << "Unhandled type in unary: " << curr << std::endl;
abort();
}
}
}
Ref visitBinary(Binary* curr) {
if (isStatement(curr)) {
ScopedTemp tempLeft(curr->left->type, parent, func);
GetLocal fakeLocalLeft(allocator);
fakeLocalLeft.index = func->getLocalIndex(tempLeft.getName());
ScopedTemp tempRight(curr->right->type, parent, func);
GetLocal fakeLocalRight(allocator);
fakeLocalRight.index = func->getLocalIndex(tempRight.getName());
Binary fakeBinary = *curr;
fakeBinary.left = &fakeLocalLeft;
fakeBinary.right = &fakeLocalRight;
Ref ret = blockify(visitAndAssign(curr->left, tempLeft));
flattenAppend(ret, visitAndAssign(curr->right, tempRight));
flattenAppend(ret, visitAndAssign(&fakeBinary, result));
return ret;
}
// normal binary
Ref left = visit(curr->left, EXPRESSION_RESULT);
Ref right = visit(curr->right, EXPRESSION_RESULT);
Ref ret;
switch (curr->op) {
case AddInt32:
ret = ValueBuilder::makeBinary(left, PLUS, right);
break;
case SubInt32:
ret = ValueBuilder::makeBinary(left, MINUS, right);
break;
case MulInt32: {
if (curr->type == i32) {
// TODO: when one operand is a small int, emit a multiply
return ValueBuilder::makeCall(MATH_IMUL, left, right);
} else {
return ValueBuilder::makeBinary(left, MUL, right);
}
}
case DivSInt32:
ret = ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), DIV,
makeSigning(right, ASM_SIGNED));
break;
case DivUInt32:
ret = ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), DIV,
makeSigning(right, ASM_UNSIGNED));
break;
case RemSInt32:
ret = ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), MOD,
makeSigning(right, ASM_SIGNED));
break;
case RemUInt32:
ret = ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), MOD,
makeSigning(right, ASM_UNSIGNED));
break;
case AndInt32:
ret = ValueBuilder::makeBinary(left, AND, right);
break;
case OrInt32:
ret = ValueBuilder::makeBinary(left, OR, right);
break;
case XorInt32:
ret = ValueBuilder::makeBinary(left, XOR, right);
break;
case ShlInt32:
ret = ValueBuilder::makeBinary(left, LSHIFT, right);
break;
case ShrUInt32:
ret = ValueBuilder::makeBinary(left, TRSHIFT, right);
break;
case ShrSInt32:
ret = ValueBuilder::makeBinary(left, RSHIFT, right);
break;
case MinFloat32:
ret = ValueBuilder::makeCall(MATH_MIN, left, right);
break;
case MaxFloat32:
ret = ValueBuilder::makeCall(MATH_MAX, left, right);
break;
case EqInt32: {
// TODO: check if this condition is still valid/necessary
if (curr->left->type == i32) {
return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), EQ,
makeSigning(right, ASM_SIGNED));
} else {
return ValueBuilder::makeBinary(left, EQ, right);
}
}
case NeInt32: {
if (curr->left->type == i32) {
return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), NE,
makeSigning(right, ASM_SIGNED));
} else {
return ValueBuilder::makeBinary(left, NE, right);
}
}
case LtSInt32:
return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), LT,
makeSigning(right, ASM_SIGNED));
case LtUInt32:
return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), LT,
makeSigning(right, ASM_UNSIGNED));
case LeSInt32:
return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), LE,
makeSigning(right, ASM_SIGNED));
case LeUInt32:
return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), LE,
makeSigning(right, ASM_UNSIGNED));
case GtSInt32:
return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), GT,
makeSigning(right, ASM_SIGNED));
case GtUInt32:
return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), GT,
makeSigning(right, ASM_UNSIGNED));
case GeSInt32:
return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), GE,
makeSigning(right, ASM_SIGNED));
case GeUInt32:
return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), GE,
makeSigning(right, ASM_UNSIGNED));
case RotLInt32:
return makeSigning(ValueBuilder::makeCall(WASM_ROTL32, left, right),
ASM_SIGNED);
case RotRInt32:
return makeSigning(ValueBuilder::makeCall(WASM_ROTR32, left, right),
ASM_SIGNED);
default: {
std::cerr << "Unhandled binary operator: " << curr << std::endl;
abort();
}
}
return makeAsmCoercion(ret, wasmToAsmType(curr->type));
}
Ref visitSelect(Select* curr) {
if (isStatement(curr)) {
ScopedTemp tempIfTrue(curr->ifTrue->type, parent, func);
GetLocal fakeLocalIfTrue(allocator);
fakeLocalIfTrue.index = func->getLocalIndex(tempIfTrue.getName());
ScopedTemp tempIfFalse(curr->ifFalse->type, parent, func);
GetLocal fakeLocalIfFalse(allocator);
fakeLocalIfFalse.index = func->getLocalIndex(tempIfFalse.getName());
ScopedTemp tempCondition(i32, parent, func);
GetLocal fakeCondition(allocator);
fakeCondition.index = func->getLocalIndex(tempCondition.getName());
Select fakeSelect = *curr;
fakeSelect.ifTrue = &fakeLocalIfTrue;
fakeSelect.ifFalse = &fakeLocalIfFalse;
fakeSelect.condition = &fakeCondition;
Ref ret = blockify(visitAndAssign(curr->ifTrue, tempIfTrue));
flattenAppend(ret, visitAndAssign(curr->ifFalse, tempIfFalse));
flattenAppend(ret, visitAndAssign(curr->condition, tempCondition));
flattenAppend(ret, visitAndAssign(&fakeSelect, result));
return ret;
}
// normal select
Ref ifTrue = visit(curr->ifTrue, EXPRESSION_RESULT);
Ref ifFalse = visit(curr->ifFalse, EXPRESSION_RESULT);
Ref condition = visit(curr->condition, EXPRESSION_RESULT);
ScopedTemp tempIfTrue(curr->type, parent, func),
tempIfFalse(curr->type, parent, func),
tempCondition(i32, parent, func);
return
ValueBuilder::makeSeq(
ValueBuilder::makeBinary(tempCondition.getAstName(), SET, condition),
ValueBuilder::makeSeq(
ValueBuilder::makeBinary(tempIfTrue.getAstName(), SET, ifTrue),
ValueBuilder::makeSeq(
ValueBuilder::makeBinary(tempIfFalse.getAstName(), SET, ifFalse),
ValueBuilder::makeConditional(
tempCondition.getAstName(),
tempIfTrue.getAstName(),
tempIfFalse.getAstName()
)
)
)
);
}
Ref visitReturn(Return* curr) {
Ref val = (curr->value == nullptr) ?
Ref() :
makeAsmCoercion(
visit(curr->value, NO_RESULT),
wasmToAsmType(curr->value->type)
);
return ValueBuilder::makeReturn(val);
}
Ref visitHost(Host* curr) {
abort();
}
Ref visitNop(Nop* curr) {
return ValueBuilder::makeToplevel();
}
Ref visitUnreachable(Unreachable* curr) {
return ValueBuilder::makeCall(ABORT_FUNC);
}
};
return ExpressionProcessor(this, func).visit(func->body, result);
}
static Ref makeInstantiation() {
Ref lib = ValueBuilder::makeObject();
auto insertItem = [&](IString item) {
ValueBuilder::appendToObject(lib, item, ValueBuilder::makeName(item));
};
insertItem(MATH);
insertItem(INT8ARRAY);
insertItem(INT16ARRAY);
insertItem(INT32ARRAY);
insertItem(UINT8ARRAY);
insertItem(UINT16ARRAY);
insertItem(UINT32ARRAY);
insertItem(FLOAT32ARRAY);
insertItem(FLOAT64ARRAY);
Ref env = ValueBuilder::makeObject();
Ref mem = ValueBuilder::makeNew(
ValueBuilder::makeCall(ARRAY_BUFFER, ValueBuilder::makeInt(0x10000)));
Ref call = ValueBuilder::makeCall(IString(ASM_FUNC), lib, env, mem);
Ref ret = ValueBuilder::makeVar();
ValueBuilder::appendToVar(ret, ASM_MODULE, call);
return ret;
}
static void prefixCalls(Ref asmjs) {
if (asmjs->isArray()) {
ArrayStorage& arr = asmjs->getArray();
for (Ref& r : arr) {
prefixCalls(r);
}
if (arr.size() > 0 && arr[0]->isString() && arr[0]->getIString() == CALL) {
assert(arr.size() >= 2);
Ref prefixed = ValueBuilder::makeDot(ValueBuilder::makeName(ASM_MODULE),
arr[1]->getIString());
arr[1]->setArray(prefixed->getArray());
}
}
}
Ref Wasm2AsmBuilder::makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
Builder& wasmBuilder,
Element& e, Name testFuncName) {
Expression* actual = sexpBuilder.parseExpression(e[1]);
Expression* body = nullptr;
if (e.size() == 2) {
if (actual->type == none) {
body = wasmBuilder.blockify(
actual,
wasmBuilder.makeConst(Literal(uint32_t(1)))
);
} else {
body = actual;
}
} else if (e.size() == 3) {
Expression* expected = sexpBuilder.parseExpression(e[2]);
WasmType resType = expected->type;
actual->type = resType;
BinaryOp eqOp;
switch (resType) {
case i32: eqOp = EqInt32; break;
case i64: eqOp = EqInt64; break;
case f32: eqOp = EqFloat32; break;
case f64: eqOp = EqFloat64; break;
default: {
std::cerr << "Unhandled type in assert: " << resType << std::endl;
abort();
}
}
body = wasmBuilder.makeBinary(eqOp, actual, expected);
} else {
assert(false && "Unexpected number of parameters in assert_return");
}
std::unique_ptr<Function> testFunc(
wasmBuilder.makeFunction(
testFuncName,
std::vector<NameType>{},
body->type,
std::vector<NameType>{},
body
)
);
Ref jsFunc = processFunction(testFunc.get());
prefixCalls(jsFunc);
return jsFunc;
}
Ref Wasm2AsmBuilder::makeAssertTrapFunc(SExpressionWasmBuilder& sexpBuilder,
Builder& wasmBuilder,
Element& e, Name testFuncName) {
Name innerFuncName("f");
Expression* expr = sexpBuilder.parseExpression(e[1]);
std::unique_ptr<Function> exprFunc(
wasmBuilder.makeFunction(innerFuncName,
std::vector<NameType>{},
expr->type,
std::vector<NameType>{},
expr)
);
IString expectedErr = e[2]->str();
Ref innerFunc = processFunction(exprFunc.get());
Ref outerFunc = ValueBuilder::makeFunction(testFuncName);
outerFunc[3]->push_back(innerFunc);
Ref tryBlock = ValueBuilder::makeBlock();
ValueBuilder::appendToBlock(tryBlock, ValueBuilder::makeCall(innerFuncName));
Ref catchBlock = ValueBuilder::makeBlock();
ValueBuilder::appendToBlock(
catchBlock,
ValueBuilder::makeReturn(
ValueBuilder::makeCall(
ValueBuilder::makeDot(
ValueBuilder::makeName(IString("e")),
ValueBuilder::makeName(IString("message")),
ValueBuilder::makeName(IString("includes"))
),
ValueBuilder::makeString(expectedErr)
)
)
);
outerFunc[3]->push_back(ValueBuilder::makeTry(
tryBlock,
ValueBuilder::makeName((IString("e"))),
catchBlock));
outerFunc[3]->push_back(ValueBuilder::makeReturn(ValueBuilder::makeInt(0)));
return outerFunc;
}
bool Wasm2AsmBuilder::isAssertHandled(Element& e) {
return e.isList() && e.size() >= 2 && e[0]->isStr()
&& (e[0]->str() == Name("assert_return") ||
(flags.pedantic && e[0]->str() == Name("assert_trap")))
&& e[1]->isList() && e[1]->size() >= 2 && (*e[1])[0]->isStr()
&& (*e[1])[0]->str() == Name("invoke");
}
Ref Wasm2AsmBuilder::processAsserts(Element& root,
SExpressionWasmBuilder& sexpBuilder) {
Builder wasmBuilder(sexpBuilder.getAllocator());
Ref ret = ValueBuilder::makeBlock();
flattenAppend(ret, makeInstantiation());
for (size_t i = 1; i < root.size(); ++i) {
Element& e = *root[i];
if (!isAssertHandled(e)) {
std::cerr << "skipping " << e << std::endl;
continue;
}
Name testFuncName(IString(("check" + std::to_string(i)).c_str(), false));
bool isReturn = (e[0]->str() == Name("assert_return"));
Element& testOp = *e[1];
// Replace "invoke" with "call"
testOp[0]->setString(IString("call"), false, false);
// Need to claim dollared to get string as function target
testOp[1]->setString(testOp[1]->str(), /*dollared=*/true, false);
Ref testFunc = isReturn ?
makeAssertReturnFunc(sexpBuilder, wasmBuilder, e, testFuncName) :
makeAssertTrapFunc(sexpBuilder, wasmBuilder, e, testFuncName);
flattenAppend(ret, testFunc);
std::stringstream failFuncName;
failFuncName << "fail" << std::to_string(i);
flattenAppend(
ret,
ValueBuilder::makeIf(
ValueBuilder::makeUnary(L_NOT, ValueBuilder::makeCall(testFuncName)),
ValueBuilder::makeCall(IString(failFuncName.str().c_str(), false)),
Ref()
)
);
}
return ret;
}
} // namespace wasm
#endif // wasm_wasm2asm_h