diff options
Diffstat (limited to 'src/tools/wasm-ctor-eval.cpp')
-rw-r--r-- | src/tools/wasm-ctor-eval.cpp | 409 |
1 files changed, 409 insertions, 0 deletions
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"]); + } +} |