summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asm2wasm.h3293
-rw-r--r--src/tools/asm2wasm.cpp296
2 files changed, 0 insertions, 3589 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h
deleted file mode 100644
index 97c079a34..000000000
--- a/src/asm2wasm.h
+++ /dev/null
@@ -1,3293 +0,0 @@
-/*
- * 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.
- */
-
-//
-// asm.js-to-WebAssembly translator. Uses the Emscripten optimizer
-// infrastructure.
-//
-
-#ifndef wasm_asm2wasm_h
-#define wasm_asm2wasm_h
-
-#include "abi/js.h"
-#include "asm_v_wasm.h"
-#include "asmjs/shared-constants.h"
-#include "emscripten-optimizer/optimizer.h"
-#include "ir/bits.h"
-#include "ir/branch-utils.h"
-#include "ir/literal-utils.h"
-#include "ir/module-utils.h"
-#include "ir/trapping.h"
-#include "ir/utils.h"
-#include "mixed_arena.h"
-#include "parsing.h"
-#include "pass.h"
-#include "passes/passes.h"
-#include "shared-constants.h"
-#include "support/debug.h"
-#include "wasm-builder.h"
-#include "wasm-emscripten.h"
-#include "wasm-module-building.h"
-#include "wasm.h"
-
-#define DEBUG_TYPE "asm2wasm"
-
-namespace wasm {
-
-using namespace cashew;
-
-// Names
-
-Name I32_CTTZ("i32_cttz");
-Name I32_CTPOP("i32_ctpop");
-Name I32_BC2F("i32_bc2f");
-Name I32_BC2I("i32_bc2i");
-Name I64("i64");
-Name I64_CONST("i64_const");
-Name I64_ADD("i64_add");
-Name I64_SUB("i64_sub");
-Name I64_MUL("i64_mul");
-Name I64_UDIV("i64_udiv");
-Name I64_SDIV("i64_sdiv");
-Name I64_UREM("i64_urem");
-Name I64_SREM("i64_srem");
-Name I64_AND("i64_and");
-Name I64_OR("i64_or");
-Name I64_XOR("i64_xor");
-Name I64_SHL("i64_shl");
-Name I64_ASHR("i64_ashr");
-Name I64_LSHR("i64_lshr");
-Name I64_EQ("i64_eq");
-Name I64_NE("i64_ne");
-Name I64_ULE("i64_ule");
-Name I64_SLE("i64_sle");
-Name I64_UGE("i64_uge");
-Name I64_SGE("i64_sge");
-Name I64_ULT("i64_ult");
-Name I64_SLT("i64_slt");
-Name I64_UGT("i64_ugt");
-Name I64_SGT("i64_sgt");
-Name I64_TRUNC("i64_trunc");
-Name I64_SEXT("i64_sext");
-Name I64_ZEXT("i64_zext");
-Name I64_S2F("i64_s2f");
-Name I64_S2D("i64_s2d");
-Name I64_U2F("i64_u2f");
-Name I64_U2D("i64_u2d");
-Name I64_F2S("i64_f2s");
-Name I64_D2S("i64_d2s");
-Name I64_F2U("i64_f2u");
-Name I64_D2U("i64_d2u");
-Name I64_BC2D("i64_bc2d");
-Name I64_BC2I("i64_bc2i");
-Name I64_CTTZ("i64_cttz");
-Name I64_CTLZ("i64_ctlz");
-Name I64_CTPOP("i64_ctpop");
-Name F32_COPYSIGN("f32_copysign");
-Name F64_COPYSIGN("f64_copysign");
-Name LOAD1("load1");
-Name LOAD2("load2");
-Name LOAD4("load4");
-Name LOAD8("load8");
-Name LOADF("loadf");
-Name LOADD("loadd");
-Name STORE1("store1");
-Name STORE2("store2");
-Name STORE4("store4");
-Name STORE8("store8");
-Name STOREF("storef");
-Name STORED("stored");
-Name FTCALL("ftCall_");
-Name MFTCALL("mftCall_");
-Name MAX_("max");
-Name MIN_("min");
-Name ATOMICS("Atomics");
-Name ATOMICS_LOAD("load");
-Name ATOMICS_STORE("store");
-Name ATOMICS_EXCHANGE("exchange");
-Name ATOMICS_COMPARE_EXCHANGE("compareExchange");
-Name ATOMICS_ADD("add");
-Name ATOMICS_SUB("sub");
-Name ATOMICS_AND("and");
-Name ATOMICS_OR("or");
-Name ATOMICS_XOR("xor");
-Name I64_ATOMICS_LOAD("i64_atomics_load");
-Name I64_ATOMICS_STORE("i64_atomics_store");
-Name I64_ATOMICS_AND("i64_atomics_and");
-Name I64_ATOMICS_OR("i64_atomics_or");
-Name I64_ATOMICS_XOR("i64_atomics_xor");
-Name I64_ATOMICS_ADD("i64_atomics_add");
-Name I64_ATOMICS_SUB("i64_atomics_sub");
-Name I64_ATOMICS_EXCHANGE("i64_atomics_exchange");
-Name I64_ATOMICS_COMPAREEXCHANGE("i64_atomics_compareExchange");
-Name TEMP_DOUBLE_PTR("tempDoublePtr");
-Name EMSCRIPTEN_DEBUGINFO("emscripten_debuginfo");
-
-// Utilities
-
-static WASM_NORETURN void abort_on(std::string why, Ref element) {
- std::cerr << why << ' ';
- element->stringify(std::cerr);
- std::cerr << '\n';
- abort();
-}
-static WASM_NORETURN void abort_on(std::string why, IString element) {
- std::cerr << why << ' ' << element.str << '\n';
- abort();
-}
-
-Index indexOr(Index x, Index y) { return x ? x : y; }
-
-// useful when we need to see our parent, in an asm.js expression stack
-struct AstStackHelper {
- static std::vector<Ref> astStack;
- AstStackHelper(Ref curr) { astStack.push_back(curr); }
- ~AstStackHelper() { astStack.pop_back(); }
- Ref getParent() {
- if (astStack.size() >= 2) {
- return astStack[astStack.size() - 2];
- } else {
- return Ref();
- }
- }
-};
-
-std::vector<Ref> AstStackHelper::astStack;
-
-static bool startsWith(const char* string, const char* prefix) {
- while (1) {
- if (*prefix == 0) {
- return true;
- }
- if (*string == 0) {
- return false;
- }
- if (*string++ != *prefix++) {
- return false;
- }
- }
-};
-
-//
-// Asm2WasmPreProcessor - does some initial parsing/processing
-// of asm.js code.
-//
-
-struct Asm2WasmPreProcessor {
- bool memoryGrowth = false;
- bool debugInfo = false;
-
- std::vector<std::string> debugInfoFileNames;
- std::unordered_map<std::string, Index> debugInfoFileIndices;
-
- char* allocatedCopy = nullptr;
-
- ~Asm2WasmPreProcessor() {
- if (allocatedCopy) {
- free(allocatedCopy);
- }
- }
-
- char* process(char* input) {
- // emcc --separate-asm modules can look like
- //
- // Module["asm"] = (function(global, env, buffer) {
- // ..
- // });
- //
- // we need to clean that up.
- if (*input == 'M') {
- size_t num = strlen(input);
- while (*input != 'f') {
- input++;
- num--;
- }
- char* end = input + num - 1;
- while (*end != '}') {
- *end = 0;
- end--;
- }
- }
-
- // asm.js memory growth uses a quite elaborate pattern. Instead of parsing
- // and matching it, we do a simpler detection on emscripten's asm.js output
- // format
- const char* START_FUNCS = "// EMSCRIPTEN_START_FUNCS";
- char* marker = strstr(input, START_FUNCS);
- if (marker) {
- // look for memory growth code just up to here, as an optimization
- *marker = 0;
- }
- // this can only show up in growth code, as normal asm.js lacks "true"
- char* growthSign = strstr(input, "return true;");
- if (growthSign) {
- memoryGrowth = true;
- // clean out this function, we don't need it. first where it starts
- char* growthFuncStart = growthSign;
- while (*growthFuncStart != '{') {
- growthFuncStart--; // skip body
- }
- while (*growthFuncStart != '(') {
- growthFuncStart--; // skip params
- }
- while (*growthFuncStart != ' ') {
- growthFuncStart--; // skip function name
- }
- while (*growthFuncStart != 'f') {
- growthFuncStart--; // skip 'function'
- }
- assert(strstr(growthFuncStart, "function ") == growthFuncStart);
- char* growthFuncEnd = strchr(growthSign, '}');
- assert(growthFuncEnd > growthFuncStart + 5);
- growthFuncStart[0] = '/';
- growthFuncStart[1] = '*';
- growthFuncEnd--;
- growthFuncEnd[0] = '*';
- growthFuncEnd[1] = '/';
- }
- if (marker) {
- *marker = START_FUNCS[0];
- }
-
- // handle debug info, if this build wants that.
- if (debugInfo) {
- // asm.js debug info comments look like
- // ..command..; //@line 4 "tests/hello_world.c"
- // we convert those into emscripten_debuginfo(file, line)
- // calls, where the params are indices into a mapping. then
- // the compiler and optimizer can operate on them. after
- // that, we can apply the debug info to the wasm node right
- // before it - this is guaranteed to be correct without opts,
- // and is usually decently accurate with them.
-
- // an upper bound on how much more space we need as a multiple of the
- // original
- const auto SCALE_FACTOR = 1.25;
- // an upper bound on how much we write for each debug info element itself
- const auto ADD_FACTOR = 100;
- auto size = strlen(input);
- auto upperBound = Index(size * SCALE_FACTOR) + ADD_FACTOR;
- char* copy = allocatedCopy = (char*)malloc(upperBound);
- char* end = copy + upperBound;
- char* out = copy;
- std::string DEBUGINFO_INTRINSIC = EMSCRIPTEN_DEBUGINFO.str;
- auto DEBUGINFO_INTRINSIC_SIZE = DEBUGINFO_INTRINSIC.size();
- const char* UNKNOWN_FILE = "(unknown)";
- bool seenUseAsm = false;
- while (input[0]) {
- if (out + ADD_FACTOR >= end) {
- Fatal() << "error in handling debug info";
- }
- if (startsWith(input, "//@line")) {
- char* linePos = input + 8;
- char* lineEnd = strpbrk(input + 8, " \n");
- if (!lineEnd) {
- // comment goes to end of input
- break;
- }
- input = lineEnd + 1;
- std::string file;
- if (*lineEnd == ' ') {
- // we have a file
- char* filePos = strpbrk(input, "\"\n");
- if (!filePos) {
- // goes to end of input
- break;
- }
- if (*filePos == '"') {
- char* fileEnd = strpbrk(filePos + 1, "\"\n");
- input = fileEnd + 1;
- *fileEnd = 0;
- file = filePos + 1;
- } else {
- file = UNKNOWN_FILE;
- input = filePos + 1;
- }
- } else {
- // no file, we found \n
- file = UNKNOWN_FILE;
- }
- *lineEnd = 0;
- std::string line = linePos;
- auto iter = debugInfoFileIndices.find(file);
- if (iter == debugInfoFileIndices.end()) {
- Index index = debugInfoFileNames.size();
- debugInfoFileNames.push_back(file);
- debugInfoFileIndices[file] = index;
- }
- std::string fileIndex = std::to_string(debugInfoFileIndices[file]);
- // write out the intrinsic
- strcpy(out, DEBUGINFO_INTRINSIC.c_str());
- out += DEBUGINFO_INTRINSIC_SIZE;
- *out++ = '(';
- strcpy(out, fileIndex.c_str());
- out += fileIndex.size();
- *out++ = ',';
- strcpy(out, line.c_str());
- out += line.size();
- *out++ = ')';
- *out++ = ';';
- } else if (!seenUseAsm &&
- (startsWith(input, "asm'") || startsWith(input, "asm\""))) {
- // end of "use asm" or "almost asm"
- // skip the end of "use asm"; (5 chars, a,s,m," or ',;)
- const auto SKIP = 5;
- seenUseAsm = true;
- memcpy(out, input, SKIP);
- out += SKIP;
- input += SKIP;
- // add a fake import for the intrinsic, so the module validates
- std::string import =
- "\n var emscripten_debuginfo = env.emscripten_debuginfo;";
- strcpy(out, import.c_str());
- out += import.size();
- } else {
- *out++ = *input++;
- }
- }
- if (out >= end) {
- Fatal() << "error in handling debug info";
- }
- *out = 0;
- input = copy;
- }
-
- return input;
- }
-};
-
-static Call* checkDebugInfo(Expression* curr) {
- if (auto* call = curr->dynCast<Call>()) {
- if (call->target == EMSCRIPTEN_DEBUGINFO) {
- return call;
- }
- }
- return nullptr;
-}
-
-// Debug info appears in the ast as calls to the debug intrinsic. These are
-// usually after the relevant node. We adjust them to a position that is not
-// dce-able, so that they are not trivially removed when optimizing.
-struct AdjustDebugInfo
- : public WalkerPass<PostWalker<AdjustDebugInfo, Visitor<AdjustDebugInfo>>> {
- bool isFunctionParallel() override { return true; }
-
- Pass* create() override { return new AdjustDebugInfo(); }
-
- AdjustDebugInfo() { name = "adjust-debug-info"; }
-
- void visitBlock(Block* curr) {
- // look for a debug info call that is unreachable
- if (curr->list.size() == 0) {
- return;
- }
- auto* back = curr->list.back();
- for (Index i = 1; i < curr->list.size(); i++) {
- if (checkDebugInfo(curr->list[i]) && !checkDebugInfo(curr->list[i - 1])) {
- // swap them
- std::swap(curr->list[i - 1], curr->list[i]);
- }
- }
- if (curr->list.back() != back) {
- // we changed the last element, update the type
- curr->finalize();
- }
- }
-};
-
-//
-// Asm2WasmBuilder - converts an asm.js module into WebAssembly
-//
-
-class Asm2WasmBuilder {
-public:
- Module& wasm;
-
- MixedArena& allocator;
-
- Builder builder;
-
- std::unique_ptr<OptimizingIncrementalModuleBuilder> optimizingBuilder;
-
- // globals
-
- struct MappedGlobal {
- Type type;
- // if true, this is an import - we should read the value, not just set a
- // zero
- bool import;
- IString module, base;
- MappedGlobal() : type(Type::none), import(false) {}
- MappedGlobal(Type type) : type(type), import(false) {}
- MappedGlobal(Type type, bool import, IString module, IString base)
- : type(type), import(import), module(module), base(base) {}
- };
-
- // function table
- // each asm function table gets a range in the one wasm table, starting at a
- // location
- std::map<IString, int> functionTableStarts;
-
- Asm2WasmPreProcessor& preprocessor;
- bool debug;
- TrapMode trapMode;
- TrappingFunctionContainer trappingFunctions;
- PassOptions passOptions;
- bool legalizeJavaScriptFFI;
- bool runOptimizationPasses;
- bool wasmOnly;
-
-public:
- std::map<IString, MappedGlobal> mappedGlobals;
-
-private:
- void allocateGlobal(IString name, Type type, Literal value = Literal()) {
- assert(mappedGlobals.find(name) == mappedGlobals.end());
- if (value.type == Type::none) {
- value = Literal::makeSingleZero(type);
- }
- mappedGlobals.emplace(name, MappedGlobal(type));
- wasm.addGlobal(builder.makeGlobal(
- name, type, builder.makeConst(value), Builder::Mutable));
- }
-
- struct View {
- unsigned bytes;
- bool integer, signed_;
- AsmType type;
- View() : bytes(0) {}
- View(unsigned bytes, bool integer, bool signed_, AsmType type)
- : bytes(bytes), integer(integer), signed_(signed_), type(type) {}
- };
-
- std::map<IString, View> views; // name (e.g. HEAP8) => view info
-
- // Imported names of Math.*
- IString Math_imul;
- IString Math_clz32;
- IString Math_fround;
- IString Math_abs;
- IString Math_floor;
- IString Math_ceil;
- IString Math_sqrt;
- IString Math_max;
- IString Math_min;
-
- // Imported names of Atomics.*
- IString Atomics_load;
- IString Atomics_store;
- IString Atomics_exchange;
- IString Atomics_compareExchange;
- IString Atomics_add;
- IString Atomics_sub;
- IString Atomics_and;
- IString Atomics_or;
- IString Atomics_xor;
-
- IString llvm_cttz_i32;
-
- IString tempDoublePtr; // imported name of tempDoublePtr
-
- // possibly-minified names, detected via their exports
- IString udivmoddi4;
- IString getTempRet0;
-
- // function types. we fill in this information as we see
- // uses, in the first pass
-
- std::map<IString, Signature> importedSignatures;
-
- void noteImportedFunctionCall(Ref ast, Type resultType, Call* call) {
- assert(ast[0] == CALL && ast[1]->isString());
- IString importName = ast[1]->getIString();
- std::vector<Type> params;
- for (auto* operand : call->operands) {
- params.push_back(operand->type);
- }
- Signature sig = Signature(Type(params), resultType);
- // if we already saw this signature, verify it's the same (or else handle
- // that)
- if (importedSignatures.find(importName) != importedSignatures.end()) {
- Signature& previous = importedSignatures[importName];
- if (sig != previous) {
- std::vector<Type> mergedParams = previous.params.expand();
- // merge it in. we'll add on extra 0 parameters for ones not actually
- // used, and upgrade types to double where there is a conflict (which is
- // ok since in JS, double can contain everything i32 and f32 can).
- for (size_t i = 0; i < params.size(); i++) {
- if (mergedParams.size() > i) {
- if (mergedParams[i] != params[i]) {
- mergedParams[i] = Type::f64; // overloaded type, make it a double
- }
- } else {
- mergedParams.push_back(params[i]); // add a new param
- }
- }
- previous.params = Type(mergedParams);
- // we accept none and a concrete type, but two concrete types mean we
- // need to use an f64 to contain anything
- if (previous.results == Type::none) {
- previous.results = sig.results; // use a more concrete type
- } else if (previous.results != sig.results &&
- sig.results != Type::none) {
- // overloaded return type, make it a double
- previous.results = Type::f64;
- }
- }
- } else {
- importedSignatures[importName] = sig;
- }
- }
-
- Type getResultTypeOfCallUsingParent(Ref parent, AsmData* data) {
- Type result = Type::none;
- if (!!parent) {
- // if the parent is a seq, we cannot be the last element in it (we would
- // have a coercion, which would be the parent), so we must be (us,
- // somethingElse), and so our return is void
- if (parent[0] != SEQ) {
- result = detectWasmType(parent, data);
- }
- }
- return result;
- }
-
- Signature getSignature(Ref parent, ExpressionList& operands, AsmData* data) {
- Type results = getResultTypeOfCallUsingParent(parent, data);
- std::vector<Type> paramTypes;
- for (auto& op : operands) {
- assert(op->type != Type::unreachable);
- paramTypes.push_back(op->type);
- }
- return Signature(Type(paramTypes), results);
- }
-
-public:
- Asm2WasmBuilder(Module& wasm,
- Asm2WasmPreProcessor& preprocessor,
- bool debug,
- TrapMode trapMode,
- PassOptions passOptions,
- bool legalizeJavaScriptFFI,
- bool runOptimizationPasses,
- bool wasmOnly)
- : wasm(wasm), allocator(wasm.allocator), builder(wasm),
- preprocessor(preprocessor), debug(debug), trapMode(trapMode),
- trappingFunctions(trapMode, wasm, /* immediate = */ true),
- passOptions(passOptions), legalizeJavaScriptFFI(legalizeJavaScriptFFI),
- runOptimizationPasses(runOptimizationPasses), wasmOnly(wasmOnly) {}
-
- void processAsm(Ref ast);
-
-private:
- AsmType detectAsmType(Ref ast, AsmData* data) {
- if (ast->isString()) {
- IString name = ast->getIString();
- if (!data->isLocal(name)) {
- // must be global
- assert(mappedGlobals.find(name) != mappedGlobals.end());
- return wasmToAsmType(mappedGlobals[name].type);
- }
- } else if (ast->isArray(SUB) && ast[1]->isString()) {
- // could be a heap access, use view info
- auto view = views.find(ast[1]->getIString());
- if (view != views.end()) {
- return view->second.type;
- }
- }
- return detectType(ast, data, false, Math_fround, wasmOnly);
- }
-
- Type detectWasmType(Ref ast, AsmData* data) {
- return asmToWasmType(detectAsmType(ast, data));
- }
-
- bool isUnsignedCoercion(Ref ast) {
- return detectSign(ast, Math_fround) == ASM_UNSIGNED;
- }
-
- bool isParentUnsignedCoercion(Ref parent) {
- // parent may not exist, or may be a non-relevant node
- if (!!parent && parent->isArray() && parent[0] == BINARY &&
- isUnsignedCoercion(parent)) {
- return true;
- }
- return false;
- }
-
- BinaryOp parseAsmBinaryOp(IString op,
- Ref left,
- Ref right,
- Expression* leftWasm,
- Expression* rightWasm) {
- Type leftType = leftWasm->type;
- bool isInteger = leftType == Type::i32;
-
- if (op == PLUS) {
- return isInteger ? BinaryOp::AddInt32
- : (leftType == Type::f32 ? BinaryOp::AddFloat32
- : BinaryOp::AddFloat64);
- }
- if (op == MINUS) {
- return isInteger ? BinaryOp::SubInt32
- : (leftType == Type::f32 ? BinaryOp::SubFloat32
- : BinaryOp::SubFloat64);
- }
- if (op == MUL) {
- return isInteger ? BinaryOp::MulInt32
- : (leftType == Type::f32 ? BinaryOp::MulFloat32
- : BinaryOp::MulFloat64);
- }
- if (op == AND) {
- return BinaryOp::AndInt32;
- }
- if (op == OR) {
- return BinaryOp::OrInt32;
- }
- if (op == XOR) {
- return BinaryOp::XorInt32;
- }
- if (op == LSHIFT) {
- return BinaryOp::ShlInt32;
- }
- if (op == RSHIFT) {
- return BinaryOp::ShrSInt32;
- }
- if (op == TRSHIFT) {
- return BinaryOp::ShrUInt32;
- }
- if (op == EQ) {
- return isInteger ? BinaryOp::EqInt32
- : (leftType == Type::f32 ? BinaryOp::EqFloat32
- : BinaryOp::EqFloat64);
- }
- if (op == NE) {
- return isInteger ? BinaryOp::NeInt32
- : (leftType == Type::f32 ? BinaryOp::NeFloat32
- : BinaryOp::NeFloat64);
- }
-
- bool isUnsigned = isUnsignedCoercion(left) || isUnsignedCoercion(right);
-
- if (op == DIV) {
- if (isInteger) {
- return isUnsigned ? BinaryOp::DivUInt32 : BinaryOp::DivSInt32;
- }
- return leftType == Type::f32 ? BinaryOp::DivFloat32
- : BinaryOp::DivFloat64;
- }
- if (op == MOD) {
- if (isInteger) {
- return isUnsigned ? BinaryOp::RemUInt32 : BinaryOp::RemSInt32;
- }
- return BinaryOp::RemSInt32; // XXX no floating-point remainder op, this
- // must be handled by the caller
- }
- if (op == GE) {
- if (isInteger) {
- return isUnsigned ? BinaryOp::GeUInt32 : BinaryOp::GeSInt32;
- }
- return leftType == Type::f32 ? BinaryOp::GeFloat32 : BinaryOp::GeFloat64;
- }
- if (op == GT) {
- if (isInteger) {
- return isUnsigned ? BinaryOp::GtUInt32 : BinaryOp::GtSInt32;
- }
- return leftType == Type::f32 ? BinaryOp::GtFloat32 : BinaryOp::GtFloat64;
- }
- if (op == LE) {
- if (isInteger) {
- return isUnsigned ? BinaryOp::LeUInt32 : BinaryOp::LeSInt32;
- }
- return leftType == Type::f32 ? BinaryOp::LeFloat32 : BinaryOp::LeFloat64;
- }
- if (op == LT) {
- if (isInteger) {
- return isUnsigned ? BinaryOp::LtUInt32 : BinaryOp::LtSInt32;
- }
- return leftType == Type::f32 ? BinaryOp::LtFloat32 : BinaryOp::LtFloat64;
- }
- abort_on("bad wasm binary op", op);
- abort(); // avoid warning
- }
-
- int32_t bytesToShift(unsigned bytes) {
- switch (bytes) {
- case 1:
- return 0;
- case 2:
- return 1;
- case 4:
- return 2;
- case 8:
- return 3;
- default: {
- }
- }
- abort();
- return -1; // avoid warning
- }
-
- std::map<unsigned, Ref> tempNums;
-
- Literal checkLiteral(Ref ast, bool rawIsInteger = true) {
- if (ast->isNumber()) {
- if (rawIsInteger) {
- return Literal((int32_t)ast->getInteger());
- } else {
- return Literal(ast->getNumber());
- }
- } else if (ast->isArray(UNARY_PREFIX)) {
- if (ast[1] == PLUS && ast[2]->isNumber()) {
- return Literal((double)ast[2]->getNumber());
- }
- if (ast[1] == MINUS && ast[2]->isNumber()) {
- double num = -ast[2]->getNumber();
- if (isSInteger32(num)) {
- return Literal((int32_t)num);
- }
- if (isUInteger32(num)) {
- return Literal((uint32_t)num);
- }
- assert(false && "expected signed or unsigned int32");
- }
- if (ast[1] == PLUS && ast[2]->isArray(UNARY_PREFIX) &&
- ast[2][1] == MINUS && ast[2][2]->isNumber()) {
- return Literal((double)-ast[2][2]->getNumber());
- }
- if (ast[1] == MINUS && ast[2]->isArray(UNARY_PREFIX) &&
- ast[2][1] == PLUS && ast[2][2]->isNumber()) {
- return Literal((double)-ast[2][2]->getNumber());
- }
- } else if (wasmOnly && ast->isArray(CALL) && ast[1]->isString() &&
- ast[1] == I64_CONST) {
- uint64_t low = ast[2][0]->getNumber();
- uint64_t high = ast[2][1]->getNumber();
- return Literal(uint64_t(low + (high << 32)));
- }
- return Literal();
- }
-
- Literal getLiteral(Ref ast) {
- Literal ret = checkLiteral(ast);
- assert(ret.type != Type::none);
- return ret;
- }
-
- void fixCallType(Expression* call, Type type) {
- if (call->is<Call>()) {
- call->cast<Call>()->type = type;
- } else if (call->is<CallIndirect>()) {
- call->cast<CallIndirect>()->type = type;
- }
- }
-
- bool getBuiltinSignature(Signature& sig,
- Name module,
- Name base,
- ExpressionList* operands = nullptr) {
- if (module == GLOBAL_MATH) {
- if (base == ABS) {
- assert(operands && operands->size() == 1);
- Type type = (*operands)[0]->type;
- if (type == Type::i32) {
- sig = Signature(Type::i32, Type::i32);
- return true;
- }
- if (type == Type::f32) {
- sig = Signature(Type::f32, Type::f32);
- return true;
- }
- if (type == Type::f64) {
- sig = Signature(Type::f64, Type::f64);
- return true;
- }
- }
- }
- return false;
- }
-
- // ensure a nameless block
- Block* blockify(Expression* expression) {
- if (expression->is<Block>() && !expression->cast<Block>()->name.is()) {
- return expression->dynCast<Block>();
- }
- auto ret = allocator.alloc<Block>();
- ret->list.push_back(expression);
- ret->finalize();
- return ret;
- }
-
- Expression* ensureDouble(Expression* expr) {
- return wasm::ensureDouble(expr, allocator);
- }
-
- Expression* truncateToInt32(Expression* value) {
- if (value->type == Type::i64) {
- return builder.makeUnary(UnaryOp::WrapInt64, value);
- }
- // either i32, or a call_import whose type we don't know yet (but would be
- // legalized to i32 anyhow)
- return value;
- }
-
- Function* processFunction(Ref ast);
-};
-
-void Asm2WasmBuilder::processAsm(Ref ast) {
- assert(ast[0] == TOPLEVEL);
- if (ast[1]->size() == 0) {
- Fatal() << "empty input";
- }
- Ref asmFunction = ast[1][0];
- assert(asmFunction[0] == DEFUN);
- Ref body = asmFunction[3];
- assert(body[0][0] == STRING &&
- (body[0][1]->getIString() == IString("use asm") ||
- body[0][1]->getIString() == IString("almost asm")));
-
- // extra functions that we add, that are not from the compiled code. we need
- // to make sure to optimize them normally (OptimizingIncrementalModuleBuilder
- // does that on the fly for compiled code)
- std::vector<Function*> extraSupportFunctions;
-
- // first, add the memory elements. we do this before the main compile+optimize
- // since the optimizer should see the memory
-
- // apply memory growth, if relevant
- if (preprocessor.memoryGrowth) {
- EmscriptenGlueGenerator generator(wasm);
- auto* func = generator.generateMemoryGrowthFunction();
- extraSupportFunctions.push_back(func);
- wasm.memory.max = Memory::kUnlimitedSize;
- }
-
- // import memory
- wasm.memory.name = MEMORY;
- wasm.memory.module = ENV;
- wasm.memory.base = MEMORY;
- wasm.memory.exists = true;
-
- // import table
- wasm.table.name = TABLE;
- wasm.table.module = ENV;
- wasm.table.base = TABLE;
- wasm.table.exists = true;
-
- // Import memory offset, if not already there
- {
- auto* import = new Global;
- import->name = MEMORY_BASE;
- import->module = "env";
- import->base = MEMORY_BASE;
- import->type = Type::i32;
- wasm.addGlobal(import);
- }
-
- // Import table offset, if not already there
- {
- auto* import = new Global;
- import->name = TABLE_BASE;
- import->module = "env";
- import->base = TABLE_BASE;
- import->type = Type::i32;
- wasm.addGlobal(import);
- }
-
- auto addImport = [&](IString name, Ref imported, Type type) {
- assert(imported[0] == DOT);
- Ref module = imported[1];
- IString moduleName;
- if (module->isArray(DOT)) {
- // we can have (global.Math).floor; skip the 'Math'
- assert(module[1]->isString());
- if (module[2] == MATH) {
- if (imported[2] == IMUL) {
- assert(Math_imul.isNull());
- Math_imul = name;
- return;
- } else if (imported[2] == CLZ32) {
- assert(Math_clz32.isNull());
- Math_clz32 = name;
- return;
- } else if (imported[2] == FROUND) {
- assert(Math_fround.isNull());
- Math_fround = name;
- return;
- } else if (imported[2] == ABS) {
- assert(Math_abs.isNull());
- Math_abs = name;
- return;
- } else if (imported[2] == FLOOR) {
- assert(Math_floor.isNull());
- Math_floor = name;
- return;
- } else if (imported[2] == CEIL) {
- assert(Math_ceil.isNull());
- Math_ceil = name;
- return;
- } else if (imported[2] == SQRT) {
- assert(Math_sqrt.isNull());
- Math_sqrt = name;
- return;
- } else if (imported[2] == MAX_) {
- assert(Math_max.isNull());
- Math_max = name;
- return;
- } else if (imported[2] == MIN_) {
- assert(Math_min.isNull());
- Math_min = name;
- return;
- }
- } else if (module[2] == ATOMICS) {
- if (imported[2] == ATOMICS_LOAD) {
- assert(Atomics_load.isNull());
- Atomics_load = name;
- return;
- } else if (imported[2] == ATOMICS_STORE) {
- assert(Atomics_store.isNull());
- Atomics_store = name;
- return;
- } else if (imported[2] == ATOMICS_EXCHANGE) {
- assert(Atomics_exchange.isNull());
- Atomics_exchange = name;
- return;
- } else if (imported[2] == ATOMICS_COMPARE_EXCHANGE) {
- assert(Atomics_compareExchange.isNull());
- Atomics_compareExchange = name;
- return;
- } else if (imported[2] == ATOMICS_ADD) {
- assert(Atomics_add.isNull());
- Atomics_add = name;
- return;
- } else if (imported[2] == ATOMICS_SUB) {
- assert(Atomics_sub.isNull());
- Atomics_sub = name;
- return;
- } else if (imported[2] == ATOMICS_AND) {
- assert(Atomics_and.isNull());
- Atomics_and = name;
- return;
- } else if (imported[2] == ATOMICS_OR) {
- assert(Atomics_or.isNull());
- Atomics_or = name;
- return;
- } else if (imported[2] == ATOMICS_XOR) {
- assert(Atomics_xor.isNull());
- Atomics_xor = name;
- return;
- }
- }
- std::string fullName = module[1]->getCString();
- fullName += '.';
- fullName += +module[2]->getCString();
- moduleName = IString(fullName.c_str(), false);
- } else {
- assert(module->isString());
- moduleName = module->getIString();
- if (moduleName == ENV) {
- auto base = imported[2]->getIString();
- if (base == TEMP_DOUBLE_PTR) {
- assert(tempDoublePtr.isNull());
- tempDoublePtr = name;
- // we don't return here, as we can only optimize out some uses of tDP.
- // So it remains imported
- } else if (base == LLVM_CTTZ_I32) {
- assert(llvm_cttz_i32.isNull());
- llvm_cttz_i32 = name;
- return;
- }
- }
- }
- auto base = imported[2]->getIString();
- // special-case some asm builtins
- if (module == GLOBAL && (base == NAN_ || base == INFINITY_)) {
- type = Type::f64;
- }
- if (type != Type::none) {
- // this is a global
- auto* import = new Global;
- import->name = name;
- import->module = moduleName;
- import->base = base;
- import->type = type;
- mappedGlobals.emplace(name, type);
- // __table_base and __memory_base are used as segment/element offsets, and
- // must be constant; otherwise, an asm.js import of a constant is mutable,
- // e.g. STACKTOP
- if (name != TABLE_BASE && name != MEMORY_BASE) {
- // we need imported globals to be mutable, but wasm doesn't support that
- // yet, so we must import an immutable and create a mutable global
- // initialized to its value
- import->name = Name(std::string(import->name.str) + "$asm2wasm$import");
- {
- wasm.addGlobal(
- builder.makeGlobal(name,
- type,
- builder.makeGlobalGet(import->name, type),
- Builder::Mutable));
- }
- }
- if ((name == TABLE_BASE || name == MEMORY_BASE) &&
- wasm.getGlobalOrNull(import->base)) {
- return;
- }
- wasm.addGlobal(import);
- } else {
- // this is a function
- auto* import = new Function;
- import->name = name;
- import->module = moduleName;
- import->base = base;
- import->sig = Signature(Type::none, Type::none);
- wasm.addFunction(import);
- }
- };
-
- IString Int8Array, Int16Array, Int32Array, UInt8Array, UInt16Array,
- UInt32Array, Float32Array, Float64Array;
-
- // set up optimization
-
- if (runOptimizationPasses) {
- Index numFunctions = 0;
- for (unsigned i = 1; i < body->size(); i++) {
- if (body[i][0] == DEFUN) {
- numFunctions++;
- }
- }
- optimizingBuilder = make_unique<OptimizingIncrementalModuleBuilder>(
- &wasm,
- numFunctions,
- passOptions,
- [&](PassRunner& passRunner) {
- // addPrePasses
- passRunner.options.lowMemoryUnused = true;
- if (debug) {
- passRunner.setDebug(true);
- passRunner.setValidateGlobally(false);
- }
- // run autodrop first, before optimizations
- passRunner.add(make_unique<AutoDrop>());
- if (preprocessor.debugInfo) {
- // fix up debug info to better survive optimization
- passRunner.add(make_unique<AdjustDebugInfo>());
- }
- // optimize relooper label variable usage at the wasm level, where it is
- // easy
- passRunner.add("relooper-jump-threading");
- },
- debug,
- false /* do not validate globally yet */);
- }
-
- // if we see no function tables in the processing below, then the table still
- // exists and has size 0
-
- wasm.table.initial = wasm.table.max = 0;
-
- // first pass - do all global things, aside from function bodies (second pass)
- // and function imports and indirect calls (last pass)
-
- for (unsigned i = 1; i < body->size(); i++) {
- Ref curr = body[i];
- if (curr[0] == VAR) {
- // import, global, or table
- for (unsigned j = 0; j < curr[1]->size(); j++) {
- Ref pair = curr[1][j];
- IString name = pair[0]->getIString();
- Ref value = pair[1];
- if (value->isNumber()) {
- // global int
- allocateGlobal(
- name, Type::i32, Literal(int32_t(value->getInteger())));
- } else if (value[0] == BINARY) {
- // int import
- assert(value[1] == OR && value[3]->isNumber() &&
- value[3]->getNumber() == 0);
- Ref import = value[2]; // env.what
- addImport(name, import, Type::i32);
- } else if (value[0] == UNARY_PREFIX) {
- // double import or global
- assert(value[1] == PLUS);
- Ref import = value[2];
- if (import->isNumber()) {
- // global
- assert(import->getNumber() == 0);
- allocateGlobal(name, Type::f64);
- } else {
- // import
- addImport(name, import, Type::f64);
- }
- } else if (value[0] == CALL) {
- assert(value[1]->isString() && value[1] == Math_fround &&
- value[2][0]->isNumber() && value[2][0]->getNumber() == 0);
- allocateGlobal(name, Type::f32);
- } else if (value[0] == DOT) {
- // simple module.base import. can be a view, or a function.
- if (value[1]->isString()) {
- IString module = value[1]->getIString();
- IString base = value[2]->getIString();
- if (module == GLOBAL) {
- if (base == INT8ARRAY) {
- Int8Array = name;
- } else if (base == INT16ARRAY) {
- Int16Array = name;
- } else if (base == INT32ARRAY) {
- Int32Array = name;
- } else if (base == UINT8ARRAY) {
- UInt8Array = name;
- } else if (base == UINT16ARRAY) {
- UInt16Array = name;
- } else if (base == UINT32ARRAY) {
- UInt32Array = name;
- } else if (base == FLOAT32ARRAY) {
- Float32Array = name;
- } else if (base == FLOAT64ARRAY) {
- Float64Array = name;
- }
- }
- }
- // function import
- addImport(name, value, Type::none);
- } else if (value[0] == NEW) {
- // ignore imports of typed arrays, but note the names of the arrays
- value = value[1];
- assert(value[0] == CALL);
- unsigned bytes;
- bool integer, signed_;
- AsmType asmType;
- Ref constructor = value[1];
- if (constructor->isArray(DOT)) { // global.*Array
- IString heap = constructor[2]->getIString();
- if (heap == INT8ARRAY) {
- bytes = 1;
- integer = true;
- signed_ = true;
- asmType = ASM_INT;
- } else if (heap == INT16ARRAY) {
- bytes = 2;
- integer = true;
- signed_ = true;
- asmType = ASM_INT;
- } else if (heap == INT32ARRAY) {
- bytes = 4;
- integer = true;
- signed_ = true;
- asmType = ASM_INT;
- } else if (heap == UINT8ARRAY) {
- bytes = 1;
- integer = true;
- signed_ = false;
- asmType = ASM_INT;
- } else if (heap == UINT16ARRAY) {
- bytes = 2;
- integer = true;
- signed_ = false;
- asmType = ASM_INT;
- } else if (heap == UINT32ARRAY) {
- bytes = 4;
- integer = true;
- signed_ = false;
- asmType = ASM_INT;
- } else if (heap == FLOAT32ARRAY) {
- bytes = 4;
- integer = false;
- signed_ = true;
- asmType = ASM_FLOAT;
- } else if (heap == FLOAT64ARRAY) {
- bytes = 8;
- integer = false;
- signed_ = true;
- asmType = ASM_DOUBLE;
- } else {
- abort_on("invalid view import", heap);
- }
- } else { // *ArrayView that was previously imported
- assert(constructor->isString());
- IString viewName = constructor->getIString();
- if (viewName == Int8Array) {
- bytes = 1;
- integer = true;
- signed_ = true;
- asmType = ASM_INT;
- } else if (viewName == Int16Array) {
- bytes = 2;
- integer = true;
- signed_ = true;
- asmType = ASM_INT;
- } else if (viewName == Int32Array) {
- bytes = 4;
- integer = true;
- signed_ = true;
- asmType = ASM_INT;
- } else if (viewName == UInt8Array) {
- bytes = 1;
- integer = true;
- signed_ = false;
- asmType = ASM_INT;
- } else if (viewName == UInt16Array) {
- bytes = 2;
- integer = true;
- signed_ = false;
- asmType = ASM_INT;
- } else if (viewName == UInt32Array) {
- bytes = 4;
- integer = true;
- signed_ = false;
- asmType = ASM_INT;
- } else if (viewName == Float32Array) {
- bytes = 4;
- integer = false;
- signed_ = true;
- asmType = ASM_FLOAT;
- } else if (viewName == Float64Array) {
- bytes = 8;
- integer = false;
- signed_ = true;
- asmType = ASM_DOUBLE;
- } else {
- abort_on("invalid short view import", viewName);
- }
- }
- assert(views.find(name) == views.end());
- views.emplace(name, View(bytes, integer, signed_, asmType));
- } else if (value[0] == ARRAY) {
- // function table. we merge them into one big table, so e.g. [foo,
- // b1] , [b2, bar] => [foo, b1, b2, bar]
- // TODO: when not using aliasing function pointers, we could merge
- // them by noticing that
- // index 0 in each table is the null func, and each other index
- // should only have one non-null func. However, that breaks down
- // when function pointer casts are emulated.
- if (wasm.table.segments.size() == 0) {
- wasm.table.segments.emplace_back(
- builder.makeGlobalGet(Name(TABLE_BASE), Type::i32));
- }
- auto& segment = wasm.table.segments[0];
- functionTableStarts[name] =
- segment.data.size(); // this table starts here
- Ref contents = value[1];
- for (unsigned k = 0; k < contents->size(); k++) {
- IString curr = contents[k]->getIString();
- segment.data.push_back(curr);
- }
- wasm.table.initial = wasm.table.max = segment.data.size();
- } else {
- abort_on("invalid var element", pair);
- }
- }
- } else if (curr[0] == RETURN) {
- // exports
- Ref object = curr[1];
- Ref contents = object[1];
- std::map<Name, Export*> exported;
- for (unsigned k = 0; k < contents->size(); k++) {
- Ref pair = contents[k];
- IString key = pair[0]->getIString();
- if (pair[1]->isString()) {
- // exporting a function
- IString value = pair[1]->getIString();
- if (key == Name("_emscripten_replace_memory")) {
- // asm.js memory growth provides this special non-asm function,
- // which we don't need (we use memory.grow)
- assert(!wasm.getFunctionOrNull(value));
- continue;
- } else if (key == UDIVMODDI4) {
- udivmoddi4 = value;
- } else if (key == GET_TEMP_RET0) {
- getTempRet0 = value;
- }
- if (exported.count(key) > 0) {
- // asm.js allows duplicate exports, but not wasm. use the last, like
- // asm.js
- exported[key]->value = value;
- } else {
- auto* export_ = new Export;
- export_->name = key;
- export_->value = value;
- export_->kind = ExternalKind::Function;
- wasm.addExport(export_);
- exported[key] = export_;
- }
- } else {
- // export a number. create a global and export it
- assert(pair[1]->isNumber());
- assert(exported.count(key) == 0);
- auto value = pair[1]->getInteger();
- auto* global =
- builder.makeGlobal(key,
- Type::i32,
- builder.makeConst(Literal(int32_t(value))),
- Builder::Immutable);
- wasm.addGlobal(global);
- auto* export_ = new Export;
- export_->name = key;
- export_->value = global->name;
- export_->kind = ExternalKind::Global;
- wasm.addExport(export_);
- exported[key] = export_;
- }
- }
- }
- }
-
- // second pass: function bodies
- for (unsigned i = 1; i < body->size(); i++) {
- Ref curr = body[i];
- if (curr[0] == DEFUN) {
- // function
- auto* func = processFunction(curr);
- if (wasm.getFunctionOrNull(func->name)) {
- Fatal() << "duplicate function: " << func->name;
- }
- if (runOptimizationPasses) {
- optimizingBuilder->addFunction(func);
- } else {
- wasm.addFunction(func);
- }
- }
- }
-
- if (runOptimizationPasses) {
- optimizingBuilder->finish();
- // Now that we have a full module, do memory packing optimizations
- {
- PassRunner passRunner(&wasm, passOptions);
- passRunner.options.lowMemoryUnused = true;
- passRunner.add("memory-packing");
- passRunner.run();
- }
- // if we added any helper functions (like non-trapping i32-div, etc.), then
- // those have not been optimized (the optimizing builder has just been fed
- // the asm.js functions). Optimize those now. Typically there are very few,
- // just do it sequentially.
- PassRunner passRunner(&wasm, passOptions);
- passRunner.options.lowMemoryUnused = true;
- passRunner.addDefaultFunctionOptimizationPasses();
- for (auto& pair : trappingFunctions.getFunctions()) {
- auto* func = pair.second;
- passRunner.runOnFunction(func);
- }
- for (auto* func : extraSupportFunctions) {
- passRunner.runOnFunction(func);
- }
- }
- wasm.debugInfoFileNames = std::move(preprocessor.debugInfoFileNames);
-
- // third pass. first, function imports
-
- std::vector<IString> toErase;
-
- ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
- IString name = import->name;
- if (importedSignatures.find(name) != importedSignatures.end()) {
- // special math builtins
- Signature builtin;
- if (getBuiltinSignature(builtin, import->module, import->base)) {
- import->sig = builtin;
- } else {
- import->sig = importedSignatures[name];
- }
- } else if (import->module != ASM2WASM) { // special-case the special module
- // never actually used, which means we don't know the function type since
- // the usage tells us, so illegal for it to remain
- toErase.push_back(name);
- }
- });
-
- for (auto curr : toErase) {
- wasm.removeFunction(curr);
- }
-
- // Finalize calls now that everything is known and generated
-
- struct FinalizeCalls : public WalkerPass<PostWalker<FinalizeCalls>> {
- bool isFunctionParallel() override { return true; }
-
- Pass* create() override { return new FinalizeCalls(parent); }
-
- Asm2WasmBuilder* parent;
-
- FinalizeCalls(Asm2WasmBuilder* parent) : parent(parent) {
- name = "finalize-calls";
- }
-
- void notifyAboutWrongOperands(std::string why, Function* calledFunc) {
- // use a mutex as this may be shown from multiple threads
- static std::mutex mutex;
- std::unique_lock<std::mutex> lock(mutex);
- static const int MAX_SHOWN = 20;
- static std::unique_ptr<std::atomic<int>> numShown;
- if (!numShown) {
- numShown = make_unique<std::atomic<int>>();
- numShown->store(0);
- }
- if (numShown->load() >= MAX_SHOWN) {
- return;
- }
- std::cerr << why << " in call from " << getFunction()->name << " to "
- << calledFunc->name
- << " (this is likely due to undefined behavior in C, like "
- "defining a function one way and calling it in another, "
- "which is important to fix)\n";
- (*numShown)++;
- if (numShown->load() >= MAX_SHOWN) {
- std::cerr << "(" << numShown->load()
- << " such warnings shown; not showing any more)\n";
- }
- }
-
- void visitCall(Call* curr) {
- // The call target may not exist if it is one of our special fake imports
- // for callIndirect fixups
- auto* calledFunc = getModule()->getFunctionOrNull(curr->target);
- if (calledFunc && !calledFunc->imported()) {
- // The result type of the function being called is now known, and can be
- // applied.
- auto results = calledFunc->sig.results;
- if (curr->type != results) {
- curr->type = results;
- }
- // Handle mismatched numbers of arguments. In clang, if a function is
- // declared one way but called in another, it inserts bitcasts to make
- // things work. Those end up working since it is "ok" to drop or add
- // parameters in native platforms, even though it's undefined behavior.
- // We warn about it here, but tolerate it, if there is a simple
- // solution.
- const std::vector<Type>& params = calledFunc->sig.params.expand();
- if (curr->operands.size() < params.size()) {
- notifyAboutWrongOperands("warning: asm2wasm adding operands",
- calledFunc);
- while (curr->operands.size() < params.size()) {
- // Add params as necessary, with zeros.
- curr->operands.push_back(LiteralUtils::makeZero(
- params[curr->operands.size()], *getModule()));
- }
- }
- if (curr->operands.size() > params.size()) {
- notifyAboutWrongOperands("warning: asm2wasm dropping operands",
- calledFunc);
- curr->operands.resize(params.size());
- }
- // If the types are wrong, validation will fail later anyhow, but add a
- // warning here, it may help people.
- for (Index i = 0; i < curr->operands.size(); i++) {
- auto sent = curr->operands[i]->type;
- if (sent != Type::unreachable && sent != params[i]) {
- notifyAboutWrongOperands(
- "error: asm2wasm seeing an invalid argument type at index " +
- std::to_string(i) + " (this will not validate)",
- calledFunc);
- }
- }
- } else {
- // A call to an import
- // fill things out: add extra params as needed, etc. asm tolerates ffi
- // overloading, wasm does not
- auto iter = parent->importedSignatures.find(curr->target);
- if (iter == parent->importedSignatures.end()) {
- return; // one of our fake imports for callIndirect fixups
- }
- const std::vector<Type>& params = iter->second.params.expand();
- for (size_t i = 0; i < params.size(); i++) {
- if (i >= curr->operands.size()) {
- // add a new param
- auto val = parent->allocator.alloc<Const>();
- val->type = val->value.type = params[i];
- curr->operands.push_back(val);
- } else if (curr->operands[i]->type != params[i]) {
- // if the param is used, then we have overloading here and the
- // combined type must be f64; if this is an unreachable param, then
- // it doesn't matter.
- assert(params[i] == Type::f64 ||
- curr->operands[i]->type == Type::unreachable);
- // overloaded, upgrade to f64
- switch (curr->operands[i]->type.getBasic()) {
- case Type::i32:
- curr->operands[i] = parent->builder.makeUnary(
- ConvertSInt32ToFloat64, curr->operands[i]);
- break;
- case Type::f32:
- curr->operands[i] =
- parent->builder.makeUnary(PromoteFloat32, curr->operands[i]);
- break;
- default: {
- // f64, unreachable, etc., are all good
- }
- }
- }
- }
- Module* wasm = getModule();
- Type importResults = wasm->getFunction(curr->target)->sig.results;
- if (curr->type != importResults) {
- auto old = curr->type;
- curr->type = importResults;
- if (importResults == Type::f64) {
- // we use a JS f64 value which is the most general, and convert to
- // it
- switch (old.getBasic()) {
- case Type::i32: {
- Unary* trunc =
- parent->builder.makeUnary(TruncSFloat64ToInt32, curr);
- replaceCurrent(
- makeTrappingUnary(trunc, parent->trappingFunctions));
- break;
- }
- case Type::f32: {
- replaceCurrent(parent->builder.makeUnary(DemoteFloat64, curr));
- break;
- }
- case Type::none: {
- // this function returns a value, but we are not using it, so it
- // must be dropped. autodrop will do that for us.
- break;
- }
- default:
- WASM_UNREACHABLE("unexpected type");
- }
- } else {
- assert(old == Type::none);
- // we don't want a return value here, but the import does provide
- // one autodrop will do that for us.
- }
- }
- }
- }
-
- void visitCallIndirect(CallIndirect* curr) {
- // we already call into target = something + offset, where offset is a
- // callImport with the name of the table. replace that with the table
- // offset note that for an ftCall or mftCall, we have no asm.js mask, so
- // have nothing to do here
- auto* target = curr->target;
- // might be a block with a fallthrough
- if (auto* block = target->dynCast<Block>()) {
- target = block->list.back();
- }
- // the something might have been optimized out, leaving only the call
- if (auto* call = target->dynCast<Call>()) {
- auto tableName = call->target;
- if (parent->functionTableStarts.find(tableName) ==
- parent->functionTableStarts.end()) {
- return;
- }
- curr->target = parent->builder.makeConst(
- Literal((int32_t)parent->functionTableStarts[tableName]));
- return;
- }
- auto* add = target->dynCast<Binary>();
- if (!add) {
- return;
- }
- if (add->right->is<Call>()) {
- auto* offset = add->right->cast<Call>();
- auto tableName = offset->target;
- if (parent->functionTableStarts.find(tableName) ==
- parent->functionTableStarts.end()) {
- return;
- }
- add->right = parent->builder.makeConst(
- Literal((int32_t)parent->functionTableStarts[tableName]));
- } else {
- auto* offset = add->left->dynCast<Call>();
- if (!offset) {
- return;
- }
- auto tableName = offset->target;
- if (parent->functionTableStarts.find(tableName) ==
- parent->functionTableStarts.end()) {
- return;
- }
- add->left = parent->builder.makeConst(
- Literal((int32_t)parent->functionTableStarts[tableName]));
- }
- }
-
- void visitFunction(Function* curr) {
- // changing call types requires we percolate types, and drop stuff.
- // we do this in this pass so that we don't look broken between passes
- AutoDrop().walkFunctionInModule(curr, getModule());
- }
- };
-
- // apply debug info, reducing intrinsic calls into annotations on the ast
- // nodes
- struct ApplyDebugInfo
- : public WalkerPass<
- ExpressionStackWalker<ApplyDebugInfo,
- UnifiedExpressionVisitor<ApplyDebugInfo>>> {
- bool isFunctionParallel() override { return true; }
-
- Pass* create() override { return new ApplyDebugInfo(); }
-
- ApplyDebugInfo() { name = "apply-debug-info"; }
-
- Call* lastDebugInfo = nullptr;
-
- void visitExpression(Expression* curr) {
- if (auto* call = checkDebugInfo(curr)) {
- lastDebugInfo = call;
- replaceCurrent(getModule()->allocator.alloc<Nop>());
- } else {
- if (lastDebugInfo) {
- auto& debugLocations = getFunction()->debugLocations;
- uint32_t fileIndex =
- lastDebugInfo->operands[0]->cast<Const>()->value.geti32();
- assert(getModule()->debugInfoFileNames.size() > fileIndex);
- uint32_t lineNumber =
- lastDebugInfo->operands[1]->cast<Const>()->value.geti32();
- // look up the stack, apply to the root expression
- Index i = expressionStack.size() - 1;
- while (1) {
- auto* exp = expressionStack[i];
- bool parentIsStructure =
- i > 0 && (expressionStack[i - 1]->is<Block>() ||
- expressionStack[i - 1]->is<Loop>() ||
- expressionStack[i - 1]->is<If>());
- if (i == 0 || parentIsStructure || exp->type == Type::none ||
- exp->type == Type::unreachable) {
- if (debugLocations.count(exp) > 0) {
- // already present, so look back up
- i++;
- while (i < expressionStack.size()) {
- exp = expressionStack[i];
- if (debugLocations.count(exp) == 0) {
- debugLocations[exp] = {fileIndex, lineNumber, 0};
- break;
- }
- i++;
- }
- } else {
- debugLocations[exp] = {fileIndex, lineNumber, 0};
- }
- break;
- }
- i--;
- }
- lastDebugInfo = nullptr;
- }
- }
- }
- };
-
- PassRunner passRunner(&wasm, passOptions);
- passRunner.options.lowMemoryUnused = true;
- if (debug) {
- passRunner.setDebug(true);
- passRunner.setValidateGlobally(false);
- }
- // finalizeCalls also does autoDrop, which is crucial for the non-optimizing
- // case, so that the output of the first pass is valid
- passRunner.add(make_unique<FinalizeCalls>(this));
- passRunner.add(ABI::getLegalizationPass(legalizeJavaScriptFFI
- ? ABI::LegalizationLevel::Full
- : ABI::LegalizationLevel::Minimal));
- if (runOptimizationPasses) {
- // autodrop can add some garbage
- passRunner.add("vacuum");
- passRunner.add("remove-unused-brs");
- passRunner.add("vacuum");
- passRunner.add("remove-unused-names");
- passRunner.add("merge-blocks");
- passRunner.add("optimize-instructions");
- passRunner.add("post-emscripten");
- } else {
- if (preprocessor.debugInfo) {
- // we would have run this before if optimizing, do it now otherwise. must
- // precede ApplyDebugInfo
- passRunner.add(make_unique<AdjustDebugInfo>());
- }
- }
- if (preprocessor.debugInfo) {
- passRunner.add(make_unique<ApplyDebugInfo>());
- // FIXME maybe just remove the nops that were debuginfo nodes, if not
- // optimizing?
- passRunner.add("vacuum");
- }
- if (runOptimizationPasses) {
- // do final global optimizations after all function work is done
- // (e.g. duplicate funcs may appear thanks to that work)
- passRunner.addDefaultGlobalOptimizationPostPasses();
- }
- passRunner.run();
-
- // remove the debug info intrinsic
- if (preprocessor.debugInfo) {
- wasm.removeFunction(EMSCRIPTEN_DEBUGINFO);
- }
-
- if (udivmoddi4.is() && getTempRet0.is()) {
- // generate a wasm-optimized __udivmoddi4 method, which we can do much more
- // efficiently in wasm we can only do this if we know getTempRet0 as well
- // since we use it to figure out which minified global is tempRet0
- // (getTempRet0 might be an import, if this is a shared module, so we can't
- // optimize that case)
- Name tempRet0;
- {
- Expression* curr = wasm.getFunction(getTempRet0)->body;
- if (curr->is<Block>()) {
- curr = curr->cast<Block>()->list.back();
- }
- if (curr->is<Return>()) {
- curr = curr->cast<Return>()->value;
- }
- auto* get = curr->cast<GlobalGet>();
- tempRet0 = get->name;
- }
- // udivmoddi4 receives xl, xh, yl, yl, r, and
- // if r then *r = x % y
- // returns x / y
- auto* func = wasm.getFunction(udivmoddi4);
- Builder::clearLocals(func);
- Index xl = Builder::addParam(func, "xl", Type::i32),
- xh = Builder::addParam(func, "xh", Type::i32),
- yl = Builder::addParam(func, "yl", Type::i32),
- yh = Builder::addParam(func, "yh", Type::i32),
- r = Builder::addParam(func, "r", Type::i32),
- x64 = Builder::addVar(func, "x64", Type::i64),
- y64 = Builder::addVar(func, "y64", Type::i64);
- auto* body = allocator.alloc<Block>();
- body->list.push_back(
- builder.makeLocalSet(x64, I64Utilities::recreateI64(builder, xl, xh)));
- body->list.push_back(
- builder.makeLocalSet(y64, I64Utilities::recreateI64(builder, yl, yh)));
- body->list.push_back(
- builder.makeIf(builder.makeLocalGet(r, Type::i32),
- builder.makeStore(
- 8,
- 0,
- 8,
- builder.makeLocalGet(r, Type::i32),
- builder.makeBinary(RemUInt64,
- builder.makeLocalGet(x64, Type::i64),
- builder.makeLocalGet(y64, Type::i64)),
- Type::i64)));
- body->list.push_back(builder.makeLocalSet(
- x64,
- builder.makeBinary(DivUInt64,
- builder.makeLocalGet(x64, Type::i64),
- builder.makeLocalGet(y64, Type::i64))));
- body->list.push_back(
- builder.makeGlobalSet(tempRet0, I64Utilities::getI64High(builder, x64)));
- body->list.push_back(I64Utilities::getI64Low(builder, x64));
- body->finalize();
- func->body = body;
- }
-}
-
-Function* Asm2WasmBuilder::processFunction(Ref ast) {
- auto name = ast[1]->getIString();
-
- BYN_TRACE("asm2wasming func: " << ast[1]->getIString().str << '\n');
-
- auto function = new Function;
- function->sig = Signature(Type::none, Type::none);
- function->name = name;
- Ref params = ast[2];
- Ref body = ast[3];
-
- UniqueNameMapper nameMapper;
-
- // given an asm.js label, returns the wasm label for breaks or continues
- auto getBreakLabelName = [](IString label) {
- return Name(std::string("label$break$") + label.str);
- };
- auto getContinueLabelName = [](IString label) {
- return Name(std::string("label$continue$") + label.str);
- };
-
- IStringSet functionVariables; // params or vars
-
- IString parentLabel; // set in LABEL, then read in WHILE/DO/SWITCH
- std::vector<IString> breakStack; // where a break will go
- std::vector<IString> continueStack; // where a continue will go
-
- AsmData asmData; // need to know var and param types, for asm type detection
-
- for (unsigned i = 0; i < params->size(); i++) {
- Ref curr = body[i];
- auto* assign = curr->asAssignName();
- IString name = assign->target();
- AsmType asmType =
- detectType(assign->value(), nullptr, false, Math_fround, wasmOnly);
- Builder::addParam(function, name, asmToWasmType(asmType));
- functionVariables.insert(name);
- asmData.addParam(name, asmType);
- }
- unsigned start = params->size();
- while (start < body->size() && body[start]->isArray(VAR)) {
- Ref curr = body[start];
- for (unsigned j = 0; j < curr[1]->size(); j++) {
- Ref pair = curr[1][j];
- IString name = pair[0]->getIString();
- AsmType asmType =
- detectType(pair[1], nullptr, true, Math_fround, wasmOnly);
- Builder::addVar(function, name, asmToWasmType(asmType));
- functionVariables.insert(name);
- asmData.addVar(name, asmType);
- }
- start++;
- }
-
- bool addedI32Temp = false;
- auto ensureI32Temp = [&]() {
- if (addedI32Temp) {
- return;
- }
- addedI32Temp = true;
- Builder::addVar(function, I32_TEMP, Type::i32);
- functionVariables.insert(I32_TEMP);
- asmData.addVar(I32_TEMP, ASM_INT);
- };
-
- bool seenReturn = false; // function->result is updated if we see a return
- // processors
- std::function<Expression*(Ref, unsigned)> processStatements;
- std::function<Expression*(Ref, unsigned)> processUnshifted;
- std::function<Expression*(Ref, unsigned)> processIgnoringShift;
-
- std::function<Expression*(Ref)> process = [&](Ref ast) -> Expression* {
- // TODO: only create one when we need it?
- AstStackHelper astStackHelper(ast);
- if (ast->isString()) {
- IString name = ast->getIString();
- if (functionVariables.has(name)) {
- // var in scope
- auto ret = allocator.alloc<LocalGet>();
- ret->index = function->getLocalIndex(name);
- ret->type = asmToWasmType(asmData.getType(name));
- return ret;
- }
- if (name == DEBUGGER) {
- Call* call = allocator.alloc<Call>();
- call->target = DEBUGGER;
- call->type = Type::none;
- static bool addedImport = false;
- if (!addedImport) {
- addedImport = true;
- auto import = new Function; // debugger = asm2wasm.debugger;
- import->name = DEBUGGER;
- import->module = ASM2WASM;
- import->base = DEBUGGER;
- import->sig = Signature(Type::none, Type::none);
- wasm.addFunction(import);
- }
- return call;
- }
- // global var
- assert(mappedGlobals.find(name) != mappedGlobals.end()
- ? true
- : (std::cerr << name.str << '\n', false));
- MappedGlobal& global = mappedGlobals[name];
- return builder.makeGlobalGet(name, global.type);
- }
- if (ast->isNumber()) {
- auto ret = allocator.alloc<Const>();
- double num = ast->getNumber();
- if (isSInteger32(num)) {
- ret->value = Literal(int32_t(toSInteger32(num)));
- } else if (isUInteger32(num)) {
- ret->value = Literal(uint32_t(toUInteger32(num)));
- } else {
- ret->value = Literal(num);
- }
- ret->type = ret->value.type;
- return ret;
- }
- if (ast->isAssignName()) {
- auto* assign = ast->asAssignName();
- IString name = assign->target();
- if (functionVariables.has(name)) {
- auto ret = allocator.alloc<LocalSet>();
- ret->index = function->getLocalIndex(assign->target());
- ret->value = process(assign->value());
- ret->makeSet();
- ret->finalize();
- return ret;
- }
- // global var
- if (mappedGlobals.find(name) == mappedGlobals.end()) {
- Fatal() << "error: access of a non-existent global var " << name.str;
- }
- auto* ret = builder.makeGlobalSet(name, process(assign->value()));
- // global.set does not return; if our value is trivially not used, don't
- // emit a load (if nontrivially not used, opts get it later)
- auto parent = astStackHelper.getParent();
- if (!parent || parent->isArray(BLOCK) || parent->isArray(IF)) {
- return ret;
- }
- return builder.makeSequence(
- ret, builder.makeGlobalGet(name, ret->value->type));
- }
- if (ast->isAssign()) {
- auto* assign = ast->asAssign();
- assert(assign->target()->isArray(SUB));
- Ref target = assign->target();
- assert(target[1]->isString());
- IString heap = target[1]->getIString();
- assert(views.find(heap) != views.end());
- View& view = views[heap];
- auto ret = allocator.alloc<Store>();
- ret->isAtomic = false;
- ret->bytes = view.bytes;
- ret->offset = 0;
- ret->align = view.bytes;
- ret->ptr = processUnshifted(target[2], view.bytes);
- ret->value = process(assign->value());
- ret->valueType = asmToWasmType(view.type);
- ret->finalize();
- if (ret->valueType != ret->value->type) {
- // in asm.js we have some implicit coercions that we must do explicitly
- // here
- if (ret->valueType == Type::f32 && ret->value->type == Type::f64) {
- auto conv = allocator.alloc<Unary>();
- conv->op = DemoteFloat64;
- conv->value = ret->value;
- conv->type = Type::f32;
- ret->value = conv;
- } else if (ret->valueType == Type::f64 &&
- ret->value->type == Type::f32) {
- ret->value = ensureDouble(ret->value);
- } else {
- abort_on("bad sub[] types", ast);
- }
- }
- return ret;
- }
- IString what = ast[0]->getIString();
- if (what == BINARY) {
- if ((ast[1] == OR || ast[1] == TRSHIFT) && ast[3]->isNumber() &&
- ast[3]->getNumber() == 0) {
- // just look through the ()|0 or ()>>>0 coercion
- auto ret = process(ast[2]);
- fixCallType(ret, Type::i32);
- return ret;
- }
- auto ret = allocator.alloc<Binary>();
- ret->left = process(ast[2]);
- ret->right = process(ast[3]);
- ret->op = parseAsmBinaryOp(
- ast[1]->getIString(), ast[2], ast[3], ret->left, ret->right);
- ret->finalize();
- if (ret->op == BinaryOp::RemSInt32 && ret->type.isFloat()) {
- // WebAssembly does not have floating-point remainder, we have to emit a
- // call to a special import of ours
- Call* call = allocator.alloc<Call>();
- call->target = F64_REM;
- call->operands.push_back(ensureDouble(ret->left));
- call->operands.push_back(ensureDouble(ret->right));
- call->type = Type::f64;
- static bool addedImport = false;
- if (!addedImport) {
- addedImport = true;
- auto import = new Function; // f64-rem = asm2wasm.f64-rem;
- import->name = F64_REM;
- import->module = ASM2WASM;
- import->base = F64_REM;
- import->sig = Signature({Type::f64, Type::f64}, Type::f64);
- wasm.addFunction(import);
- }
- return call;
- }
- return makeTrappingBinary(ret, trappingFunctions);
- } else if (what == SUB) {
- Ref target = ast[1];
- assert(target->isString());
- IString heap = target->getIString();
- assert(views.find(heap) != views.end());
- View& view = views[heap];
- auto ret = allocator.alloc<Load>();
- ret->isAtomic = false;
- ret->bytes = view.bytes;
- ret->signed_ = view.signed_;
- ret->offset = 0;
- ret->align = view.bytes;
- ret->ptr = processUnshifted(ast[2], view.bytes);
- ret->type = Type::get(view.bytes, !view.integer);
- return ret;
- } else if (what == UNARY_PREFIX) {
- if (ast[1] == PLUS) {
- Literal literal = checkLiteral(ast);
- if (literal.type != Type::none) {
- return builder.makeConst(literal);
- }
- auto ret = process(ast[2]); // we are a +() coercion
- if (ret->type == Type::i32) {
- auto conv = allocator.alloc<Unary>();
- conv->op = isUnsignedCoercion(ast[2]) ? ConvertUInt32ToFloat64
- : ConvertSInt32ToFloat64;
- conv->value = ret;
- conv->type = Type::f64;
- return conv;
- }
- if (ret->type == Type::f32) {
- return ensureDouble(ret);
- }
- fixCallType(ret, Type::f64);
- return ret;
- } else if (ast[1] == MINUS) {
- if (ast[2]->isNumber() ||
- (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == PLUS &&
- ast[2][2]->isNumber())) {
- auto ret = allocator.alloc<Const>();
- ret->value = getLiteral(ast);
- ret->type = ret->value.type;
- return ret;
- }
- AsmType asmType = detectAsmType(ast[2], &asmData);
- if (asmType == ASM_INT) {
- // wasm has no unary negation for int, so do 0-
- auto ret = allocator.alloc<Binary>();
- ret->op = SubInt32;
- ret->left = builder.makeConst(Literal((int32_t)0));
- ret->right = process(ast[2]);
- ret->type = Type::i32;
- return ret;
- }
- auto ret = allocator.alloc<Unary>();
- ret->value = process(ast[2]);
- if (asmType == ASM_DOUBLE) {
- ret->op = NegFloat64;
- ret->type = Type::f64;
- } else if (asmType == ASM_FLOAT) {
- ret->op = NegFloat32;
- ret->type = Type::f32;
- } else {
- WASM_UNREACHABLE("unexpected asm type");
- }
- return ret;
- } else if (ast[1] == B_NOT) {
- // ~, might be ~~ as a coercion or just a not
- if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == B_NOT) {
- // if we have an unsigned coercion on us, it is an unsigned op
- Expression* expr = process(ast[2][2]);
- bool isSigned = !isParentUnsignedCoercion(astStackHelper.getParent());
- bool isF64 = expr->type == Type::f64;
- UnaryOp op;
- if (isSigned && isF64) {
- op = UnaryOp::TruncSFloat64ToInt32;
- } else if (isSigned && !isF64) {
- op = UnaryOp::TruncSFloat32ToInt32;
- } else if (!isSigned && isF64) {
- op = UnaryOp::TruncUFloat64ToInt32;
- } else { // !isSigned && !isF64
- op = UnaryOp::TruncUFloat32ToInt32;
- }
- return makeTrappingUnary(builder.makeUnary(op, expr),
- trappingFunctions);
- }
- // no bitwise unary not, so do xor with -1
- auto ret = allocator.alloc<Binary>();
- ret->op = XorInt32;
- ret->left = process(ast[2]);
- ret->right = builder.makeConst(Literal(int32_t(-1)));
- ret->type = Type::i32;
- return ret;
- } else if (ast[1] == L_NOT) {
- auto ret = allocator.alloc<Unary>();
- ret->op = EqZInt32;
- ret->value = process(ast[2]);
- ret->type = Type::i32;
- return ret;
- }
- abort_on("bad unary", ast);
- } else if (what == IF) {
- auto* condition = process(ast[1]);
- auto* ifTrue = process(ast[2]);
- return builder.makeIf(truncateToInt32(condition),
- ifTrue,
- !!ast[3] ? process(ast[3]) : nullptr);
- } else if (what == CALL) {
- if (ast[1]->isString()) {
- IString name = ast[1]->getIString();
- if (name == Math_imul) {
- assert(ast[2]->size() == 2);
- auto ret = allocator.alloc<Binary>();
- ret->op = MulInt32;
- ret->left = process(ast[2][0]);
- ret->right = process(ast[2][1]);
- ret->type = Type::i32;
- return ret;
- }
- if (name == Math_clz32 || name == llvm_cttz_i32) {
- assert(ast[2]->size() == 1);
- auto ret = allocator.alloc<Unary>();
- ret->op = name == Math_clz32 ? ClzInt32 : CtzInt32;
- ret->value = process(ast[2][0]);
- ret->type = Type::i32;
- return ret;
- }
- if (name == Math_fround) {
- assert(ast[2]->size() == 1);
- Literal lit = checkLiteral(ast[2][0], false /* raw is float */);
- if (lit.type == Type::f64) {
- return builder.makeConst(Literal((float)lit.getf64()));
- }
- auto ret = allocator.alloc<Unary>();
- ret->value = process(ast[2][0]);
- if (ret->value->type == Type::f64) {
- ret->op = DemoteFloat64;
- } else if (ret->value->type == Type::i32) {
- if (isUnsignedCoercion(ast[2][0])) {
- ret->op = ConvertUInt32ToFloat32;
- } else {
- ret->op = ConvertSInt32ToFloat32;
- }
- } else if (ret->value->type == Type::f32) {
- return ret->value;
- } else if (ret->value->type == Type::none) { // call, etc.
- ret->value->type = Type::f32;
- return ret->value;
- } else {
- abort_on("confusing fround target", ast[2][0]);
- }
- ret->type = Type::f32;
- return ret;
- }
- if (name == Math_abs) {
- // overloaded on type: i32, f32 or f64
- Expression* value = process(ast[2][0]);
- if (value->type == Type::i32) {
- // No wasm support, so use a temp local
- ensureI32Temp();
- auto set = allocator.alloc<LocalSet>();
- set->index = function->getLocalIndex(I32_TEMP);
- set->value = value;
- set->makeSet();
- set->finalize();
- auto get = [&]() {
- auto ret = allocator.alloc<LocalGet>();
- ret->index = function->getLocalIndex(I32_TEMP);
- ret->type = Type::i32;
- return ret;
- };
- auto isNegative = allocator.alloc<Binary>();
- isNegative->op = LtSInt32;
- isNegative->left = get();
- isNegative->right = builder.makeConst(Literal(0));
- isNegative->finalize();
- auto block = allocator.alloc<Block>();
- block->list.push_back(set);
- auto flip = allocator.alloc<Binary>();
- flip->op = SubInt32;
- flip->left = builder.makeConst(Literal(0));
- flip->right = get();
- flip->type = Type::i32;
- auto select = allocator.alloc<Select>();
- select->ifTrue = flip;
- select->ifFalse = get();
- select->condition = isNegative;
- select->type = Type::i32;
- block->list.push_back(select);
- block->finalize();
- return block;
- } else if (value->type == Type::f32 || value->type == Type::f64) {
- auto ret = allocator.alloc<Unary>();
- ret->op = value->type == Type::f32 ? AbsFloat32 : AbsFloat64;
- ret->value = value;
- ret->type = value->type;
- return ret;
- } else {
- WASM_UNREACHABLE("unexpected type");
- }
- }
- if (name == Math_floor || name == Math_sqrt || name == Math_ceil) {
- // overloaded on type: f32 or f64
- Expression* value = process(ast[2][0]);
- auto ret = allocator.alloc<Unary>();
- ret->value = value;
- if (value->type == Type::f32) {
- ret->op = name == Math_floor
- ? FloorFloat32
- : name == Math_ceil ? CeilFloat32 : SqrtFloat32;
- ret->type = value->type;
- } else if (value->type == Type::f64) {
- ret->op = name == Math_floor
- ? FloorFloat64
- : name == Math_ceil ? CeilFloat64 : SqrtFloat64;
- ret->type = value->type;
- } else {
- Fatal()
- << "floor/sqrt/ceil only work on float/double in asm.js and wasm";
- }
- return ret;
- }
- if (name == Math_max || name == Math_min) {
- // overloaded on type: f32 or f64
- assert(ast[2]->size() == 2);
- auto ret = allocator.alloc<Binary>();
- ret->left = process(ast[2][0]);
- ret->right = process(ast[2][1]);
- if (ret->left->type == Type::f32) {
- ret->op = name == Math_max ? MaxFloat32 : MinFloat32;
- } else if (ret->left->type == Type::f64) {
- ret->op = name == Math_max ? MaxFloat64 : MinFloat64;
- } else {
- Fatal() << "min/max only work on float/double in asm.js and wasm";
- }
- ret->type = ret->left->type;
- return ret;
- }
- if (name == Atomics_load || name == Atomics_store ||
- name == Atomics_exchange || name == Atomics_compareExchange ||
- name == Atomics_add || name == Atomics_sub || name == Atomics_and ||
- name == Atomics_or || name == Atomics_xor) {
- // atomic operation
- Ref target = ast[2][0];
- assert(target->isString());
- IString heap = target->getIString();
- assert(views.find(heap) != views.end());
- View& view = views[heap];
- wasm.memory.shared = true;
- if (name == Atomics_load) {
- Expression* ret =
- builder.makeAtomicLoad(view.bytes,
- 0,
- processUnshifted(ast[2][1], view.bytes),
- asmToWasmType(view.type));
- if (view.signed_) {
- // atomic loads are unsigned; add a signing
- ret = Bits::makeSignExt(ret, view.bytes, wasm);
- }
- return ret;
- } else if (name == Atomics_store) {
- // asm.js stores return the value, wasm does not
- auto type = asmToWasmType(view.type);
- auto temp = Builder::addVar(function, type);
- return builder.makeSequence(
- builder.makeAtomicStore(
- view.bytes,
- 0,
- processUnshifted(ast[2][1], view.bytes),
- builder.makeLocalTee(temp, process(ast[2][2]), type),
- type),
- builder.makeLocalGet(temp, type));
- } else if (name == Atomics_exchange) {
- return builder.makeAtomicRMW(
- AtomicRMWOp::Xchg,
- view.bytes,
- 0,
- processUnshifted(ast[2][1], view.bytes),
- process(ast[2][2]),
- asmToWasmType(view.type));
- } else if (name == Atomics_compareExchange) {
- // cmpxchg is odd in fastcomp output - we must ignore the shift, a
- // cmpxchg of a i8 will look like compareExchange(HEAP8, ptr >> 2)
- return builder.makeAtomicCmpxchg(
- view.bytes,
- 0,
- processIgnoringShift(ast[2][1], view.bytes),
- process(ast[2][2]),
- process(ast[2][3]),
- asmToWasmType(view.type));
- } else if (name == Atomics_add) {
- return builder.makeAtomicRMW(
- AtomicRMWOp::Add,
- view.bytes,
- 0,
- processUnshifted(ast[2][1], view.bytes),
- process(ast[2][2]),
- asmToWasmType(view.type));
- } else if (name == Atomics_sub) {
- return builder.makeAtomicRMW(
- AtomicRMWOp::Sub,
- view.bytes,
- 0,
- processUnshifted(ast[2][1], view.bytes),
- process(ast[2][2]),
- asmToWasmType(view.type));
- } else if (name == Atomics_and) {
- return builder.makeAtomicRMW(
- AtomicRMWOp::And,
- view.bytes,
- 0,
- processUnshifted(ast[2][1], view.bytes),
- process(ast[2][2]),
- asmToWasmType(view.type));
- } else if (name == Atomics_or) {
- return builder.makeAtomicRMW(
- AtomicRMWOp::Or,
- view.bytes,
- 0,
- processUnshifted(ast[2][1], view.bytes),
- process(ast[2][2]),
- asmToWasmType(view.type));
- } else if (name == Atomics_xor) {
- return builder.makeAtomicRMW(
- AtomicRMWOp::Xor,
- view.bytes,
- 0,
- processUnshifted(ast[2][1], view.bytes),
- process(ast[2][2]),
- asmToWasmType(view.type));
- }
- WASM_UNREACHABLE("unexpected atomic op");
- }
- bool tableCall = false;
- if (wasmOnly) {
- auto num = ast[2]->size();
- switch (name.str[0]) {
- case 'l': {
- auto align = num == 2 ? ast[2][1]->getInteger() : 0;
- if (name == LOAD1) {
- return builder.makeLoad(
- 1, true, 0, 1, process(ast[2][0]), Type::i32);
- }
- if (name == LOAD2) {
- return builder.makeLoad(
- 2, true, 0, indexOr(align, 2), process(ast[2][0]), Type::i32);
- }
- if (name == LOAD4) {
- return builder.makeLoad(
- 4, true, 0, indexOr(align, 4), process(ast[2][0]), Type::i32);
- }
- if (name == LOAD8) {
- return builder.makeLoad(
- 8, true, 0, indexOr(align, 8), process(ast[2][0]), Type::i64);
- }
- if (name == LOADF) {
- return builder.makeLoad(
- 4, true, 0, indexOr(align, 4), process(ast[2][0]), Type::f32);
- }
- if (name == LOADD) {
- return builder.makeLoad(
- 8, true, 0, indexOr(align, 8), process(ast[2][0]), Type::f64);
- }
- break;
- }
- case 's': {
- auto align = num == 3 ? ast[2][2]->getInteger() : 0;
- if (name == STORE1) {
- return builder.makeStore(
- 1, 0, 1, process(ast[2][0]), process(ast[2][1]), Type::i32);
- }
- if (name == STORE2) {
- return builder.makeStore(2,
- 0,
- indexOr(align, 2),
- process(ast[2][0]),
- process(ast[2][1]),
- Type::i32);
- }
- if (name == STORE4) {
- return builder.makeStore(4,
- 0,
- indexOr(align, 4),
- process(ast[2][0]),
- process(ast[2][1]),
- Type::i32);
- }
- if (name == STORE8) {
- return builder.makeStore(8,
- 0,
- indexOr(align, 8),
- process(ast[2][0]),
- process(ast[2][1]),
- Type::i64);
- }
- if (name == STOREF) {
- auto* value = process(ast[2][1]);
- if (value->type == Type::f64) {
- // asm.js allows storing a double to HEAPF32, we must cast
- // here
- value = builder.makeUnary(DemoteFloat64, value);
- }
- return builder.makeStore(4,
- 0,
- indexOr(align, 4),
- process(ast[2][0]),
- value,
- Type::f32);
- }
- if (name == STORED) {
- return builder.makeStore(8,
- 0,
- indexOr(align, 8),
- process(ast[2][0]),
- process(ast[2][1]),
- Type::f64);
- }
- break;
- }
- case 'i': {
- if (num == 1) {
- auto* value = process(ast[2][0]);
- if (name == I64) {
- // no-op "coercion" / "cast", although we also tolerate i64(0)
- // for constants that fit in i32
- if (value->type == Type::i32) {
- return builder.makeConst(
- Literal(int64_t(value->cast<Const>()->value.geti32())));
- } else {
- fixCallType(value, Type::i64);
- return value;
- }
- }
- if (name == I32_CTTZ) {
- return builder.makeUnary(UnaryOp::CtzInt32, value);
- }
- if (name == I32_CTPOP) {
- return builder.makeUnary(UnaryOp::PopcntInt32, value);
- }
- if (name == I32_BC2F) {
- return builder.makeUnary(UnaryOp::ReinterpretInt32, value);
- }
- if (name == I32_BC2I) {
- return builder.makeUnary(UnaryOp::ReinterpretFloat32, value);
- }
-
- if (name == I64_TRUNC) {
- return builder.makeUnary(UnaryOp::WrapInt64, value);
- }
- if (name == I64_SEXT) {
- return builder.makeUnary(UnaryOp::ExtendSInt32, value);
- }
- if (name == I64_ZEXT) {
- return builder.makeUnary(UnaryOp::ExtendUInt32, value);
- }
- if (name == I64_S2F) {
- return builder.makeUnary(UnaryOp::ConvertSInt64ToFloat32,
- value);
- }
- if (name == I64_S2D) {
- return builder.makeUnary(UnaryOp::ConvertSInt64ToFloat64,
- value);
- }
- if (name == I64_U2F) {
- return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat32,
- value);
- }
- if (name == I64_U2D) {
- return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat64,
- value);
- }
- if (name == I64_F2S) {
- Unary* conv =
- builder.makeUnary(UnaryOp::TruncSFloat32ToInt64, value);
- return makeTrappingUnary(conv, trappingFunctions);
- }
- if (name == I64_D2S) {
- Unary* conv =
- builder.makeUnary(UnaryOp::TruncSFloat64ToInt64, value);
- return makeTrappingUnary(conv, trappingFunctions);
- }
- if (name == I64_F2U) {
- Unary* conv =
- builder.makeUnary(UnaryOp::TruncUFloat32ToInt64, value);
- return makeTrappingUnary(conv, trappingFunctions);
- }
- if (name == I64_D2U) {
- Unary* conv =
- builder.makeUnary(UnaryOp::TruncUFloat64ToInt64, value);
- return makeTrappingUnary(conv, trappingFunctions);
- }
- if (name == I64_BC2D) {
- return builder.makeUnary(UnaryOp::ReinterpretInt64, value);
- }
- if (name == I64_BC2I) {
- return builder.makeUnary(UnaryOp::ReinterpretFloat64, value);
- }
- if (name == I64_CTTZ) {
- return builder.makeUnary(UnaryOp::CtzInt64, value);
- }
- if (name == I64_CTLZ) {
- return builder.makeUnary(UnaryOp::ClzInt64, value);
- }
- if (name == I64_CTPOP) {
- return builder.makeUnary(UnaryOp::PopcntInt64, value);
- }
- if (name == I64_ATOMICS_LOAD) {
- return builder.makeAtomicLoad(8, 0, value, Type::i64);
- }
- } else if (num == 2) { // 2 params,binary
- if (name == I64_CONST) {
- return builder.makeConst(getLiteral(ast));
- }
- auto* left = process(ast[2][0]);
- auto* right = process(ast[2][1]);
- // maths
- if (name == I64_ADD) {
- return builder.makeBinary(BinaryOp::AddInt64, left, right);
- }
- if (name == I64_SUB) {
- return builder.makeBinary(BinaryOp::SubInt64, left, right);
- }
- if (name == I64_MUL) {
- return builder.makeBinary(BinaryOp::MulInt64, left, right);
- }
- if (name == I64_UDIV) {
- Binary* div =
- builder.makeBinary(BinaryOp::DivUInt64, left, right);
- return makeTrappingBinary(div, trappingFunctions);
- }
- if (name == I64_SDIV) {
- Binary* div =
- builder.makeBinary(BinaryOp::DivSInt64, left, right);
- return makeTrappingBinary(div, trappingFunctions);
- }
- if (name == I64_UREM) {
- Binary* rem =
- builder.makeBinary(BinaryOp::RemUInt64, left, right);
- return makeTrappingBinary(rem, trappingFunctions);
- }
- if (name == I64_SREM) {
- Binary* rem =
- builder.makeBinary(BinaryOp::RemSInt64, left, right);
- return makeTrappingBinary(rem, trappingFunctions);
- }
- if (name == I64_AND) {
- return builder.makeBinary(BinaryOp::AndInt64, left, right);
- }
- if (name == I64_OR) {
- return builder.makeBinary(BinaryOp::OrInt64, left, right);
- }
- if (name == I64_XOR) {
- return builder.makeBinary(BinaryOp::XorInt64, left, right);
- }
- if (name == I64_SHL) {
- return builder.makeBinary(BinaryOp::ShlInt64, left, right);
- }
- if (name == I64_ASHR) {
- return builder.makeBinary(BinaryOp::ShrSInt64, left, right);
- }
- if (name == I64_LSHR) {
- return builder.makeBinary(BinaryOp::ShrUInt64, left, right);
- }
- // comps
- if (name == I64_EQ) {
- return builder.makeBinary(BinaryOp::EqInt64, left, right);
- }
- if (name == I64_NE) {
- return builder.makeBinary(BinaryOp::NeInt64, left, right);
- }
- if (name == I64_ULE) {
- return builder.makeBinary(BinaryOp::LeUInt64, left, right);
- }
- if (name == I64_SLE) {
- return builder.makeBinary(BinaryOp::LeSInt64, left, right);
- }
- if (name == I64_UGE) {
- return builder.makeBinary(BinaryOp::GeUInt64, left, right);
- }
- if (name == I64_SGE) {
- return builder.makeBinary(BinaryOp::GeSInt64, left, right);
- }
- if (name == I64_ULT) {
- return builder.makeBinary(BinaryOp::LtUInt64, left, right);
- }
- if (name == I64_SLT) {
- return builder.makeBinary(BinaryOp::LtSInt64, left, right);
- }
- if (name == I64_UGT) {
- return builder.makeBinary(BinaryOp::GtUInt64, left, right);
- }
- if (name == I64_SGT) {
- return builder.makeBinary(BinaryOp::GtSInt64, left, right);
- }
- // atomics
- if (name == I64_ATOMICS_STORE) {
- wasm.memory.shared = true;
- return builder.makeAtomicStore(8, 0, left, right, Type::i64);
- }
- if (name == I64_ATOMICS_ADD) {
- wasm.memory.shared = true;
- return builder.makeAtomicRMW(
- AtomicRMWOp::Add, 8, 0, left, right, Type::i64);
- }
- if (name == I64_ATOMICS_SUB) {
- wasm.memory.shared = true;
- return builder.makeAtomicRMW(
- AtomicRMWOp::Sub, 8, 0, left, right, Type::i64);
- }
- if (name == I64_ATOMICS_AND) {
- wasm.memory.shared = true;
- return builder.makeAtomicRMW(
- AtomicRMWOp::And, 8, 0, left, right, Type::i64);
- }
- if (name == I64_ATOMICS_OR) {
- wasm.memory.shared = true;
- return builder.makeAtomicRMW(
- AtomicRMWOp::Or, 8, 0, left, right, Type::i64);
- }
- if (name == I64_ATOMICS_XOR) {
- wasm.memory.shared = true;
- return builder.makeAtomicRMW(
- AtomicRMWOp::Xor, 8, 0, left, right, Type::i64);
- }
- if (name == I64_ATOMICS_EXCHANGE) {
- wasm.memory.shared = true;
- return builder.makeAtomicRMW(
- AtomicRMWOp::Xchg, 8, 0, left, right, Type::i64);
- }
- } else if (num == 3) {
- if (name == I64_ATOMICS_COMPAREEXCHANGE) {
- wasm.memory.shared = true;
- return builder.makeAtomicCmpxchg(8,
- 0,
- process(ast[2][0]),
- process(ast[2][1]),
- process(ast[2][2]),
- Type::i64);
- }
- }
- break;
- }
- case 'f': {
- if (name == F32_COPYSIGN) {
- return builder.makeBinary(BinaryOp::CopySignFloat32,
- process(ast[2][0]),
- process(ast[2][1]));
- }
- if (name == F64_COPYSIGN) {
- return builder.makeBinary(BinaryOp::CopySignFloat64,
- process(ast[2][0]),
- process(ast[2][1]));
- }
- break;
- }
- }
- }
- // ftCall_* and mftCall_* represent function table calls, either from
- // the outside, or from the inside of the module. when compiling to
- // wasm, we can just convert those into table calls
- if ((name.str[0] == 'f' && strncmp(name.str, FTCALL.str, 7) == 0) ||
- (name.str[0] == 'm' && strncmp(name.str, MFTCALL.str, 8) == 0)) {
- tableCall = true;
- }
- Expression* ret;
- ExpressionList* operands;
- bool callImport = false;
- Index firstOperand = 0;
- Ref args = ast[2];
- if (tableCall) {
- auto specific = allocator.alloc<CallIndirect>();
- specific->target = process(args[0]);
- firstOperand = 1;
- operands = &specific->operands;
- ret = specific;
- } else {
- // if we call an import, it definitely exists already; if it's a
- // defined function then it might not have been seen yet
- auto* target = wasm.getFunctionOrNull(name);
- callImport = target && target->imported();
- auto specific = allocator.alloc<Call>();
- specific->target = name;
- operands = &specific->operands;
- ret = specific;
- }
- for (unsigned i = firstOperand; i < args->size(); i++) {
- operands->push_back(process(args[i]));
- }
- if (tableCall) {
- auto specific = ret->dynCast<CallIndirect>();
- // note that we could also get the type from the suffix of the name,
- // e.g., mftCall_vi
- auto sig = getSignature(
- astStackHelper.getParent(), specific->operands, &asmData);
- specific->sig = sig;
- specific->type = sig.results;
- }
- if (callImport) {
- // apply the detected type from the parent
- // note that this may not be complete, e.g. we may see f(); but f is
- // an import which does return a value, and we use that elsewhere.
- // finalizeCalls fixes that up. what we do here is wherever a value is
- // used, we set the right value, which is enough to ensure that the
- // wasm ast is valid for such uses. this is important as we run the
- // optimizer on functions before we get to finalizeCalls (which we can
- // only do once we've read all the functions, and we optimize in
- // parallel starting earlier).
- auto* call = ret->cast<Call>();
- call->type = getResultTypeOfCallUsingParent(
- astStackHelper.getParent(), &asmData);
- noteImportedFunctionCall(ast, call->type, call);
- }
- return ret;
- }
- // function pointers
- auto ret = allocator.alloc<CallIndirect>();
- Ref target = ast[1];
- assert(target[0] == SUB && target[1]->isString() &&
- target[2][0] == BINARY && target[2][1] == AND &&
- target[2][3]->isNumber()); // FUNCTION_TABLE[(expr) & mask]
- // TODO: as an optimization, we could look through the mask
- ret->target = process(target[2]);
- Ref args = ast[2];
- for (unsigned i = 0; i < args->size(); i++) {
- ret->operands.push_back(process(args[i]));
- }
- auto sig =
- getSignature(astStackHelper.getParent(), ret->operands, &asmData);
- ret->sig = sig;
- ret->type = sig.results;
- // we don't know the table offset yet. emit target = target +
- // callImport(tableName), which we fix up later when we know how asm
- // function tables are layed out inside the wasm table.
- ret->target = builder.makeBinary(
- BinaryOp::AddInt32,
- ret->target,
- builder.makeCall(target[1]->getIString(), {}, Type::i32));
- return ret;
- } else if (what == RETURN) {
- Type type = !!ast[1] ? detectWasmType(ast[1], &asmData) : Type::none;
- if (seenReturn) {
- assert(function->sig.results == type);
- } else {
- function->sig.results = type;
- }
- // wasm has no return, so we just break on the topmost block
- auto ret = allocator.alloc<Return>();
- ret->value = !!ast[1] ? process(ast[1]) : nullptr;
- return ret;
- } else if (what == BLOCK) {
- Name name;
- if (parentLabel.is()) {
- name = nameMapper.pushLabelName(getBreakLabelName(parentLabel));
- parentLabel = IString();
- breakStack.push_back(name);
- }
- auto ret = processStatements(ast[1], 0);
- if (name.is()) {
- breakStack.pop_back();
- nameMapper.popLabelName(name);
- Block* block = ret->dynCast<Block>();
- if (block && block->name.isNull()) {
- block->name = name;
- } else {
- block = allocator.alloc<Block>();
- block->name = name;
- block->list.push_back(ret);
- block->finalize();
- ret = block;
- }
- }
- return ret;
- } else if (what == BREAK) {
- auto ret = allocator.alloc<Break>();
- assert(breakStack.size() > 0);
- ret->name =
- !!ast[1]
- ? nameMapper.sourceToUnique(getBreakLabelName(ast[1]->getIString()))
- : breakStack.back();
- return ret;
- } else if (what == CONTINUE) {
- auto ret = allocator.alloc<Break>();
- assert(continueStack.size() > 0);
- ret->name = !!ast[1] ? nameMapper.sourceToUnique(
- getContinueLabelName(ast[1]->getIString()))
- : continueStack.back();
- return ret;
- } else if (what == WHILE) {
- bool forever = ast[1]->isNumber() && ast[1]->getInteger() == 1;
- auto ret = allocator.alloc<Loop>();
- IString out, in;
- if (!parentLabel.isNull()) {
- out = getBreakLabelName(parentLabel);
- in = getContinueLabelName(parentLabel);
- parentLabel = IString();
- } else {
- out = "while-out";
- in = "while-in";
- }
- out = nameMapper.pushLabelName(out);
- in = nameMapper.pushLabelName(in);
- ret->name = in;
- breakStack.push_back(out);
- continueStack.push_back(in);
- if (forever) {
- ret->body = process(ast[2]);
- } else {
- Break* breakOut = allocator.alloc<Break>();
- breakOut->name = out;
- If* condition = allocator.alloc<If>();
- condition->condition = builder.makeUnary(EqZInt32, process(ast[1]));
- condition->ifTrue = breakOut;
- condition->finalize();
- auto body = allocator.alloc<Block>();
- body->list.push_back(condition);
- body->list.push_back(process(ast[2]));
- body->finalize();
- ret->body = body;
- }
- // loops do not automatically loop, add a branch back
- Block* block = builder.blockifyWithName(ret->body, out);
- auto continuer = allocator.alloc<Break>();
- continuer->name = ret->name;
- block->list.push_back(continuer);
- block->finalize();
- ret->body = block;
- ret->finalize();
- continueStack.pop_back();
- breakStack.pop_back();
- nameMapper.popLabelName(in);
- nameMapper.popLabelName(out);
- return ret;
- } else if (what == DO) {
- if (ast[1]->isNumber() && ast[1]->getNumber() == 0) {
- // one-time loop, unless there is a continue
- IString stop;
- if (!parentLabel.isNull()) {
- stop = getBreakLabelName(parentLabel);
- parentLabel = IString();
- } else {
- stop = "do-once";
- }
- stop = nameMapper.pushLabelName(stop);
- Name more = nameMapper.pushLabelName("unlikely-continue");
- breakStack.push_back(stop);
- continueStack.push_back(more);
- auto child = process(ast[2]);
- continueStack.pop_back();
- breakStack.pop_back();
- nameMapper.popLabelName(more);
- nameMapper.popLabelName(stop);
- // if we never continued, we don't need a loop
- BranchUtils::BranchSeeker seeker(more);
- seeker.walk(child);
- if (seeker.found == 0) {
- auto block = allocator.alloc<Block>();
- block->list.push_back(child);
- if (child->type.isConcrete()) {
- // ensure a nop at the end, so the block has guaranteed none type
- // and no values fall through
- block->list.push_back(builder.makeNop());
- }
- block->name = stop;
- block->finalize();
- return block;
- } else {
- auto loop = allocator.alloc<Loop>();
- loop->body = child;
- loop->name = more;
- loop->finalize();
- return builder.blockifyWithName(loop, stop);
- }
- }
- // general do-while loop
- auto loop = allocator.alloc<Loop>();
- IString out, in;
- if (!parentLabel.isNull()) {
- out = getBreakLabelName(parentLabel);
- in = getContinueLabelName(parentLabel);
- parentLabel = IString();
- } else {
- out = "do-out";
- in = "do-in";
- }
- out = nameMapper.pushLabelName(out);
- in = nameMapper.pushLabelName(in);
- loop->name = in;
- breakStack.push_back(out);
- continueStack.push_back(in);
- loop->body = process(ast[2]);
- continueStack.pop_back();
- breakStack.pop_back();
- nameMapper.popLabelName(in);
- nameMapper.popLabelName(out);
- Break* continuer = allocator.alloc<Break>();
- continuer->name = in;
- continuer->condition = process(ast[1]);
- continuer->finalize();
- Block* block = builder.blockifyWithName(loop->body, out, continuer);
- loop->body = block;
- loop->finalize();
- return loop;
- } else if (what == FOR) {
- Ref finit = ast[1], fcond = ast[2], finc = ast[3], fbody = ast[4];
- auto ret = allocator.alloc<Loop>();
- IString out, in;
- if (!parentLabel.isNull()) {
- out = getBreakLabelName(parentLabel);
- in = getContinueLabelName(parentLabel);
- parentLabel = IString();
- } else {
- out = "for-out";
- in = "for-in";
- }
- out = nameMapper.pushLabelName(out);
- in = nameMapper.pushLabelName(in);
- ret->name = in;
- breakStack.push_back(out);
- continueStack.push_back(in);
- Break* breakOut = allocator.alloc<Break>();
- breakOut->name = out;
- If* condition = allocator.alloc<If>();
- condition->condition = builder.makeUnary(EqZInt32, process(fcond));
- condition->ifTrue = breakOut;
- condition->finalize();
- auto body = allocator.alloc<Block>();
- body->list.push_back(condition);
- body->list.push_back(process(fbody));
- body->list.push_back(process(finc));
- body->finalize();
- ret->body = body;
- // loops do not automatically loop, add a branch back
- auto continuer = allocator.alloc<Break>();
- continuer->name = ret->name;
- Block* block = builder.blockifyWithName(ret->body, out, continuer);
- ret->body = block;
- ret->finalize();
- continueStack.pop_back();
- breakStack.pop_back();
- nameMapper.popLabelName(in);
- nameMapper.popLabelName(out);
- Block* outer = allocator.alloc<Block>();
- // add an outer block for the init as well
- outer->list.push_back(process(finit));
- outer->list.push_back(ret);
- outer->finalize();
- return outer;
- } else if (what == LABEL) {
- assert(parentLabel.isNull());
- parentLabel = ast[1]->getIString();
- return process(ast[2]);
- } else if (what == CONDITIONAL) {
- auto ret = allocator.alloc<If>();
- ret->condition = process(ast[1]);
- ret->ifTrue = process(ast[2]);
- ret->ifFalse = process(ast[3]);
- ret->finalize();
- return ret;
- } else if (what == SEQ) {
- // Some (x, y) patterns can be optimized, like bitcasts,
- // (HEAP32[tempDoublePtr >> 2] = i,
- // Math_fround(HEAPF32[tempDoublePtr >> 2])); // i32->f32
- // (HEAP32[tempDoublePtr >> 2] = i,
- // +HEAPF32[tempDoublePtr >> 2]); // i32->f32, no fround
- // (HEAPF32[tempDoublePtr >> 2] = f,
- // HEAP32[tempDoublePtr >> 2] | 0); // f32->i32
- if (ast[1]->isAssign()) {
- auto* assign = ast[1]->asAssign();
- Ref target = assign->target();
- if (target->isArray(SUB) && target[1]->isString() &&
- target[2]->isArray(BINARY) && target[2][1] == RSHIFT &&
- target[2][2]->isString() && target[2][2] == tempDoublePtr &&
- target[2][3]->isNumber() && target[2][3]->getNumber() == 2) {
- // (?[tempDoublePtr >> 2] = ?, ?) so far
- auto heap = target[1]->getIString();
- if (views.find(heap) != views.end()) {
- AsmType writeType = views[heap].type;
- AsmType readType = ASM_NONE;
- Ref readValue;
- if (ast[2]->isArray(BINARY) && ast[2][1] == OR &&
- ast[2][3]->isNumber() && ast[2][3]->getNumber() == 0) {
- readType = ASM_INT;
- readValue = ast[2][2];
- } else if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == PLUS) {
- readType = ASM_DOUBLE;
- readValue = ast[2][2];
- } else if (ast[2]->isArray(CALL) && ast[2][1]->isString() &&
- ast[2][1] == Math_fround) {
- readType = ASM_FLOAT;
- readValue = ast[2][2][0];
- }
- if (readType != ASM_NONE) {
- if (readValue->isArray(SUB) && readValue[1]->isString() &&
- readValue[2]->isArray(BINARY) && readValue[2][1] == RSHIFT &&
- readValue[2][2]->isString() &&
- readValue[2][2] == tempDoublePtr &&
- readValue[2][3]->isNumber() &&
- readValue[2][3]->getNumber() == 2) {
- // pattern looks right!
- Ref writtenValue = assign->value();
- if (writeType == ASM_INT &&
- (readType == ASM_FLOAT || readType == ASM_DOUBLE)) {
- auto conv = allocator.alloc<Unary>();
- conv->op = ReinterpretInt32;
- conv->value = process(writtenValue);
- conv->type = Type::f32;
- if (readType == ASM_DOUBLE) {
- return ensureDouble(conv);
- }
- return conv;
- } else if (writeType == ASM_FLOAT && readType == ASM_INT) {
- auto conv = allocator.alloc<Unary>();
- conv->op = ReinterpretFloat32;
- conv->value = process(writtenValue);
- if (conv->value->type == Type::f64) {
- // this has an implicit f64->f32 in the write to memory
- conv->value = builder.makeUnary(DemoteFloat64, conv->value);
- }
- conv->type = Type::i32;
- return conv;
- }
- }
- }
- }
- }
- }
- auto ret = allocator.alloc<Block>();
- ret->list.push_back(process(ast[1]));
- ret->list.push_back(process(ast[2]));
- ret->finalize();
- return ret;
- } else if (what == SWITCH) {
- IString name; // for breaking out of the entire switch
- if (!parentLabel.isNull()) {
- name = getBreakLabelName(parentLabel);
- parentLabel = IString();
- } else {
- name = "switch";
- }
- name = nameMapper.pushLabelName(name);
- breakStack.push_back(name);
-
- auto br = allocator.alloc<Switch>();
- br->condition = process(ast[1]);
-
- Ref cases = ast[2];
- bool seen = false;
- int64_t min = 0; // the lowest index we see; we will offset to it
- int64_t max = 0; // the highest, to check if the range is too big
- for (unsigned i = 0; i < cases->size(); i++) {
- Ref curr = cases[i];
- Ref condition = curr[0];
- if (!condition->isNull()) {
- int64_t index = getLiteral(condition).getInteger();
- if (!seen) {
- seen = true;
- min = index;
- max = index;
- } else {
- if (index < min) {
- min = index;
- }
- if (index > max) {
- max = index;
- }
- }
- }
- }
- // we can use a switch if it's not too big
- auto range = double(max) - double(min); // test using doubles to avoid UB
- bool canSwitch = 0 <= range && range < 10240;
-
- auto top = allocator.alloc<Block>();
- if (canSwitch) {
-
- // we may need a break for the case where the condition doesn't match
- // any of the cases. it should go to the default, if we have one, or
- // outside if not
- Break* breakWhenNotMatching = nullptr;
-
- if (br->condition->type == Type::i32) {
- Binary* offsetor = allocator.alloc<Binary>();
- offsetor->op = BinaryOp::SubInt32;
- offsetor->left = br->condition;
- offsetor->right = builder.makeConst(Literal(int32_t(min)));
- offsetor->type = Type::i32;
- br->condition = offsetor;
- } else {
- assert(br->condition->type == Type::i64);
- // 64-bit condition. after offsetting it must be in a reasonable
- // range, but the offsetting itself must be 64-bit
- Binary* offsetor = allocator.alloc<Binary>();
- offsetor->op = BinaryOp::SubInt64;
- offsetor->left = br->condition;
- offsetor->right = builder.makeConst(Literal(int64_t(min)));
- offsetor->type = Type::i64;
- // the switch itself can be 32-bit, as the range is in a reasonable
- // range. so after offsetting, we need to make sure there are no high
- // bits, then we can just look at the lower 32 bits
- auto temp = Builder::addVar(function, Type::i64);
- auto* block = builder.makeBlock();
- block->list.push_back(builder.makeLocalSet(temp, offsetor));
- // if high bits, we can break to the default (we'll fill in the name
- // later)
- breakWhenNotMatching = builder.makeBreak(
- Name(),
- nullptr,
- builder.makeUnary(
- UnaryOp::WrapInt64,
- builder.makeBinary(BinaryOp::ShrUInt64,
- builder.makeLocalGet(temp, Type::i64),
- builder.makeConst(Literal(int64_t(32))))));
- block->list.push_back(breakWhenNotMatching);
- block->list.push_back(builder.makeLocalGet(temp, Type::i64));
- block->finalize();
- br->condition = builder.makeUnary(UnaryOp::WrapInt64, block);
- }
-
- top->list.push_back(br);
-
- for (unsigned i = 0; i < cases->size(); i++) {
- Ref curr = cases[i];
- Ref condition = curr[0];
- Ref body = curr[1];
- auto case_ = processStatements(body, 0);
- Name name;
- if (condition->isNull()) {
- name = br->default_ = nameMapper.pushLabelName("switch-default");
- } else {
- auto index = getLiteral(condition).getInteger();
- assert(index >= min);
- index -= min;
- assert(index >= 0);
- uint64_t index_s = index;
- name = nameMapper.pushLabelName("switch-case");
- if (br->targets.size() <= index_s) {
- br->targets.resize(index_s + 1);
- }
- br->targets[index_s] = name;
- }
- auto next = allocator.alloc<Block>();
- top->name = name;
- next->list.push_back(top);
- next->list.push_back(case_);
- next->finalize();
- top = next;
- nameMapper.popLabelName(name);
- }
-
- // the outermost block can be branched to to exit the whole switch
- top->name = name;
-
- // ensure a default
- if (br->default_.isNull()) {
- br->default_ = top->name;
- }
- if (breakWhenNotMatching) {
- breakWhenNotMatching->name = br->default_;
- }
- for (size_t i = 0; i < br->targets.size(); i++) {
- if (br->targets[i].isNull()) {
- br->targets[i] = br->default_;
- }
- }
- } else {
- // we can't switch, make an if-chain instead of br_table
- auto var = Builder::addVar(function, br->condition->type);
- top->list.push_back(builder.makeLocalSet(var, br->condition));
- auto* brHolder = top;
- If* chain = nullptr;
- If* first = nullptr;
-
- for (unsigned i = 0; i < cases->size(); i++) {
- Ref curr = cases[i];
- Ref condition = curr[0];
- Ref body = curr[1];
- auto case_ = processStatements(body, 0);
- Name name;
- if (condition->isNull()) {
- name = br->default_ = nameMapper.pushLabelName("switch-default");
- } else {
- name = nameMapper.pushLabelName("switch-case");
- auto* iff = builder.makeIf(
- builder.makeBinary(br->condition->type == Type::i32 ? EqInt32
- : EqInt64,
- builder.makeLocalGet(var, br->condition->type),
- builder.makeConst(getLiteral(condition))),
- builder.makeBreak(name),
- chain);
- chain = iff;
- if (!first) {
- first = iff;
- }
- }
- auto next = allocator.alloc<Block>();
- top->name = name;
- next->list.push_back(top);
- next->list.push_back(case_);
- top = next;
- nameMapper.popLabelName(name);
- }
-
- // the outermost block can be branched to to exit the whole switch
- top->name = name;
-
- // ensure a default
- if (br->default_.isNull()) {
- br->default_ = top->name;
- }
-
- first->ifFalse = builder.makeBreak(br->default_);
-
- brHolder->list.push_back(chain);
- }
-
- breakStack.pop_back();
- nameMapper.popLabelName(name);
-
- return top;
- }
- abort_on("confusing expression", ast);
- return (Expression*)nullptr; // avoid warning
- };
-
- // given HEAP32[addr >> 2], we need an absolute address, and would like to
- // remove that shift. if there is a shift, we can just look through it, etc.
- processUnshifted = [&](Ref ptr, unsigned bytes) {
- auto shifts = bytesToShift(bytes);
- // HEAP?[addr >> ?], or HEAP8[x | 0]
- if ((ptr->isArray(BINARY) && ptr[1] == RSHIFT && ptr[3]->isNumber() &&
- ptr[3]->getInteger() == shifts) ||
- (bytes == 1 && ptr->isArray(BINARY) && ptr[1] == OR &&
- ptr[3]->isNumber() && ptr[3]->getInteger() == 0)) {
- return process(ptr[2]); // look through it
- } else if (ptr->isNumber()) {
- // constant, apply a shift (e.g. HEAP32[1] is address 4)
- unsigned addr = ptr->getInteger();
- unsigned shifted = addr << shifts;
- return (Expression*)builder.makeConst(Literal(int32_t(shifted)));
- }
- abort_on("bad processUnshifted", ptr);
- return (Expression*)nullptr; // avoid warning
- };
-
- processIgnoringShift = [&](Ref ptr, unsigned bytes) {
- // If there is a shift here, no matter the size look through it.
- if ((ptr->isArray(BINARY) && ptr[1] == RSHIFT && ptr[3]->isNumber()) ||
- (bytes == 1 && ptr->isArray(BINARY) && ptr[1] == OR &&
- ptr[3]->isNumber() && ptr[3]->getInteger() == 0)) {
- return process(ptr[2]);
- }
- // If there is no shift at all, process the variable directly
- // E.g. the address variable "$4" in Atomics_compareExchange(HEAP8, $4, $7,
- // $8);
- if (ptr->isString()) {
- return process(ptr);
- }
- // Otherwise do the same as processUnshifted.
- return processUnshifted(ptr, bytes);
- };
-
- processStatements = [&](Ref ast, unsigned from) -> Expression* {
- unsigned size = ast->size() - from;
- if (size == 0) {
- return allocator.alloc<Nop>();
- }
- if (size == 1) {
- return process(ast[from]);
- }
- auto block = allocator.alloc<Block>();
- for (unsigned i = from; i < ast->size(); i++) {
- block->list.push_back(process(ast[i]));
- }
- block->finalize();
- return block;
- };
- // body
- function->body = processStatements(body, start);
- // cleanups/checks
- assert(breakStack.size() == 0 && continueStack.size() == 0);
- assert(parentLabel.isNull());
- return function;
-}
-
-} // namespace wasm
-
-#endif // wasm_asm2wasm_h
diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp
deleted file mode 100644
index 8b7e4b1b7..000000000
--- a/src/tools/asm2wasm.cpp
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * 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.
- */
-
-//
-// asm2wasm console tool
-//
-
-#include <exception>
-
-#include "ir/trapping.h"
-#include "optimization-options.h"
-#include "support/colors.h"
-#include "support/command-line.h"
-#include "support/file.h"
-#include "wasm-builder.h"
-#include "wasm-io.h"
-#include "wasm-printing.h"
-#include "wasm-validator.h"
-
-#include "asm2wasm.h"
-
-using namespace cashew;
-using namespace wasm;
-
-int main(int argc, const char* argv[]) {
- bool legalizeJavaScriptFFI = true;
- TrapMode trapMode = TrapMode::JS;
- bool wasmOnly = false;
- std::string sourceMapFilename;
- std::string sourceMapUrl;
- std::string symbolMap;
- bool emitBinary = true;
-
- OptimizationOptions options("asm2wasm",
- "Translate asm.js files to .wast files");
- options
- .add("--output",
- "-o",
- "Output file (stdout if not specified)",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- o->extra["output"] = argument;
- Colors::setEnabled(false);
- })
- .add(
- "--mapped-globals",
- "-n",
- "Mapped globals",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- std::cerr
- << "warning: the --mapped-globals/-m option is deprecated (a mapped "
- "globals file is no longer needed as we use wasm globals)"
- << std::endl;
- })
- .add("--mem-init",
- "-t",
- "Import a memory initialization file into the output module",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- o->extra["mem init"] = argument;
- })
- .add("--mem-base",
- "-mb",
- "Set the location to write the memory initialization (--mem-init) "
- "file (GLOBAL_BASE in emscripten). If not provided, the __memory_base "
- "global import is used.",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- o->extra["mem base"] = argument;
- })
- .add("--mem-max",
- "-mm",
- "Set the maximum size of memory in the wasm module (in bytes). -1 "
- "means no limit. Without this, TOTAL_MEMORY is used (as it is used "
- "for the initial value), or if memory growth is enabled, no limit is "
- "set. This overrides both of those.",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- o->extra["mem max"] = argument;
- })
- .add("--total-memory",
- "-m",
- "Total memory size",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- o->extra["total memory"] = argument;
- })
- .add("--table-max",
- "-tM",
- "Set the maximum size of the table. Without this, it is set depending "
- "on how many functions are in the module. -1 means no limit",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- o->extra["table max"] = argument;
- })
- .add("--no-opts",
- "-n",
- "Disable optimization passes (deprecated)",
- Options::Arguments::Zero,
- [](Options* o, const std::string&) {
- std::cerr << "--no-opts is deprecated (use -O0, etc.)\n";
- })
- .add("--trap-mode",
- "",
- "Strategy for handling potentially trapping instructions. Valid "
- "values are \"allow\", \"js\", and \"clamp\"",
- Options::Arguments::One,
- [&trapMode](Options* o, const std::string& argument) {
- try {
- trapMode = trapModeFromString(argument);
- } catch (std::invalid_argument& e) {
- std::cerr << "Error: " << e.what() << "\n";
- exit(EXIT_FAILURE);
- }
- })
- .add("--wasm-only",
- "-w",
- "Input is in WebAssembly-only format, and not actually valid asm.js",
- Options::Arguments::Zero,
- [&wasmOnly](Options* o, const std::string&) { wasmOnly = true; })
- .add("--no-legalize-javascript-ffi",
- "-nj",
- "Do not fully legalize (i64->i32, f32->f64) the imports and exports "
- "for interfacing with JS",
- Options::Arguments::Zero,
- [&legalizeJavaScriptFFI](Options* o, const std::string&) {
- legalizeJavaScriptFFI = false;
- })
- .add("--source-map",
- "-sm",
- "Emit source map (if using binary output) to the specified file",
- Options::Arguments::One,
- [&sourceMapFilename](Options* o, const std::string& argument) {
- sourceMapFilename = argument;
- })
- .add("--source-map-url",
- "-su",
- "Use specified string as source map URL",
- Options::Arguments::One,
- [&sourceMapUrl](Options* o, const std::string& argument) {
- sourceMapUrl = argument;
- })
- .add("--symbolmap",
- "-s",
- "Emit a symbol map (indexes => names)",
- Options::Arguments::One,
- [&](Options* o, const std::string& argument) { symbolMap = argument; })
- .add("--emit-text",
- "-S",
- "Emit text instead of binary for the output file",
- Options::Arguments::Zero,
- [&](Options* o, const std::string& argument) { emitBinary = false; })
- .add_positional("INFILE",
- Options::Arguments::One,
- [](Options* o, const std::string& argument) {
- o->extra["infile"] = argument;
- });
- options.parse(argc, argv);
-
- // finalize arguments
- if (options.extra["output"].size() == 0) {
- // when no output file is specified, we emit text to stdout
- emitBinary = false;
- }
-
- if (options.runningDefaultOptimizationPasses()) {
- if (options.passes.size() > 1) {
- Fatal() << "asm2wasm can only run default optimization passes (-O, -Ox, "
- "etc.), and not specific additional passes";
- }
- }
-
- const auto& tm_it = options.extra.find("total memory");
- size_t totalMemory = tm_it == options.extra.end()
- ? 16 * 1024 * 1024
- : atoll(tm_it->second.c_str());
- if (totalMemory & ~Memory::kPageMask) {
- std::cerr << "Error: total memory size " << totalMemory
- << " is not a multiple of the 64k wasm page size\n";
- exit(EXIT_FAILURE);
- }
-
- Asm2WasmPreProcessor pre;
- // wasm binaries can contain a names section, but not full debug info --
- // debug info is disabled if a map file is not specified with wasm binary
- pre.debugInfo =
- options.passOptions.debugInfo && (!emitBinary || sourceMapFilename.size());
- auto input(
- read_file<std::vector<char>>(options.extra["infile"], Flags::Text));
- char* start = pre.process(input.data());
-
- if (options.debug) {
- std::cerr << "parsing..." << std::endl;
- }
- cashew::Parser<Ref, DotZeroValueBuilder> builder;
- Ref asmjs = builder.parseToplevel(start);
-
- if (options.debug) {
- std::cerr << "wasming..." << std::endl;
- }
- Module wasm;
-
- // set up memory
- wasm.memory.initial = wasm.memory.max = totalMemory / Memory::kPageSize;
-
- // import mem init file, if provided (do this before compiling the module,
- // since the optimizer should see the memory segments)
- const auto& memInit = options.extra.find("mem init");
- if (memInit != options.extra.end()) {
- auto filename = memInit->second.c_str();
- auto data(read_file<std::vector<char>>(filename, Flags::Binary));
- // create the memory segment
- Expression* init;
- const auto& memBase = options.extra.find("mem base");
- if (memBase == options.extra.end()) {
- init = Builder(wasm).makeGlobalGet(MEMORY_BASE, Type::i32);
- } else {
- init = Builder(wasm).makeConst(
- Literal(int32_t(atoi(memBase->second.c_str()))));
- }
- wasm.memory.segments.emplace_back(init, data);
- }
-
- // set up the module's features, needed by optimization and validation passes
- options.applyFeatures(wasm);
-
- // compile the code
- Asm2WasmBuilder asm2wasm(wasm,
- pre,
- options.debug,
- trapMode,
- options.passOptions,
- legalizeJavaScriptFFI,
- options.runningDefaultOptimizationPasses(),
- wasmOnly);
- asm2wasm.processAsm(asmjs);
-
- // Set the max memory size, if requested
- const auto& memMax = options.extra.find("mem max");
- if (memMax != options.extra.end()) {
- uint64_t max = strtoull(memMax->second.c_str(), nullptr, 10);
- if (max != uint64_t(-1)) {
- wasm.memory.max = max / Memory::kPageSize;
- } else {
- wasm.memory.max = Memory::kUnlimitedSize;
- }
- }
- // Set the table sizes, if requested
- const auto& tableMax = options.extra.find("table max");
- if (tableMax != options.extra.end()) {
- int max = atoi(tableMax->second.c_str());
- if (max >= 0) {
- wasm.table.max = max;
- } else {
- wasm.table.max = Table::kUnlimitedSize;
- }
- }
-
- if (options.passOptions.validate) {
- if (!WasmValidator().validate(wasm)) {
- WasmPrinter::printModule(&wasm);
- Fatal() << "error in validating output";
- }
- }
-
- if (options.debug) {
- std::cerr << "emitting..." << std::endl;
- }
- ModuleWriter writer;
- writer.setDebugInfo(options.passOptions.debugInfo);
- writer.setSymbolMap(symbolMap);
- writer.setBinary(emitBinary);
- if (emitBinary) {
- writer.setSourceMapFilename(sourceMapFilename);
- writer.setSourceMapUrl(sourceMapUrl);
- }
- writer.write(wasm, options.extra["output"]);
-
- if (options.debug) {
- std::cerr << "done." << std::endl;
- }
-}