summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2017-04-28 14:34:25 -0700
committerGitHub <noreply@github.com>2017-04-28 14:34:25 -0700
commita88d9b83a4629f4bf4c3b210b07d11d2396c594d (patch)
tree0df6fa75b22d4eb5a1590e6ee9fe5e6352ace5e4 /src
parent5d4f9eb82226acc0fdb5e2dea1a04e17c340c371 (diff)
downloadbinaryen-a88d9b83a4629f4bf4c3b210b07d11d2396c594d.tar.gz
binaryen-a88d9b83a4629f4bf4c3b210b07d11d2396c594d.tar.bz2
binaryen-a88d9b83a4629f4bf4c3b210b07d11d2396c594d.zip
ctor evaller (#982)
Add wasm-ctor-eval, which evaluates functions at compile time - typically static constructor functions - and applies their effects into memory, saving work at startup. If we encounter something we can't evaluate at compile time in our interpreter, stop there. This is similar to ctor_evaller.py in emscripten (which was for asm.js).
Diffstat (limited to 'src')
-rw-r--r--src/ast/memory-utils.h56
-rw-r--r--src/passes/Precompute.cpp2
-rw-r--r--src/shell-interface.h73
-rw-r--r--src/tools/wasm-ctor-eval.cpp409
-rw-r--r--src/wasm-interpreter.h136
-rw-r--r--src/wasm-io.h1
6 files changed, 594 insertions, 83 deletions
diff --git a/src/ast/memory-utils.h b/src/ast/memory-utils.h
new file mode 100644
index 000000000..dfb33837d
--- /dev/null
+++ b/src/ast/memory-utils.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 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_ast_memory_h
+#define wasm_ast_memory_h
+
+#include <algorithm>
+#include <vector>
+
+#include "literal.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace MemoryUtils {
+ // flattens memory into a single data segment. returns true if successful
+ inline bool flatten(Memory& memory) {
+ if (memory.segments.size() == 0) return true;
+ std::vector<char> data;
+ for (auto& segment : memory.segments) {
+ auto* offset = segment.offset->dynCast<Const>();
+ if (!offset) return false;
+ }
+ for (auto& segment : memory.segments) {
+ auto* offset = segment.offset->dynCast<Const>();
+ auto start = offset->value.getInteger();
+ auto end = start + segment.data.size();
+ if (end > data.size()) {
+ data.resize(end);
+ }
+ std::copy(segment.data.begin(), segment.data.end(), data.begin() + start);
+ }
+ memory.segments.resize(1);
+ memory.segments[0].offset->cast<Const>()->value = Literal(int32_t(0));
+ memory.segments[0].data.swap(data);
+ return true;
+ }
+};
+
+} // namespace wasm
+
+#endif // wasm_ast_memory_h
+
diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp
index bcfa4a980..dfb45f686 100644
--- a/src/passes/Precompute.cpp
+++ b/src/passes/Precompute.cpp
@@ -26,7 +26,7 @@
namespace wasm {
-Name NONSTANDALONE_FLOW("Binaryen|nonstandalone");
+static const Name NONSTANDALONE_FLOW("Binaryen|nonstandalone");
// Execute an expression by itself. Errors if we hit anything we need anything not in the expression itself standalone.
class StandaloneExpressionRunner : public ExpressionRunner<StandaloneExpressionRunner> {
diff --git a/src/shell-interface.h b/src/shell-interface.h
index 3fb2da2b0..076787c9b 100644
--- a/src/shell-interface.h
+++ b/src/shell-interface.h
@@ -95,7 +95,7 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
memory.resize(wasm.memory.initial * wasm::Memory::kPageSize);
// apply memory segments
for (auto& segment : wasm.memory.segments) {
- Address offset = ConstantExpressionRunner(instance.globals).visit(segment.offset).value.geti32();
+ Address offset = ConstantExpressionRunner<TrivialGlobalManager>(instance.globals).visit(segment.offset).value.geti32();
assert(offset + segment.data.size() <= wasm.memory.initial * wasm::Memory::kPageSize);
for (size_t i = 0; i != segment.data.size(); ++i) {
memory.set(offset + i, segment.data[i]);
@@ -104,7 +104,7 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
table.resize(wasm.table.initial);
for (auto& segment : wasm.table.segments) {
- Address offset = ConstantExpressionRunner(instance.globals).visit(segment.offset).value.geti32();
+ Address offset = ConstantExpressionRunner<TrivialGlobalManager>(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) {
table[offset + i] = segment.data[i];
@@ -143,7 +143,7 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
throw ExitException();
}
std::cout << "callImport " << import->name.str << "\n";
- abort();
+ WASM_UNREACHABLE();
}
Literal callTable(Index index, LiteralList& arguments, WasmType result, ModuleInstance& instance) override {
@@ -159,60 +159,19 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
return instance.callFunctionInternal(func->name, arguments);
}
- Literal load(Load* load, Address addr) override {
- switch (load->type) {
- case i32: {
- switch (load->bytes) {
- case 1: return load->signed_ ? Literal((int32_t)memory.get<int8_t>(addr)) : Literal((int32_t)memory.get<uint8_t>(addr));
- case 2: return load->signed_ ? Literal((int32_t)memory.get<int16_t>(addr)) : Literal((int32_t)memory.get<uint16_t>(addr));
- case 4: return load->signed_ ? Literal((int32_t)memory.get<int32_t>(addr)) : Literal((int32_t)memory.get<uint32_t>(addr));
- default: abort();
- }
- break;
- }
- case i64: {
- switch (load->bytes) {
- case 1: return load->signed_ ? Literal((int64_t)memory.get<int8_t>(addr)) : Literal((int64_t)memory.get<uint8_t>(addr));
- case 2: return load->signed_ ? Literal((int64_t)memory.get<int16_t>(addr)) : Literal((int64_t)memory.get<uint16_t>(addr));
- case 4: return load->signed_ ? Literal((int64_t)memory.get<int32_t>(addr)) : Literal((int64_t)memory.get<uint32_t>(addr));
- case 8: return load->signed_ ? Literal((int64_t)memory.get<int64_t>(addr)) : Literal((int64_t)memory.get<uint64_t>(addr));
- default: abort();
- }
- break;
- }
- case f32: return Literal(memory.get<float>(addr));
- case f64: return Literal(memory.get<double>(addr));
- default: abort();
- }
- }
-
- void store(Store* store, Address addr, Literal value) override {
- switch (store->valueType) {
- case i32: {
- switch (store->bytes) {
- case 1: memory.set<int8_t>(addr, value.geti32()); break;
- case 2: memory.set<int16_t>(addr, value.geti32()); break;
- case 4: memory.set<int32_t>(addr, value.geti32()); break;
- default: abort();
- }
- break;
- }
- case i64: {
- switch (store->bytes) {
- case 1: memory.set<int8_t>(addr, (int8_t)value.geti64()); break;
- case 2: memory.set<int16_t>(addr, (int16_t)value.geti64()); break;
- case 4: memory.set<int32_t>(addr, (int32_t)value.geti64()); break;
- case 8: memory.set<int64_t>(addr, value.geti64()); break;
- default: abort();
- }
- break;
- }
- // write floats carefully, ensuring all bits reach memory
- case f32: memory.set<int32_t>(addr, value.reinterpreti32()); break;
- case f64: memory.set<int64_t>(addr, value.reinterpreti64()); break;
- default: abort();
- }
- }
+ int8_t load8s(Address addr) override { return memory.get<int8_t>(addr); }
+ uint8_t load8u(Address addr) override { return memory.get<uint8_t>(addr); }
+ int16_t load16s(Address addr) override { return memory.get<int16_t>(addr); }
+ uint16_t load16u(Address addr) override { return memory.get<uint16_t>(addr); }
+ int32_t load32s(Address addr) override { return memory.get<int32_t>(addr); }
+ uint32_t load32u(Address addr) override { return memory.get<uint32_t>(addr); }
+ int64_t load64s(Address addr) override { return memory.get<int64_t>(addr); }
+ uint64_t load64u(Address addr) override { return memory.get<uint64_t>(addr); }
+
+ void store8(Address addr, int8_t value) override { memory.set<int8_t>(addr, value); }
+ void store16(Address addr, int16_t value) override { memory.set<int16_t>(addr, value); }
+ void store32(Address addr, int32_t value) override { memory.set<int32_t>(addr, value); }
+ void store64(Address addr, int64_t value) override { memory.set<int64_t>(addr, value); }
void growMemory(Address /*oldSize*/, Address newSize) override {
memory.resize(newSize);
diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp
new file mode 100644
index 000000000..160e4a4f2
--- /dev/null
+++ b/src/tools/wasm-ctor-eval.cpp
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2017 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.
+ */
+
+//
+// Loads wasm plus a list of functions that are global ctors, i.e.,
+// are to be executed. It then executes as many of them as it can,
+// applying their changes to memory as needed, then writes it. In
+// other words, this executes code at compile time to speed up
+// startup later.
+//
+
+#include <memory>
+
+#include "pass.h"
+#include "support/command-line.h"
+#include "support/file.h"
+#include "support/colors.h"
+#include "wasm-io.h"
+#include "wasm-interpreter.h"
+#include "wasm-builder.h"
+#include "ast/memory-utils.h"
+
+using namespace wasm;
+
+struct FailToEvalException {
+ std::string why;
+ FailToEvalException(std::string why) : why(why) {}
+};
+
+// We do not have access to imported globals
+class EvallingGlobalManager {
+ // values of globals
+ std::map<Name, Literal> globals;
+
+ // globals that are dangerous to modify in the module
+ std::set<Name> dangerousGlobals;
+
+ // whether we are done adding new globals
+ bool sealed = false;
+
+public:
+ void addDangerous(Name name) {
+ dangerousGlobals.insert(name);
+ }
+
+ void seal() {
+ sealed = true;
+ }
+
+ // for equality purposes, we just care about the globals
+ // and whether they have changed
+ bool operator==(const EvallingGlobalManager& other) {
+ return globals == other.globals;
+ }
+ bool operator!=(const EvallingGlobalManager& other) {
+ return !(*this == other);
+ }
+
+ Literal& operator[](Name name) {
+ if (dangerousGlobals.count(name) > 0) {
+ std::string extra;
+ if (name == "___dso_handle") {
+ extra = "\nrecommendation: build with -s NO_EXIT_RUNTIME=1 so that calls to atexit that use ___dso_handle are not emitted";
+ }
+ throw FailToEvalException(std::string("tried to access a dangerous (import-initialized) global: ") + name.str + extra);
+ }
+ if (sealed) {
+ if (globals.find(name) == globals.end()) {
+ throw FailToEvalException(std::string("tried to access missing global: ") + name.str);
+ }
+ }
+ return globals[name];
+ }
+
+ struct Iterator {
+ Name first;
+ Literal second;
+ bool found;
+
+ Iterator() : found(false) {}
+ Iterator(Name name, Literal value) : first(name), second(value), found(true) {}
+
+ bool operator==(const Iterator& other) {
+ return first == other.first && second == other.second && found == other.found;
+ }
+ bool operator!=(const Iterator& other) {
+ return !(*this == other);
+ }
+ };
+
+ Iterator find(Name name) {
+ if (globals.find(name) == globals.end()) {
+ return end();
+ }
+ return Iterator(name, globals[name]);
+ }
+
+ Iterator end() {
+ return Iterator();
+ }
+};
+
+class EvallingModuleInstance : public ModuleInstanceBase<EvallingGlobalManager, EvallingModuleInstance> {
+public:
+ EvallingModuleInstance(Module& wasm, ExternalInterface* externalInterface) : ModuleInstanceBase(wasm, externalInterface) {
+ // if any global in the module has a non-const constructor, it is using a global import,
+ // which we don't have, and is illegal to use
+ for (auto& global : wasm.globals) {
+ if (!global->init->is<Const>()) {
+ // some constants are ok to use
+ if (auto* get = global->init->dynCast<GetGlobal>()) {
+ auto name = get->name;
+ auto* import = wasm.getImport(name);
+ if (import->module == Name("env") && (
+ import->base == Name("STACKTOP") || // stack constants are special, we handle them
+ import->base == Name("STACK_MAX")
+ )) {
+ continue; // this is fine
+ }
+ }
+ // this global is dangerously initialized by an import, so if it is used, we must fail
+ globals.addDangerous(global->name);
+ }
+ }
+ }
+
+ enum {
+ // put the stack in some ridiculously high location
+ STACK_START = 0x40000000,
+ // use a ridiculously large stack size
+ STACK_SIZE = 32 * 1024 * 1024
+ };
+
+ std::vector<char> stack;
+
+ // create C stack space for us to use. We do *NOT* care about their contents,
+ // assuming the stack top was unwound. the memory may have been modified,
+ // but it should not be read afterwards, doing so would be undefined behavior
+ void setupEnvironment() {
+ // prepare scratch memory
+ stack.resize(STACK_SIZE);
+ // fill usable values for stack imports
+ auto total = STACK_START + STACK_SIZE;
+ globals["STACKTOP"] = Literal(int32_t(STACK_START));
+ globals["STACK_MAX"] = Literal(int32_t(STACK_START + STACK_SIZE));
+ // tell the module to accept writes up to the stack end
+ memorySize = total / Memory::kPageSize;
+ }
+
+ // flatten memory into a single segment
+ void flattenMemory() {
+ MemoryUtils::flatten(wasm.memory);
+ }
+};
+
+struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
+ Module* wasm;
+ EvallingModuleInstance* instance;
+
+ void init(Module& wasm_, EvallingModuleInstance& instance_) override {
+ wasm = &wasm_;
+ instance = &instance_;
+ }
+
+ void importGlobals(EvallingGlobalManager& globals, Module& wasm_) override {
+ }
+
+ Literal callImport(Import *import, LiteralList& arguments) override {
+ std::string extra;
+ if (import->module == "env" && import->base == "___cxa_atexit") {
+ extra = "\nrecommendation: build with -s NO_EXIT_RUNTIME=1 so that calls to atexit are not emitted";
+ }
+ throw FailToEvalException(std::string("call import: ") + import->module.str + "." + import->base.str + extra);
+ }
+
+ Literal callTable(Index index, LiteralList& arguments, WasmType result, EvallingModuleInstance& instance) override {
+ // we assume the table is not modified (hmm)
+ // look through the segments, try to find the function
+ for (auto& segment : wasm->table.segments) {
+ Index start;
+ // look for the index in this segment. if it has a constant offset, we look in
+ // the proper range. if it instead gets a global, we rely on the fact that when
+ // not dynamically linking then the table is loaded at offset 0.
+ if (auto* c = segment.offset->dynCast<Const>()) {
+ start = c->value.getInteger();
+ } else if (segment.offset->is<GetGlobal>()) {
+ start = 0;
+ } else {
+ WASM_UNREACHABLE(); // wasm spec only allows const and get_global there
+ }
+ auto end = start + segment.data.size();
+ if (start <= index && index < end) {
+ auto name = segment.data[index - start];
+ // if this is one of our functions, we can call it; if it was imported, fail
+ if (wasm->getFunctionOrNull(name)) {
+ return instance.callFunctionInternal(name, arguments);
+ } else {
+ throw FailToEvalException(std::string("callTable on imported function: ") + name.str);
+ }
+ }
+ }
+ throw FailToEvalException(std::string("callTable on index not found in static segments: ") + std::to_string(index));
+ }
+
+ int8_t load8s(Address addr) override { return doLoad<int8_t>(addr); }
+ uint8_t load8u(Address addr) override { return doLoad<uint8_t>(addr); }
+ int16_t load16s(Address addr) override { return doLoad<int16_t>(addr); }
+ uint16_t load16u(Address addr) override { return doLoad<uint16_t>(addr); }
+ int32_t load32s(Address addr) override { return doLoad<int32_t>(addr); }
+ uint32_t load32u(Address addr) override { return doLoad<uint32_t>(addr); }
+ int64_t load64s(Address addr) override { return doLoad<int64_t>(addr); }
+ uint64_t load64u(Address addr) override { return doLoad<uint64_t>(addr); }
+
+ void store8(Address addr, int8_t value) override { doStore<int8_t>(addr, value); }
+ void store16(Address addr, int16_t value) override { doStore<int16_t>(addr, value); }
+ void store32(Address addr, int32_t value) override { doStore<int32_t>(addr, value); }
+ void store64(Address addr, int64_t value) override { doStore<int64_t>(addr, value); }
+
+ void growMemory(Address /*oldSize*/, Address newSize) override {
+ throw FailToEvalException("grow memory");
+ }
+
+ void trap(const char* why) override {
+ throw FailToEvalException(std::string("trap: ") + why);
+ }
+
+private:
+ // TODO: handle unaligned too, see shell-interface
+
+ template <typename T>
+ T* getMemory(Address address) {
+ // if memory is on the stack, use the stack
+ if (address >= instance->STACK_START) {
+ Address relative = address - instance->STACK_START;
+ if (relative + sizeof(T) > instance->STACK_SIZE) {
+ throw FailToEvalException("stack usage too high");
+ }
+ // in range, all is good, use the stack
+ return (T*)(&instance->stack[relative]);
+ }
+
+ // otherwise, this must be in the singleton segment. resize as needed
+ if (wasm->memory.segments.size() == 0) {
+ std::vector<char> temp;
+ Builder builder(*wasm);
+ wasm->memory.segments.push_back(
+ Memory::Segment(
+ builder.makeConst(Literal(int32_t(0))),
+ temp
+ )
+ );
+ }
+ assert(wasm->memory.segments[0].offset->cast<Const>()->value.getInteger() == 0);
+ auto max = address + sizeof(T);
+ auto& data = wasm->memory.segments[0].data;
+ if (max > data.size()) {
+ data.resize(max);
+ }
+ return (T*)(&data[address]);
+ }
+
+ template <typename T>
+ void doStore(Address address, T value) {
+ // do a memcpy to avoid undefined behavior if unaligned
+ memcpy(getMemory<T>(address), &value, sizeof(T));
+ }
+
+ template <typename T>
+ T doLoad(Address address) {
+ // do a memcpy to avoid undefined behavior if unaligned
+ T ret;
+ memcpy(&ret, getMemory<T>(address), sizeof(T));
+ return ret;
+ }
+};
+
+void evalCtors(Module& wasm, std::vector<std::string> ctors) {
+ CtorEvalExternalInterface interface;
+ try {
+ // create an instance for evalling
+ EvallingModuleInstance instance(wasm, &interface);
+ // flatten memory, so we do not depend on the layout of data segments
+ instance.flattenMemory();
+ // set up the stack area and other environment details
+ instance.setupEnvironment();
+ // we should not add new globals from here on; as a result, using
+ // an imported global will fail, as it is missing and so looks new
+ instance.globals.seal();
+ // go one by one, in order, until we fail
+ // TODO: if we knew priorities, we could reorder?
+ for (auto& ctor : ctors) {
+ std::cerr << "trying to eval " << ctor << '\n';
+ // snapshot memory, as either the entire function is done, or none
+ auto memoryBefore = wasm.memory;
+ // snapshot globals (note that STACKTOP might be modified, but should
+ // be returned, so that works out)
+ auto globalsBefore = instance.globals;
+ try {
+ instance.callExport(ctor);
+ } catch (FailToEvalException& fail) {
+ // that's it, we failed, so stop here, cleaning up partial
+ // memory changes first
+ std::cerr << " ...stopping since could not eval: " << fail.why << "\n";
+ wasm.memory = memoryBefore;
+ return;
+ }
+ if (instance.globals != globalsBefore) {
+ std::cerr << " ...stopping since globals modified\n";
+ wasm.memory = memoryBefore;
+ return;
+ }
+ std::cerr << " ...success on " << ctor << ".\n";
+ // success, the entire function was evalled!
+ auto* exp = wasm.getExport(ctor);
+ auto* func = wasm.getFunction(exp->value);
+ func->body = wasm.allocator.alloc<Nop>();
+ }
+ } catch (FailToEvalException& fail) {
+ // that's it, we failed to even create the instance
+ std::cerr << " ...stopping since could not create module instance: " << fail.why << "\n";
+ return;
+ }
+}
+
+//
+// main
+//
+
+int main(int argc, const char* argv[]) {
+ Name entry;
+ std::vector<std::string> passes;
+ PassOptions passOptions;
+ bool emitBinary = true;
+ bool debugInfo = false;
+ std::string ctorsString;
+
+ Options options("wasm-opt", "Optimize .wast files");
+ options
+ .add("--output", "-o", "Output file (stdout if not specified)",
+ Options::Arguments::One,
+ [](Options* o, const std::string& argument) {
+ o->extra["output"] = argument;
+ Colors::disable();
+ })
+ .add("--emit-text", "-S", "Emit text instead of binary for the output file",
+ Options::Arguments::Zero,
+ [&](Options *o, const std::string &argument) { emitBinary = false; })
+ .add("--debuginfo", "-g", "Emit names section and debug info",
+ Options::Arguments::Zero,
+ [&](Options *o, const std::string &arguments) { debugInfo = true; })
+ .add("--ctors", "-c", "Comma-separated list of global constructor functions to evaluate",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) {
+ ctorsString = argument;
+ })
+ .add_positional("INFILE", Options::Arguments::One,
+ [](Options* o, const std::string& argument) {
+ o->extra["infile"] = argument;
+ });
+ options.parse(argc, argv);
+
+ auto input(read_file<std::string>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release));
+
+ Module wasm;
+
+ {
+ if (options.debug) std::cerr << "reading...\n";
+ ModuleReader reader;
+ reader.setDebug(options.debug);
+
+ try {
+ reader.read(options.extra["infile"], wasm);
+ } catch (ParseException& p) {
+ p.dump(std::cerr);
+ Fatal() << "error in parsing input";
+ }
+ }
+
+ // get list of ctors, and eval them
+ std::vector<std::string> ctors;
+ std::istringstream stream(ctorsString);
+ std::string temp;
+ while (std::getline(stream, temp, ',')) {
+ ctors.push_back(temp);
+ }
+ evalCtors(wasm, ctors);
+
+ if (options.extra.count("output") > 0) {
+ if (options.debug) std::cerr << "writing..." << std::endl;
+ ModuleWriter writer;
+ writer.setDebug(options.debug);
+ writer.setBinary(emitBinary);
+ writer.setDebugInfo(debugInfo);
+ writer.write(wasm, options.extra["output"]);
+ }
+}
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 9558a3c38..37cf7a7a6 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -510,10 +510,11 @@ public:
};
// Execute an constant expression in a global init or memory offset
-class ConstantExpressionRunner : public ExpressionRunner<ConstantExpressionRunner> {
- std::map<Name, Literal>& globals;
+template<typename GlobalManager>
+class ConstantExpressionRunner : public ExpressionRunner<ConstantExpressionRunner<GlobalManager>> {
+ GlobalManager& globals;
public:
- ConstantExpressionRunner(std::map<Name, Literal>& globals) : globals(globals) {}
+ ConstantExpressionRunner(GlobalManager& globals) : globals(globals) {}
Flow visitLoop(Loop* curr) { WASM_UNREACHABLE(); }
Flow visitCall(Call* curr) { WASM_UNREACHABLE(); }
@@ -538,7 +539,8 @@ public:
// To call into the interpreter, use callExport.
//
-class ModuleInstance {
+template<typename GlobalManager, typename SubType>
+class ModuleInstanceBase {
public:
//
// You need to implement one of these to create a concrete interpreter. The
@@ -546,32 +548,104 @@ public:
// an imported function or accessing memory.
//
struct ExternalInterface {
- virtual void init(Module& wasm, ModuleInstance& instance) {}
- virtual void importGlobals(std::map<Name, Literal>& globals, Module& wasm) = 0;
+ virtual void init(Module& wasm, SubType& instance) {}
+ virtual void importGlobals(GlobalManager& globals, Module& wasm) = 0;
virtual Literal callImport(Import* import, LiteralList& arguments) = 0;
- virtual Literal callTable(Index index, LiteralList& arguments, WasmType result, ModuleInstance& instance) = 0;
- virtual Literal load(Load* load, Address addr) = 0;
- virtual void store(Store* store, Address addr, Literal value) = 0;
+ virtual Literal callTable(Index index, LiteralList& arguments, WasmType result, SubType& instance) = 0;
virtual void growMemory(Address oldSize, Address newSize) = 0;
virtual void trap(const char* why) = 0;
+
+ // the default impls for load and store switch on the sizes. you can either
+ // customize load/store, or the sub-functions which they call
+ virtual Literal load(Load* load, Address addr) {
+ switch (load->type) {
+ case i32: {
+ switch (load->bytes) {
+ case 1: return load->signed_ ? Literal((int32_t)load8s(addr)) : Literal((int32_t)load8u(addr));
+ case 2: return load->signed_ ? Literal((int32_t)load16s(addr)) : Literal((int32_t)load16u(addr));
+ case 4: return load->signed_ ? Literal((int32_t)load32s(addr)) : Literal((int32_t)load32u(addr));
+ default: WASM_UNREACHABLE();
+ }
+ break;
+ }
+ case i64: {
+ switch (load->bytes) {
+ case 1: return load->signed_ ? Literal((int64_t)load8s(addr)) : Literal((int64_t)load8u(addr));
+ case 2: return load->signed_ ? Literal((int64_t)load16s(addr)) : Literal((int64_t)load16u(addr));
+ case 4: return load->signed_ ? Literal((int64_t)load32s(addr)) : Literal((int64_t)load32u(addr));
+ case 8: return load->signed_ ? Literal((int64_t)load64s(addr)) : Literal((int64_t)load64u(addr));
+ default: WASM_UNREACHABLE();
+ }
+ break;
+ }
+ case f32: return Literal(load32u(addr)).castToF32();
+ case f64: return Literal(load64u(addr)).castToF64();
+ default: WASM_UNREACHABLE();
+ }
+ }
+ virtual void store(Store* store, Address addr, Literal value) {
+ switch (store->valueType) {
+ case i32: {
+ switch (store->bytes) {
+ case 1: store8(addr, value.geti32()); break;
+ case 2: store16(addr, value.geti32()); break;
+ case 4: store32(addr, value.geti32()); break;
+ default: WASM_UNREACHABLE();
+ }
+ break;
+ }
+ case i64: {
+ switch (store->bytes) {
+ case 1: store8(addr, value.geti64()); break;
+ case 2: store16(addr, value.geti64()); break;
+ case 4: store32(addr, value.geti64()); break;
+ case 8: store64(addr, value.geti64()); break;
+ default: WASM_UNREACHABLE();
+ }
+ break;
+ }
+ // write floats carefully, ensuring all bits reach memory
+ case f32: store32(addr, value.reinterpreti32()); break;
+ case f64: store64(addr, value.reinterpreti64()); break;
+ default: WASM_UNREACHABLE();
+ }
+ }
+
+ virtual int8_t load8s(Address addr) { WASM_UNREACHABLE(); }
+ virtual uint8_t load8u(Address addr) { WASM_UNREACHABLE(); }
+ virtual int16_t load16s(Address addr) { WASM_UNREACHABLE(); }
+ virtual uint16_t load16u(Address addr) { WASM_UNREACHABLE(); }
+ virtual int32_t load32s(Address addr) { WASM_UNREACHABLE(); }
+ virtual uint32_t load32u(Address addr) { WASM_UNREACHABLE(); }
+ virtual int64_t load64s(Address addr) { WASM_UNREACHABLE(); }
+ virtual uint64_t load64u(Address addr) { WASM_UNREACHABLE(); }
+
+ virtual void store8(Address addr, int8_t value) { WASM_UNREACHABLE(); }
+ virtual void store16(Address addr, int16_t value) { WASM_UNREACHABLE(); }
+ virtual void store32(Address addr, int32_t value) { WASM_UNREACHABLE(); }
+ virtual void store64(Address addr, int64_t value) { WASM_UNREACHABLE(); }
};
+ SubType* self() {
+ return static_cast<SubType*>(this);
+ }
+
Module& wasm;
// Values of globals
- std::map<Name, Literal> globals;
+ GlobalManager globals;
- ModuleInstance(Module& wasm, ExternalInterface* externalInterface) : wasm(wasm), externalInterface(externalInterface) {
+ ModuleInstanceBase(Module& wasm, ExternalInterface* externalInterface) : wasm(wasm), externalInterface(externalInterface) {
// import globals from the outside
externalInterface->importGlobals(globals, wasm);
// prepare memory
memorySize = wasm.memory.initial;
// generate internal (non-imported) globals
for (auto& global : wasm.globals) {
- globals[global->name] = ConstantExpressionRunner(globals).visit(global->init).value;
+ globals[global->name] = ConstantExpressionRunner<GlobalManager>(globals).visit(global->init).value;
}
// initialize the rest of the external interface
- externalInterface->init(wasm, *this);
+ externalInterface->init(wasm, *self());
// run start, if present
if (wasm.start.is()) {
LiteralList arguments;
@@ -586,6 +660,11 @@ public:
return callFunction(export_->value, arguments);
}
+ Literal callExport(Name name) {
+ LiteralList arguments;
+ return callExport(name, arguments);
+ }
+
// get an exported global
Literal getExport(Name name) {
Export *export_ = wasm.getExportOrNull(name);
@@ -660,17 +739,17 @@ public:
// Executes expresions with concrete runtime info, the function and module at runtime
class RuntimeExpressionRunner : public ExpressionRunner<RuntimeExpressionRunner> {
- ModuleInstance& instance;
+ ModuleInstanceBase& instance;
FunctionScope& scope;
public:
- RuntimeExpressionRunner(ModuleInstance& instance, FunctionScope& scope) : instance(instance), scope(scope) {}
+ RuntimeExpressionRunner(ModuleInstanceBase& instance, FunctionScope& scope) : instance(instance), scope(scope) {}
Flow generateArguments(const ExpressionList& operands, LiteralList& arguments) {
NOTE_ENTER_("generateArguments");
arguments.reserve(operands.size());
for (auto expression : operands) {
- Flow flow = visit(expression);
+ Flow flow = this->visit(expression);
if (flow.breaking()) return flow;
NOTE_EVAL1(flow.value);
arguments.push_back(flow.value);
@@ -702,10 +781,10 @@ public:
LiteralList arguments;
Flow flow = generateArguments(curr->operands, arguments);
if (flow.breaking()) return flow;
- Flow target = visit(curr->target);
+ Flow target = this->visit(curr->target);
if (target.breaking()) return target;
Index index = target.value.geti32();
- return instance.externalInterface->callTable(index, arguments, curr->type, instance);
+ return instance.externalInterface->callTable(index, arguments, curr->type, *instance.self());
}
Flow visitGetLocal(GetLocal *curr) {
@@ -718,7 +797,7 @@ public:
Flow visitSetLocal(SetLocal *curr) {
NOTE_ENTER("SetLocal");
auto index = curr->index;
- Flow flow = visit(curr->value);
+ Flow flow = this->visit(curr->value);
if (flow.breaking()) return flow;
NOTE_EVAL1(index);
NOTE_EVAL1(flow.value);
@@ -738,7 +817,7 @@ public:
Flow visitSetGlobal(SetGlobal *curr) {
NOTE_ENTER("SetGlobal");
auto name = curr->name;
- Flow flow = visit(curr->value);
+ Flow flow = this->visit(curr->value);
if (flow.breaking()) return flow;
NOTE_EVAL1(name);
NOTE_EVAL1(flow.value);
@@ -748,7 +827,7 @@ public:
Flow visitLoad(Load *curr) {
NOTE_ENTER("Load");
- Flow flow = visit(curr->ptr);
+ Flow flow = this->visit(curr->ptr);
if (flow.breaking()) return flow;
NOTE_EVAL1(flow);
auto addr = instance.getFinalAddress(curr, flow.value);
@@ -759,9 +838,9 @@ public:
}
Flow visitStore(Store *curr) {
NOTE_ENTER("Store");
- Flow ptr = visit(curr->ptr);
+ Flow ptr = this->visit(curr->ptr);
if (ptr.breaking()) return ptr;
- Flow value = visit(curr->value);
+ Flow value = this->visit(curr->value);
if (value.breaking()) return value;
auto addr = instance.getFinalAddress(curr, ptr.value);
NOTE_EVAL1(addr);
@@ -777,7 +856,7 @@ public:
case CurrentMemory: return Literal(int32_t(instance.memorySize));
case GrowMemory: {
auto fail = Literal(int32_t(-1));
- Flow flow = visit(curr->operands[0]);
+ Flow flow = this->visit(curr->operands[0]);
if (flow.breaking()) return flow;
int32_t ret = instance.memorySize;
uint32_t delta = flow.value.geti32();
@@ -840,7 +919,7 @@ public:
return ret;
}
-private:
+protected:
Address memorySize; // in pages
@@ -866,6 +945,13 @@ private:
ExternalInterface* externalInterface;
};
+// The default ModuleInstance uses a trivial global manager
+typedef std::map<Name, Literal> TrivialGlobalManager;
+class ModuleInstance : public ModuleInstanceBase<TrivialGlobalManager, ModuleInstance> {
+public:
+ ModuleInstance(Module& wasm, ExternalInterface* externalInterface) : ModuleInstanceBase(wasm, externalInterface) {}
+};
+
} // namespace wasm
#endif // wasm_wasm_interpreter_h
diff --git a/src/wasm-io.h b/src/wasm-io.h
index 7d7358f9c..803cbaf90 100644
--- a/src/wasm-io.h
+++ b/src/wasm-io.h
@@ -22,6 +22,7 @@
#define wasm_wasm_io_h
#include "wasm.h"
+#include "parsing.h"
namespace wasm {