text: /*
   * 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));
      }
      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<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;
        }
      }
    } memory;
  
    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_ = {})
      : memory() {
      linkedInstances.swap(linkedInstances_);
    }
    virtual ~ShellExternalInterface() = default;
  
    ModuleRunner* getImportInstance(Importable* import) {
      auto it = linkedInstances.find(import->module);
      if (it == linkedInstances.end()) {
        Fatal() << "importGlobals: unknown import: " << import->module.str << "."
                << import->base.str;
      }
      return it->second.get();
    }
  
    void init(Module& wasm, ModuleRunner& instance) override {
      if (wasm.memory.exists && !wasm.memory.imported()) {
        memory.resize(wasm.memory.initial * wasm::Memory::kPageSize);
      }
      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) {
          Fatal() << "importGlobals: unknown import: " << import->module.str
                  << "." << import->name.str;
        }
        globals[import->name] = inst->globals[exportedGlobal->value];
      });
    }
  
    Literals callImport(Function* import, Literals& 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();
      } else if (auto* inst = getImportInstance(import)) {
        return inst->callExport(import->base, arguments);
      }
      Fatal() << "callImport: unknown import: " << import->module.str << "."
              << import->name.str;
    }
  
    Literals callTable(Name tableName,
                       Index index,
                       HeapType sig,
                       Literals& arguments,
                       Type results,
                       ModuleRunner& instance) override {
  
      auto it = tables.find(tableName);
      if (it == tables.end()) {
        trap("callTable on non-existing table");
      }
  
      auto& table = it->second;
      if (index >= table.size()) {
        trap("callTable overflow");
      }
      Function* func = nullptr;
      if (table[index].isFunction() && !table[index].isNull()) {
        func = instance.wasm.getFunctionOrNull(table[index].getFunc());
      }
      if (!func) {
        trap("uninitialized table element");
      }
      if (sig != func->type) {
        trap("callIndirect: function types don't match");
      }
      if (func->getParams().size() != arguments.size()) {
        trap("callIndirect: bad # of arguments");
      }
      size_t i = 0;
      for (const auto& param : func->getParams()) {
        if (!Type::isSubType(arguments[i++].type, param)) {
          trap("callIndirect: bad argument type");
        }
      }
      if (func->getResults() != 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<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); }
    std::array<uint8_t, 16> load128(Address addr) override {
      return memory.get<std::array<uint8_t, 16>>(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 store128(Address addr, const std::array<uint8_t, 16>& value) override {
      memory.set<std::array<uint8_t, 16>>(addr, value);
    }
  
    Index tableSize(Name tableName) override {
      return (Index)tables[tableName].size();
    }
  
    void tableStore(Name tableName, Index 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, Index 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(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;
    }
  
    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(const char* why) override {
      std::cout << "[trap " << why << "]\n";
      throw TrapException();
    }
  
    void hostLimit(const char* 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
  