blob: 03b4c819e73918daf743c0eab44f6719db90dd2a [file] [log] [blame]
/*
* 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 "support/utilities.h"
#include "wasm-interpreter.h"
#include "wasm.h"
namespace wasm {
// An exception emitted when exit() is called.
struct ExitException {};
// An exception emitted when a wasm trap occurs.
struct TrapException {};
// An exception emitted when a host limitation is hit. (These are not wasm traps
// as they are not in the spec; for example, the spec has no limit on how much
// GC memory may be allocated, but hosts have limits.)
struct HostLimitException {};
struct ShellExternalInterface : ModuleRunner::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<char> memory;
template<typename T> static bool aligned(const char* address) {
static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2");
return 0 == (reinterpret_cast<uintptr_t>(address) & (sizeof(T) - 1));
}
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) {
if (aligned<T>(&memory[address])) {
*reinterpret_cast<T*>(&memory[address]) = value;
} else {
std::memcpy(&memory[address], &value, sizeof(T));
}
}
template<typename T> T get(size_t address) {
if (aligned<T>(&memory[address])) {
return *reinterpret_cast<T*>(&memory[address]);
} else {
T loaded;
std::memcpy(&loaded, &memory[address], sizeof(T));
return loaded;
}
}
};
std::map<Name, Memory> memories;
std::unordered_map<Name, std::vector<Literal>> tables;
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) {
auto shellMemory = Memory();
shellMemory.resize(memory->initial * wasm::Memory::kPageSize);
memories[memory->name] = shellMemory;
});
ModuleUtils::iterDefinedTables(
wasm, [&](Table* table) { tables[table->name].resize(table->initial); });
}
void importGlobals(std::map<Name, Literals>& globals, Module& wasm) override {
ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) {
auto inst = getImportInstance(import);
auto* exportedGlobal = inst->wasm.getExportOrNull(import->base);
if (!exportedGlobal || exportedGlobal->kind != ExternalKind::Global) {
Fatal() << "importGlobals: unknown import: " << import->module.str
<< "." << import->name.str;
}
globals[import->name] = inst->globals[*exportedGlobal->getInternalName()];
});
}
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);
}
Tag* getImportedTag(Tag* tag) override {
WASM_UNREACHABLE("missing imported tag");
}
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);
}
Index tableSize(Name tableName) override {
return (Index)tables[tableName].size();
}
void
tableStore(Name tableName, Address index, const Literal& entry) override {
auto& table = tables[tableName];
if (index >= table.size()) {
trap("out of bounds table access");
} else {
table[index] = entry;
}
}
Literal tableLoad(Name tableName, Address index) override {
auto it = tables.find(tableName);
if (it == tables.end()) {
trap("tableGet on non-existing table");
}
auto& table = it->second;
if (index >= table.size()) {
trap("out of bounds table access");
}
return table[index];
}
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;
}
bool growTable(Name name,
const Literal& value,
Index /*oldSize*/,
Index newSize) override {
// Apply a reasonable limit on table size, 1GB, to avoid DOS on the
// interpreter.
if (newSize > 1024 * 1024 * 1024) {
return false;
}
tables[name].resize(newSize, value);
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