| /* |
| * Copyright 2015 WebAssembly Community Group participants |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // |
| // WebAssembly-to-JS code translator. Converts wasm functions into |
| // valid JavaScript (with a somewhat asm.js-ish flavor). |
| // |
| |
| #ifndef wasm_wasm2js_h |
| #define wasm_wasm2js_h |
| |
| #include <cmath> |
| #include <numeric> |
| |
| #include "asmjs/shared-constants.h" |
| #include "asmjs/asmangle.h" |
| #include "wasm.h" |
| #include "wasm-builder.h" |
| #include "wasm-io.h" |
| #include "wasm-validator.h" |
| #include "emscripten-optimizer/optimizer.h" |
| #include "mixed_arena.h" |
| #include "asm_v_wasm.h" |
| #include "abi/js.h" |
| #include "ir/find_all.h" |
| #include "ir/import-utils.h" |
| #include "ir/load-utils.h" |
| #include "ir/module-utils.h" |
| #include "ir/names.h" |
| #include "ir/table-utils.h" |
| #include "ir/utils.h" |
| #include "passes/passes.h" |
| #include "support/base64.h" |
| |
| namespace wasm { |
| |
| using namespace cashew; |
| |
| IString ASM_FUNC("asmFunc"), |
| ABORT_FUNC("abort"), |
| FUNCTION_TABLE("FUNCTION_TABLE"), |
| NO_RESULT("wasm2js$noresult"), // no result at all |
| EXPRESSION_RESULT("wasm2js$expresult"); // result in an expression, no temp var |
| |
| // Appends extra to block, flattening out if extra is a block as well |
| void flattenAppend(Ref ast, Ref extra) { |
| int index; |
| if (ast[0] == BLOCK || ast[0] == TOPLEVEL) index = 1; |
| else if (ast[0] == DEFUN) index = 3; |
| else abort(); |
| if (extra->isArray() && extra[0] == BLOCK) { |
| for (size_t i = 0; i < extra[1]->size(); i++) { |
| ast[index]->push_back(extra[1][i]); |
| } |
| } else { |
| ast[index]->push_back(extra); |
| } |
| } |
| |
| // Appends extra to a chain of sequence elements |
| void sequenceAppend(Ref& ast, Ref extra) { |
| if (!ast.get()) { |
| ast = extra; |
| return; |
| } |
| ast = ValueBuilder::makeSeq(ast, extra); |
| } |
| |
| // Used when taking a wasm name and generating a JS identifier. Each scope here |
| // is used to ensure that all names have a unique name but the same wasm name |
| // within a scope always resolves to the same symbol. |
| enum class NameScope { |
| Top, |
| Local, |
| Label, |
| Max, |
| }; |
| |
| template<typename T> |
| static uint64_t constOffset(const T& segment) { |
| auto* c = segment.offset->template dynCast<Const>(); |
| if (!c) { |
| Fatal() << "non-constant offsets aren't supported yet\n"; |
| abort(); |
| } |
| return c->value.getInteger(); |
| } |
| |
| // |
| // Wasm2JSBuilder - converts a WebAssembly module's functions into JS |
| // |
| // In general, JS (asm.js) => wasm is very straightforward, as can |
| // be seen in asm2wasm.h. Just a single pass, plus a little |
| // state bookkeeping (breakStack, etc.), and a few after-the |
| // fact corrections for imports, etc. However, wasm => JS |
| // is tricky because wasm has statements == expressions, or in |
| // other words, things like `break` and `if` can show up |
| // in places where JS can't handle them, like inside an |
| // a loop's condition check. For that reason we use flat IR here. |
| // We do optimize it later, to allow some nesting, but we avoid |
| // non-JS-compatible nesting like block return values control |
| // flow in an if condition, etc. |
| // |
| |
| class Wasm2JSBuilder { |
| MixedArena allocator; |
| |
| public: |
| struct Flags { |
| bool debug = false; |
| bool pedantic = false; |
| bool allowAsserts = false; |
| bool emscripten = false; |
| }; |
| |
| Wasm2JSBuilder(Flags f) : flags(f) {} |
| |
| Ref processWasm(Module* wasm, Name funcName = ASM_FUNC); |
| Ref processFunction(Module* wasm, Function* func, bool standalone=false); |
| Ref processStandaloneFunction(Module* wasm, Function* func) { |
| return processFunction(wasm, func, true); |
| } |
| |
| // The second pass on an expression: process it fully, generating |
| // JS |
| Ref processFunctionBody(Module* m, Function* func); |
| |
| // Get a temp var. |
| IString getTemp(Type type, Function* func) { |
| IString ret; |
| if (frees[type].size() > 0) { |
| ret = frees[type].back(); |
| frees[type].pop_back(); |
| } else { |
| size_t index = temps[type]++; |
| ret = IString((std::string("wasm2js_") + printType(type) + "$" + |
| std::to_string(index)).c_str(), false); |
| } |
| if (func->localIndices.find(ret) == func->localIndices.end()) { |
| Builder::addVar(func, ret, type); |
| } |
| return ret; |
| } |
| |
| // Free a temp var. |
| void freeTemp(Type type, IString temp) { |
| frees[type].push_back(temp); |
| } |
| |
| // Generates a mangled name from `name` within the specified scope. |
| // |
| // The goal of this function is to ensure that all identifiers in JS ar |
| // unique. Otherwise there can be clashes with locals and functions and cause |
| // unwanted name shadowing. |
| // |
| // The returned string from this function is constant for a particular `name` |
| // within a `scope`. Or in other words, the same `name` and `scope` pair will |
| // always return the same result. If `scope` changes, however, the return |
| // value may differ even if the same `name` is passed in. |
| IString fromName(Name name, NameScope scope) { |
| // TODO: checking names do not collide after mangling |
| |
| // First up check our cached of mangled names to avoid doing extra work |
| // below |
| auto &mangledScope = mangledNames[(int) scope]; |
| auto it = mangledScope.find(name.c_str()); |
| if (it != mangledScope.end()) { |
| return it->second; |
| } |
| |
| // This is the first time we've seen the `name` and `scope` pair. Generate a |
| // globally unique name based on `name` and then register that in our cache |
| // and return it. |
| // |
| // Identifiers here generated are of the form `${name}_${n}` where `_${n}` |
| // is omitted if `n==0` and otherwise `n` is just looped over to find the |
| // next unused identifier. |
| IString ret; |
| for (int i = 0;; i++) { |
| std::ostringstream out; |
| out << name.c_str(); |
| if (i > 0) { |
| out << "_" << i; |
| } |
| auto mangled = asmangle(out.str()); |
| ret = IString(mangled.c_str(), false); |
| if (!allMangledNames.count(ret)) { |
| break; |
| } |
| |
| // In the global scope that's how you refer to actual function exports, so |
| // it's a bug currently if they're not globally unique. This should |
| // probably be fixed via a different namespace for exports or something |
| // like that. |
| // XXX This is not actually a valid check atm, since functions are not in the |
| // global-most scope, but rather in the "asmFunc" scope which is inside it. |
| // Also, for emscripten style glue, we emit the exports as a return, so there |
| // is no name placed into the scope. For these reasons, just warn here, don't |
| // error. |
| if (scope == NameScope::Top) { |
| std::cerr << "wasm2js: warning: global scope may be colliding with other scope: " << mangled << '\n'; |
| } |
| } |
| allMangledNames.insert(ret); |
| mangledScope[name.c_str()] = ret; |
| return ret; |
| } |
| |
| size_t getTableSize() { |
| return tableSize; |
| } |
| |
| private: |
| Flags flags; |
| |
| // How many temp vars we need |
| std::vector<size_t> temps; // type => num temps |
| // Which are currently free to use |
| std::vector<std::vector<IString>> frees; // type => list of free names |
| |
| // Mangled names cache by interned names. |
| // Utilizes the usually reused underlying cstring's pointer as the key. |
| std::unordered_map<const char*, IString> mangledNames[(int) NameScope::Max]; |
| std::unordered_set<IString> allMangledNames; |
| |
| size_t tableSize; |
| |
| void addBasics(Ref ast); |
| void addFunctionImport(Ref ast, Function* import); |
| void addGlobalImport(Ref ast, Global* import); |
| void addTable(Ref ast, Module* wasm); |
| void addExports(Ref ast, Module* wasm); |
| void addGlobal(Ref ast, Global* global); |
| void addMemoryGrowthFuncs(Ref ast, Module* wasm); |
| |
| Wasm2JSBuilder() = delete; |
| Wasm2JSBuilder(const Wasm2JSBuilder &) = delete; |
| Wasm2JSBuilder &operator=(const Wasm2JSBuilder&) = delete; |
| }; |
| |
| Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { |
| // Ensure the scratch memory helpers. |
| // If later on they aren't needed, we'll clean them up. |
| ABI::wasm2js::ensureScratchMemoryHelpers(wasm); |
| |
| PassRunner runner(wasm); |
| runner.add<AutoDrop>(); |
| runner.add("legalize-js-interface"); |
| // First up remove as many non-JS operations we can, including things like |
| // 64-bit integer multiplication/division, `f32.nearest` instructions, etc. |
| // This may inject intrinsics which use i64 so it needs to be run before the |
| // i64-to-i32 lowering pass. |
| runner.add("remove-non-js-ops"); |
| // Currently the i64-to-32 lowering pass requires that `flatten` be run before |
| // it to produce correct code. For some more details about this see #1480 |
| runner.add("flatten"); |
| runner.add("i64-to-i32-lowering"); |
| runner.add("flatten"); |
| runner.add("simplify-locals-notee-nostructure"); |
| runner.add("reorder-locals"); |
| runner.add("remove-unused-names"); |
| runner.add("vacuum"); |
| runner.add("remove-unused-module-elements"); |
| runner.setDebug(flags.debug); |
| runner.run(); |
| |
| // Make sure we didn't corrupt anything if we're in --allow-asserts mode (aka |
| // tests) |
| #ifndef NDEBUG |
| if (!WasmValidator().validate(*wasm)) { |
| WasmPrinter::printModule(wasm); |
| Fatal() << "error in validating wasm2js output"; |
| } |
| #endif |
| |
| Ref ret = ValueBuilder::makeToplevel(); |
| Ref asmFunc = ValueBuilder::makeFunction(funcName); |
| ret[1]->push_back(asmFunc); |
| ValueBuilder::appendArgumentToFunction(asmFunc, GLOBAL); |
| ValueBuilder::appendArgumentToFunction(asmFunc, ENV); |
| ValueBuilder::appendArgumentToFunction(asmFunc, BUFFER); |
| asmFunc[3]->push_back(ValueBuilder::makeStatement(ValueBuilder::makeString(ALMOST_ASM))); |
| // add memory import |
| if (wasm->memory.exists && wasm->memory.imported()) { |
| Ref theVar = ValueBuilder::makeVar(); |
| asmFunc[3]->push_back(theVar); |
| ValueBuilder::appendToVar(theVar, |
| "memory", |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName(ENV), |
| ValueBuilder::makeName("memory") |
| ) |
| ); |
| } |
| // create heaps, etc |
| addBasics(asmFunc[3]); |
| ModuleUtils::iterImportedFunctions(*wasm, [&](Function* import) { |
| addFunctionImport(asmFunc[3], import); |
| }); |
| ModuleUtils::iterImportedGlobals(*wasm, [&](Global* import) { |
| addGlobalImport(asmFunc[3], import); |
| }); |
| // figure out the table size |
| tableSize = std::accumulate(wasm->table.segments.begin(), |
| wasm->table.segments.end(), |
| 0, [&](size_t size, Table::Segment seg) -> size_t { |
| return size + seg.data.size() + constOffset(seg); |
| }); |
| size_t pow2ed = 1; |
| while (pow2ed < tableSize) { |
| pow2ed <<= 1; |
| } |
| tableSize = pow2ed; |
| |
| // make sure exports get their expected names |
| for (auto& e : wasm->exports) { |
| if (e->kind == ExternalKind::Function) { |
| fromName(e->name, NameScope::Top); |
| } |
| } |
| for (auto& f : wasm->functions) { |
| fromName(f->name, NameScope::Top); |
| } |
| fromName(WASM_FETCH_HIGH_BITS, NameScope::Top); |
| // globals |
| bool generateFetchHighBits = false; |
| ModuleUtils::iterDefinedGlobals(*wasm, [&](Global* global) { |
| addGlobal(asmFunc[3], global); |
| if (flags.allowAsserts && global->name == INT64_TO_32_HIGH_BITS) { |
| generateFetchHighBits = true; |
| } |
| }); |
| if (flags.emscripten) { |
| asmFunc[3]->push_back(ValueBuilder::makeName("// EMSCRIPTEN_START_FUNCS")); |
| } |
| // functions |
| ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) { |
| asmFunc[3]->push_back(processFunction(wasm, func)); |
| }); |
| if (generateFetchHighBits) { |
| Builder builder(allocator); |
| std::vector<Type> params; |
| std::vector<Type> vars; |
| asmFunc[3]->push_back(processFunction(wasm, builder.makeFunction( |
| WASM_FETCH_HIGH_BITS, |
| std::move(params), |
| i32, |
| std::move(vars), |
| builder.makeReturn(builder.makeGetGlobal(INT64_TO_32_HIGH_BITS, i32)) |
| ))); |
| auto e = new Export(); |
| e->name = WASM_FETCH_HIGH_BITS; |
| e->value = WASM_FETCH_HIGH_BITS; |
| e->kind = ExternalKind::Function; |
| wasm->addExport(e); |
| } |
| if (flags.emscripten) { |
| asmFunc[3]->push_back(ValueBuilder::makeName("// EMSCRIPTEN_END_FUNCS")); |
| } |
| |
| addTable(asmFunc[3], wasm); |
| // memory XXX |
| addExports(asmFunc[3], wasm); |
| return ret; |
| } |
| |
| void Wasm2JSBuilder::addBasics(Ref ast) { |
| // heaps, var HEAP8 = new global.Int8Array(buffer); etc |
| auto addHeap = [&](IString name, IString view) { |
| Ref theVar = ValueBuilder::makeVar(); |
| ast->push_back(theVar); |
| ValueBuilder::appendToVar(theVar, |
| name, |
| ValueBuilder::makeNew( |
| ValueBuilder::makeCall( |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName(GLOBAL), |
| view |
| ), |
| ValueBuilder::makeName(BUFFER) |
| ) |
| ) |
| ); |
| }; |
| addHeap(HEAP8, INT8ARRAY); |
| addHeap(HEAP16, INT16ARRAY); |
| addHeap(HEAP32, INT32ARRAY); |
| addHeap(HEAPU8, UINT8ARRAY); |
| addHeap(HEAPU16, UINT16ARRAY); |
| addHeap(HEAPU32, UINT32ARRAY); |
| addHeap(HEAPF32, FLOAT32ARRAY); |
| addHeap(HEAPF64, FLOAT64ARRAY); |
| // core asm.js imports |
| auto addMath = [&](IString name, IString base) { |
| Ref theVar = ValueBuilder::makeVar(); |
| ast->push_back(theVar); |
| ValueBuilder::appendToVar(theVar, |
| name, |
| ValueBuilder::makeDot( |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName(GLOBAL), |
| MATH |
| ), |
| base |
| ) |
| ); |
| }; |
| addMath(MATH_IMUL, IMUL); |
| addMath(MATH_FROUND, FROUND); |
| addMath(MATH_ABS, ABS); |
| addMath(MATH_CLZ32, CLZ32); |
| addMath(MATH_MIN, MIN); |
| addMath(MATH_MAX, MAX); |
| addMath(MATH_FLOOR, FLOOR); |
| addMath(MATH_CEIL, CEIL); |
| addMath(MATH_SQRT, SQRT); |
| // abort function |
| Ref abortVar = ValueBuilder::makeVar(); |
| ast->push_back(abortVar); |
| ValueBuilder::appendToVar(abortVar, |
| "abort", |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName(ENV), |
| ABORT_FUNC |
| ) |
| ); |
| // TODO: this shouldn't be needed once we stop generating literal asm.js code |
| // NaN and Infinity variables |
| Ref nanVar = ValueBuilder::makeVar(); |
| ast->push_back(nanVar); |
| ValueBuilder::appendToVar(nanVar, |
| "nan", |
| ValueBuilder::makeDot(ValueBuilder::makeName(GLOBAL), "NaN") |
| ); |
| Ref infinityVar = ValueBuilder::makeVar(); |
| ast->push_back(infinityVar); |
| ValueBuilder::appendToVar(infinityVar, |
| "infinity", |
| ValueBuilder::makeDot(ValueBuilder::makeName(GLOBAL), "Infinity") |
| ); |
| } |
| |
| void Wasm2JSBuilder::addFunctionImport(Ref ast, Function* import) { |
| // The scratch memory helpers are emitted in the glue, see code and comments below. |
| if (ABI::wasm2js::isScratchMemoryHelper(import->base)) { |
| return; |
| } |
| Ref theVar = ValueBuilder::makeVar(); |
| ast->push_back(theVar); |
| Ref module = ValueBuilder::makeName(ENV); // TODO: handle nested module imports |
| ValueBuilder::appendToVar(theVar, |
| fromName(import->name, NameScope::Top), |
| ValueBuilder::makeDot( |
| module, |
| fromName(import->base, NameScope::Top) |
| ) |
| ); |
| } |
| |
| void Wasm2JSBuilder::addGlobalImport(Ref ast, Global* import) { |
| Ref theVar = ValueBuilder::makeVar(); |
| ast->push_back(theVar); |
| Ref module = ValueBuilder::makeName(ENV); // TODO: handle nested module imports |
| Ref value = ValueBuilder::makeDot( |
| module, |
| fromName(import->base, NameScope::Top) |
| ); |
| if (import->type == i32) { |
| value = makeAsmCoercion(value, ASM_INT); |
| } |
| ValueBuilder::appendToVar(theVar, |
| fromName(import->name, NameScope::Top), |
| value |
| ); |
| } |
| |
| void Wasm2JSBuilder::addTable(Ref ast, Module* wasm) { |
| // Emit a simple flat table as a JS array literal. Otherwise, |
| // emit assignments separately for each index. |
| FlatTable flat(wasm->table); |
| assert(flat.valid); // TODO: non-flat tables |
| if (!wasm->table.imported()) { |
| Ref theVar = ValueBuilder::makeVar(); |
| ast->push_back(theVar); |
| Ref theArray = ValueBuilder::makeArray(); |
| ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, theArray); |
| Name null("null"); |
| for (auto& name : flat.names) { |
| if (name.is()) { |
| name = fromName(name, NameScope::Top); |
| } else { |
| name = null; |
| } |
| ValueBuilder::appendToArray(theArray, ValueBuilder::makeName(name)); |
| } |
| } else { |
| // TODO: optimize for size |
| for (auto& segment : wasm->table.segments) { |
| auto offset = segment.offset; |
| Index start = offset->cast<Const>()->value.geti32(); |
| for (Index i = 0; i < segment.data.size(); i++) { |
| ast->push_back(ValueBuilder::makeStatement( |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeSub( |
| ValueBuilder::makeName(FUNCTION_TABLE), |
| ValueBuilder::makeInt(start + i) |
| ), |
| SET, |
| ValueBuilder::makeName(fromName(segment.data[i], NameScope::Top)) |
| ) |
| )); |
| } |
| } |
| } |
| } |
| |
| void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) { |
| Ref exports = ValueBuilder::makeObject(); |
| for (auto& export_ : wasm->exports) { |
| if (export_->kind == ExternalKind::Function) { |
| ValueBuilder::appendToObjectWithQuotes( |
| exports, |
| fromName(export_->name, NameScope::Top), |
| ValueBuilder::makeName(fromName(export_->value, NameScope::Top)) |
| ); |
| } |
| if (export_->kind == ExternalKind::Memory) { |
| Ref descs = ValueBuilder::makeObject(); |
| Ref growDesc = ValueBuilder::makeObject(); |
| ValueBuilder::appendToObjectWithQuotes( |
| descs, |
| IString("grow"), |
| growDesc); |
| ValueBuilder::appendToObjectWithQuotes( |
| growDesc, |
| IString("value"), |
| ValueBuilder::makeName(WASM_GROW_MEMORY)); |
| Ref bufferDesc = ValueBuilder::makeObject(); |
| Ref bufferGetter = ValueBuilder::makeFunction(IString("")); |
| bufferGetter[3]->push_back(ValueBuilder::makeReturn( |
| ValueBuilder::makeName(BUFFER) |
| )); |
| ValueBuilder::appendToObjectWithQuotes( |
| bufferDesc, |
| IString("get"), |
| bufferGetter); |
| ValueBuilder::appendToObjectWithQuotes( |
| descs, |
| IString("buffer"), |
| bufferDesc); |
| Ref memory = ValueBuilder::makeCall( |
| ValueBuilder::makeDot(ValueBuilder::makeName(IString("Object")), IString("create")), |
| ValueBuilder::makeDot(ValueBuilder::makeName(IString("Object")), IString("prototype"))); |
| ValueBuilder::appendToCall( |
| memory, |
| descs); |
| ValueBuilder::appendToObjectWithQuotes( |
| exports, |
| fromName(export_->name, NameScope::Top), |
| memory); |
| } |
| } |
| if (wasm->memory.exists && wasm->memory.max > wasm->memory.initial) { |
| addMemoryGrowthFuncs(ast, wasm); |
| } |
| ast->push_back(ValueBuilder::makeStatement(ValueBuilder::makeReturn(exports))); |
| } |
| |
| void Wasm2JSBuilder::addGlobal(Ref ast, Global* global) { |
| if (auto* const_ = global->init->dynCast<Const>()) { |
| Ref theValue; |
| switch (const_->type) { |
| case Type::i32: { |
| theValue = ValueBuilder::makeInt(const_->value.geti32()); |
| break; |
| } |
| case Type::f32: { |
| theValue = ValueBuilder::makeCall(MATH_FROUND, |
| makeAsmCoercion(ValueBuilder::makeDouble(const_->value.getf32()), ASM_DOUBLE) |
| ); |
| break; |
| } |
| case Type::f64: { |
| theValue = makeAsmCoercion(ValueBuilder::makeDouble(const_->value.getf64()), ASM_DOUBLE); |
| break; |
| } |
| default: { |
| assert(false && "Top const type not supported"); |
| } |
| } |
| Ref theVar = ValueBuilder::makeVar(); |
| ast->push_back(theVar); |
| ValueBuilder::appendToVar(theVar, |
| fromName(global->name, NameScope::Top), |
| theValue |
| ); |
| } else if (auto* get = global->init->dynCast<GetGlobal>()) { |
| Ref theVar = ValueBuilder::makeVar(); |
| ast->push_back(theVar); |
| ValueBuilder::appendToVar(theVar, |
| fromName(global->name, NameScope::Top), |
| ValueBuilder::makeName(fromName(get->name, NameScope::Top)) |
| ); |
| } else { |
| assert(false && "Top init type not supported"); |
| } |
| } |
| |
| Ref Wasm2JSBuilder::processFunction(Module* m, Function* func, bool standaloneFunction) { |
| if (standaloneFunction) { |
| // We are only printing a function, not a whole module. Prepare it for |
| // translation now (if there were a module, we'd have done this for all |
| // functions in parallel, earlier). |
| PassRunner runner(m); |
| // We only run a subset of all passes here. TODO: create a full valid module |
| // for each assertion body. |
| runner.add("flatten"); |
| runner.add("simplify-locals-notee-nostructure"); |
| runner.add("reorder-locals"); |
| runner.add("remove-unused-names"); |
| runner.add("vacuum"); |
| runner.runOnFunction(func); |
| } |
| |
| // We will be symbolically referring to all variables in the function, so make |
| // sure that everything has a name and it's unique. |
| Names::ensureNames(func); |
| Ref ret = ValueBuilder::makeFunction(fromName(func->name, NameScope::Top)); |
| frees.clear(); |
| frees.resize(std::max(i32, std::max(f32, f64)) + 1); |
| temps.clear(); |
| temps.resize(std::max(i32, std::max(f32, f64)) + 1); |
| temps[i32] = temps[f32] = temps[f64] = 0; |
| // arguments |
| for (Index i = 0; i < func->getNumParams(); i++) { |
| IString name = fromName(func->getLocalNameOrGeneric(i), NameScope::Local); |
| ValueBuilder::appendArgumentToFunction(ret, name); |
| ret[3]->push_back( |
| ValueBuilder::makeStatement( |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeName(name), SET, |
| makeAsmCoercion( |
| ValueBuilder::makeName(name), |
| wasmToAsmType(func->getLocalType(i)) |
| ) |
| ) |
| ) |
| ); |
| } |
| Ref theVar = ValueBuilder::makeVar(); |
| size_t theVarIndex = ret[3]->size(); |
| ret[3]->push_back(theVar); |
| // body |
| flattenAppend(ret, processFunctionBody(m, func)); |
| // vars, including new temp vars |
| for (Index i = func->getVarIndexBase(); i < func->getNumLocals(); i++) { |
| ValueBuilder::appendToVar( |
| theVar, |
| fromName(func->getLocalNameOrGeneric(i), NameScope::Local), |
| makeAsmCoercedZero(wasmToAsmType(func->getLocalType(i))) |
| ); |
| } |
| if (theVar[1]->size() == 0) { |
| ret[3]->splice(theVarIndex, 1); |
| } |
| // checks |
| assert(frees[i32].size() == temps[i32]); // all temp vars should be free at the end |
| assert(frees[f32].size() == temps[f32]); // all temp vars should be free at the end |
| assert(frees[f64].size() == temps[f64]); // all temp vars should be free at the end |
| return ret; |
| } |
| |
| Ref Wasm2JSBuilder::processFunctionBody(Module* m, Function* func) { |
| struct ExpressionProcessor : public Visitor<ExpressionProcessor, Ref> { |
| Wasm2JSBuilder* parent; |
| IString result; // TODO: remove |
| Function* func; |
| Module* module; |
| MixedArena allocator; |
| ExpressionProcessor(Wasm2JSBuilder* parent, Module* m, Function* func) |
| : parent(parent), func(func), module(m) {} |
| |
| // A scoped temporary variable. |
| struct ScopedTemp { |
| Wasm2JSBuilder* parent; |
| Type type; |
| IString temp; // TODO: switch to indexes; avoid names |
| bool needFree; |
| // @param possible if provided, this is a variable we can use as our temp. it has already been |
| // allocated in a higher scope, and we can just assign to it as our result is |
| // going there anyhow. |
| ScopedTemp(Type type, Wasm2JSBuilder* parent, Function* func, |
| IString possible = NO_RESULT) : parent(parent), type(type) { |
| assert(possible != EXPRESSION_RESULT); |
| if (possible == NO_RESULT) { |
| temp = parent->getTemp(type, func); |
| needFree = true; |
| } else { |
| temp = possible; |
| needFree = false; |
| } |
| } |
| ~ScopedTemp() { |
| if (needFree) { |
| parent->freeTemp(type, temp); |
| } |
| } |
| |
| IString getName() { |
| return temp; |
| } |
| Ref getAstName() { |
| return ValueBuilder::makeName(temp); |
| } |
| }; |
| |
| Ref visit(Expression* curr, IString nextResult) { |
| IString old = result; |
| result = nextResult; |
| Ref ret = Visitor::visit(curr); |
| result = old; // keep it consistent for the rest of this frame, which may call visit on multiple children |
| return ret; |
| } |
| |
| Ref visit(Expression* curr, ScopedTemp& temp) { |
| return visit(curr, temp.temp); |
| } |
| |
| Ref visitAndAssign(Expression* curr, IString result) { |
| assert(result != NO_RESULT); |
| Ref ret = visit(curr, result); |
| return ValueBuilder::makeStatement(ValueBuilder::makeBinary(ValueBuilder::makeName(result), SET, ret)); |
| } |
| |
| Ref visitAndAssign(Expression* curr, ScopedTemp& temp) { |
| return visitAndAssign(curr, temp.getName()); |
| } |
| |
| // Expressions with control flow turn into a block, which we must |
| // then handle, even if we are an expression. |
| bool isBlock(Ref ast) { |
| return !!ast && ast->isArray() && ast[0] == BLOCK; |
| } |
| |
| Ref blockify(Ref ast) { |
| if (isBlock(ast)) return ast; |
| Ref ret = ValueBuilder::makeBlock(); |
| ret[1]->push_back(ValueBuilder::makeStatement(ast)); |
| return ret; |
| } |
| |
| // Breaks to the top of a loop should be emitted as continues, to that loop's main label |
| std::unordered_set<Name> continueLabels; |
| |
| IString fromName(Name name, NameScope scope) { |
| return parent->fromName(name, scope); |
| } |
| |
| // Visitors |
| |
| Ref visitBlock(Block* curr) { |
| Ref ret = ValueBuilder::makeBlock(); |
| size_t size = curr->list.size(); |
| auto noResults = result == NO_RESULT ? size : size-1; |
| for (size_t i = 0; i < noResults; i++) { |
| flattenAppend(ret, ValueBuilder::makeStatement(visit(curr->list[i], NO_RESULT))); |
| } |
| if (result != NO_RESULT) { |
| flattenAppend(ret, visitAndAssign(curr->list[size-1], result)); |
| } |
| if (curr->name.is()) { |
| ret = ValueBuilder::makeLabel(fromName(curr->name, NameScope::Label), ret); |
| } |
| return ret; |
| } |
| |
| Ref visitIf(If* curr) { |
| Ref condition = visit(curr->condition, EXPRESSION_RESULT); |
| Ref ifTrue = visit(curr->ifTrue, NO_RESULT); |
| Ref ifFalse; |
| if (curr->ifFalse) { |
| ifFalse = visit(curr->ifFalse, NO_RESULT); |
| } |
| return ValueBuilder::makeIf(condition, ifTrue, ifFalse); // simple if |
| } |
| |
| Ref visitLoop(Loop* curr) { |
| Name asmLabel = curr->name; |
| continueLabels.insert(asmLabel); |
| Ref body = blockify(visit(curr->body, result)); |
| flattenAppend(body, ValueBuilder::makeBreak(fromName(asmLabel, NameScope::Label))); |
| Ref ret = ValueBuilder::makeDo(body, ValueBuilder::makeInt(1)); |
| return ValueBuilder::makeLabel(fromName(asmLabel, NameScope::Label), ret); |
| } |
| |
| Ref makeBreakOrContinue(Name name) { |
| if (continueLabels.count(name)) { |
| return ValueBuilder::makeContinue(fromName(name, NameScope::Label)); |
| } else { |
| return ValueBuilder::makeBreak(fromName(name, NameScope::Label)); |
| } |
| } |
| |
| Ref visitBreak(Break* curr) { |
| if (curr->condition) { |
| // we need an equivalent to an if here, so use that code |
| Break fakeBreak = *curr; |
| fakeBreak.condition = nullptr; |
| If fakeIf(allocator); |
| fakeIf.condition = curr->condition; |
| fakeIf.ifTrue = &fakeBreak; |
| return visit(&fakeIf, result); |
| } |
| return makeBreakOrContinue(curr->name); |
| } |
| |
| Expression* defaultBody = nullptr; // default must be last in asm.js |
| |
| Ref visitSwitch(Switch* curr) { |
| assert(!curr->value); |
| Ref ret = ValueBuilder::makeBlock(); |
| Ref condition = visit(curr->condition, EXPRESSION_RESULT); |
| Ref theSwitch = |
| ValueBuilder::makeSwitch(makeAsmCoercion(condition, ASM_INT)); |
| ret[1]->push_back(theSwitch); |
| for (size_t i = 0; i < curr->targets.size(); i++) { |
| ValueBuilder::appendCaseToSwitch(theSwitch, ValueBuilder::makeNum(i)); |
| ValueBuilder::appendCodeToSwitch(theSwitch, blockify(makeBreakOrContinue(curr->targets[i])), false); |
| } |
| ValueBuilder::appendDefaultToSwitch(theSwitch); |
| ValueBuilder::appendCodeToSwitch(theSwitch, blockify(makeBreakOrContinue(curr->default_)), false); |
| return ret; |
| } |
| |
| Ref visitCall(Call* curr) { |
| Ref theCall = ValueBuilder::makeCall(fromName(curr->target, NameScope::Top)); |
| for (auto operand : curr->operands) { |
| theCall[2]->push_back( |
| makeAsmCoercion(visit(operand, EXPRESSION_RESULT), |
| wasmToAsmType(operand->type))); |
| } |
| return makeAsmCoercion(theCall, wasmToAsmType(curr->type)); |
| } |
| |
| Ref visitCallIndirect(CallIndirect* curr) { |
| // TODO: the codegen here is a pessimization of what the ideal codegen |
| // looks like. Eventually if necessary this should be tightened up in the |
| // case that the argument expression doesn't have any side effects. |
| Ref ret; |
| ScopedTemp idx(i32, parent, func); |
| std::vector<ScopedTemp*> temps; // TODO: utility class, with destructor? |
| for (auto& operand : curr->operands) { |
| temps.push_back(new ScopedTemp(operand->type, parent, func)); |
| IString temp = temps.back()->temp; |
| sequenceAppend(ret, visitAndAssign(operand, temp)); |
| } |
| sequenceAppend(ret, visitAndAssign(curr->target, idx)); |
| Ref theCall = ValueBuilder::makeCall(ValueBuilder::makeSub( |
| ValueBuilder::makeName(FUNCTION_TABLE), |
| idx.getAstName() |
| )); |
| for (size_t i = 0; i < temps.size(); i++) { |
| IString temp = temps[i]->temp; |
| auto &operand = curr->operands[i]; |
| theCall[2]->push_back(makeAsmCoercion(ValueBuilder::makeName(temp), wasmToAsmType(operand->type))); |
| } |
| theCall = makeAsmCoercion(theCall, wasmToAsmType(curr->type)); |
| sequenceAppend(ret, theCall); |
| for (auto temp : temps) { |
| delete temp; |
| } |
| return ret; |
| } |
| |
| // TODO: remove |
| Ref makeSetVar(Expression* curr, Expression* value, Name name, NameScope scope) { |
| return ValueBuilder::makeBinary( |
| ValueBuilder::makeName(fromName(name, scope)), SET, |
| visit(value, EXPRESSION_RESULT) |
| ); |
| } |
| |
| Ref visitGetLocal(GetLocal* curr) { |
| return ValueBuilder::makeName( |
| fromName(func->getLocalNameOrGeneric(curr->index), NameScope::Local) |
| ); |
| } |
| |
| Ref visitSetLocal(SetLocal* curr) { |
| return makeSetVar( |
| curr, |
| curr->value, |
| func->getLocalNameOrGeneric(curr->index), |
| NameScope::Local |
| ); |
| } |
| |
| Ref visitGetGlobal(GetGlobal* curr) { |
| return ValueBuilder::makeName(fromName(curr->name, NameScope::Top)); |
| } |
| |
| Ref visitSetGlobal(SetGlobal* curr) { |
| return makeSetVar(curr, curr->value, curr->name, NameScope::Top); |
| } |
| |
| Ref visitLoad(Load* curr) { |
| if (curr->align != 0 && curr->align < curr->bytes) { |
| // set the pointer to a local |
| ScopedTemp temp(i32, parent, func); |
| SetLocal set(allocator); |
| set.index = func->getLocalIndex(temp.getName()); |
| set.value = curr->ptr; |
| Ref ptrSet = visit(&set, NO_RESULT); |
| GetLocal get(allocator); |
| get.index = func->getLocalIndex(temp.getName()); |
| // fake loads |
| Load load = *curr; |
| load.ptr = &get; |
| load.bytes = 1; // do the worst |
| load.signed_ = false; |
| Ref rest; |
| switch (curr->type) { |
| case i32: { |
| rest = makeAsmCoercion(visit(&load, EXPRESSION_RESULT), ASM_INT); |
| for (size_t i = 1; i < curr->bytes; i++) { |
| ++load.offset; |
| Ref add = makeAsmCoercion(visit(&load, EXPRESSION_RESULT), ASM_INT); |
| add = ValueBuilder::makeBinary(add, LSHIFT, ValueBuilder::makeNum(8*i)); |
| rest = ValueBuilder::makeBinary(rest, OR, add); |
| } |
| break; |
| } |
| default: { |
| std::cerr << "Unhandled type in load: " << curr->type << std::endl; |
| abort(); |
| } |
| } |
| return ValueBuilder::makeSeq(ptrSet, rest); |
| } |
| // normal load |
| Ref ptr = makePointer(curr->ptr, curr->offset); |
| Ref ret; |
| switch (curr->type) { |
| case i32: { |
| switch (curr->bytes) { |
| case 1: |
| ret = ValueBuilder::makeSub( |
| ValueBuilder::makeName(LoadUtils::isSignRelevant(curr) && curr->signed_ ? HEAP8 : HEAPU8), |
| ValueBuilder::makePtrShift(ptr, 0)); |
| break; |
| case 2: |
| ret = ValueBuilder::makeSub( |
| ValueBuilder::makeName(LoadUtils::isSignRelevant(curr) && curr->signed_ ? HEAP16 : HEAPU16), |
| ValueBuilder::makePtrShift(ptr, 1)); |
| break; |
| case 4: |
| ret = ValueBuilder::makeSub( |
| ValueBuilder::makeName(HEAP32), |
| ValueBuilder::makePtrShift(ptr, 2)); |
| break; |
| default: { |
| std::cerr << "Unhandled number of bytes in i32 load: " |
| << curr->bytes << std::endl; |
| abort(); |
| } |
| } |
| break; |
| } |
| case f32: |
| ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), |
| ValueBuilder::makePtrShift(ptr, 2)); |
| break; |
| case f64: |
| ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64), |
| ValueBuilder::makePtrShift(ptr, 3)); |
| break; |
| default: { |
| std::cerr << "Unhandled type in load: " << curr->type << std::endl; |
| abort(); |
| } |
| } |
| return makeAsmCoercion(ret, wasmToAsmType(curr->type)); |
| } |
| |
| Ref visitStore(Store* curr) { |
| if (module->memory.initial < module->memory.max && curr->type != unreachable) { |
| // In JS, if memory grows then it is dangerous to write |
| // HEAP[f()] = .. |
| // or |
| // HEAP[..] = f() |
| // since if the call swaps HEAP (in a growth operation) then |
| // we will not actually write to the new version (since the |
| // semantics of JS mean we already looked at HEAP and have |
| // decided where to assign to). |
| if (!FindAll<Call>(curr->ptr).list.empty() || |
| !FindAll<Call>(curr->value).list.empty() || |
| !FindAll<CallIndirect>(curr->ptr).list.empty() || |
| !FindAll<CallIndirect>(curr->value).list.empty() || |
| !FindAll<Host>(curr->ptr).list.empty() || |
| !FindAll<Host>(curr->value).list.empty()) { |
| Ref ret; |
| ScopedTemp ptr(i32, parent, func); |
| sequenceAppend(ret, visitAndAssign(curr->ptr, ptr)); |
| ScopedTemp value(curr->value->type, parent, func); |
| sequenceAppend(ret, visitAndAssign(curr->value, value)); |
| GetLocal getPtr; |
| getPtr.index = func->getLocalIndex(ptr.getName()); |
| getPtr.type = i32; |
| GetLocal getValue; |
| getValue.index = func->getLocalIndex(value.getName()); |
| getValue.type = curr->value->type; |
| Store fakeStore = *curr; |
| fakeStore.ptr = &getPtr; |
| fakeStore.value = &getValue; |
| sequenceAppend(ret, visitStore(&fakeStore)); |
| return ret; |
| } |
| } |
| // FIXME if memory growth, store ptr cannot contain a function call |
| // also other stores to memory, check them, all makeSub's |
| if (curr->align != 0 && curr->align < curr->bytes) { |
| // set the pointer to a local |
| ScopedTemp temp(i32, parent, func); |
| SetLocal set(allocator); |
| set.index = func->getLocalIndex(temp.getName()); |
| set.value = curr->ptr; |
| Ref ptrSet = visit(&set, NO_RESULT); |
| GetLocal get(allocator); |
| get.index = func->getLocalIndex(temp.getName()); |
| // set the value to a local |
| ScopedTemp tempValue(curr->value->type, parent, func); |
| SetLocal setValue(allocator); |
| setValue.index = func->getLocalIndex(tempValue.getName()); |
| setValue.value = curr->value; |
| Ref valueSet = visit(&setValue, NO_RESULT); |
| GetLocal getValue(allocator); |
| getValue.index = func->getLocalIndex(tempValue.getName()); |
| // fake stores |
| Store store = *curr; |
| store.ptr = &get; |
| store.bytes = 1; // do the worst |
| Ref rest; |
| switch (curr->valueType) { |
| case i32: { |
| Const _255(allocator); |
| _255.value = Literal(int32_t(255)); |
| _255.type = i32; |
| for (size_t i = 0; i < curr->bytes; i++) { |
| Const shift(allocator); |
| shift.value = Literal(int32_t(8*i)); |
| shift.type = i32; |
| Binary shifted(allocator); |
| shifted.op = ShrUInt32; |
| shifted.left = &getValue; |
| shifted.right = &shift; |
| shifted.type = i32; |
| Binary anded(allocator); |
| anded.op = AndInt32; |
| anded.left = i > 0 ? static_cast<Expression*>(&shifted) : static_cast<Expression*>(&getValue); |
| anded.right = &_255; |
| anded.type = i32; |
| store.value = &anded; |
| Ref part = visit(&store, NO_RESULT); |
| if (i == 0) { |
| rest = part; |
| } else { |
| rest = ValueBuilder::makeSeq(rest, part); |
| } |
| ++store.offset; |
| } |
| break; |
| } |
| default: { |
| std::cerr << "Unhandled type in store: " << curr->valueType |
| << std::endl; |
| abort(); |
| } |
| } |
| return ValueBuilder::makeSeq(ValueBuilder::makeSeq(ptrSet, valueSet), rest); |
| } |
| // normal store |
| Ref ptr = makePointer(curr->ptr, curr->offset); |
| Ref value = visit(curr->value, EXPRESSION_RESULT); |
| Ref ret; |
| switch (curr->valueType) { |
| case i32: { |
| switch (curr->bytes) { |
| case 1: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP8), ValueBuilder::makePtrShift(ptr, 0)); break; |
| case 2: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP16), ValueBuilder::makePtrShift(ptr, 1)); break; |
| case 4: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32), ValueBuilder::makePtrShift(ptr, 2)); break; |
| default: abort(); |
| } |
| break; |
| } |
| case f32: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), ValueBuilder::makePtrShift(ptr, 2)); break; |
| case f64: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64), ValueBuilder::makePtrShift(ptr, 3)); break; |
| default: { |
| std::cerr << "Unhandled type in store: " << curr->valueType |
| << std::endl; |
| abort(); |
| } |
| } |
| return ValueBuilder::makeBinary(ret, SET, value); |
| } |
| |
| Ref visitDrop(Drop* curr) { |
| return visit(curr->value, NO_RESULT); |
| } |
| |
| Ref visitConst(Const* curr) { |
| switch (curr->type) { |
| case i32: return ValueBuilder::makeInt(curr->value.geti32()); |
| // An i64 argument translates to two actual arguments to asm.js |
| // functions, so we do a bit of a hack here to get our one `Ref` to look |
| // like two function arguments. |
| case i64: { |
| auto lo = (unsigned) curr->value.geti64(); |
| auto hi = (unsigned) (curr->value.geti64() >> 32); |
| std::ostringstream out; |
| out << lo << "," << hi; |
| std::string os = out.str(); |
| IString name(os.c_str(), false); |
| return ValueBuilder::makeName(name); |
| } |
| case f32: { |
| Ref ret = ValueBuilder::makeCall(MATH_FROUND); |
| Const fake(allocator); |
| fake.value = Literal(double(curr->value.getf32())); |
| fake.type = f64; |
| ret[2]->push_back(visitConst(&fake)); |
| return ret; |
| } |
| case f64: { |
| double d = curr->value.getf64(); |
| if (d == 0 && std::signbit(d)) { // negative zero |
| return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeUnary(MINUS, ValueBuilder::makeDouble(0))); |
| } |
| return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeDouble(curr->value.getf64())); |
| } |
| default: abort(); |
| } |
| } |
| |
| Ref visitUnary(Unary* curr) { |
| // normal unary |
| switch (curr->type) { |
| case i32: { |
| switch (curr->op) { |
| case ClzInt32: |
| return ValueBuilder::makeCall( |
| MATH_CLZ32, |
| visit(curr->value, EXPRESSION_RESULT) |
| ); |
| case CtzInt32: |
| case PopcntInt32: |
| std::cerr << "i32 unary should have been removed: " << curr |
| << std::endl; |
| WASM_UNREACHABLE(); |
| case EqZInt32: |
| return ValueBuilder::makeBinary( |
| makeAsmCoercion(visit(curr->value, |
| EXPRESSION_RESULT), ASM_INT), EQ, |
| makeAsmCoercion(ValueBuilder::makeInt(0), ASM_INT)); |
| case ReinterpretFloat32: { |
| ABI::wasm2js::ensureScratchMemoryHelpers(module, ABI::wasm2js::SCRATCH_STORE_F32); |
| ABI::wasm2js::ensureScratchMemoryHelpers(module, ABI::wasm2js::SCRATCH_LOAD_I32); |
| |
| Ref store = ValueBuilder::makeCall( |
| ABI::wasm2js::SCRATCH_STORE_F32, |
| visit(curr->value, EXPRESSION_RESULT) |
| ); |
| Ref load = ValueBuilder::makeCall(ABI::wasm2js::SCRATCH_LOAD_I32, ValueBuilder::makeInt(0)); |
| return ValueBuilder::makeSeq(store, load); |
| } |
| // generate (~~expr), what Emscripten does |
| case TruncSFloat32ToInt32: |
| case TruncSFloat64ToInt32: |
| return ValueBuilder::makeUnary( |
| B_NOT, |
| ValueBuilder::makeUnary( |
| B_NOT, |
| visit(curr->value, EXPRESSION_RESULT) |
| )); |
| |
| // generate (~~expr >>> 0), what Emscripten does |
| case TruncUFloat32ToInt32: |
| case TruncUFloat64ToInt32: |
| return ValueBuilder::makeBinary( |
| ValueBuilder::makeUnary( |
| B_NOT, |
| ValueBuilder::makeUnary( |
| B_NOT, |
| visit(curr->value, EXPRESSION_RESULT) |
| ) |
| ), |
| TRSHIFT, |
| ValueBuilder::makeNum(0) |
| ); |
| |
| default: { |
| std::cerr << "Unhandled unary i32 operator: " << curr |
| << std::endl; |
| abort(); |
| } |
| } |
| } |
| case f32: |
| case f64: { |
| Ref ret; |
| switch (curr->op) { |
| case NegFloat32: |
| case NegFloat64: |
| ret = ValueBuilder::makeUnary( |
| MINUS, |
| visit(curr->value, EXPRESSION_RESULT) |
| ); |
| break; |
| case AbsFloat32: |
| case AbsFloat64: |
| ret = ValueBuilder::makeCall( |
| MATH_ABS, |
| visit(curr->value, EXPRESSION_RESULT) |
| ); |
| break; |
| case CeilFloat32: |
| case CeilFloat64: |
| ret = ValueBuilder::makeCall( |
| MATH_CEIL, |
| visit(curr->value, EXPRESSION_RESULT) |
| ); |
| break; |
| case FloorFloat32: |
| case FloorFloat64: |
| ret = ValueBuilder::makeCall( |
| MATH_FLOOR, |
| visit(curr->value, EXPRESSION_RESULT) |
| ); |
| break; |
| case SqrtFloat32: |
| case SqrtFloat64: |
| ret = ValueBuilder::makeCall( |
| MATH_SQRT, |
| visit(curr->value, EXPRESSION_RESULT) |
| ); |
| break; |
| case PromoteFloat32: |
| return makeAsmCoercion(visit(curr->value, EXPRESSION_RESULT), |
| ASM_DOUBLE); |
| case DemoteFloat64: |
| return makeAsmCoercion(visit(curr->value, EXPRESSION_RESULT), |
| ASM_FLOAT); |
| case ReinterpretInt32: { |
| ABI::wasm2js::ensureScratchMemoryHelpers(module, ABI::wasm2js::SCRATCH_STORE_I32); |
| ABI::wasm2js::ensureScratchMemoryHelpers(module, ABI::wasm2js::SCRATCH_LOAD_F32); |
| |
| Ref store = ValueBuilder::makeCall( |
| ABI::wasm2js::SCRATCH_STORE_I32, |
| ValueBuilder::makeNum(0), |
| visit(curr->value, EXPRESSION_RESULT) |
| ); |
| Ref load = ValueBuilder::makeCall(ABI::wasm2js::SCRATCH_LOAD_F32); |
| return ValueBuilder::makeSeq(store, load); |
| } |
| // Coerce the integer to a float as emscripten does |
| case ConvertSInt32ToFloat32: |
| return makeAsmCoercion( |
| makeAsmCoercion(visit(curr->value, EXPRESSION_RESULT), ASM_INT), |
| ASM_FLOAT |
| ); |
| case ConvertSInt32ToFloat64: |
| return makeAsmCoercion( |
| makeAsmCoercion(visit(curr->value, EXPRESSION_RESULT), ASM_INT), |
| ASM_DOUBLE |
| ); |
| |
| // Generate (expr >>> 0), followed by a coercion |
| case ConvertUInt32ToFloat32: |
| return makeAsmCoercion( |
| ValueBuilder::makeBinary( |
| visit(curr->value, EXPRESSION_RESULT), |
| TRSHIFT, |
| ValueBuilder::makeInt(0) |
| ), |
| ASM_FLOAT |
| ); |
| case ConvertUInt32ToFloat64: |
| return makeAsmCoercion( |
| ValueBuilder::makeBinary( |
| visit(curr->value, EXPRESSION_RESULT), |
| TRSHIFT, |
| ValueBuilder::makeInt(0) |
| ), |
| ASM_DOUBLE |
| ); |
| // TODO: more complex unary conversions |
| case NearestFloat32: |
| case NearestFloat64: |
| case TruncFloat32: |
| case TruncFloat64: |
| std::cerr << "operation should have been removed in previous passes" |
| << std::endl; |
| WASM_UNREACHABLE(); |
| |
| default: |
| std::cerr << "Unhandled unary float operator: " << curr |
| << std::endl; |
| abort(); |
| } |
| if (curr->type == f32) { // doubles need much less coercing |
| return makeAsmCoercion(ret, ASM_FLOAT); |
| } |
| return ret; |
| } |
| default: { |
| std::cerr << "Unhandled type in unary: " << curr << std::endl; |
| abort(); |
| } |
| } |
| } |
| |
| Ref visitBinary(Binary* curr) { |
| // normal binary |
| Ref left = visit(curr->left, EXPRESSION_RESULT); |
| Ref right = visit(curr->right, EXPRESSION_RESULT); |
| Ref ret; |
| switch (curr->type) { |
| case i32: { |
| switch (curr->op) { |
| case AddInt32: |
| ret = ValueBuilder::makeBinary(left, PLUS, right); |
| break; |
| case SubInt32: |
| ret = ValueBuilder::makeBinary(left, MINUS, right); |
| break; |
| case MulInt32: { |
| if (curr->type == i32) { |
| // TODO: when one operand is a small int, emit a multiply |
| return ValueBuilder::makeCall(MATH_IMUL, left, right); |
| } else { |
| return ValueBuilder::makeBinary(left, MUL, right); |
| } |
| } |
| case DivSInt32: |
| ret = ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), DIV, |
| makeSigning(right, ASM_SIGNED)); |
| break; |
| case DivUInt32: |
| ret = ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), DIV, |
| makeSigning(right, ASM_UNSIGNED)); |
| break; |
| case RemSInt32: |
| ret = ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), MOD, |
| makeSigning(right, ASM_SIGNED)); |
| break; |
| case RemUInt32: |
| ret = ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), MOD, |
| makeSigning(right, ASM_UNSIGNED)); |
| break; |
| case AndInt32: |
| ret = ValueBuilder::makeBinary(left, AND, right); |
| break; |
| case OrInt32: |
| ret = ValueBuilder::makeBinary(left, OR, right); |
| break; |
| case XorInt32: |
| ret = ValueBuilder::makeBinary(left, XOR, right); |
| break; |
| case ShlInt32: |
| ret = ValueBuilder::makeBinary(left, LSHIFT, right); |
| break; |
| case ShrUInt32: |
| ret = ValueBuilder::makeBinary(left, TRSHIFT, right); |
| break; |
| case ShrSInt32: |
| ret = ValueBuilder::makeBinary(left, RSHIFT, right); |
| break; |
| case EqInt32: { |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), EQ, |
| makeSigning(right, ASM_SIGNED)); |
| } |
| case NeInt32: { |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), NE, |
| makeSigning(right, ASM_SIGNED)); |
| } |
| case LtSInt32: |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), LT, |
| makeSigning(right, ASM_SIGNED)); |
| case LtUInt32: |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), LT, |
| makeSigning(right, ASM_UNSIGNED)); |
| case LeSInt32: |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), LE, |
| makeSigning(right, ASM_SIGNED)); |
| case LeUInt32: |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), LE, |
| makeSigning(right, ASM_UNSIGNED)); |
| case GtSInt32: |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), GT, |
| makeSigning(right, ASM_SIGNED)); |
| case GtUInt32: |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), GT, |
| makeSigning(right, ASM_UNSIGNED)); |
| case GeSInt32: |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), GE, |
| makeSigning(right, ASM_SIGNED)); |
| case GeUInt32: |
| return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), GE, |
| makeSigning(right, ASM_UNSIGNED)); |
| case EqFloat32: |
| case EqFloat64: |
| return ValueBuilder::makeBinary(left, EQ, right); |
| case NeFloat32: |
| case NeFloat64: |
| return ValueBuilder::makeBinary(left, NE, right); |
| case GeFloat32: |
| case GeFloat64: |
| return ValueBuilder::makeBinary(left, GE, right); |
| case GtFloat32: |
| case GtFloat64: |
| return ValueBuilder::makeBinary(left, GT, right); |
| case LeFloat32: |
| case LeFloat64: |
| return ValueBuilder::makeBinary(left, LE, right); |
| case LtFloat32: |
| case LtFloat64: |
| return ValueBuilder::makeBinary(left, LT, right); |
| case RotLInt32: |
| case RotRInt32: |
| std::cerr << "should be removed already" << std::endl; |
| WASM_UNREACHABLE(); |
| default: { |
| std::cerr << "Unhandled i32 binary operator: " << curr << std::endl; |
| abort(); |
| } |
| } |
| break; |
| } |
| case f32: |
| case f64: |
| switch (curr->op) { |
| case AddFloat32: |
| case AddFloat64: |
| ret = ValueBuilder::makeBinary(left, PLUS, right); |
| break; |
| case SubFloat32: |
| case SubFloat64: |
| ret = ValueBuilder::makeBinary(left, MINUS, right); |
| break; |
| case MulFloat32: |
| case MulFloat64: |
| ret = ValueBuilder::makeBinary(left, MUL, right); |
| break; |
| case DivFloat32: |
| case DivFloat64: |
| ret = ValueBuilder::makeBinary(left, DIV, right); |
| break; |
| case MinFloat32: |
| case MinFloat64: |
| ret = ValueBuilder::makeCall(MATH_MIN, left, right); |
| break; |
| case MaxFloat32: |
| case MaxFloat64: |
| ret = ValueBuilder::makeCall(MATH_MAX, left, right); |
| break; |
| case CopySignFloat32: |
| case CopySignFloat64: |
| default: |
| std::cerr << "Unhandled binary float operator: " << curr << std::endl; |
| abort(); |
| } |
| if (curr->type == f32) { |
| return makeAsmCoercion(ret, ASM_FLOAT); |
| } |
| return ret; |
| default: |
| std::cerr << "Unhandled type in binary: " << curr << std::endl; |
| abort(); |
| } |
| return makeAsmCoercion(ret, wasmToAsmType(curr->type)); |
| } |
| |
| Ref visitSelect(Select* curr) { |
| // normal select |
| ScopedTemp tempIfTrue(curr->type, parent, func), |
| tempIfFalse(curr->type, parent, func), |
| tempCondition(i32, parent, func); |
| Ref ifTrue = visit(curr->ifTrue, EXPRESSION_RESULT); |
| Ref ifFalse = visit(curr->ifFalse, EXPRESSION_RESULT); |
| Ref condition = visit(curr->condition, EXPRESSION_RESULT); |
| return |
| ValueBuilder::makeSeq( |
| ValueBuilder::makeBinary(tempIfTrue.getAstName(), SET, ifTrue), |
| ValueBuilder::makeSeq( |
| ValueBuilder::makeBinary(tempIfFalse.getAstName(), SET, ifFalse), |
| ValueBuilder::makeSeq( |
| ValueBuilder::makeBinary(tempCondition.getAstName(), SET, condition), |
| ValueBuilder::makeConditional( |
| tempCondition.getAstName(), |
| tempIfTrue.getAstName(), |
| tempIfFalse.getAstName() |
| ) |
| ) |
| ) |
| ); |
| } |
| |
| Ref visitReturn(Return* curr) { |
| if (!curr->value) { |
| return ValueBuilder::makeReturn(Ref()); |
| } |
| Ref val = makeAsmCoercion( |
| visit(curr->value, EXPRESSION_RESULT), |
| wasmToAsmType(curr->value->type) |
| ); |
| return ValueBuilder::makeReturn(val); |
| } |
| |
| Ref visitHost(Host* curr) { |
| if (curr->op == HostOp::GrowMemory) { |
| if (module->memory.exists && module->memory.max > module->memory.initial) { |
| return ValueBuilder::makeCall(WASM_GROW_MEMORY, |
| makeAsmCoercion( |
| visit(curr->operands[0], EXPRESSION_RESULT), |
| wasmToAsmType(curr->operands[0]->type))); |
| } else { |
| return ValueBuilder::makeCall(ABORT_FUNC); |
| } |
| } else if (curr->op == HostOp::CurrentMemory) { |
| return ValueBuilder::makeCall(WASM_CURRENT_MEMORY); |
| } |
| WASM_UNREACHABLE(); // TODO |
| } |
| |
| Ref visitNop(Nop* curr) { |
| return ValueBuilder::makeToplevel(); |
| } |
| |
| Ref visitUnreachable(Unreachable* curr) { |
| return ValueBuilder::makeCall(ABORT_FUNC); |
| } |
| |
| private: |
| Ref makePointer(Expression* ptr, Address offset) { |
| auto ret = visit(ptr, EXPRESSION_RESULT); |
| if (offset) { |
| ret = makeAsmCoercion( |
| ValueBuilder::makeBinary(ret, PLUS, ValueBuilder::makeNum(offset)), |
| ASM_INT); |
| } |
| return ret; |
| } |
| }; |
| |
| return ExpressionProcessor(this, m, func).visit(func->body, NO_RESULT); |
| } |
| |
| void Wasm2JSBuilder::addMemoryGrowthFuncs(Ref ast, Module* wasm) { |
| Ref growMemoryFunc = ValueBuilder::makeFunction(WASM_GROW_MEMORY); |
| ValueBuilder::appendArgumentToFunction(growMemoryFunc, IString("pagesToAdd")); |
| |
| growMemoryFunc[3]->push_back( |
| ValueBuilder::makeStatement( |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeName(IString("pagesToAdd")), SET, |
| makeAsmCoercion( |
| ValueBuilder::makeName(IString("pagesToAdd")), |
| AsmType::ASM_INT |
| ) |
| ) |
| ) |
| ); |
| |
| Ref oldPages = ValueBuilder::makeVar(); |
| growMemoryFunc[3]->push_back(oldPages); |
| ValueBuilder::appendToVar( |
| oldPages, |
| IString("oldPages"), |
| makeAsmCoercion(ValueBuilder::makeCall(WASM_CURRENT_MEMORY), AsmType::ASM_INT)); |
| |
| Ref newPages = ValueBuilder::makeVar(); |
| growMemoryFunc[3]->push_back(newPages); |
| ValueBuilder::appendToVar( |
| newPages, |
| IString("newPages"), |
| makeAsmCoercion(ValueBuilder::makeBinary( |
| ValueBuilder::makeName(IString("oldPages")), |
| PLUS, |
| ValueBuilder::makeName(IString("pagesToAdd")) |
| ), AsmType::ASM_INT)); |
| |
| Ref block = ValueBuilder::makeBlock(); |
| growMemoryFunc[3]->push_back(ValueBuilder::makeIf( |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeName(IString("oldPages")), |
| LT, |
| ValueBuilder::makeName(IString("newPages")) |
| ), |
| IString("&&"), |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeName(IString("newPages")), |
| LT, |
| ValueBuilder::makeInt(Memory::kMaxSize) |
| ) |
| ), block, NULL)); |
| |
| Ref newBuffer = ValueBuilder::makeVar(); |
| ValueBuilder::appendToBlock(block, newBuffer); |
| ValueBuilder::appendToVar( |
| newBuffer, |
| IString("newBuffer"), |
| ValueBuilder::makeNew(ValueBuilder::makeCall( |
| ARRAY_BUFFER, |
| ValueBuilder::makeCall( |
| MATH_IMUL, |
| ValueBuilder::makeName(IString("newPages")), |
| ValueBuilder::makeInt(Memory::kPageSize))))); |
| |
| Ref newHEAP8 = ValueBuilder::makeVar(); |
| ValueBuilder::appendToBlock(block, newHEAP8); |
| ValueBuilder::appendToVar( |
| newHEAP8, |
| IString("newHEAP8"), |
| ValueBuilder::makeNew( |
| ValueBuilder::makeCall( |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName(GLOBAL), |
| INT8ARRAY |
| ), |
| ValueBuilder::makeName(IString("newBuffer")) |
| ) |
| )); |
| |
| ValueBuilder::appendToBlock(block, |
| ValueBuilder::makeCall( |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName(IString("newHEAP8")), |
| IString("set") |
| ), |
| ValueBuilder::makeName(HEAP8) |
| ) |
| ); |
| |
| ValueBuilder::appendToBlock(block, |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeName(HEAP8), |
| SET, |
| ValueBuilder::makeName(IString("newHEAP8")) |
| ) |
| ); |
| |
| auto setHeap = [&](IString name, IString view) { |
| ValueBuilder::appendToBlock(block, |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeName(name), |
| SET, |
| ValueBuilder::makeNew( |
| ValueBuilder::makeCall( |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName(GLOBAL), |
| view |
| ), |
| ValueBuilder::makeName(IString("newBuffer")) |
| ) |
| ) |
| ) |
| ); |
| }; |
| |
| setHeap(HEAP16, INT16ARRAY); |
| setHeap(HEAP32, INT32ARRAY); |
| setHeap(HEAPU8, UINT8ARRAY); |
| setHeap(HEAPU16, UINT16ARRAY); |
| setHeap(HEAPU32, UINT32ARRAY); |
| setHeap(HEAPF32, FLOAT32ARRAY); |
| setHeap(HEAPF64, FLOAT64ARRAY); |
| |
| ValueBuilder::appendToBlock(block, |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeName(BUFFER), |
| SET, |
| ValueBuilder::makeName(IString("newBuffer")) |
| ) |
| ); |
| |
| // apply the changes to the memory import |
| if (wasm->memory.imported()) { |
| ValueBuilder::appendToBlock(block, |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName("memory"), |
| ValueBuilder::makeName(BUFFER) |
| ), |
| SET, |
| ValueBuilder::makeName(IString("newBuffer")) |
| ) |
| ); |
| } |
| |
| growMemoryFunc[3]->push_back( |
| ValueBuilder::makeReturn( |
| ValueBuilder::makeName(IString("oldPages")))); |
| |
| Ref currentMemoryFunc = ValueBuilder::makeFunction(WASM_CURRENT_MEMORY); |
| currentMemoryFunc[3]->push_back(ValueBuilder::makeReturn( |
| makeAsmCoercion( |
| ValueBuilder::makeBinary( |
| ValueBuilder::makeDot( |
| ValueBuilder::makeName(BUFFER), |
| IString("byteLength") |
| ), |
| DIV, |
| ValueBuilder::makeInt(Memory::kPageSize) |
| ), |
| AsmType::ASM_INT |
| ) |
| )); |
| ast->push_back(growMemoryFunc); |
| ast->push_back(currentMemoryFunc); |
| } |
| |
| // Wasm2JSGlue emits the core of the module - the functions etc. that would |
| // be the asm.js function in an asm.js world. This class emits the rest of the |
| // "glue" around that. |
| class Wasm2JSGlue { |
| public: |
| Wasm2JSGlue(Module& wasm, Output& out, Wasm2JSBuilder::Flags flags, Name moduleName) : wasm(wasm), out(out), flags(flags), moduleName(moduleName) {} |
| |
| void emitPre(); |
| void emitPost(); |
| |
| private: |
| Module& wasm; |
| Output& out; |
| Wasm2JSBuilder::Flags flags; |
| Name moduleName; |
| |
| void emitPreEmscripten(); |
| void emitPreES6(); |
| void emitPostEmscripten(); |
| void emitPostES6(); |
| |
| void emitMemory(std::string buffer, std::string segmentWriter); |
| void emitScratchMemorySupport(); |
| }; |
| |
| void Wasm2JSGlue::emitPre() { |
| if (flags.emscripten) { |
| emitPreEmscripten(); |
| } else { |
| emitPreES6(); |
| } |
| |
| emitScratchMemorySupport(); |
| } |
| |
| void Wasm2JSGlue::emitPreEmscripten() { |
| out << "function instantiate(asmLibraryArg, wasmMemory, FUNCTION_TABLE) {\n\n"; |
| } |
| |
| void Wasm2JSGlue::emitPreES6() { |
| std::unordered_map<Name, Name> baseModuleMap; |
| |
| auto noteImport = [&](Name module, Name base) { |
| // Right now codegen requires a flat namespace going into the module, |
| // meaning we don't support importing the same name from multiple namespaces yet. |
| if (baseModuleMap.count(base) && baseModuleMap[base] != module) { |
| Fatal() << "the name " << base << " cannot be imported from " |
| << "two different modules yet\n"; |
| abort(); |
| } |
| baseModuleMap[base] = module; |
| |
| out << "import { " |
| << base.str |
| << " } from '" |
| << module.str |
| << "';\n"; |
| }; |
| |
| ImportInfo imports(wasm); |
| |
| ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) { |
| noteImport(import->module, import->base); |
| }); |
| ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { |
| // The scratch memory helpers are emitted in the glue, see code and comments below. |
| if (ABI::wasm2js::isScratchMemoryHelper(import->base)) { |
| return; |
| } |
| noteImport(import->module, import->base); |
| }); |
| |
| out << '\n'; |
| } |
| |
| void Wasm2JSGlue::emitPost() { |
| if (flags.emscripten) { |
| emitPostEmscripten(); |
| } else { |
| emitPostES6(); |
| } |
| } |
| |
| void Wasm2JSGlue::emitPostEmscripten() { |
| emitMemory("wasmMemory.buffer", "writeSegment"); |
| |
| out << "return asmFunc({\n" |
| << " 'Int8Array': Int8Array,\n" |
| << " 'Int16Array': Int16Array,\n" |
| << " 'Int32Array': Int32Array,\n" |
| << " 'Uint8Array': Uint8Array,\n" |
| << " 'Uint16Array': Uint16Array,\n" |
| << " 'Uint32Array': Uint32Array,\n" |
| << " 'Float32Array': Float32Array,\n" |
| << " 'Float64Array': Float64Array,\n" |
| << " 'NaN': NaN,\n" |
| << " 'Infinity': Infinity,\n" |
| << " 'Math': Math\n" |
| << " },\n" |
| << " asmLibraryArg,\n" |
| << " wasmMemory.buffer\n" |
| << ")" |
| << "\n" |
| << "\n" |
| << "}"; |
| } |
| |
| void Wasm2JSGlue::emitPostES6() { |
| // Create an initial `ArrayBuffer` and populate it with static data. |
| // Currently we use base64 encoding to encode static data and we decode it at |
| // instantiation time. |
| // |
| // Note that the translation here expects that the lower values of this memory |
| // can be used for conversions, so make sure there's at least one page. |
| { |
| auto pages = wasm.memory.initial == 0 ? 1 : wasm.memory.initial.addr; |
| out << "var mem" << moduleName.str << " = new ArrayBuffer(" |
| << pages * Memory::kPageSize |
| << ");\n"; |
| } |
| |
| emitMemory(std::string("mem") + moduleName.str, |
| std::string("assign") + moduleName.str); |
| |
| // Actually invoke the `asmFunc` generated function, passing in all global |
| // values followed by all imports |
| out << "var ret" << moduleName.str << " = " << moduleName.str << "({" |
| << "Math," |
| << "Int8Array," |
| << "Uint8Array," |
| << "Int16Array," |
| << "Uint16Array," |
| << "Int32Array," |
| << "Uint32Array," |
| << "Float32Array," |
| << "Float64Array," |
| << "NaN," |
| << "Infinity" |
| << "}, {"; |
| |
| out << "abort:function() { throw new Error('abort'); }"; |
| |
| ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { |
| // The scratch memory helpers are emitted in the glue, see code and comments below. |
| if (ABI::wasm2js::isScratchMemoryHelper(import->base)) { |
| return; |
| } |
| out << "," << import->base.str; |
| }); |
| out << "},mem" << moduleName.str << ");\n"; |
| |
| if (flags.allowAsserts) { |
| return; |
| } |
| |
| // And now that we have our returned instance, export all our functions |
| // that are hanging off it. |
| for (auto& exp : wasm.exports) { |
| switch (exp->kind) { |
| case ExternalKind::Function: |
| case ExternalKind::Memory: |
| break; |
| |
| // Exported globals and function tables aren't supported yet |
| default: |
| continue; |
| } |
| std::ostringstream export_name; |
| for (auto *ptr = exp->name.str; *ptr; ptr++) { |
| if (*ptr == '-') { |
| export_name << '_'; |
| } else { |
| export_name << *ptr; |
| } |
| } |
| out << "export var " |
| << asmangle(exp->name.str) |
| << " = ret" |
| << moduleName.str |
| << "." |
| << asmangle(exp->name.str) |
| << ";\n"; |
| } |
| } |
| |
| void Wasm2JSGlue::emitMemory(std::string buffer, std::string segmentWriter) { |
| if (wasm.memory.segments.empty()) return; |
| |
| auto expr = R"( |
| function(mem) { |
| var _mem = new Uint8Array(mem); |
| return function(offset, s) { |
| if (typeof Buffer === 'undefined') { |
| var bytes = atob(s); |
| for (var i = 0; i < bytes.length; i++) |
| _mem[offset + i] = bytes.charCodeAt(i); |
| } else { |
| var bytes = Buffer.from(s, 'base64'); |
| for (var i = 0; i < bytes.length; i++) |
| _mem[offset + i] = bytes[i]; |
| } |
| } |
| } |
| )"; |
| |
| // var assign$name = ($expr)(mem$name); |
| out << "var " << segmentWriter |
| << " = (" << expr << ")(" << buffer << ");\n"; |
| |
| for (auto& seg : wasm.memory.segments) { |
| assert(!seg.isPassive && "passive segments not implemented yet"); |
| out << segmentWriter << "(" |
| << constOffset(seg) |
| << ", \"" |
| << base64Encode(seg.data) |
| << "\");\n"; |
| } |
| } |
| |
| void Wasm2JSGlue::emitScratchMemorySupport() { |
| // The scratch memory helpers are emitted here the glue. We may also want to |
| // emit them inline at some point. (The reason they are imports is so that |
| // they appear as "intrinsics" placeholders, and not normal functions that |
| // the optimizer might want to do something with.) |
| bool needScratchMemory = false; |
| ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { |
| if (ABI::wasm2js::isScratchMemoryHelper(import->base)) { |
| needScratchMemory = true; |
| } |
| }); |
| if (!needScratchMemory) return; |
| |
| out << R"( |
| var scratchBuffer = new ArrayBuffer(8); |
| var i32ScratchView = new Int32Array(scratchBuffer); |
| var f32ScratchView = new Float32Array(scratchBuffer); |
| var f64ScratchView = new Float64Array(scratchBuffer); |
| )"; |
| |
| ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { |
| if (import->base == ABI::wasm2js::SCRATCH_STORE_I32) { |
| out << R"( |
| function wasm2js_scratch_store_i32(index, value) { |
| i32ScratchView[index] = value; |
| } |
| )"; |
| } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_I32) { |
| out << R"( |
| function wasm2js_scratch_load_i32(index) { |
| return i32ScratchView[index]; |
| } |
| )"; |
| } else if (import->base == ABI::wasm2js::SCRATCH_STORE_I64) { |
| out << R"( |
| function legalimport$wasm2js_scratch_store_i64(low, high) { |
| i32ScratchView[0] = low; |
| i32ScratchView[1] = high; |
| } |
| )"; |
| } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_I64) { |
| out << R"( |
| function legalimport$wasm2js_scratch_load_i64() { |
| if (typeof setTempRet0 === 'function') setTempRet0(i32ScratchView[1]); |
| return i32ScratchView[0]; |
| } |
| )"; |
| } else if (import->base == ABI::wasm2js::SCRATCH_STORE_F32) { |
| out << R"( |
| function wasm2js_scratch_store_f32(value) { |
| f32ScratchView[0] = value; |
| } |
| )"; |
| } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_F32) { |
| out << R"( |
| function wasm2js_scratch_load_f32() { |
| return f32ScratchView[0]; |
| } |
| )"; |
| } else if (import->base == ABI::wasm2js::SCRATCH_STORE_F64) { |
| out << R"( |
| function wasm2js_scratch_store_f64(value) { |
| f64ScratchView[0] = value; |
| } |
| )"; |
| } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_F64) { |
| out << R"( |
| function wasm2js_scratch_load_f64() { |
| return f64ScratchView[0]; |
| } |
| )"; |
| } |
| }); |
| out << '\n'; |
| } |
| |
| } // namespace wasm |
| |
| #endif // wasm_wasm2js_h |