diff options
-rw-r--r-- | CMakeLists.txt | 14 | ||||
-rwxr-xr-x | check.py | 2 | ||||
-rw-r--r-- | src/asm_v_wasm.h | 106 | ||||
-rw-r--r-- | src/asmjs/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/asmjs/asm_v_wasm.cpp | 128 | ||||
-rw-r--r-- | src/mixed_arena.h | 1 | ||||
-rw-r--r-- | src/support/utilities.h | 4 | ||||
-rw-r--r-- | src/wasm-linker.cpp | 277 | ||||
-rw-r--r-- | src/wasm-linker.h | 255 |
9 files changed, 441 insertions, 350 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5440db9ab..9e6834c57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,9 @@ IF (UNIX AND ADD_COMPILE_FLAG("-fcolor-diagnostics") ENDIF() +# Static libraries +ADD_SUBDIRECTORY(src/asmjs) + # Sources. SET(support_SOURCES @@ -106,7 +109,7 @@ SET(binaryen-shell_SOURCES ) ADD_EXECUTABLE(binaryen-shell ${binaryen-shell_SOURCES}) -TARGET_LINK_LIBRARIES(binaryen-shell support) +TARGET_LINK_LIBRARIES(binaryen-shell asmjs support) SET_PROPERTY(TARGET binaryen-shell PROPERTY CXX_STANDARD 11) SET_PROPERTY(TARGET binaryen-shell PROPERTY CXX_STANDARD_REQUIRED ON) INSTALL(TARGETS binaryen-shell DESTINATION bin) @@ -129,7 +132,7 @@ SET(asm2wasm_SOURCES ) ADD_EXECUTABLE(asm2wasm ${asm2wasm_SOURCES}) -TARGET_LINK_LIBRARIES(asm2wasm support) +TARGET_LINK_LIBRARIES(asm2wasm asmjs support) SET_PROPERTY(TARGET asm2wasm PROPERTY CXX_STANDARD 11) SET_PROPERTY(TARGET asm2wasm PROPERTY CXX_STANDARD_REQUIRED ON) INSTALL(TARGETS asm2wasm DESTINATION bin) @@ -142,7 +145,7 @@ SET(wasm2asm_SOURCES ) ADD_EXECUTABLE(wasm2asm ${wasm2asm_SOURCES}) -TARGET_LINK_LIBRARIES(wasm2asm support) +TARGET_LINK_LIBRARIES(wasm2asm asmjs support) SET_PROPERTY(TARGET wasm2asm PROPERTY CXX_STANDARD 11) SET_PROPERTY(TARGET wasm2asm PROPERTY CXX_STANDARD_REQUIRED ON) INSTALL(TARGETS wasm2asm DESTINATION bin) @@ -150,11 +153,12 @@ INSTALL(TARGETS wasm2asm DESTINATION bin) SET(s2wasm_SOURCES src/pass.cpp src/passes/Print.cpp + src/wasm-linker.cpp src/s2wasm-main.cpp ) ADD_EXECUTABLE(s2wasm ${s2wasm_SOURCES}) -TARGET_LINK_LIBRARIES(s2wasm support) +TARGET_LINK_LIBRARIES(s2wasm asmjs support) SET_PROPERTY(TARGET s2wasm PROPERTY CXX_STANDARD 11) SET_PROPERTY(TARGET s2wasm PROPERTY CXX_STANDARD_REQUIRED ON) INSTALL(TARGETS s2wasm DESTINATION bin) @@ -164,7 +168,7 @@ SET(wasm_as_SOURCES ) ADD_EXECUTABLE(wasm-as ${wasm_as_SOURCES}) -TARGET_LINK_LIBRARIES(wasm-as support) +TARGET_LINK_LIBRARIES(wasm-as asmjs support) SET_PROPERTY(TARGET wasm-as PROPERTY CXX_STANDARD 11) SET_PROPERTY(TARGET wasm-as PROPERTY CXX_STANDARD_REQUIRED ON) INSTALL(TARGETS wasm-as DESTINATION bin) @@ -573,7 +573,7 @@ cmd = [os.environ.get('CXX') or 'g++', '-std=c++11', os.path.join('test', 'example', 'find_div0s.cpp'), os.path.join('src', 'pass.cpp'), os.path.join('src', 'passes', 'Print.cpp'), - '-Isrc', '-g', '-lsupport', '-Llib/.', '-pthread'] + '-Isrc', '-g', '-lasmjs', '-lsupport', '-Llib/.', '-pthread'] if os.environ.get('COMPILER_FLAGS'): for f in os.environ.get('COMPILER_FLAGS').split(' '): cmd.append(f) diff --git a/src/asm_v_wasm.h b/src/asm_v_wasm.h index 440c9ec89..27acf3dcc 100644 --- a/src/asm_v_wasm.h +++ b/src/asm_v_wasm.h @@ -19,113 +19,29 @@ #include "mixed_arena.h" #include "emscripten-optimizer/optimizer.h" +#include "wasm.h" namespace wasm { -WasmType asmToWasmType(AsmType asmType) { - switch (asmType) { - case ASM_INT: return WasmType::i32; - case ASM_DOUBLE: return WasmType::f64; - case ASM_FLOAT: return WasmType::f32; - case ASM_NONE: return WasmType::none; - default: {} - } - abort(); -} +WasmType asmToWasmType(AsmType asmType); -AsmType wasmToAsmType(WasmType type) { - switch (type) { - case WasmType::i32: return ASM_INT; - case WasmType::f32: return ASM_FLOAT; - case WasmType::f64: return ASM_DOUBLE; - case WasmType::none: return ASM_NONE; - default: {} - } - abort(); -} +AsmType wasmToAsmType(WasmType type); -char getSig(WasmType type) { - switch (type) { - case i32: return 'i'; - case i64: return 'j'; - case f32: return 'f'; - case f64: return 'd'; - case none: return 'v'; - default: abort(); - } -} +char getSig(WasmType type); -std::string getSig(FunctionType *type) { - std::string ret; - ret += getSig(type->result); - for (auto param : type->params) { - ret += getSig(param); - } - return ret; -} +std::string getSig(FunctionType *type); -std::string getSig(Function *func) { - std::string ret; - ret += getSig(func->result); - for (auto type : func->params) { - ret += getSig(type); - } - return ret; -} +std::string getSig(Function *func); -std::string getSig(CallBase *call) { - std::string ret; - ret += getSig(call->type); - for (auto operand : call->operands) { - ret += getSig(operand->type); - } - return ret; -} +std::string getSig(CallBase *call); -std::string getSig(WasmType result, const ExpressionList& operands) { - std::string ret; - ret += getSig(result); - for (auto operand : operands) { - ret += getSig(operand->type); - } - return ret; -} +std::string getSig(WasmType result, const ExpressionList& operands); -WasmType sigToWasmType(char sig) { - switch (sig) { - case 'i': return i32; - case 'j': return i64; - case 'f': return f32; - case 'd': return f64; - case 'v': return none; - default: abort(); - } -} +WasmType sigToWasmType(char sig); -FunctionType sigToFunctionType(std::string sig) { - FunctionType ret; - ret.result = sigToWasmType(sig[0]); - for (size_t i = 1; i < sig.size(); i++) { - ret.params.push_back(sigToWasmType(sig[i])); - } - return ret; -} +FunctionType sigToFunctionType(std::string sig); -FunctionType* ensureFunctionType(std::string sig, Module* wasm, MixedArena& allocator) { - cashew::IString name(("FUNCSIG$" + sig).c_str(), false); - if (wasm->checkFunctionType(name)) { - return wasm->getFunctionType(name); - } - // add new type - auto type = allocator.alloc<FunctionType>(); - type->name = name; - type->result = sigToWasmType(sig[0]); - for (size_t i = 1; i < sig.size(); i++) { - type->params.push_back(sigToWasmType(sig[i])); - } - wasm->addFunctionType(type); - return type; -} +FunctionType* ensureFunctionType(std::string sig, Module* wasm, MixedArena& allocator); } // namespace wasm diff --git a/src/asmjs/CMakeLists.txt b/src/asmjs/CMakeLists.txt new file mode 100644 index 000000000..8be8ea2c4 --- /dev/null +++ b/src/asmjs/CMakeLists.txt @@ -0,0 +1,4 @@ +SET(asmjs_SOURCES + asm_v_wasm.cpp +) +ADD_LIBRARY(asmjs STATIC ${asmjs_SOURCES})
\ No newline at end of file diff --git a/src/asmjs/asm_v_wasm.cpp b/src/asmjs/asm_v_wasm.cpp new file mode 100644 index 000000000..317a8b625 --- /dev/null +++ b/src/asmjs/asm_v_wasm.cpp @@ -0,0 +1,128 @@ +/* + * 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. + */ + +#include "asm_v_wasm.h" +#include "wasm.h" + + +namespace wasm { + +WasmType asmToWasmType(AsmType asmType) { + switch (asmType) { + case ASM_INT: return WasmType::i32; + case ASM_DOUBLE: return WasmType::f64; + case ASM_FLOAT: return WasmType::f32; + case ASM_NONE: return WasmType::none; + default: {} + } + abort(); +} + +AsmType wasmToAsmType(WasmType type) { + switch (type) { + case WasmType::i32: return ASM_INT; + case WasmType::f32: return ASM_FLOAT; + case WasmType::f64: return ASM_DOUBLE; + case WasmType::none: return ASM_NONE; + default: {} + } + abort(); +} + +char getSig(WasmType type) { + switch (type) { + case i32: return 'i'; + case i64: return 'j'; + case f32: return 'f'; + case f64: return 'd'; + case none: return 'v'; + default: abort(); + } +} + +std::string getSig(FunctionType *type) { + std::string ret; + ret += getSig(type->result); + for (auto param : type->params) { + ret += getSig(param); + } + return ret; +} + +std::string getSig(Function *func) { + std::string ret; + ret += getSig(func->result); + for (auto type : func->params) { + ret += getSig(type); + } + return ret; +} + +std::string getSig(CallBase *call) { + std::string ret; + ret += getSig(call->type); + for (auto operand : call->operands) { + ret += getSig(operand->type); + } + return ret; +} + +std::string getSig(WasmType result, const ExpressionList& operands) { + std::string ret; + ret += getSig(result); + for (auto operand : operands) { + ret += getSig(operand->type); + } + return ret; +} + +WasmType sigToWasmType(char sig) { + switch (sig) { + case 'i': return i32; + case 'j': return i64; + case 'f': return f32; + case 'd': return f64; + case 'v': return none; + default: abort(); + } +} + +FunctionType sigToFunctionType(std::string sig) { + FunctionType ret; + ret.result = sigToWasmType(sig[0]); + for (size_t i = 1; i < sig.size(); i++) { + ret.params.push_back(sigToWasmType(sig[i])); + } + return ret; +} + +FunctionType* ensureFunctionType(std::string sig, Module* wasm, MixedArena& allocator) { + cashew::IString name(("FUNCSIG$" + sig).c_str(), false); + if (wasm->checkFunctionType(name)) { + return wasm->getFunctionType(name); + } + // add new type + auto type = allocator.alloc<FunctionType>(); + type->name = name; + type->result = sigToWasmType(sig[0]); + for (size_t i = 1; i < sig.size(); i++) { + type->params.push_back(sigToWasmType(sig[i])); + } + wasm->addFunctionType(type); + return type; +} + +} // namespace wasm diff --git a/src/mixed_arena.h b/src/mixed_arena.h index dc8cbad5b..d03612df5 100644 --- a/src/mixed_arena.h +++ b/src/mixed_arena.h @@ -17,6 +17,7 @@ #ifndef wasm_mixed_arena_h #define wasm_mixed_arena_h +#include <cassert> #include <vector> #include "support/threads.h" diff --git a/src/support/utilities.h b/src/support/utilities.h index 3ff1f9c5e..92e7c226f 100644 --- a/src/support/utilities.h +++ b/src/support/utilities.h @@ -17,7 +17,11 @@ #ifndef wasm_support_utilities_h #define wasm_support_utilities_h +#include <cassert> +#include <cstdint> #include <cstring> +#include <memory> +#include <iostream> #include <type_traits> namespace wasm { diff --git a/src/wasm-linker.cpp b/src/wasm-linker.cpp new file mode 100644 index 000000000..90ef4c2b9 --- /dev/null +++ b/src/wasm-linker.cpp @@ -0,0 +1,277 @@ +/* + * Copyright 2016 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. + */ + +#include "wasm-linker.h" +#include "asm_v_wasm.h" +#include "support/utilities.h" +#include "wasm-builder.h" +#include "wasm-printing.h" + +using namespace wasm; + +cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const"); +namespace wasm { +// These are defined (not just declared) in shared-constants.h, so we can't just +// include that header. TODO: Move the definitions into a cpp file. +extern cashew::IString ENV; +extern cashew::IString MEMORY; +} + + +void Linker::placeStackPointer(size_t stackAllocation) { + // ensure this is the first allocation + assert(nextStatic == globalBase || nextStatic == 1); + const size_t pointerSize = 4; + // Unconditionally allocate space for the stack pointer. Emscripten + // allocates the stack itself, and initializes the stack pointer itself. + size_t address = allocateStatic(pointerSize, pointerSize, "__stack_pointer"); + if (stackAllocation) { + // If we are allocating the stack, set up a relocation to initialize the + // stack pointer to point to one past-the-end of the stack allocation. + auto* raw = new uint32_t; + relocations.emplace_back( + make_unique<Relocation>(raw, ".stack", stackAllocation)); + assert(wasm.memory.segments.size() == 0); + addressSegments[address] = wasm.memory.segments.size(); + wasm.memory.segments.emplace_back( + address, reinterpret_cast<char*>(raw), pointerSize); + } +} + +void Linker::layout() { + // Place the stack after the user's static data, to keep those addresses + // small. + if (stackAllocation) allocateStatic(stackAllocation, 16, ".stack"); + + // The minimum initial memory size is the amount of static variables we have + // allocated. Round it up to a page, and update the page-increment versions + // of initial and max + size_t initialMem = roundUpToPageSize(nextStatic); + if (userInitialMemory) { + if (initialMem > userInitialMemory) { + Fatal() << "Specified initial memory size " << userInitialMemory << + " is smaller than required size " << initialMem; + } + wasm.memory.initial = userInitialMemory / Memory::kPageSize; + } else { + wasm.memory.initial = initialMem / Memory::kPageSize; + } + + if (userMaxMemory) wasm.memory.max = userMaxMemory / Memory::kPageSize; + wasm.memory.exportName = MEMORY; + + // XXX For now, export all functions marked .globl. + for (Name name : globls) exportFunction(name, false); + for (Name name : initializerFunctions) exportFunction(name, true); + + auto ensureFunctionIndex = [this](Name name) { + if (functionIndexes.count(name) == 0) { + functionIndexes[name] = wasm.table.names.size(); + wasm.table.names.push_back(name); + if (debug) { + std::cerr << "function index: " << name << ": " + << functionIndexes[name] << '\n'; + } + } + }; + for (auto& relocation : relocations) { + Name name = relocation->value; + if (debug) std::cerr << "fix relocation " << name << '\n'; + const auto& symbolAddress = staticAddresses.find(name); + if (symbolAddress != staticAddresses.end()) { + *(relocation->data) = symbolAddress->second + relocation->offset; + if (debug) std::cerr << " ==> " << *(relocation->data) << '\n'; + } else { + // must be a function address + auto aliased = aliasedFunctions.find(name); + if (aliased != aliasedFunctions.end()) name = aliased->second; + if (!wasm.checkFunction(name)) { + std::cerr << "Unknown symbol: " << name << '\n'; + if (!ignoreUnknownSymbols) abort(); + *(relocation->data) = 0; + } else { + ensureFunctionIndex(name); + *(relocation->data) = functionIndexes[name] + relocation->offset; + } + } + } + if (!!startFunction) { + if (implementedFunctions.count(startFunction) == 0) { + std::cerr << "Unknown start function: `" << startFunction << "`\n"; + abort(); + } + const auto *target = wasm.getFunction(startFunction); + Name start("_start"); + if (implementedFunctions.count(start) != 0) { + std::cerr << "Start function already present: `" << start << "`\n"; + abort(); + } + auto* func = wasm.allocator.alloc<Function>(); + func->name = start; + wasm.addFunction(func); + exportFunction(start, true); + wasm.addStart(start); + auto* block = wasm.allocator.alloc<Block>(); + func->body = block; + { + // Create the call, matching its parameters. + // TODO allow calling with non-default values. + auto* call = wasm.allocator.alloc<Call>(); + call->target = startFunction; + size_t paramNum = 0; + for (WasmType type : target->params) { + Name name = Name::fromInt(paramNum++); + Builder::addVar(func, name, type); + auto* param = wasm.allocator.alloc<GetLocal>(); + param->index = func->getLocalIndex(name); + param->type = type; + call->operands.push_back(param); + } + block->list.push_back(call); + block->finalize(); + } + } + + // ensure an explicit function type for indirect call targets + for (auto& name : wasm.table.names) { + auto* func = wasm.getFunction(name); + func->type = ensureFunctionType(getSig(func), &wasm, wasm.allocator)->name; + } +} + +void Linker::emscriptenGlue(std::ostream& o) { + if (debug) { + WasmPrinter::printModule(&wasm, std::cerr); + } + + wasm.removeImport(EMSCRIPTEN_ASM_CONST); // we create _sig versions + + makeDynCallThunks(); + + o << ";; METADATA: { "; + // find asmConst calls, and emit their metadata + struct AsmConstWalker : public PostWalker<AsmConstWalker, Visitor<AsmConstWalker>> { + Linker* parent; + + std::map<std::string, std::set<std::string>> sigsForCode; + std::map<std::string, size_t> ids; + std::set<std::string> allSigs; + + void visitCallImport(CallImport* curr) { + if (curr->target == EMSCRIPTEN_ASM_CONST) { + auto arg = curr->operands[0]->cast<Const>(); + size_t segmentIndex = parent->addressSegments[arg->value.geti32()]; + std::string code = escape(parent->wasm.memory.segments[segmentIndex].data); + int32_t id; + if (ids.count(code) == 0) { + id = ids.size(); + ids[code] = id; + } else { + id = ids[code]; + } + std::string sig = getSig(curr); + sigsForCode[code].insert(sig); + std::string fixedTarget = EMSCRIPTEN_ASM_CONST.str + std::string("_") + sig; + curr->target = cashew::IString(fixedTarget.c_str(), false); + arg->value = Literal(id); + // add import, if necessary + if (allSigs.count(sig) == 0) { + allSigs.insert(sig); + auto import = parent->wasm.allocator.alloc<Import>(); + import->name = import->base = curr->target; + import->module = ENV; + import->type = ensureFunctionType(getSig(curr), &parent->wasm, parent->wasm.allocator); + parent->wasm.addImport(import); + } + } + } + + std::string escape(const char *input) { + std::string code = input; + // replace newlines quotes with escaped newlines + size_t curr = 0; + while ((curr = code.find("\\n", curr)) != std::string::npos) { + code = code.replace(curr, 2, "\\\\n"); + curr += 3; // skip this one + } + // replace double quotes with escaped single quotes + curr = 0; + while ((curr = code.find('"', curr)) != std::string::npos) { + if (curr == 0 || code[curr-1] != '\\') { + code = code.replace(curr, 1, "\\" "\""); + curr += 2; // skip this one + } else { // already escaped, escape the slash as well + code = code.replace(curr, 1, "\\" "\\" "\""); + curr += 3; // skip this one + } + } + return code; + } + }; + AsmConstWalker walker; + walker.parent = this; + walker.startWalk(&wasm); + // print + o << "\"asmConsts\": {"; + bool first = true; + for (auto& pair : walker.sigsForCode) { + auto& code = pair.first; + auto& sigs = pair.second; + if (first) first = false; + else o << ","; + o << '"' << walker.ids[code] << "\": [\"" << code << "\", "; + printSet(o, sigs); + o << "]"; + } + o << "}"; + o << ","; + o << "\"staticBump\": " << (nextStatic - globalBase) << ", "; + + o << "\"initializers\": ["; + first = true; + for (const auto& func : initializerFunctions) { + if (first) first = false; + else o << ", "; + o << "\"" << func.c_str() << "\""; + } + o << "]"; + + o << " }"; +} + +void Linker::makeDynCallThunks() { + std::unordered_set<std::string> sigs; + wasm::Builder wasmBuilder(wasm); + for (const auto& indirectFunc : wasm.table.names) { + std::string sig(getSig(wasm.getFunction(indirectFunc))); + auto* funcType = ensureFunctionType(sig, &wasm, wasm.allocator); + if (!sigs.insert(sig).second) continue; // Sig is already in the set + std::vector<NameType> params; + params.emplace_back("fptr", i32); // function pointer param + int p = 0; + for (const auto& ty : funcType->params) params.emplace_back("$" + std::to_string(p++), ty); + Function* f = wasmBuilder.makeFunction(std::string("dynCall_") + sig, std::move(params), funcType->result, {}); + Expression* fptr = wasmBuilder.makeGetLocal(0, i32); + std::vector<Expression*> args; + for (unsigned i = 0; i < funcType->params.size(); ++i) { + args.push_back(wasmBuilder.makeGetLocal(i + 1, funcType->params[i])); + } + Expression* call = wasmBuilder.makeCallIndirect(funcType, fptr, std::move(args)); + f->body = funcType->result == none ? call : wasmBuilder.makeReturn(call); + wasm.addFunction(f); + exportFunction(f->name, true); + } +} diff --git a/src/wasm-linker.h b/src/wasm-linker.h index ceb948c4e..9929a6f70 100644 --- a/src/wasm-linker.h +++ b/src/wasm-linker.h @@ -21,19 +21,14 @@ // is no merging of multiple modules). Currently this is only inteded to turn // a .s file produced by LLVM into a usable wast file. - #ifndef WASM_WASM_LINK_H #define WASM_WASM_LINK_H #include "support/utilities.h" #include "wasm.h" -#include "wasm-printing.h" namespace wasm { -cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const"); - - // Wasm module linking/layout information class Linker { public: @@ -90,6 +85,9 @@ class Linker { return address; } + // Allocate space for a stack pointer and (if stackAllocation > 0) set up a + // relocation for it to point to the top of the stack. + void placeStackPointer(size_t stackAllocation); void addGlobal(Name name) { globls.push_back(name); } @@ -131,232 +129,13 @@ class Linker { // Allocate the user stack, set up the initial memory size of the module, lay // out the linear memory, process the relocations, and set up the indirect // function table. - void layout() { - // Place the stack after the user's static data, to keep those addresses - // small. - if (stackAllocation) allocateStatic(stackAllocation, 16, ".stack"); - - // The minimum initial memory size is the amount of static variables we have - // allocated. Round it up to a page, and update the page-increment versions - // of initial and max - size_t initialMem = roundUpToPageSize(nextStatic); - if (userInitialMemory) { - if (initialMem > userInitialMemory) { - Fatal() << "Specified initial memory size " << userInitialMemory << - " is smaller than required size " << initialMem; - } - wasm.memory.initial = userInitialMemory / Memory::kPageSize; - } else { - wasm.memory.initial = initialMem / Memory::kPageSize; - } - - if (userMaxMemory) wasm.memory.max = userMaxMemory / Memory::kPageSize; - wasm.memory.exportName = MEMORY; - - // XXX For now, export all functions marked .globl. - for (Name name : globls) exportFunction(name, false); - for (Name name : initializerFunctions) exportFunction(name, true); - - auto ensureFunctionIndex = [this](Name name) { - if (functionIndexes.count(name) == 0) { - functionIndexes[name] = wasm.table.names.size(); - wasm.table.names.push_back(name); - if (debug) { - std::cerr << "function index: " << name << ": " - << functionIndexes[name] << '\n'; - } - } - }; - for (auto& relocation : relocations) { - Name name = relocation->value; - if (debug) std::cerr << "fix relocation " << name << '\n'; - const auto& symbolAddress = staticAddresses.find(name); - if (symbolAddress != staticAddresses.end()) { - *(relocation->data) = symbolAddress->second + relocation->offset; - if (debug) std::cerr << " ==> " << *(relocation->data) << '\n'; - } else { - // must be a function address - auto aliased = aliasedFunctions.find(name); - if (aliased != aliasedFunctions.end()) name = aliased->second; - if (!wasm.checkFunction(name)) { - std::cerr << "Unknown symbol: " << name << '\n'; - if (!ignoreUnknownSymbols) abort(); - *(relocation->data) = 0; - } else { - ensureFunctionIndex(name); - *(relocation->data) = functionIndexes[name] + relocation->offset; - } - } - } - if (!!startFunction) { - if (implementedFunctions.count(startFunction) == 0) { - std::cerr << "Unknown start function: `" << startFunction << "`\n"; - abort(); - } - const auto *target = wasm.getFunction(startFunction); - Name start("_start"); - if (implementedFunctions.count(start) != 0) { - std::cerr << "Start function already present: `" << start << "`\n"; - abort(); - } - auto* func = wasm.allocator.alloc<Function>(); - func->name = start; - wasm.addFunction(func); - exportFunction(start, true); - wasm.addStart(start); - auto* block = wasm.allocator.alloc<Block>(); - func->body = block; - { - // Create the call, matching its parameters. - // TODO allow calling with non-default values. - auto* call = wasm.allocator.alloc<Call>(); - call->target = startFunction; - size_t paramNum = 0; - for (WasmType type : target->params) { - Name name = Name::fromInt(paramNum++); - Builder::addVar(func, name, type); - auto* param = wasm.allocator.alloc<GetLocal>(); - param->index = func->getLocalIndex(name); - param->type = type; - call->operands.push_back(param); - } - block->list.push_back(call); - block->finalize(); - } - } - - // ensure an explicit function type for indirect call targets - for (auto& name : wasm.table.names) { - auto* func = wasm.getFunction(name); - func->type = ensureFunctionType(getSig(func), &wasm, wasm.allocator)->name; - } - } + void layout(); // Support for emscripten integration: generates dyncall thunks, emits // metadata for asmConsts, staticBump and initializer functions. - void emscriptenGlue(std::ostream& o) { - if (debug) { - WasmPrinter::printModule(&wasm, std::cerr); - } - - wasm.removeImport(EMSCRIPTEN_ASM_CONST); // we create _sig versions - - makeDynCallThunks(); - - o << ";; METADATA: { "; - // find asmConst calls, and emit their metadata - struct AsmConstWalker : public PostWalker<AsmConstWalker, Visitor<AsmConstWalker>> { - Linker* parent; - - std::map<std::string, std::set<std::string>> sigsForCode; - std::map<std::string, size_t> ids; - std::set<std::string> allSigs; - - void visitCallImport(CallImport* curr) { - if (curr->target == EMSCRIPTEN_ASM_CONST) { - auto arg = curr->operands[0]->cast<Const>(); - size_t segmentIndex = parent->addressSegments[arg->value.geti32()]; - std::string code = escape(parent->wasm.memory.segments[segmentIndex].data); - int32_t id; - if (ids.count(code) == 0) { - id = ids.size(); - ids[code] = id; - } else { - id = ids[code]; - } - std::string sig = getSig(curr); - sigsForCode[code].insert(sig); - std::string fixedTarget = EMSCRIPTEN_ASM_CONST.str + std::string("_") + sig; - curr->target = cashew::IString(fixedTarget.c_str(), false); - arg->value = Literal(id); - // add import, if necessary - if (allSigs.count(sig) == 0) { - allSigs.insert(sig); - auto import = parent->wasm.allocator.alloc<Import>(); - import->name = import->base = curr->target; - import->module = ENV; - import->type = ensureFunctionType(getSig(curr), &parent->wasm, parent->wasm.allocator); - parent->wasm.addImport(import); - } - } - } - - std::string escape(const char *input) { - std::string code = input; - // replace newlines quotes with escaped newlines - size_t curr = 0; - while ((curr = code.find("\\n", curr)) != std::string::npos) { - code = code.replace(curr, 2, "\\\\n"); - curr += 3; // skip this one - } - // replace double quotes with escaped single quotes - curr = 0; - while ((curr = code.find('"', curr)) != std::string::npos) { - if (curr == 0 || code[curr-1] != '\\') { - code = code.replace(curr, 1, "\\" "\""); - curr += 2; // skip this one - } else { // already escaped, escape the slash as well - code = code.replace(curr, 1, "\\" "\\" "\""); - curr += 3; // skip this one - } - } - return code; - } - }; - AsmConstWalker walker; - walker.parent = this; - walker.startWalk(&wasm); - // print - o << "\"asmConsts\": {"; - bool first = true; - for (auto& pair : walker.sigsForCode) { - auto& code = pair.first; - auto& sigs = pair.second; - if (first) first = false; - else o << ","; - o << '"' << walker.ids[code] << "\": [\"" << code << "\", "; - printSet(o, sigs); - o << "]"; - } - o << "}"; - o << ","; - o << "\"staticBump\": " << (nextStatic - globalBase) << ", "; - - o << "\"initializers\": ["; - first = true; - for (const auto& func : initializerFunctions) { - if (first) first = false; - else o << ", "; - o << "\"" << func.c_str() << "\""; - } - o << "]"; - - o << " }"; - } + void emscriptenGlue(std::ostream& o); private: - // Allocate space for a stack pointer and (if stackAllocation > 0) set up a - // relocation for it to point to the top of the stack. - void placeStackPointer(size_t stackAllocation) { - // ensure this is the first allocation - assert(nextStatic == globalBase || nextStatic == 1); - const size_t pointerSize = 4; - // Unconditionally allocate space for the stack pointer. Emscripten - // allocates the stack itself, and initializes the stack pointer itself. - size_t address = allocateStatic(pointerSize, pointerSize, "__stack_pointer"); - if (stackAllocation) { - // If we are allocating the stack, set up a relocation to initialize the - // stack pointer to point to one past-the-end of the stack allocation. - auto* raw = new uint32_t; - relocations.emplace_back( - make_unique<Relocation>(raw, ".stack", stackAllocation)); - assert(wasm.memory.segments.size() == 0); - addressSegments[address] = wasm.memory.segments.size(); - wasm.memory.segments.emplace_back( - address, reinterpret_cast<char*>(raw), pointerSize); - } - } - template<class C> void printSet(std::ostream& o, C& c) { o << "["; @@ -371,29 +150,7 @@ class Linker { // Create thunks for use with emscripten Runtime.dynCall. Creates one for each // signature in the indirect function table. - void makeDynCallThunks() { - std::unordered_set<std::string> sigs; - wasm::Builder wasmBuilder(wasm); - for (const auto& indirectFunc : wasm.table.names) { - std::string sig(getSig(wasm.getFunction(indirectFunc))); - auto* funcType = ensureFunctionType(sig, &wasm, wasm.allocator); - if (!sigs.insert(sig).second) continue; // Sig is already in the set - std::vector<NameType> params; - params.emplace_back("fptr", i32); // function pointer param - int p = 0; - for (const auto& ty : funcType->params) params.emplace_back("$" + std::to_string(p++), ty); - Function* f = wasmBuilder.makeFunction(std::string("dynCall_") + sig, std::move(params), funcType->result, {}); - Expression* fptr = wasmBuilder.makeGetLocal(0, i32); - std::vector<Expression*> args; - for (unsigned i = 0; i < funcType->params.size(); ++i) { - args.push_back(wasmBuilder.makeGetLocal(i + 1, funcType->params[i])); - } - Expression* call = wasmBuilder.makeCallIndirect(funcType, fptr, std::move(args)); - f->body = funcType->result == none ? call : wasmBuilder.makeReturn(call); - wasm.addFunction(f); - exportFunction(f->name, true); - } - } + void makeDynCallThunks(); static size_t roundUpToPageSize(size_t size) { return (size + Memory::kPageSize - 1) & Memory::kPageMask; |