| /* | |
| * Copyright 2016 WebAssembly Community Group participants | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| // | |
| // Implementation of the shell interpreter execution environment | |
| // | |
| #ifndef wasm_shell_interface_h | |
| #define wasm_shell_interface_h | |
| #include "asmjs/shared-constants.h" | |
| #include "interpreter/exception.h" | |
| #include "ir/module-utils.h" | |
| #include "shared-constants.h" | |
| #include "support/bits.h" | |
| #include "support/name.h" | |
| #include "support/utilities.h" | |
| #include "wasm-interpreter.h" | |
| #include "wasm.h" | |
| namespace wasm { | |
| struct ShellExternalInterface : ModuleRunner::ExternalInterface { | |
| class Memory { | |
| // Use char because it doesn't run afoul of aliasing rules. | |
| std::vector<char> memory; | |
| public: | |
| Memory() = default; | |
| void resize(size_t newSize) { | |
| // Ensure the smallest allocation is large enough that most allocators | |
| // will provide page-aligned storage. This hopefully allows the | |
| // interpreter's memory to be as aligned as the memory being simulated, | |
| // ensuring that the performance doesn't needlessly degrade. | |
| // | |
| // The code is optimistic this will work until WG21's p0035r0 happens. | |
| const size_t minSize = 1 << 12; | |
| size_t oldSize = memory.size(); | |
| memory.resize(std::max(minSize, newSize)); | |
| if (newSize < oldSize && newSize < minSize) { | |
| std::memset(&memory[newSize], 0, minSize - newSize); | |
| } | |
| } | |
| template<typename T> void set(size_t address, T value) { | |
| Bits::writeLE<T>(value, &memory[address]); | |
| } | |
| template<typename T> T get(size_t address) { | |
| return Bits::readLE<T>(&memory[address]); | |
| } | |
| }; | |
| std::map<Name, Memory> memories; | |
| std::map<Name, std::shared_ptr<ModuleRunner>> linkedInstances; | |
| ShellExternalInterface( | |
| std::map<Name, std::shared_ptr<ModuleRunner>> linkedInstances_ = {}) { | |
| linkedInstances.swap(linkedInstances_); | |
| } | |
| virtual ~ShellExternalInterface() = default; | |
| ModuleRunner* getImportInstanceOrNull(Importable* import) { | |
| auto it = linkedInstances.find(import->module); | |
| if (it == linkedInstances.end()) { | |
| return nullptr; | |
| } | |
| return it->second.get(); | |
| } | |
| ModuleRunner* getImportInstance(Importable* import) { | |
| auto* ret = getImportInstanceOrNull(import); | |
| if (!ret) { | |
| Fatal() << "getImportInstance: unknown import: " << import->module.str | |
| << "." << import->base.str; | |
| } | |
| return ret; | |
| } | |
| void init(Module& wasm, ModuleRunner& instance) override { | |
| ModuleUtils::iterDefinedMemories(wasm, [&](wasm::Memory* memory) { | |
| // Compute the initial size in bytes carefully: the byte size of a | |
| // maximal memory64 (2^48 pages of 64KiB) does not fit in 64 bits. If | |
| // we let it wrap, instantiation would "succeed" with an empty buffer | |
| // and every later access would spuriously trap as out of bounds. | |
| uint64_t bytes = uint64_t(memory->initial) << memory->pageSizeLog2; | |
| if ((bytes >> memory->pageSizeLog2) != uint64_t(memory->initial)) { | |
| // The byte size is not even representable; we certainly cannot | |
| // allocate it, just like a real VM could not. | |
| hostLimit("memory too large"); | |
| } | |
| auto shellMemory = Memory(); | |
| try { | |
| shellMemory.resize(bytes); | |
| } catch (const std::exception&) { | |
| // bad_alloc or length_error from the underlying vector: the memory | |
| // is representable but too large to actually allocate. | |
| hostLimit("memory too large"); | |
| } | |
| memories[memory->name] = shellMemory; | |
| }); | |
| } | |
| Literal getImportedFunction(Function* import) override { | |
| // TODO: We should perhaps restrict the types with which the well-known | |
| // functions can be imported. | |
| if (import->module == SPECTEST && import->base.startsWith(PRINT)) { | |
| // Use a null instance because these are host functions. | |
| return Literal( | |
| std::make_shared<FuncData>(import->name, | |
| nullptr, | |
| [](const Literals& arguments) -> Flow { | |
| for (auto argument : arguments) { | |
| std::cout << argument << " : " | |
| << argument.type << '\n'; | |
| } | |
| return Flow(); | |
| }), | |
| import->type); | |
| } else if (import->module == ENV && import->base == EXIT) { | |
| return Literal(std::make_shared<FuncData>(import->name, | |
| nullptr, | |
| [](const Literals&) -> Flow { | |
| // XXX hack for torture tests | |
| std::cout << "exit()\n"; | |
| throw ExitException(); | |
| }), | |
| import->type); | |
| } else if (auto* inst = getImportInstanceOrNull(import)) { | |
| return inst->getExportedFunction(import->base); | |
| } | |
| // This is not a known import. Create a literal for it, which is good enough | |
| // if it is never called (see the ref_func.wast spec test, which does that). | |
| std::cerr << "warning: getImportedFunction: unknown import: " | |
| << import->module.str << "." << import->name.str << '\n'; | |
| return Literal::makeFunc(import->name, import->type); | |
| } | |
| int8_t load8s(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<int8_t>(addr); | |
| } | |
| uint8_t load8u(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<uint8_t>(addr); | |
| } | |
| int16_t load16s(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<int16_t>(addr); | |
| } | |
| uint16_t load16u(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<uint16_t>(addr); | |
| } | |
| int32_t load32s(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<int32_t>(addr); | |
| } | |
| uint32_t load32u(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<uint32_t>(addr); | |
| } | |
| int64_t load64s(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<int64_t>(addr); | |
| } | |
| uint64_t load64u(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<uint64_t>(addr); | |
| } | |
| std::array<uint8_t, 16> load128(Address addr, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| return memory.get<std::array<uint8_t, 16>>(addr); | |
| } | |
| void store8(Address addr, int8_t value, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| memory.set<int8_t>(addr, value); | |
| } | |
| void store16(Address addr, int16_t value, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| memory.set<int16_t>(addr, value); | |
| } | |
| void store32(Address addr, int32_t value, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| memory.set<int32_t>(addr, value); | |
| } | |
| void store64(Address addr, int64_t value, Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| memory.set<int64_t>(addr, value); | |
| } | |
| void store128(Address addr, | |
| const std::array<uint8_t, 16>& value, | |
| Name memoryName) override { | |
| auto it = memories.find(memoryName); | |
| assert(it != memories.end()); | |
| auto& memory = it->second; | |
| memory.set<std::array<uint8_t, 16>>(addr, value); | |
| } | |
| bool | |
| growMemory(Name memoryName, Address /*oldSize*/, Address newSize) override { | |
| // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the | |
| // interpreter. | |
| if (newSize > 1024 * 1024 * 1024) { | |
| return false; | |
| } | |
| auto it = memories.find(memoryName); | |
| if (it == memories.end()) { | |
| trap("growMemory on non-existing memory"); | |
| } | |
| auto& memory = it->second; | |
| memory.resize(newSize); | |
| return true; | |
| } | |
| void trap(std::string_view why) override { | |
| std::cout << "[trap " << why << "]\n"; | |
| throw TrapException(); | |
| } | |
| void hostLimit(std::string_view why) override { | |
| std::cout << "[host limit " << why << "]\n"; | |
| throw HostLimitException(); | |
| } | |
| void throwException(const WasmException& exn) override { throw exn; } | |
| }; | |
| } // namespace wasm | |
| #endif // wasm_shell_interface_h |