diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/asm2wasm.h | 19 | ||||
-rw-r--r-- | src/tools/s2wasm.cpp | 16 | ||||
-rw-r--r-- | src/wasm-emscripten.cpp | 208 | ||||
-rw-r--r-- | src/wasm-emscripten.h | 42 | ||||
-rw-r--r-- | src/wasm-linker.cpp | 169 | ||||
-rw-r--r-- | src/wasm-linker.h | 24 |
6 files changed, 303 insertions, 175 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 53fa78db4..b19451a7b 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -32,6 +32,7 @@ #include "pass.h" #include "ast_utils.h" #include "wasm-builder.h" +#include "wasm-emscripten.h" #include "wasm-validator.h" #include "wasm-module-building.h" @@ -814,23 +815,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) { // apply memory growth, if relevant if (memoryGrowth) { - // create and export a function that just calls memory growth - Builder builder(wasm); - wasm.addFunction(builder.makeFunction( - GROW_WASM_MEMORY, - { { NEW_SIZE, i32 } }, - i32, - {}, - builder.makeHost( - GrowMemory, - Name(), - { builder.makeGetLocal(0, i32) } - ) - )); - auto export_ = new Export; - export_->name = export_->value = GROW_WASM_MEMORY; - export_->kind = Export::Function; - wasm.addExport(export_); + emscripten::generateMemoryGrowthFunction(wasm); } #if 0 diff --git a/src/tools/s2wasm.cpp b/src/tools/s2wasm.cpp index 2ee094af4..8c36a0d58 100644 --- a/src/tools/s2wasm.cpp +++ b/src/tools/s2wasm.cpp @@ -22,6 +22,7 @@ #include "support/command-line.h" #include "support/file.h" #include "s2wasm.h" +#include "wasm-emscripten.h" #include "wasm-linker.h" #include "wasm-printing.h" #include "wasm-validator.h" @@ -32,6 +33,7 @@ using namespace wasm; int main(int argc, const char *argv[]) { bool ignoreUnknownSymbols = false; bool generateEmscriptenGlue = false; + bool allowMemoryGrowth = false; std::string startFunction; std::vector<std::string> archiveLibraries; Options options("s2wasm", "Link .s file into .wast"); @@ -73,6 +75,11 @@ int main(int argc, const char *argv[]) { [](Options *o, const std::string &argument) { o->extra["max-memory"] = argument; }) + .add("--allow-memory-growth", "", "Allow linear memory to grow at runtime", + Options::Arguments::Zero, + [&allowMemoryGrowth](Options *, const std::string &) { + allowMemoryGrowth = true; + }) .add("--emscripten-glue", "-e", "Generate emscripten glue", Options::Arguments::Zero, [&generateEmscriptenGlue](Options *, const std::string &) { @@ -98,6 +105,11 @@ int main(int argc, const char *argv[]) { }); options.parse(argc, argv); + if (allowMemoryGrowth && !generateEmscriptenGlue) { + Fatal() << "Error: adding memory growth code without Emscripten glue. " + "This doesn't do anything.\n"; + } + auto debugFlag = options.debug ? Flags::Debug : Flags::Release; auto input(read_file<std::string>(options.extra["infile"], Flags::Text, debugFlag)); @@ -138,6 +150,10 @@ int main(int argc, const char *argv[]) { std::stringstream meta; if (generateEmscriptenGlue) { if (options.debug) std::cerr << "Emscripten gluing..." << std::endl; + if (allowMemoryGrowth) { + emscripten::generateMemoryGrowthFunction(linker.getOutput().wasm); + } + // dyncall thunks linker.emscriptenGlue(meta); } diff --git a/src/wasm-emscripten.cpp b/src/wasm-emscripten.cpp new file mode 100644 index 000000000..912ca6d61 --- /dev/null +++ b/src/wasm-emscripten.cpp @@ -0,0 +1,208 @@ +/* + * 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-emscripten.h" + +#include "asm_v_wasm.h" +#include "asmjs/shared-constants.h" +#include "shared-constants.h" +#include "wasm-builder.h" +#include "wasm-traversal.h" +#include "wasm.h" + +namespace wasm { + +namespace emscripten { + +cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const"); + +void generateMemoryGrowthFunction(Module& wasm) { + Builder wasmBuilder(wasm); + Name name(GROW_WASM_MEMORY); + std::vector<NameType> params { { NEW_SIZE, i32 } }; + Function* growFunction = wasmBuilder.makeFunction( + name, std::move(params), i32, {} + ); + growFunction->body = wasmBuilder.makeHost( + GrowMemory, + Name(), + { wasmBuilder.makeGetLocal(0, i32) } + ); + + wasm.addFunction(growFunction); + auto export_ = new Export; + export_->name = export_->value = name; + export_->kind = Export::Function; + wasm.addExport(export_); +} + +static bool hasI64ResultOrParam(FunctionType* ft) { + if (ft->result == i64) return true; + for (auto ty : ft->params) { + if (ty == i64) return true; + } + return false; +} + +std::vector<Function*> makeDynCallThunks(Module& wasm, std::vector<Name> const& tableSegmentData) { + wasm.removeImport(EMSCRIPTEN_ASM_CONST); // we create _sig versions + + std::vector<Function*> generatedFunctions; + std::unordered_set<std::string> sigs; + Builder wasmBuilder(wasm); + for (const auto& indirectFunc : tableSegmentData) { + std::string sig(getSig(wasm.getFunction(indirectFunc))); + auto* funcType = ensureFunctionType(sig, &wasm); + if (hasI64ResultOrParam(funcType)) continue; // Can't export i64s on the web. + 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, args); + f->body = call; + wasm.addFunction(f); + generatedFunctions.push_back(f); + } + return generatedFunctions; +} + +struct AsmConstWalker : public PostWalker<AsmConstWalker, Visitor<AsmConstWalker>> { + Module& wasm; + std::unordered_map<Address, Address> segmentsByAddress; // address => segment index + + std::map<std::string, std::set<std::string>> sigsForCode; + std::map<std::string, Address> ids; + std::set<std::string> allSigs; + + AsmConstWalker(Module& _wasm, std::unordered_map<Address, Address> _segmentsByAddress) : + wasm(_wasm), segmentsByAddress(_segmentsByAddress) { } + + void visitCallImport(CallImport* curr); + + std::string escape(const char *input); +}; + +void AsmConstWalker::visitCallImport(CallImport* curr) { + if (curr->target == EMSCRIPTEN_ASM_CONST) { + auto arg = curr->operands[0]->cast<Const>(); + Address segmentIndex = segmentsByAddress[arg->value.geti32()]; + std::string code = escape(&wasm.memory.segments[segmentIndex].data[0]); + 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 = new Import; + import->name = import->base = curr->target; + import->module = ENV; + import->functionType = ensureFunctionType(getSig(curr), &wasm); + import->kind = Import::Function; + wasm.addImport(import); + } + } +} + +std::string AsmConstWalker::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; +} + +template<class C> +void printSet(std::ostream& o, C& c) { + o << "["; + bool first = true; + for (auto& item : c) { + if (first) first = false; + else o << ","; + o << '"' << item << '"'; + } + o << "]"; +} + +void generateEmscriptenMetadata(std::ostream& o, + Module& wasm, + std::unordered_map<Address, Address> segmentsByAddress, + Address staticBump, + std::vector<Name> const& initializerFunctions) { + o << ";; METADATA: { "; + // find asmConst calls, and emit their metadata + AsmConstWalker walker(wasm, segmentsByAddress); + walker.walkModule(&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\": " << staticBump << ", "; + + o << "\"initializers\": ["; + first = true; + for (const auto& func : initializerFunctions) { + if (first) first = false; + else o << ", "; + o << "\"" << func.c_str() << "\""; + } + o << "]"; + + o << " }\n"; +} + +} // namespace emscripten + +} // namespace wasm diff --git a/src/wasm-emscripten.h b/src/wasm-emscripten.h new file mode 100644 index 000000000..07c470898 --- /dev/null +++ b/src/wasm-emscripten.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef wasm_emscripten_h +#define wasm_emscripten_h + +#include "wasm.h" + +namespace wasm { + +namespace emscripten { + +void generateMemoryGrowthFunction(Module&); + +// Create thunks for use with emscripten Runtime.dynCall. Creates one for each +// signature in the indirect function table. +std::vector<Function*> makeDynCallThunks(Module& wasm, std::vector<Name> const& tableSegmentData); + +void generateEmscriptenMetadata(std::ostream& o, + Module& wasm, + std::unordered_map<Address, Address> segmentsByAddress, + Address staticBump, + std::vector<Name> const& initializerFunctions); + +} // namespace emscripten + +} // namespace wasm + +#endif // wasm_emscripten_h diff --git a/src/wasm-linker.cpp b/src/wasm-linker.cpp index 04deddf4a..527ff80ee 100644 --- a/src/wasm-linker.cpp +++ b/src/wasm-linker.cpp @@ -20,11 +20,13 @@ #include "s2wasm.h" #include "support/utilities.h" #include "wasm-builder.h" +#include "wasm-emscripten.h" #include "wasm-printing.h" using namespace wasm; -cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const"); +// Name of the dummy function to prevent erroneous nullptr comparisons. +static constexpr const char* dummyFunction = "__wasm_nullptr"; void Linker::placeStackPointer(Address stackAllocation) { // ensure this is the first allocation @@ -138,7 +140,8 @@ void Linker::layout() { // Emit the pre-assigned function names in sorted order for (const auto& P : functionNames) { - getTableSegment().data.push_back(P.second); + ensureTableIsPopulated(); + getTableDataRef().push_back(P.second); } for (auto& relocation : out.relocations) { @@ -236,8 +239,9 @@ void Linker::layout() { } // finalize function table - if (out.wasm.table.segments.size() > 0) { - out.wasm.table.initial = out.wasm.table.max = getTableSegment().data.size(); + unsigned int tableSize = getTableData().size(); + if (tableSize > 0) { + out.wasm.table.initial = out.wasm.table.max = tableSize; } } @@ -303,115 +307,40 @@ void Linker::emscriptenGlue(std::ostream& o) { WasmPrinter::printModule(&out.wasm, std::cerr); } - out.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, Address> ids; - std::set<std::string> allSigs; + auto functionsToThunk = getTableData(); + std::remove(functionsToThunk.begin(), functionsToThunk.end(), dummyFunction); + for (auto f : emscripten::makeDynCallThunks(out.wasm, functionsToThunk)) { + exportFunction(f->name, true); + } - void visitCallImport(CallImport* curr) { - if (curr->target == EMSCRIPTEN_ASM_CONST) { - auto arg = curr->operands[0]->cast<Const>(); - Address segmentIndex = parent->segmentsByAddress[arg->value.geti32()]; - std::string code = escape(&parent->out.wasm.memory.segments[segmentIndex].data[0]); - 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 = new Import; - import->name = import->base = curr->target; - import->module = ENV; - import->functionType = ensureFunctionType(getSig(curr), &parent->out.wasm); - import->kind = Import::Function; - parent->out.wasm.addImport(import); - } - } - } + auto staticBump = nextStatic - globalBase; + emscripten::generateEmscriptenMetadata(o, out.wasm, segmentsByAddress, staticBump, out.initializerFunctions); +} - 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.walkModule(&out.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 : out.initializerFunctions) { - if (first) first = false; - else o << ", "; - o << "\"" << func.c_str() << "\""; +void Linker::ensureTableIsPopulated() { + if (out.wasm.table.segments.size() == 0) { + auto emptySegment = out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0))); + out.wasm.table.segments.emplace_back(emptySegment); } - o << "]"; +} - o << " }\n"; +std::vector<Name>& Linker::getTableDataRef() { + assert(out.wasm.table.segments.size() == 1); + return out.wasm.table.segments[0].data; } -Table::Segment& Linker::getTableSegment() { - if (out.wasm.table.segments.size() == 0) { - out.wasm.table.segments.emplace_back(out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0)))); - } else { - assert(out.wasm.table.segments.size() == 1); +std::vector<Name> Linker::getTableData() { + if (out.wasm.table.segments.size() > 0) { + return getTableDataRef(); } - return out.wasm.table.segments[0]; + return {}; } Index Linker::getFunctionIndex(Name name) { if (!functionIndexes.count(name)) { - functionIndexes[name] = getTableSegment().data.size(); - getTableSegment().data.push_back(name); + ensureTableIsPopulated(); + functionIndexes[name] = getTableData().size(); + getTableDataRef().push_back(name); if (debug) { std::cerr << "function index: " << name << ": " << functionIndexes[name] << '\n'; @@ -420,14 +349,6 @@ Index Linker::getFunctionIndex(Name name) { return functionIndexes[name]; } -bool hasI64ResultOrParam(FunctionType* ft) { - if (ft->result == i64) return true; - for (auto ty : ft->params) { - if (ty == i64) return true; - } - return false; -} - void Linker::makeDummyFunction() { bool create = false; // Check if there are address-taken functions @@ -445,34 +366,6 @@ void Linker::makeDummyFunction() { getFunctionIndex(dummy->name); } -void Linker::makeDynCallThunks() { - if (out.wasm.table.segments.size() == 0) return; - std::unordered_set<std::string> sigs; - wasm::Builder wasmBuilder(out.wasm); - for (const auto& indirectFunc : getTableSegment().data) { - // Skip generating thunks for the dummy function - if (indirectFunc == dummyFunction) continue; - std::string sig(getSig(out.wasm.getFunction(indirectFunc))); - auto* funcType = ensureFunctionType(sig, &out.wasm); - if (hasI64ResultOrParam(funcType)) continue; // Can't export i64s on the web. - 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, args); - f->body = call; - out.wasm.addFunction(f); - exportFunction(f->name, true); - } -} - Function* Linker::getImportThunk(Name name, const FunctionType* funcType) { Name thunkName = std::string("__importThunk_") + name.c_str(); if (Function* thunk = out.wasm.checkFunction(thunkName)) return thunk; diff --git a/src/wasm-linker.h b/src/wasm-linker.h index 21d336273..015e3a085 100644 --- a/src/wasm-linker.h +++ b/src/wasm-linker.h @@ -252,9 +252,6 @@ class Linker { // Returns false if an error occurred. bool linkArchive(Archive& archive); - // Name of the dummy function to prevent erroneous nullptr comparisons. - static constexpr const char* dummyFunction = "__wasm_nullptr"; - private: // Allocate a static variable and return its address in linear memory Address allocateStatic(Address allocSize, Address alignment, Name name) { @@ -268,23 +265,14 @@ class Linker { // relocation for it to point to the top of the stack. void placeStackPointer(Address stackAllocation); - template<class C> - void printSet(std::ostream& o, C& c) { - o << "["; - bool first = true; - for (auto& item : c) { - if (first) first = false; - else o << ","; - o << '"' << item << '"'; - } - o << "]"; - } - void ensureImport(Name target, std::string signature); // Makes sure the table has a single segment, with offset 0, // to which we can add content. - Table::Segment& getTableSegment(); + void ensureTableIsPopulated(); + + std::vector<Name>& getTableDataRef(); + std::vector<Name> getTableData(); // Retrieves (and assigns) an entry index in the indirect function table for // a given function. @@ -294,10 +282,6 @@ class Linker { // pointer miscomparisons. void makeDummyFunction(); - // Create thunks for use with emscripten Runtime.dynCall. Creates one for each - // signature in the indirect function table. - void makeDynCallThunks(); - static Address roundUpToPageSize(Address size) { return (size + Memory::kPageSize - 1) & Memory::kPageMask; } |