Merge pull request #695 from WebAssembly/opts
Get optimizer on par with emscripten asm.js optimizer
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 38b2df7..0d7b274 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -133,6 +133,7 @@
SET(asm2wasm_SOURCES
src/tools/asm2wasm.cpp
+ src/wasm-emscripten.cpp
src/wasm.cpp
)
ADD_EXECUTABLE(asm2wasm
@@ -144,6 +145,7 @@
SET(s2wasm_SOURCES
src/tools/s2wasm.cpp
+ src/wasm-emscripten.cpp
src/wasm-linker.cpp
src/wasm.cpp
)
diff --git a/check.py b/check.py
index 3da8946..28bdb43 100755
--- a/check.py
+++ b/check.py
@@ -521,6 +521,12 @@
# bar should be linked from the archive
fail_if_not_contained(output, '(func $bar')
+ # Test exporting memory growth function
+ cmd = [s2wasm, os.path.join('test', 'linker', 'main.s'), '--emscripten-glue', '--allow-memory-growth']
+ output = run_command(cmd)
+ fail_if_not_contained(output, '(export "__growWasmMemory" $__growWasmMemory)')
+ fail_if_not_contained(output, '(func $__growWasmMemory (param $newSize i32)')
+
print '\n[ running validation tests... ]\n'
wasm_as = os.path.join('bin', 'wasm-as')
# Ensure the tests validate by default
diff --git a/src/asm2wasm.h b/src/asm2wasm.h
index 478a0dd..eb5413e 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"
@@ -819,23 +820,7 @@
// 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 2ee094a..8c36a0d 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 @@
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 @@
[](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 @@
});
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 @@
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 0000000..f1e544a
--- /dev/null
+++ b/src/wasm-emscripten.cpp
@@ -0,0 +1,216 @@
+/*
+ * 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>();
+ auto address = arg->value.geti32();
+ auto segmentIterator = segmentsByAddress.find(address);
+ std::string code;
+ if (segmentIterator != segmentsByAddress.end()) {
+ Address segmentIndex = segmentsByAddress[address];
+ code = escape(&wasm.memory.segments[segmentIndex].data[0]);
+ } else {
+ // If we can't find the segment corresponding with the address, then we omitted the segment and the address points to an empty string.
+ code = escape("");
+ }
+ 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 0000000..07c4708
--- /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 04deddf..527ff80 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 @@
// 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 @@
}
// 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 @@
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;
-
- 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);
- }
- }
- }
-
- 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 << "]";
+ auto functionsToThunk = getTableData();
+ std::remove(functionsToThunk.begin(), functionsToThunk.end(), dummyFunction);
+ for (auto f : emscripten::makeDynCallThunks(out.wasm, functionsToThunk)) {
+ exportFunction(f->name, true);
}
- 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() << "\"";
- }
- o << "]";
-
- o << " }\n";
+ auto staticBump = nextStatic - globalBase;
+ emscripten::generateEmscriptenMetadata(o, out.wasm, segmentsByAddress, staticBump, out.initializerFunctions);
}
-Table::Segment& Linker::getTableSegment() {
+void Linker::ensureTableIsPopulated() {
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);
+ auto emptySegment = out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0)));
+ out.wasm.table.segments.emplace_back(emptySegment);
}
- return out.wasm.table.segments[0];
+}
+
+std::vector<Name>& Linker::getTableDataRef() {
+ assert(out.wasm.table.segments.size() == 1);
+ return out.wasm.table.segments[0].data;
+}
+
+std::vector<Name> Linker::getTableData() {
+ if (out.wasm.table.segments.size() > 0) {
+ return getTableDataRef();
+ }
+ 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 @@
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 @@
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 21d3362..015e3a0 100644
--- a/src/wasm-linker.h
+++ b/src/wasm-linker.h
@@ -252,9 +252,6 @@
// 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 @@
// 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 @@
// 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;
}