/* * 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 "ir/module-utils.h" #include "shared-constants.h" #include "support/name.h" #include "wasm-interpreter.h" #include "wasm.h" namespace wasm { struct ExitException {}; struct TrapException {}; struct ShellExternalInterface : ModuleInstance::ExternalInterface { // The underlying memory can be accessed through unaligned pointers which // isn't well-behaved in C++. WebAssembly nonetheless expects it to behave // properly. Avoid emitting unaligned load/store by checking for alignment // explicitly, and performing memcpy if unaligned. // // The allocated memory tries to have the same alignment as the memory being // simulated. class Memory { // Use char because it doesn't run afoul of aliasing rules. std::vector memory; template static bool aligned(const char* address) { static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2"); return 0 == (reinterpret_cast(address) & (sizeof(T) - 1)); } Memory(Memory&) = delete; Memory& operator=(const Memory&) = delete; 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 void set(size_t address, T value) { if (aligned(&memory[address])) { *reinterpret_cast(&memory[address]) = value; } else { std::memcpy(&memory[address], &value, sizeof(T)); } } template T get(size_t address) { if (aligned(&memory[address])) { return *reinterpret_cast(&memory[address]); } else { T loaded; std::memcpy(&loaded, &memory[address], sizeof(T)); return loaded; } } } memory; std::vector table; ShellExternalInterface() : memory() {} virtual ~ShellExternalInterface() = default; void init(Module& wasm, ModuleInstance& instance) override { memory.resize(wasm.memory.initial * wasm::Memory::kPageSize); table.resize(wasm.table.initial); } void importGlobals(std::map& globals, Module& wasm) override { // add spectest globals ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) { if (import->module == SPECTEST && import->base.startsWith("global_")) { TODO_SINGLE_COMPOUND(import->type); switch (import->type.getBasic()) { case Type::i32: globals[import->name] = {Literal(int32_t(666))}; break; case Type::i64: globals[import->name] = {Literal(int64_t(666))}; break; case Type::f32: globals[import->name] = {Literal(float(666.6))}; break; case Type::f64: globals[import->name] = {Literal(double(666.6))}; break; case Type::v128: assert(false && "v128 not implemented yet"); case Type::funcref: case Type::externref: case Type::exnref: case Type::anyref: case Type::eqref: globals[import->name] = {Literal::makeNull(import->type)}; break; case Type::i31ref: WASM_UNREACHABLE("TODO: i31ref"); case Type::none: case Type::unreachable: WASM_UNREACHABLE("unexpected type"); } } }); if (wasm.memory.imported() && wasm.memory.module == SPECTEST && wasm.memory.base == MEMORY) { // imported memory has initial 1 and max 2 wasm.memory.initial = 1; wasm.memory.max = 2; } } Literals callImport(Function* import, LiteralList& arguments) override { if (import->module == SPECTEST && import->base.startsWith(PRINT)) { for (auto argument : arguments) { std::cout << argument << " : " << argument.type << '\n'; } return {}; } else if (import->module == ENV && import->base == EXIT) { // XXX hack for torture tests std::cout << "exit()\n"; throw ExitException(); } Fatal() << "callImport: unknown import: " << import->module.str << "." << import->name.str; } Literals callTable(Index index, Signature sig, LiteralList& arguments, Type results, ModuleInstance& instance) override { if (index >= table.size()) { trap("callTable overflow"); } auto* func = instance.wasm.getFunctionOrNull(table[index]); if (!func) { trap("uninitialized table element"); } if (sig != func->sig) { trap("callIndirect: function signatures don't match"); } if (func->sig.params.size() != arguments.size()) { trap("callIndirect: bad # of arguments"); } size_t i = 0; for (const auto& param : func->sig.params) { if (!Type::isSubType(arguments[i++].type, param)) { trap("callIndirect: bad argument type"); } } if (func->sig.results != results) { trap("callIndirect: bad result type"); } if (func->imported()) { return callImport(func, arguments); } else { return instance.callFunctionInternal(func->name, arguments); } } int8_t load8s(Address addr) override { return memory.get(addr); } uint8_t load8u(Address addr) override { return memory.get(addr); } int16_t load16s(Address addr) override { return memory.get(addr); } uint16_t load16u(Address addr) override { return memory.get(addr); } int32_t load32s(Address addr) override { return memory.get(addr); } uint32_t load32u(Address addr) override { return memory.get(addr); } int64_t load64s(Address addr) override { return memory.get(addr); } uint64_t load64u(Address addr) override { return memory.get(addr); } std::array load128(Address addr) override { return memory.get>(addr); } void store8(Address addr, int8_t value) override { memory.set(addr, value); } void store16(Address addr, int16_t value) override { memory.set(addr, value); } void store32(Address addr, int32_t value) override { memory.set(addr, value); } void store64(Address addr, int64_t value) override { memory.set(addr, value); } void store128(Address addr, const std::array& value) override { memory.set>(addr, value); } void tableStore(Address addr, Name entry) override { table[addr] = entry; } bool growMemory(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; } memory.resize(newSize); return true; } void trap(const char* why) override { std::cout << "[trap " << why << "]\n"; throw TrapException(); } void throwException(Literal exn) override { throw WasmException(exn); } }; } // namespace wasm #endif // wasm_shell_interface_h