| /* |
| * 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 intepreter for asm2wasm output, in a js environment. |
| // |
| // Receives asm.js, generates a runnable module that executes the code in a WebAssembly |
| // interpreter. This is suitable as a polyfill for WebAssembly support in browsers. |
| // |
| |
| #include <emscripten.h> |
| |
| #include "asm2wasm.h" |
| #include "wasm-interpreter.h" |
| #include "wasm-s-parser.h" |
| #include "wasm-binary.h" |
| #include "wasm-printing.h" |
| |
| using namespace cashew; |
| using namespace wasm; |
| |
| namespace wasm { |
| int debug = 0; |
| } |
| |
| // global singletons |
| Asm2WasmBuilder* asm2wasm = nullptr; |
| SExpressionParser* sExpressionParser = nullptr; |
| SExpressionWasmBuilder* sExpressionWasmBuilder = nullptr; |
| ModuleInstance* instance = nullptr; |
| Module* module = nullptr; |
| bool wasmJSDebug = false; |
| |
| static void prepare2wasm() { |
| assert(asm2wasm == nullptr && sExpressionParser == nullptr && sExpressionWasmBuilder == nullptr && instance == nullptr); // singletons |
| #if WASM_JS_DEBUG |
| wasmJSDebug = 1; |
| #else |
| wasmJSDebug = EM_ASM_INT_V({ return !!Module['outside']['WASM_JS_DEBUG'] }); // Set WASM_JS_DEBUG on the outside Module to get debugging |
| #endif |
| } |
| |
| // receives asm.js code, parses into wasm. |
| // note: this modifies the input. |
| extern "C" void EMSCRIPTEN_KEEPALIVE load_asm2wasm(char *input) { |
| prepare2wasm(); |
| |
| Asm2WasmPreProcessor pre; |
| input = pre.process(input); |
| |
| // proceed to parse and wasmify |
| if (wasmJSDebug) std::cerr << "asm parsing...\n"; |
| |
| cashew::Parser<Ref, DotZeroValueBuilder> builder; |
| Ref asmjs = builder.parseToplevel(input); |
| |
| module = new Module(); |
| uint32_t providedMemory = EM_ASM_INT_V({ |
| return Module['providedTotalMemory']; // we receive the size of memory from emscripten |
| }); |
| if (providedMemory & ~Memory::kPageMask) { |
| std::cerr << "Error: provided memory is not a multiple of the 64k wasm page size\n"; |
| exit(EXIT_FAILURE); |
| } |
| module->memory.initial = Address(providedMemory / Memory::kPageSize); |
| module->memory.max = pre.memoryGrowth ? Address(Memory::kMaxSize) : module->memory.initial; |
| |
| if (wasmJSDebug) std::cerr << "wasming...\n"; |
| asm2wasm = new Asm2WasmBuilder(*module, pre.memoryGrowth, debug, false /* TODO: support imprecise? */, false /* TODO: support optimizing? */); |
| asm2wasm->processAsm(asmjs); |
| } |
| |
| void finalizeModule() { |
| uint32_t providedMemory = EM_ASM_INT_V({ |
| return Module['providedTotalMemory']; // we receive the size of memory from emscripten |
| }); |
| if (providedMemory & ~Memory::kPageMask) { |
| std::cerr << "Error: provided memory is not a multiple of the 64k wasm page size\n"; |
| exit(EXIT_FAILURE); |
| } |
| module->memory.initial = Address(providedMemory / Memory::kPageSize); |
| module->memory.max = module->checkExport(GROW_WASM_MEMORY) ? Address(Memory::kMaxSize) : module->memory.initial; |
| |
| // global mapping is done in js in post.js |
| } |
| |
| // loads wasm code in s-expression format |
| extern "C" void EMSCRIPTEN_KEEPALIVE load_s_expr2wasm(char *input) { |
| prepare2wasm(); |
| |
| if (wasmJSDebug) std::cerr << "wasm-s-expression parsing...\n"; |
| |
| sExpressionParser = new SExpressionParser(input); |
| Element& root = *sExpressionParser->root; |
| if (wasmJSDebug) std::cout << root << '\n'; |
| |
| if (wasmJSDebug) std::cerr << "wasming...\n"; |
| |
| module = new Module(); |
| // A .wast may have multiple modules, with some asserts after them, but we just read the first here. |
| sExpressionWasmBuilder = new SExpressionWasmBuilder(*module, *root[0]); |
| |
| finalizeModule(); |
| } |
| |
| // loads wasm code in binary format |
| extern "C" void EMSCRIPTEN_KEEPALIVE load_binary2wasm(char *raw, int32_t size) { |
| prepare2wasm(); |
| |
| if (wasmJSDebug) std::cerr << "wasm-binary parsing...\n"; |
| |
| module = new Module(); |
| std::vector<char> input; |
| input.resize(size); |
| for (int32_t i = 0; i < size; i++) { |
| input[i] = raw[i]; |
| } |
| WasmBinaryBuilder parser(*module, input, debug); |
| parser.read(); |
| |
| finalizeModule(); |
| } |
| |
| // instantiates the loaded wasm (which might be from asm2wasm, or |
| // s-expressions, or something else) with a JS external interface. |
| extern "C" void EMSCRIPTEN_KEEPALIVE instantiate() { |
| if (wasmJSDebug) std::cerr << "instantiating module: \n" << module << '\n'; |
| |
| if (wasmJSDebug) std::cerr << "generating exports...\n"; |
| |
| EM_ASM({ |
| Module['asmExports'] = {}; |
| }); |
| for (auto& curr : module->exports) { |
| if (curr->kind == Export::Function) { |
| EM_ASM_({ |
| var name = Pointer_stringify($0); |
| Module['asmExports'][name] = function() { |
| Module['tempArguments'] = Array.prototype.slice.call(arguments); |
| Module['_call_from_js']($0); |
| return Module['tempReturn']; |
| }; |
| }, curr->name.str); |
| } |
| } |
| |
| // verify imports are provided |
| for (auto& import : module->imports) { |
| EM_ASM_({ |
| var mod = Pointer_stringify($0); |
| var base = Pointer_stringify($1); |
| var name = Pointer_stringify($2); |
| assert(Module['lookupImport'](mod, base) !== undefined, 'checking import ' + name + ' = ' + mod + '.' + base); |
| }, import->module.str, import->base.str, import->name.str); |
| } |
| |
| if (wasmJSDebug) std::cerr << "creating instance...\n"; |
| |
| struct JSExternalInterface : ModuleInstance::ExternalInterface { |
| void init(Module& wasm, ModuleInstance& instance) override { |
| // look for imported memory |
| { |
| bool found = false; |
| for (auto& import : wasm.imports) { |
| if (import->module == ENV && import->base == MEMORY) { |
| assert(import->kind == Import::Memory); |
| // memory is imported |
| EM_ASM({ |
| Module['asmExports']['memory'] = Module['lookupImport']('env', 'memory'); |
| }); |
| found = true; |
| } |
| } |
| if (!found) { |
| // no memory import; create a new buffer here, just like native wasm support would. |
| EM_ASM_({ |
| Module['asmExports']['memory'] = Module['outside']['newBuffer'] = new ArrayBuffer($0); |
| }, wasm.memory.initial * Memory::kPageSize); |
| } |
| } |
| for (auto segment : wasm.memory.segments) { |
| EM_ASM_({ |
| var source = Module['HEAP8'].subarray($1, $1 + $2); |
| var target = new Int8Array(Module['asmExports']['memory']); |
| target.set(source, $0); |
| }, ConstantExpressionRunner(instance.globals).visit(segment.offset).value.geti32(), &segment.data[0], segment.data.size()); |
| } |
| // look for imported table |
| { |
| bool found = false; |
| for (auto& import : wasm.imports) { |
| if (import->module == ENV && import->base == TABLE) { |
| assert(import->kind == Import::Table); |
| // table is imported |
| EM_ASM({ |
| Module['outside']['wasmTable'] = Module['lookupImport']('env', 'table'); |
| }); |
| found = true; |
| } |
| } |
| if (!found) { |
| // no table import; create a new one here, just like native wasm support would. |
| EM_ASM_({ |
| Module['outside']['wasmTable'] = new Array($0); |
| }, wasm.table.initial); |
| } |
| } |
| EM_ASM({ |
| Module['asmExports']['table'] = Module['outside']['wasmTable']; |
| }); |
| // Emulated table support is in a JS array. If the entry is a number, it's a function pointer. If not, it's a JS method to be called directly |
| // TODO: make them all JS methods, wrapping a dynCall where necessary? |
| for (auto segment : wasm.table.segments) { |
| Address offset = ConstantExpressionRunner(instance.globals).visit(segment.offset).value.geti32(); |
| assert(offset + segment.data.size() <= wasm.table.initial); |
| for (size_t i = 0; i != segment.data.size(); ++i) { |
| EM_ASM_({ |
| Module['outside']['wasmTable'][$0] = $1; |
| }, offset + i, wasm.getFunction(segment.data[i])); |
| } |
| } |
| } |
| |
| void prepareTempArgments(LiteralList& arguments) { |
| EM_ASM({ |
| Module['tempArguments'] = []; |
| }); |
| for (auto& argument : arguments) { |
| if (argument.type == i32) { |
| EM_ASM_({ Module['tempArguments'].push($0) }, argument.geti32()); |
| } else if (argument.type == f32) { |
| EM_ASM_({ Module['tempArguments'].push($0) }, argument.getf32()); |
| } else if (argument.type == f64) { |
| EM_ASM_({ Module['tempArguments'].push($0) }, argument.getf64()); |
| } else { |
| abort(); |
| } |
| } |
| } |
| |
| Literal getResultFromJS(double ret, WasmType type) { |
| switch (type) { |
| case none: return Literal(0); |
| case i32: return Literal((int32_t)ret); |
| case f32: return Literal((float)ret); |
| case f64: return Literal((double)ret); |
| default: abort(); |
| } |
| } |
| |
| void importGlobals(std::map<Name, Literal>& globals, Module& wasm) override { |
| for (auto& import : wasm.imports) { |
| if (import->kind == Import::Global) { |
| double ret = EM_ASM_DOUBLE({ |
| var mod = Pointer_stringify($0); |
| var base = Pointer_stringify($1); |
| var lookup = Module['lookupImport'](mod, base); |
| return lookup; |
| }, import->module.str, import->base.str); |
| |
| if (wasmJSDebug) std::cout << "calling importGlobal for " << import->name << " returning " << ret << '\n'; |
| |
| globals[import->name] = getResultFromJS(ret, import->globalType); |
| } |
| } |
| } |
| |
| Literal callImport(Import *import, LiteralList& arguments) override { |
| if (wasmJSDebug) std::cout << "calling import " << import->name.str << '\n'; |
| prepareTempArgments(arguments); |
| double ret = EM_ASM_DOUBLE({ |
| var mod = Pointer_stringify($0); |
| var base = Pointer_stringify($1); |
| var tempArguments = Module['tempArguments']; |
| Module['tempArguments'] = null; |
| var lookup = Module['lookupImport'](mod, base); |
| return lookup.apply(null, tempArguments); |
| }, import->module.str, import->base.str); |
| |
| if (wasmJSDebug) std::cout << "calling import returning " << ret << '\n'; |
| |
| return getResultFromJS(ret, import->functionType->result); |
| } |
| |
| Literal callTable(Index index, LiteralList& arguments, WasmType result, ModuleInstance& instance) override { |
| void* ptr = (void*)EM_ASM_INT({ |
| var value = Module['outside']['wasmTable'][$0]; |
| return typeof value === "number" ? value : -1; |
| }, index); |
| if (ptr == nullptr) trap("callTable overflow"); |
| if (ptr != (void*)-1) { |
| // a Function we can call |
| Function* func = (Function*)ptr; |
| if (func->params.size() != arguments.size()) trap("callIndirect: bad # of arguments"); |
| for (size_t i = 0; i < func->params.size(); i++) { |
| if (func->params[i] != arguments[i].type) { |
| trap("callIndirect: bad argument type"); |
| } |
| } |
| return instance.callFunctionInternal(func->name, arguments); |
| } else { |
| // A JS function JS can call |
| prepareTempArgments(arguments); |
| double ret = EM_ASM_DOUBLE({ |
| var func = Module['outside']['wasmTable'][$0]; |
| var tempArguments = Module['tempArguments']; |
| Module['tempArguments'] = null; |
| return func.apply(null, tempArguments); |
| }, index); |
| return getResultFromJS(ret, result); |
| } |
| } |
| |
| Literal load(Load* load, Address address) override { |
| uint32_t addr = address; |
| if (load->align < load->bytes || (addr & (load->bytes-1))) { |
| int64_t out64; |
| double ret = EM_ASM_DOUBLE({ |
| var addr = $0; |
| var bytes = $1; |
| var isFloat = $2; |
| var isSigned = $3; |
| var out64 = $4; |
| var save0 = HEAP32[0]; |
| var save1 = HEAP32[1]; |
| for (var i = 0; i < bytes; i++) { |
| HEAPU8[i] = Module["info"].parent["HEAPU8"][addr + i]; |
| } |
| var ret; |
| if (!isFloat) { |
| if (bytes === 1) ret = isSigned ? HEAP8[0] : HEAPU8[0]; |
| else if (bytes === 2) ret = isSigned ? HEAP16[0] : HEAPU16[0]; |
| else if (bytes === 4) ret = isSigned ? HEAP32[0] : HEAPU32[0]; |
| else if (bytes === 8) { |
| for (var i = 0; i < bytes; i++) { |
| HEAPU8[out64 + i] = HEAPU8[i]; |
| } |
| } else abort(); |
| } else { |
| if (bytes === 4) ret = HEAPF32[0]; |
| else if (bytes === 8) ret = HEAPF64[0]; |
| else abort(); |
| } |
| HEAP32[0] = save0; HEAP32[1] = save1; |
| return ret; |
| }, (uint32_t)addr, load->bytes, isWasmTypeFloat(load->type), load->signed_, &out64); |
| if (!isWasmTypeFloat(load->type)) { |
| if (load->type == i64) { |
| if (load->bytes == 8) { |
| return Literal(out64); |
| } else { |
| if (load->signed_) { |
| return Literal(int64_t(int32_t(ret))); |
| } else { |
| return Literal(int64_t(uint32_t(ret))); |
| } |
| } |
| } |
| return Literal((int32_t)ret); |
| } else if (load->bytes == 4) { |
| return Literal((float)ret); |
| } else if (load->bytes == 8) { |
| return Literal((double)ret); |
| } |
| abort(); |
| } |
| // nicely aligned |
| if (!isWasmTypeFloat(load->type)) { |
| int64_t ret; |
| if (load->bytes == 1) { |
| if (load->signed_) { |
| ret = EM_ASM_INT({ return Module['info'].parent['HEAP8'][$0] }, addr); |
| } else { |
| ret = EM_ASM_INT({ return Module['info'].parent['HEAPU8'][$0] }, addr); |
| } |
| } else if (load->bytes == 2) { |
| if (load->signed_) { |
| ret = EM_ASM_INT({ return Module['info'].parent['HEAP16'][$0 >> 1] }, addr); |
| } else { |
| ret = EM_ASM_INT({ return Module['info'].parent['HEAPU16'][$0 >> 1] }, addr); |
| } |
| } else if (load->bytes == 4) { |
| if (load->signed_) { |
| ret = EM_ASM_INT({ return Module['info'].parent['HEAP32'][$0 >> 2] }, addr); |
| } else { |
| ret = uint32_t(EM_ASM_INT({ return Module['info'].parent['HEAPU32'][$0 >> 2] }, addr)); |
| } |
| } else if (load->bytes == 8) { |
| uint32_t low = EM_ASM_INT({ return Module['info'].parent['HEAP32'][$0 >> 2] }, addr); |
| uint32_t high = EM_ASM_INT({ return Module['info'].parent['HEAP32'][$0 >> 2] }, addr + 4); |
| ret = uint64_t(low) | (uint64_t(high) << 32); |
| } else abort(); |
| return load->type == i32 ? Literal(int32_t(ret)) : Literal(ret); |
| } else { |
| if (load->bytes == 4) { |
| return Literal((float)EM_ASM_DOUBLE({ return Module['info'].parent['HEAPF32'][$0 >> 2] }, addr)); |
| } else if (load->bytes == 8) { |
| return Literal(EM_ASM_DOUBLE({ return Module['info'].parent['HEAPF64'][$0 >> 3] }, addr)); |
| } |
| abort(); |
| } |
| } |
| |
| void store(Store* store_, Address address, Literal value) override { |
| uint32_t addr = address; |
| // support int64 stores |
| if (value.type == WasmType::i64) { |
| Store fake = *store_; |
| fake.bytes = 4; |
| fake.type = i32; |
| uint64_t v = value.geti64(); |
| store(&fake, addr, Literal(uint32_t(v))); |
| v >>= 32; |
| store(&fake, addr + 4, Literal(uint32_t(v))); |
| return; |
| } |
| // normal non-int64 value |
| if (store_->align < store_->bytes || (addr & (store_->bytes-1))) { |
| EM_ASM_DOUBLE({ |
| var addr = $0; |
| var bytes = $1; |
| var isFloat = $2; |
| var value = $3; |
| var save0 = HEAP32[0]; |
| var save1 = HEAP32[1]; |
| if (!isFloat) { |
| if (bytes === 1) HEAPU8[0] = value; |
| else if (bytes === 2) HEAPU16[0] = value; |
| else if (bytes === 4) HEAPU32[0] = value; |
| else abort(); |
| } else { |
| if (bytes === 4) HEAPF32[0] = value; |
| else if (bytes === 8) HEAPF64[0] = value; |
| else abort(); |
| } |
| for (var i = 0; i < bytes; i++) { |
| Module["info"].parent["HEAPU8"][addr + i] = HEAPU8[i]; |
| } |
| HEAP32[0] = save0; HEAP32[1] = save1; |
| }, (uint32_t)addr, store_->bytes, isWasmTypeFloat(store_->valueType), isWasmTypeFloat(store_->valueType) ? value.getFloat() : (double)value.getInteger()); |
| return; |
| } |
| // nicely aligned |
| if (!isWasmTypeFloat(store_->valueType)) { |
| if (store_->bytes == 1) { |
| EM_ASM_INT({ Module['info'].parent['HEAP8'][$0] = $1 }, addr, value.geti32()); |
| } else if (store_->bytes == 2) { |
| EM_ASM_INT({ Module['info'].parent['HEAP16'][$0 >> 1] = $1 }, addr, value.geti32()); |
| } else if (store_->bytes == 4) { |
| EM_ASM_INT({ Module['info'].parent['HEAP32'][$0 >> 2] = $1 }, addr, value.geti32()); |
| } else { |
| abort(); |
| } |
| } else { |
| if (store_->bytes == 4) { |
| EM_ASM_DOUBLE({ Module['info'].parent['HEAPF32'][$0 >> 2] = $1 }, addr, value.getf32()); |
| } else if (store_->bytes == 8) { |
| EM_ASM_DOUBLE({ Module['info'].parent['HEAPF64'][$0 >> 3] = $1 }, addr, value.getf64()); |
| } else { |
| abort(); |
| } |
| } |
| } |
| |
| void growMemory(Address oldSize, Address newSize) override { |
| EM_ASM_({ |
| var size = $0; |
| var buffer; |
| try { |
| buffer = new ArrayBuffer(size); |
| } catch(e) { |
| // fail to grow memory. post.js notices this since the buffer is unchanged |
| return; |
| } |
| var oldHEAP8 = Module['outside']['HEAP8']; |
| var temp = new Int8Array(buffer); |
| temp.set(oldHEAP8); |
| Module['outside']['buffer'] = buffer; |
| }, (uint32_t)newSize); |
| } |
| |
| void trap(const char* why) override { |
| EM_ASM_({ |
| abort("wasm trap: " + Pointer_stringify($0)); |
| }, why); |
| } |
| }; |
| |
| instance = new ModuleInstance(*module, new JSExternalInterface()); |
| |
| // stack trace hooks |
| EM_ASM({ |
| Module['outside']['extraStackTrace'] = function() { |
| return Pointer_stringify(Module['_interpreter_stack_trace']()); |
| }; |
| }); |
| } |
| |
| extern "C" int EMSCRIPTEN_KEEPALIVE interpreter_stack_trace() { |
| std::string stack = instance->printFunctionStack(); |
| return (int)strdup(stack.c_str()); // XXX leak |
| } |
| |
| // Does a call from js into an export of the module. |
| extern "C" void EMSCRIPTEN_KEEPALIVE call_from_js(const char *target) { |
| if (wasmJSDebug) std::cout << "call_from_js " << target << '\n'; |
| |
| IString exportName(target); |
| IString functionName = instance->wasm.getExport(exportName)->value; |
| Function *function = instance->wasm.getFunction(functionName); |
| assert(function); |
| size_t seen = EM_ASM_INT_V({ return Module['tempArguments'].length }); |
| size_t actual = function->params.size(); |
| LiteralList arguments; |
| for (size_t i = 0; i < actual; i++) { |
| WasmType type = function->params[i]; |
| // add the parameter, with a zero value if JS did not provide it. |
| if (type == i32) { |
| arguments.push_back(Literal(i < seen ? EM_ASM_INT({ return Module['tempArguments'][$0] }, i) : (int32_t)0)); |
| } else if (type == f32) { |
| arguments.push_back(Literal(i < seen ? (float)EM_ASM_DOUBLE({ return Module['tempArguments'][$0] }, i) : (float)0.0)); |
| } else if (type == f64) { |
| arguments.push_back(Literal(i < seen ? EM_ASM_DOUBLE({ return Module['tempArguments'][$0] }, i) : (double)0.0)); |
| } else { |
| abort(); |
| } |
| } |
| Literal ret = instance->callExport(exportName, arguments); |
| |
| if (wasmJSDebug) std::cout << "call_from_js returning " << ret << '\n'; |
| |
| if (ret.type == none) EM_ASM({ Module['tempReturn'] = undefined }); |
| else if (ret.type == i32) EM_ASM_({ Module['tempReturn'] = $0 }, ret.geti32()); |
| else if (ret.type == f32) EM_ASM_({ Module['tempReturn'] = $0 }, ret.getf32()); |
| else if (ret.type == f64) EM_ASM_({ Module['tempReturn'] = $0 }, ret.getf64()); |
| else abort(); |
| } |