diff options
51 files changed, 1248 insertions, 83 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 46e22b7e0..c6acad7dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,3 +265,14 @@ TARGET_LINK_LIBRARIES(wasm-dis passes wasm asmjs ast support) SET_PROPERTY(TARGET wasm-dis PROPERTY CXX_STANDARD 11) SET_PROPERTY(TARGET wasm-dis PROPERTY CXX_STANDARD_REQUIRED ON) INSTALL(TARGETS wasm-dis DESTINATION ${CMAKE_INSTALL_BINDIR}) + +SET(wasm-ctor-eval_SOURCES + src/tools/wasm-ctor-eval.cpp +) +ADD_EXECUTABLE(wasm-ctor-eval + ${wasm-ctor-eval_SOURCES}) +TARGET_LINK_LIBRARIES(wasm-ctor-eval wasm asmjs emscripten-optimizer ${all_passes} ast support) +SET_PROPERTY(TARGET wasm-ctor-eval PROPERTY CXX_STANDARD 11) +SET_PROPERTY(TARGET wasm-ctor-eval PROPERTY CXX_STANDARD_REQUIRED ON) +INSTALL(TARGETS wasm-ctor-eval DESTINATION bin) + diff --git a/auto_update_tests.py b/auto_update_tests.py index 76cd02cbb..9eb0fb50c 100755 --- a/auto_update_tests.py +++ b/auto_update_tests.py @@ -226,4 +226,17 @@ for s in sorted(os.listdir(os.path.join('test', 'binaryen.js'))): out = run_command(cmd, stderr=subprocess.STDOUT) open(os.path.join('test', 'binaryen.js', s + '.txt'), 'w').write(out) +print '\n[ checking wasm-ctor-eval... ]\n' + +for t in os.listdir(os.path.join('test', 'ctor-eval')): + if t.endswith(('.wast', '.wasm')): + print '..', t + t = os.path.join('test', 'ctor-eval', t) + ctors = open(t + '.ctors').read().strip() + cmd = [os.path.join('bin', 'wasm-ctor-eval'), t, '-o', 'a.wast', '-S', '--ctors', ctors] + stdout = run_command(cmd) + actual = open('a.wast').read() + out = t + '.out' + with open(out, 'w') as o: o.write(actual) + print '\n[ success! ]' @@ -237,6 +237,20 @@ for t in os.listdir(os.path.join('test', 'merge')): with open(out + '.stdout') as f: fail_if_not_identical(f.read(), stdout) +print '\n[ checking wasm-ctor-eval... ]\n' + +for t in os.listdir(os.path.join('test', 'ctor-eval')): + if t.endswith(('.wast', '.wasm')): + print '..', t + t = os.path.join('test', 'ctor-eval', t) + ctors = open(t + '.ctors').read().strip() + cmd = [os.path.join('bin', 'wasm-ctor-eval'), t, '-o', 'a.wast', '-S', '--ctors', ctors] + stdout = run_command(cmd) + actual = open('a.wast').read() + out = t + '.out' + with open(out) as f: + fail_if_not_identical(f.read(), actual) + print '\n[ checking wasm-shell spec testcases... ]\n' if len(requested) == 0: 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 { diff --git a/test/ctor-eval/bad-indirect-call.wast b/test/ctor-eval/bad-indirect-call.wast new file mode 100644 index 000000000..bed3112c3 --- /dev/null +++ b/test/ctor-eval/bad-indirect-call.wast @@ -0,0 +1,15 @@ +(module + (type $v (func)) + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (table 1 1 anyfunc) + (elem (i32.const 0) $call-indirect) + (export "test1" $test1) + (func $test1 + (call_indirect $v (i32.const 1)) ;; unsafe to call, out of range + (i32.store8 (i32.const 20) (i32.const 120)) + ) + (func $call-indirect + (i32.store8 (i32.const 40) (i32.const 67)) + ) +) diff --git a/test/ctor-eval/bad-indirect-call.wast.ctors b/test/ctor-eval/bad-indirect-call.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/bad-indirect-call.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/bad-indirect-call.wast.out b/test/ctor-eval/bad-indirect-call.wast.out new file mode 100644 index 000000000..27c849701 --- /dev/null +++ b/test/ctor-eval/bad-indirect-call.wast.out @@ -0,0 +1,23 @@ +(module + (type $v (func)) + (table 1 1 anyfunc) + (elem (i32.const 0) $call-indirect) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00waka waka waka waka waka") + (export "test1" (func $test1)) + (func $test1 (type $v) + (call_indirect $v + (i32.const 1) + ) + (i32.store8 + (i32.const 20) + (i32.const 120) + ) + ) + (func $call-indirect (type $v) + (i32.store8 + (i32.const 40) + (i32.const 67) + ) + ) +) diff --git a/test/ctor-eval/bad-indirect-call2.wast b/test/ctor-eval/bad-indirect-call2.wast new file mode 100644 index 000000000..52eaab67c --- /dev/null +++ b/test/ctor-eval/bad-indirect-call2.wast @@ -0,0 +1,16 @@ +(module + (type $v (func)) + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (import "env" "_abort" (func $_abort)) + (table 2 2 anyfunc) + (elem (i32.const 0) $_abort $call-indirect) + (export "test1" $test1) + (func $test1 + (call_indirect $v (i32.const 0)) ;; unsafe to call, imported + (i32.store8 (i32.const 20) (i32.const 120)) + ) + (func $call-indirect + (i32.store8 (i32.const 40) (i32.const 67)) + ) +) diff --git a/test/ctor-eval/bad-indirect-call2.wast.ctors b/test/ctor-eval/bad-indirect-call2.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/bad-indirect-call2.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/bad-indirect-call2.wast.out b/test/ctor-eval/bad-indirect-call2.wast.out new file mode 100644 index 000000000..b07a2d40b --- /dev/null +++ b/test/ctor-eval/bad-indirect-call2.wast.out @@ -0,0 +1,25 @@ +(module + (type $v (func)) + (type $FUNCSIG$v (func)) + (import "env" "_abort" (func $_abort)) + (table 2 2 anyfunc) + (elem (i32.const 0) $_abort $call-indirect) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00waka waka waka waka waka") + (export "test1" (func $test1)) + (func $test1 (type $v) + (call_indirect $v + (i32.const 0) + ) + (i32.store8 + (i32.const 20) + (i32.const 120) + ) + ) + (func $call-indirect (type $v) + (i32.store8 + (i32.const 40) + (i32.const 67) + ) + ) +) diff --git a/test/ctor-eval/basics-flatten.wast b/test/ctor-eval/basics-flatten.wast new file mode 100644 index 000000000..3bbd27a6b --- /dev/null +++ b/test/ctor-eval/basics-flatten.wast @@ -0,0 +1,36 @@ +(module + (type $v (func)) + (memory 256 256) + ;; test flattening of multiple segments + (data (i32.const 10) "waka ") + (data (i32.const 15) "waka") ;; skip a byte here + (data (i32.const 20) "waka waka waka") + (table 1 1 anyfunc) + (elem (i32.const 0) $call-indirect) + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + (func $test1 + (drop (i32.const 0)) ;; no work at all, really + (call $safe-to-call) ;; safe to call + (call_indirect $v (i32.const 0)) ;; safe to call + ) + (func $test2 + (drop (i32.load (i32.const 12))) ;; a safe load + (drop (i32.load16 (i32.const 12))) + (drop (i32.load8 (i32.const 12))) + ) + (func $test3 + (i32.store (i32.const 12) (i32.const 115)) ;; a safe store, should alter memory + (i32.store16 (i32.const 20) (i32.const 31353)) + (i32.store8 (i32.const 23) (i32.const 120)) + ) + (func $safe-to-call + (drop (i32.const 1)) + (i32.store8 (i32.const 10) (i32.const 110)) ;; safe write too (lowest possible) + (i32.store8 (i32.const 33) (i32.const 109)) ;; safe write too (highest possible) + ) + (func $call-indirect + (i32.store8 (i32.const 40) (i32.const 67)) + ) +) diff --git a/test/ctor-eval/basics-flatten.wast.ctors b/test/ctor-eval/basics-flatten.wast.ctors new file mode 100644 index 000000000..c7060ede5 --- /dev/null +++ b/test/ctor-eval/basics-flatten.wast.ctors @@ -0,0 +1 @@ +test1,test2,test3 diff --git a/test/ctor-eval/basics-flatten.wast.out b/test/ctor-eval/basics-flatten.wast.out new file mode 100644 index 000000000..33177707e --- /dev/null +++ b/test/ctor-eval/basics-flatten.wast.out @@ -0,0 +1,38 @@ +(module + (type $v (func)) + (table 1 1 anyfunc) + (elem (i32.const 0) $call-indirect) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00nas\00\00\00aka\00yzkx waka wakm\00\00\00\00\00\00C") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $v) + (nop) + ) + (func $test2 (type $v) + (nop) + ) + (func $test3 (type $v) + (nop) + ) + (func $safe-to-call (type $v) + (drop + (i32.const 1) + ) + (i32.store8 + (i32.const 10) + (i32.const 110) + ) + (i32.store8 + (i32.const 33) + (i32.const 109) + ) + ) + (func $call-indirect (type $v) + (i32.store8 + (i32.const 40) + (i32.const 67) + ) + ) +) diff --git a/test/ctor-eval/basics.wast b/test/ctor-eval/basics.wast new file mode 100644 index 000000000..a81b2de2f --- /dev/null +++ b/test/ctor-eval/basics.wast @@ -0,0 +1,33 @@ +(module + (type $v (func)) + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (table 1 1 anyfunc) + (elem (i32.const 0) $call-indirect) + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + (func $test1 + (drop (i32.const 0)) ;; no work at all, really + (call $safe-to-call) ;; safe to call + (call_indirect $v (i32.const 0)) ;; safe to call + ) + (func $test2 + (drop (i32.load (i32.const 12))) ;; a safe load + (drop (i32.load16 (i32.const 12))) + (drop (i32.load8 (i32.const 12))) + ) + (func $test3 + (i32.store (i32.const 12) (i32.const 115)) ;; a safe store, should alter memory + (i32.store16 (i32.const 20) (i32.const 31353)) + (i32.store8 (i32.const 23) (i32.const 120)) + ) + (func $safe-to-call + (drop (i32.const 1)) + (i32.store8 (i32.const 10) (i32.const 110)) ;; safe write too (lowest possible) + (i32.store8 (i32.const 33) (i32.const 109)) ;; safe write too (highest possible) + ) + (func $call-indirect + (i32.store8 (i32.const 40) (i32.const 67)) + ) +) diff --git a/test/ctor-eval/basics.wast.ctors b/test/ctor-eval/basics.wast.ctors new file mode 100644 index 000000000..c7060ede5 --- /dev/null +++ b/test/ctor-eval/basics.wast.ctors @@ -0,0 +1 @@ +test1,test2,test3 diff --git a/test/ctor-eval/basics.wast.out b/test/ctor-eval/basics.wast.out new file mode 100644 index 000000000..bf7735185 --- /dev/null +++ b/test/ctor-eval/basics.wast.out @@ -0,0 +1,38 @@ +(module + (type $v (func)) + (table 1 1 anyfunc) + (elem (i32.const 0) $call-indirect) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00nas\00\00\00aka yzkx waka wakm\00\00\00\00\00\00C") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $v) + (nop) + ) + (func $test2 (type $v) + (nop) + ) + (func $test3 (type $v) + (nop) + ) + (func $safe-to-call (type $v) + (drop + (i32.const 1) + ) + (i32.store8 + (i32.const 10) + (i32.const 110) + ) + (i32.store8 + (i32.const 33) + (i32.const 109) + ) + ) + (func $call-indirect (type $v) + (i32.store8 + (i32.const 40) + (i32.const 67) + ) + ) +) diff --git a/test/ctor-eval/imported.wast b/test/ctor-eval/imported.wast new file mode 100644 index 000000000..3f065377d --- /dev/null +++ b/test/ctor-eval/imported.wast @@ -0,0 +1,45 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + ;; stack imports are special + (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32)) + (import "env" "STACK_MAX" (global $STACK_MAX$asm2wasm$import i32)) + ;; other imports must not be touched! + (import "env" "tempDoublePtr" (global $tempDoublePtr i32)) + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + ;; ok to modify a global, if we keep it the same value + (global $mine (mut i32) (i32.const 1)) + ;; stack imports are ok to use. their uses are the same as other + ;; globals - must keep the same value (which means, unwind the stack) + (global $STACKTOP (mut i32) (get_global $STACKTOP$asm2wasm$import)) + (global $STACK_MAX (mut i32) (get_global $STACK_MAX$asm2wasm$import)) + ;; a global initialized by an import, so bad, but ok if not used + (global $do-not-use (mut i32) (get_global $tempDoublePtr)) + (func $test1 + (local $temp i32) + (set_global $mine (i32.const 1)) + (set_local $temp (get_global $STACKTOP)) + (set_global $STACKTOP (i32.const 1337)) ;; bad + (set_global $STACKTOP (get_local $temp)) ;; save us + ;; use the stack memory + (i32.store (get_local $temp) (i32.const 1337)) + (if + (i32.ne + (i32.load (get_local $temp)) + (i32.const 1337) + ) + (unreachable) ;; they should be equal, never get here + ) + ;; finally, do a valid store + (i32.store8 (i32.const 12) (i32.const 115)) + ) + (func $test2 + (set_global $tempDoublePtr (i32.const 1)) ;; bad! + (i32.store8 (i32.const 13) (i32.const 115)) + ) + (func $test3 + (i32.store8 (i32.const 14) (i32.const 115)) + ) +) diff --git a/test/ctor-eval/imported.wast.ctors b/test/ctor-eval/imported.wast.ctors new file mode 100644 index 000000000..c7060ede5 --- /dev/null +++ b/test/ctor-eval/imported.wast.ctors @@ -0,0 +1 @@ +test1,test2,test3 diff --git a/test/ctor-eval/imported.wast.out b/test/ctor-eval/imported.wast.out new file mode 100644 index 000000000..eeb405454 --- /dev/null +++ b/test/ctor-eval/imported.wast.out @@ -0,0 +1,34 @@ +(module + (type $0 (func)) + (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32)) + (import "env" "STACK_MAX" (global $STACK_MAX$asm2wasm$import i32)) + (import "env" "tempDoublePtr" (global $tempDoublePtr i32)) + (global $mine (mut i32) (i32.const 1)) + (global $STACKTOP (mut i32) (get_global $STACKTOP$asm2wasm$import)) + (global $STACK_MAX (mut i32) (get_global $STACK_MAX$asm2wasm$import)) + (global $do-not-use (mut i32) (get_global $tempDoublePtr)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00wasa waka waka waka waka") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $0) + (local $temp i32) + (nop) + ) + (func $test2 (type $0) + (set_global $tempDoublePtr + (i32.const 1) + ) + (i32.store8 + (i32.const 13) + (i32.const 115) + ) + ) + (func $test3 (type $0) + (i32.store8 + (i32.const 14) + (i32.const 115) + ) + ) +) diff --git a/test/ctor-eval/imported2.wast b/test/ctor-eval/imported2.wast new file mode 100644 index 000000000..bfdbc5ee8 --- /dev/null +++ b/test/ctor-eval/imported2.wast @@ -0,0 +1,26 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + ;; stack imports are special-cased + (import "env" "STACKTOP" (global $STACKTOP i32)) + (import "env" "STACK_MAX" (global $STACK_MAX i32)) + ;; other imports must not be touched! + (import "env" "tempDoublePtr" (global $tempDoublePtr i32)) + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + ;; ok to modify a global, if we keep it the same value + (global $mine (mut i32) (i32.const 1)) + (func $test1 + (set_global $mine (i32.const 2)) + (set_global $mine (i32.const 1)) ;; restore! + (i32.store8 (i32.const 12) (i32.const 115)) + ) + (func $test2 + (set_global $mine (i32.const 2)) ;; embadden + (i32.store8 (i32.const 13) (i32.const 115)) + ) + (func $test3 + (i32.store8 (i32.const 14) (i32.const 115)) + ) +) diff --git a/test/ctor-eval/imported2.wast.ctors b/test/ctor-eval/imported2.wast.ctors new file mode 100644 index 000000000..c7060ede5 --- /dev/null +++ b/test/ctor-eval/imported2.wast.ctors @@ -0,0 +1 @@ +test1,test2,test3 diff --git a/test/ctor-eval/imported2.wast.out b/test/ctor-eval/imported2.wast.out new file mode 100644 index 000000000..7922dd944 --- /dev/null +++ b/test/ctor-eval/imported2.wast.out @@ -0,0 +1,30 @@ +(module + (type $0 (func)) + (import "env" "STACKTOP" (global $STACKTOP i32)) + (import "env" "STACK_MAX" (global $STACK_MAX i32)) + (import "env" "tempDoublePtr" (global $tempDoublePtr i32)) + (global $mine (mut i32) (i32.const 1)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00wasa waka waka waka waka") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $0) + (nop) + ) + (func $test2 (type $0) + (set_global $mine + (i32.const 2) + ) + (i32.store8 + (i32.const 13) + (i32.const 115) + ) + ) + (func $test3 (type $0) + (i32.store8 + (i32.const 14) + (i32.const 115) + ) + ) +) diff --git a/test/ctor-eval/imported3.wast b/test/ctor-eval/imported3.wast new file mode 100644 index 000000000..b43ce3038 --- /dev/null +++ b/test/ctor-eval/imported3.wast @@ -0,0 +1,14 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + ;; imports must not be used + (import "env" "tempDoublePtr" (global $tempDoublePtr i32)) + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + (global $mine (mut i32) (get_global $tempDoublePtr)) ;; BAD, if used + (func $test1 + (drop (get_global $mine)) + (i32.store8 (i32.const 13) (i32.const 115)) ;; we never get here. + ) +) diff --git a/test/ctor-eval/imported3.wast.ctors b/test/ctor-eval/imported3.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/imported3.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/imported3.wast.out b/test/ctor-eval/imported3.wast.out new file mode 100644 index 000000000..4322a0237 --- /dev/null +++ b/test/ctor-eval/imported3.wast.out @@ -0,0 +1,19 @@ +(module + (type $0 (func)) + (import "env" "tempDoublePtr" (global $tempDoublePtr i32)) + (global $mine (mut i32) (get_global $tempDoublePtr)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00waka waka waka waka waka") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $0) + (drop + (get_global $mine) + ) + (i32.store8 + (i32.const 13) + (i32.const 115) + ) + ) +) diff --git a/test/ctor-eval/indirect-call3.wast b/test/ctor-eval/indirect-call3.wast new file mode 100644 index 000000000..9eaa90821 --- /dev/null +++ b/test/ctor-eval/indirect-call3.wast @@ -0,0 +1,17 @@ +(module + (type $v (func)) + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (import "env" "tableBase" (global $tableBase i32)) + (import "env" "_abort" (func $_abort)) + (table 2 2 anyfunc) + (elem (get_global $tableBase) $_abort $call-indirect) + (export "test1" $test1) + (func $test1 + (call_indirect $v (i32.const 1)) ;; safe to call + (i32.store8 (i32.const 20) (i32.const 120)) + ) + (func $call-indirect + (i32.store8 (i32.const 40) (i32.const 67)) + ) +) diff --git a/test/ctor-eval/indirect-call3.wast.ctors b/test/ctor-eval/indirect-call3.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/indirect-call3.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/indirect-call3.wast.out b/test/ctor-eval/indirect-call3.wast.out new file mode 100644 index 000000000..63d6273da --- /dev/null +++ b/test/ctor-eval/indirect-call3.wast.out @@ -0,0 +1,20 @@ +(module + (type $v (func)) + (type $FUNCSIG$v (func)) + (import "env" "tableBase" (global $tableBase i32)) + (import "env" "_abort" (func $_abort)) + (table 2 2 anyfunc) + (elem (get_global $tableBase) $_abort $call-indirect) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00waka waka xaka waka waka\00\00\00\00\00\00C") + (export "test1" (func $test1)) + (func $test1 (type $v) + (nop) + ) + (func $call-indirect (type $v) + (i32.store8 + (i32.const 40) + (i32.const 67) + ) + ) +) diff --git a/test/ctor-eval/just_some.wast b/test/ctor-eval/just_some.wast new file mode 100644 index 000000000..64aa18d27 --- /dev/null +++ b/test/ctor-eval/just_some.wast @@ -0,0 +1,17 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + (func $test1 + (i32.store8 (i32.const 12) (i32.const 115)) ;; a safe store, should alter memory + ) + (func $test2 + (unreachable) + (i32.store8 (i32.const 13) (i32.const 114)) ;; a safe store, should alter memory, but we trapped already + ) + (func $test3 + (i32.store8 (i32.const 13) (i32.const 113)) ;; a safe store, should alter memory, but we trapped already + ) +) diff --git a/test/ctor-eval/just_some.wast.ctors b/test/ctor-eval/just_some.wast.ctors new file mode 100644 index 000000000..c7060ede5 --- /dev/null +++ b/test/ctor-eval/just_some.wast.ctors @@ -0,0 +1 @@ +test1,test2,test3 diff --git a/test/ctor-eval/just_some.wast.out b/test/ctor-eval/just_some.wast.out new file mode 100644 index 000000000..41e3d7338 --- /dev/null +++ b/test/ctor-eval/just_some.wast.out @@ -0,0 +1,24 @@ +(module + (type $0 (func)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00wasa waka waka waka waka") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $0) + (nop) + ) + (func $test2 (type $0) + (unreachable) + (i32.store8 + (i32.const 13) + (i32.const 114) + ) + ) + (func $test3 (type $0) + (i32.store8 + (i32.const 13) + (i32.const 113) + ) + ) +) diff --git a/test/ctor-eval/no_partial.wast b/test/ctor-eval/no_partial.wast new file mode 100644 index 000000000..18ef177b7 --- /dev/null +++ b/test/ctor-eval/no_partial.wast @@ -0,0 +1,10 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (export "test1" $test1) + (func $test1 + (i32.store8 (i32.const 12) (i32.const 115)) ;; a safe store, should alter memory + (unreachable) + (i32.store8 (i32.const 13) (i32.const 114)) ;; a safe store, should alter memory, but we trapped already, and so must roll back the first one too + ) +) diff --git a/test/ctor-eval/no_partial.wast.ctors b/test/ctor-eval/no_partial.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/no_partial.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/no_partial.wast.out b/test/ctor-eval/no_partial.wast.out new file mode 100644 index 000000000..fc25804a7 --- /dev/null +++ b/test/ctor-eval/no_partial.wast.out @@ -0,0 +1,17 @@ +(module + (type $0 (func)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00waka waka waka waka waka") + (export "test1" (func $test1)) + (func $test1 (type $0) + (i32.store8 + (i32.const 12) + (i32.const 115) + ) + (unreachable) + (i32.store8 + (i32.const 13) + (i32.const 114) + ) + ) +) diff --git a/test/ctor-eval/unsafe_call.wast b/test/ctor-eval/unsafe_call.wast new file mode 100644 index 000000000..703760744 --- /dev/null +++ b/test/ctor-eval/unsafe_call.wast @@ -0,0 +1,16 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + (func $test1 + (call $unsafe-to-call) ;; unsafe to call + (i32.store (i32.const 12) (i32.const 115)) ;; a safe store, should alter memory + (i32.store16 (i32.const 20) (i32.const 31353)) + (i32.store8 (i32.const 23) (i32.const 120)) + ) + (func $unsafe-to-call + (unreachable) + ) +) diff --git a/test/ctor-eval/unsafe_call.wast.ctors b/test/ctor-eval/unsafe_call.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/unsafe_call.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/unsafe_call.wast.out b/test/ctor-eval/unsafe_call.wast.out new file mode 100644 index 000000000..124cf2578 --- /dev/null +++ b/test/ctor-eval/unsafe_call.wast.out @@ -0,0 +1,26 @@ +(module + (type $0 (func)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00waka waka waka waka waka") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $0) + (call $unsafe-to-call) + (i32.store + (i32.const 12) + (i32.const 115) + ) + (i32.store16 + (i32.const 20) + (i32.const 31353) + ) + (i32.store8 + (i32.const 23) + (i32.const 120) + ) + ) + (func $unsafe-to-call (type $0) + (unreachable) + ) +) diff --git a/test/ctor-eval/unsafe_store.wast b/test/ctor-eval/unsafe_store.wast new file mode 100644 index 000000000..f851672a7 --- /dev/null +++ b/test/ctor-eval/unsafe_store.wast @@ -0,0 +1,10 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + (func $test1 + (i32.store8 (i32.const 9) (i32.const 109)) ;; before first segment + ) +) diff --git a/test/ctor-eval/unsafe_store.wast.ctors b/test/ctor-eval/unsafe_store.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/unsafe_store.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/unsafe_store.wast.out b/test/ctor-eval/unsafe_store.wast.out new file mode 100644 index 000000000..8bbc2fed4 --- /dev/null +++ b/test/ctor-eval/unsafe_store.wast.out @@ -0,0 +1,11 @@ +(module + (type $0 (func)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00mwaka waka waka waka waka") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $0) + (nop) + ) +) diff --git a/test/ctor-eval/unsafe_store2.wast b/test/ctor-eval/unsafe_store2.wast new file mode 100644 index 000000000..cc5eb1105 --- /dev/null +++ b/test/ctor-eval/unsafe_store2.wast @@ -0,0 +1,10 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + (func $test1 + (i32.store8 (i32.const 34) (i32.const 109)) ;; after last segment + ) +) diff --git a/test/ctor-eval/unsafe_store2.wast.ctors b/test/ctor-eval/unsafe_store2.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/unsafe_store2.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/unsafe_store2.wast.out b/test/ctor-eval/unsafe_store2.wast.out new file mode 100644 index 000000000..798a4a23d --- /dev/null +++ b/test/ctor-eval/unsafe_store2.wast.out @@ -0,0 +1,11 @@ +(module + (type $0 (func)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00waka waka waka waka wakam") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $0) + (nop) + ) +) diff --git a/test/ctor-eval/unsafe_store3.wast b/test/ctor-eval/unsafe_store3.wast new file mode 100644 index 000000000..701e4b32e --- /dev/null +++ b/test/ctor-eval/unsafe_store3.wast @@ -0,0 +1,10 @@ +(module + (memory 256 256) + (data (i32.const 10) "waka waka waka waka waka") + (export "test1" $test1) + (export "test2" $test2) + (export "test3" $test3) + (func $test1 + (i32.store16 (i32.const 33) (i32.const 109)) ;; after last segment due to size of type + ) +) diff --git a/test/ctor-eval/unsafe_store3.wast.ctors b/test/ctor-eval/unsafe_store3.wast.ctors new file mode 100644 index 000000000..a5bce3fd2 --- /dev/null +++ b/test/ctor-eval/unsafe_store3.wast.ctors @@ -0,0 +1 @@ +test1 diff --git a/test/ctor-eval/unsafe_store3.wast.out b/test/ctor-eval/unsafe_store3.wast.out new file mode 100644 index 000000000..e8ad4f5b8 --- /dev/null +++ b/test/ctor-eval/unsafe_store3.wast.out @@ -0,0 +1,11 @@ +(module + (type $0 (func)) + (memory $0 256 256) + (data (i32.const 0) "\00\00\00\00\00\00\00\00\00\00waka waka waka waka wakm\00") + (export "test1" (func $test1)) + (export "test2" (func $test2)) + (export "test3" (func $test3)) + (func $test1 (type $0) + (nop) + ) +) |