/* * 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. */ #ifndef WABT_INTERP_H_ #define WABT_INTERP_H_ #include #include #include #include #include "src/binding-hash.h" #include "src/common.h" #include "src/opcode.h" #include "src/stream.h" namespace wabt { namespace interp { #define FOREACH_INTERP_RESULT(V) \ V(Ok, "ok") \ /* returned from the top-most function */ \ V(Returned, "returned") \ /* memory access is out of bounds */ \ V(TrapMemoryAccessOutOfBounds, "out of bounds memory access") \ /* atomic memory access is unaligned */ \ V(TrapAtomicMemoryAccessUnaligned, "atomic memory access is unaligned") \ /* converting from float -> int would overflow int */ \ V(TrapIntegerOverflow, "integer overflow") \ /* dividend is zero in integer divide */ \ V(TrapIntegerDivideByZero, "integer divide by zero") \ /* converting from float -> int where float is nan */ \ V(TrapInvalidConversionToInteger, "invalid conversion to integer") \ /* function table index is out of bounds */ \ V(TrapUndefinedTableIndex, "undefined table index") \ /* function table element is uninitialized */ \ V(TrapUninitializedTableElement, "uninitialized table element") \ /* unreachable instruction executed */ \ V(TrapUnreachable, "unreachable executed") \ /* call indirect signature doesn't match function table signature */ \ V(TrapIndirectCallSignatureMismatch, "indirect call signature mismatch") \ /* ran out of call stack frames (probably infinite recursion) */ \ V(TrapCallStackExhausted, "call stack exhausted") \ /* ran out of value stack space */ \ V(TrapValueStackExhausted, "value stack exhausted") \ /* we called a host function, but the return value didn't match the */ \ /* expected type */ \ V(TrapHostResultTypeMismatch, "host result type mismatch") \ /* we called an import function, but it didn't complete succesfully */ \ V(TrapHostTrapped, "host function trapped") \ /* we attempted to call a function with the an argument list that doesn't \ * match the function signature */ \ V(ArgumentTypeMismatch, "argument type mismatch") \ /* we tried to get an export by name that doesn't exist */ \ V(UnknownExport, "unknown export") \ /* the expected export kind doesn't match. */ \ V(ExportKindMismatch, "export kind mismatch") enum class Result { #define V(Name, str) Name, FOREACH_INTERP_RESULT(V) #undef V }; typedef uint32_t IstreamOffset; static const IstreamOffset kInvalidIstreamOffset = ~0; // A table entry has the following packed layout: // // struct { // IstreamOffset offset; // uint32_t drop_count; // uint8_t keep_count; // }; #define WABT_TABLE_ENTRY_SIZE \ (sizeof(IstreamOffset) + sizeof(uint32_t) + sizeof(uint8_t)) #define WABT_TABLE_ENTRY_OFFSET_OFFSET 0 #define WABT_TABLE_ENTRY_DROP_OFFSET sizeof(uint32_t) #define WABT_TABLE_ENTRY_KEEP_OFFSET (sizeof(IstreamOffset) + sizeof(uint32_t)) struct FuncSignature { FuncSignature() = default; FuncSignature(Index param_count, Type* param_types, Index result_count, Type* result_types); std::vector param_types; std::vector result_types; }; struct Table { explicit Table(const Limits& limits) : limits(limits), func_indexes(limits.initial, kInvalidIndex) {} Limits limits; std::vector func_indexes; }; struct Memory { Memory() = default; explicit Memory(const Limits& limits) : page_limits(limits), data(limits.initial * WABT_PAGE_SIZE) {} Limits page_limits; std::vector data; }; // ValueTypeRep converts from one type to its representation on the // stack. For example, float -> uint32_t. See Value below. template struct ValueTypeRepT; template <> struct ValueTypeRepT { typedef uint32_t type; }; template <> struct ValueTypeRepT { typedef uint32_t type; }; template <> struct ValueTypeRepT { typedef uint64_t type; }; template <> struct ValueTypeRepT { typedef uint64_t type; }; template <> struct ValueTypeRepT { typedef uint32_t type; }; template <> struct ValueTypeRepT { typedef uint64_t type; }; template <> struct ValueTypeRepT { typedef v128 type; }; template using ValueTypeRep = typename ValueTypeRepT::type; union Value { uint32_t i32; uint64_t i64; ValueTypeRep f32_bits; ValueTypeRep f64_bits; ValueTypeRep v128_bits; }; struct TypedValue { TypedValue() {} explicit TypedValue(Type type) : type(type) {} TypedValue(Type type, const Value& value) : type(type), value(value) {} Type type; Value value; }; typedef std::vector TypedValues; struct Global { Global() : mutable_(false), import_index(kInvalidIndex) {} Global(const TypedValue& typed_value, bool mutable_) : typed_value(typed_value), mutable_(mutable_) {} TypedValue typed_value; bool mutable_; Index import_index; /* or INVALID_INDEX if not imported */ }; struct Import { explicit Import(ExternalKind kind) : kind(kind) {} Import(ExternalKind kind, string_view module_name, string_view field_name) : kind(kind), module_name(module_name.to_string()), field_name(field_name.to_string()) {} ExternalKind kind; std::string module_name; std::string field_name; }; struct FuncImport : Import { FuncImport() : Import(ExternalKind::Func) {} FuncImport(string_view module_name, string_view field_name) : Import(ExternalKind::Func, module_name, field_name) {} Index sig_index = kInvalidIndex; }; struct TableImport : Import { TableImport() : Import(ExternalKind::Table) {} TableImport(string_view module_name, string_view field_name) : Import(ExternalKind::Table, module_name, field_name) {} Limits limits; }; struct MemoryImport : Import { MemoryImport() : Import(ExternalKind::Memory) {} MemoryImport(string_view module_name, string_view field_name) : Import(ExternalKind::Memory, module_name, field_name) {} Limits limits; }; struct GlobalImport : Import { GlobalImport() : Import(ExternalKind::Global) {} GlobalImport(string_view module_name, string_view field_name) : Import(ExternalKind::Global, module_name, field_name) {} Type type = Type::Void; bool mutable_ = false; }; struct ExceptImport : Import { ExceptImport() : Import(ExternalKind::Except) {} ExceptImport(string_view module_name, string_view field_name) : Import(ExternalKind::Except, module_name, field_name) {} }; struct Func; typedef Result (*HostFuncCallback)(const struct HostFunc* func, const FuncSignature* sig, Index num_args, TypedValue* args, Index num_results, TypedValue* out_results, void* user_data); struct Func { WABT_DISALLOW_COPY_AND_ASSIGN(Func); Func(Index sig_index, bool is_host) : sig_index(sig_index), is_host(is_host) {} virtual ~Func() {} Index sig_index; bool is_host; }; struct DefinedFunc : Func { DefinedFunc(Index sig_index) : Func(sig_index, false), offset(kInvalidIstreamOffset), local_decl_count(0), local_count(0) {} static bool classof(const Func* func) { return !func->is_host; } IstreamOffset offset; Index local_decl_count; Index local_count; std::vector param_and_local_types; }; struct HostFunc : Func { HostFunc(string_view module_name, string_view field_name, Index sig_index) : Func(sig_index, true), module_name(module_name.to_string()), field_name(field_name.to_string()) {} static bool classof(const Func* func) { return func->is_host; } std::string module_name; std::string field_name; HostFuncCallback callback; void* user_data; }; struct Export { Export(string_view name, ExternalKind kind, Index index) : name(name.to_string()), kind(kind), index(index) {} std::string name; ExternalKind kind; Index index; }; class HostImportDelegate { public: typedef std::function ErrorCallback; virtual ~HostImportDelegate() {} virtual wabt::Result ImportFunc(FuncImport*, Func*, FuncSignature*, const ErrorCallback&) = 0; virtual wabt::Result ImportTable(TableImport*, Table*, const ErrorCallback&) = 0; virtual wabt::Result ImportMemory(MemoryImport*, Memory*, const ErrorCallback&) = 0; virtual wabt::Result ImportGlobal(GlobalImport*, Global*, const ErrorCallback&) = 0; }; struct Module { WABT_DISALLOW_COPY_AND_ASSIGN(Module); explicit Module(bool is_host); Module(string_view name, bool is_host); virtual ~Module() = default; Export* GetExport(string_view name); std::string name; std::vector exports; BindingHash export_bindings; Index memory_index; /* kInvalidIndex if not defined */ Index table_index; /* kInvalidIndex if not defined */ bool is_host; }; struct DefinedModule : Module { DefinedModule(); static bool classof(const Module* module) { return !module->is_host; } std::vector func_imports; std::vector table_imports; std::vector memory_imports; std::vector global_imports; std::vector except_imports; Index start_func_index; /* kInvalidIndex if not defined */ IstreamOffset istream_start; IstreamOffset istream_end; }; struct HostModule : Module { explicit HostModule(string_view name); static bool classof(const Module* module) { return module->is_host; } std::unique_ptr import_delegate; }; class Environment { public: // Used to track and reset the state of the environment. struct MarkPoint { size_t modules_size = 0; size_t sigs_size = 0; size_t funcs_size = 0; size_t memories_size = 0; size_t tables_size = 0; size_t globals_size = 0; size_t istream_size = 0; }; Environment(); OutputBuffer& istream() { return *istream_; } void SetIstream(std::unique_ptr istream) { istream_ = std::move(istream); } std::unique_ptr ReleaseIstream() { return std::move(istream_); } Index GetFuncSignatureCount() const { return sigs_.size(); } Index GetFuncCount() const { return funcs_.size(); } Index GetGlobalCount() const { return globals_.size(); } Index GetMemoryCount() const { return memories_.size(); } Index GetTableCount() const { return tables_.size(); } Index GetModuleCount() const { return modules_.size(); } Index GetLastModuleIndex() const { return modules_.empty() ? kInvalidIndex : modules_.size() - 1; } Index FindModuleIndex(string_view name) const; FuncSignature* GetFuncSignature(Index index) { return &sigs_[index]; } Func* GetFunc(Index index) { assert(index < funcs_.size()); return funcs_[index].get(); } Global* GetGlobal(Index index) { assert(index < globals_.size()); return &globals_[index]; } Memory* GetMemory(Index index) { assert(index < memories_.size()); return &memories_[index]; } Table* GetTable(Index index) { assert(index < tables_.size()); return &tables_[index]; } Module* GetModule(Index index) { assert(index < modules_.size()); return modules_[index].get(); } Module* GetLastModule() { return modules_.empty() ? nullptr : modules_.back().get(); } Module* FindModule(string_view name); Module* FindRegisteredModule(string_view name); template FuncSignature* EmplaceBackFuncSignature(Args&&... args) { sigs_.emplace_back(std::forward(args)...); return &sigs_.back(); } template Func* EmplaceBackFunc(Args&&... args) { funcs_.emplace_back(std::forward(args)...); return funcs_.back().get(); } template Global* EmplaceBackGlobal(Args&&... args) { globals_.emplace_back(std::forward(args)...); return &globals_.back(); } template Table* EmplaceBackTable(Args&&... args) { tables_.emplace_back(std::forward(args)...); return &tables_.back(); } template Memory* EmplaceBackMemory(Args&&... args) { memories_.emplace_back(std::forward(args)...); return &memories_.back(); } template Module* EmplaceBackModule(Args&&... args) { modules_.emplace_back(std::forward(args)...); return modules_.back().get(); } template void EmplaceModuleBinding(Args&&... args) { module_bindings_.emplace(std::forward(args)...); } template void EmplaceRegisteredModuleBinding(Args&&... args) { registered_module_bindings_.emplace(std::forward(args)...); } HostModule* AppendHostModule(string_view name); bool FuncSignaturesAreEqual(Index sig_index_0, Index sig_index_1) const; MarkPoint Mark(); void ResetToMarkPoint(const MarkPoint&); void Disassemble(Stream* stream, IstreamOffset from, IstreamOffset to); void DisassembleModule(Stream* stream, Module*); private: friend class Thread; std::vector> modules_; std::vector sigs_; std::vector> funcs_; std::vector memories_; std::vector tables_; std::vector globals_; std::unique_ptr istream_; BindingHash module_bindings_; BindingHash registered_module_bindings_; }; class Thread { public: struct Options { static const uint32_t kDefaultValueStackSize = 512 * 1024 / sizeof(Value); static const uint32_t kDefaultCallStackSize = 64 * 1024; explicit Options(uint32_t value_stack_size = kDefaultValueStackSize, uint32_t call_stack_size = kDefaultCallStackSize); uint32_t value_stack_size; uint32_t call_stack_size; }; explicit Thread(Environment*, const Options& = Options()); Environment* env() { return env_; } void set_pc(IstreamOffset offset) { pc_ = offset; } IstreamOffset pc() const { return pc_; } void Reset(); Index NumValues() const { return value_stack_top_; } Result Push(Value) WABT_WARN_UNUSED; Value Pop(); Value ValueAt(Index at) const; void Trace(Stream*); Result Run(int num_instructions = 1); Result CallHost(HostFunc*); private: const uint8_t* GetIstream() const { return env_->istream_->data.data(); } Memory* ReadMemory(const uint8_t** pc); template Result GetAccessAddress(const uint8_t** pc, void** out_address); template Result GetAtomicAccessAddress(const uint8_t** pc, void** out_address); Value& Top(); Value& Pick(Index depth); // Push/Pop values with conversions, e.g. Push will convert to the // ValueTypeRep (uint32_t) and push that. Similarly, Pop will pop the // value and convert to float. template Result Push(T) WABT_WARN_UNUSED; template T Pop(); // Push/Pop values without conversions, e.g. Push will take a uint32_t // argument which is the integer representation of that float value. // Similarly, PopRep will not convert the value to a float. template Result PushRep(ValueTypeRep) WABT_WARN_UNUSED; template ValueTypeRep PopRep(); void DropKeep(uint32_t drop_count, uint8_t keep_count); Result PushCall(const uint8_t* pc) WABT_WARN_UNUSED; IstreamOffset PopCall(); template using UnopFunc = R(T); template using UnopTrapFunc = Result(T, R*); template using BinopFunc = R(T, T); template using BinopTrapFunc = Result(T, T, R*); template Result Load(const uint8_t** pc) WABT_WARN_UNUSED; template Result Store(const uint8_t** pc) WABT_WARN_UNUSED; template Result AtomicLoad(const uint8_t** pc) WABT_WARN_UNUSED; template Result AtomicStore(const uint8_t** pc) WABT_WARN_UNUSED; template Result AtomicRmw(BinopFunc, const uint8_t** pc) WABT_WARN_UNUSED; template Result AtomicRmwCmpxchg(const uint8_t** pc) WABT_WARN_UNUSED; template Result Unop(UnopFunc func) WABT_WARN_UNUSED; template Result UnopTrap(UnopTrapFunc func) WABT_WARN_UNUSED; template Result SimdUnop(UnopFunc func) WABT_WARN_UNUSED; template Result Binop(BinopFunc func) WABT_WARN_UNUSED; template Result BinopTrap(BinopTrapFunc func) WABT_WARN_UNUSED; template Result SimdBinop(BinopFunc func) WABT_WARN_UNUSED; Environment* env_ = nullptr; std::vector value_stack_; std::vector call_stack_; uint32_t value_stack_top_ = 0; uint32_t call_stack_top_ = 0; IstreamOffset pc_ = 0; }; struct ExecResult { ExecResult() = default; explicit ExecResult(Result result) : result(result) {} ExecResult(Result result, const TypedValues& values) : result(result), values(values) {} Result result = Result::Ok; TypedValues values; }; class Executor { public: explicit Executor(Environment*, Stream* trace_stream = nullptr, const Thread::Options& options = Thread::Options()); ExecResult RunFunction(Index func_index, const TypedValues& args); ExecResult RunStartFunction(DefinedModule* module); ExecResult RunExport(const Export*, const TypedValues& args); ExecResult RunExportByName(Module* module, string_view name, const TypedValues& args); private: Result RunDefinedFunction(IstreamOffset function_offset); Result PushArgs(const FuncSignature*, const TypedValues& args); void CopyResults(const FuncSignature*, TypedValues* out_results); Environment* env_ = nullptr; Stream* trace_stream_ = nullptr; Thread thread_; }; bool IsCanonicalNan(uint32_t f32_bits); bool IsCanonicalNan(uint64_t f64_bits); bool IsArithmeticNan(uint32_t f32_bits); bool IsArithmeticNan(uint64_t f64_bits); std::string TypedValueToString(const TypedValue&); const char* ResultToString(Result); void WriteTypedValue(Stream* stream, const TypedValue&); void WriteTypedValues(Stream* stream, const TypedValues&); void WriteResult(Stream* stream, const char* desc, Result); void WriteCall(Stream* stream, string_view module_name, string_view func_name, const TypedValues& args, const TypedValues& results, Result); } // namespace interp } // namespace wabt #endif /* WABT_INTERP_H_ */