| /* |
| * 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 <stdint.h> |
| |
| #include <functional> |
| #include <memory> |
| #include <vector> |
| |
| #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; |
| // uint32_t keep_count; |
| // }; |
| #define WABT_TABLE_ENTRY_SIZE \ |
| (sizeof(IstreamOffset) + sizeof(uint32_t) + sizeof(uint32_t)) |
| #define WABT_TABLE_ENTRY_OFFSET_OFFSET 0 |
| #define WABT_TABLE_ENTRY_DROP_OFFSET sizeof(IstreamOffset) |
| #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<Type> param_types; |
| std::vector<Type> result_types; |
| }; |
| |
| struct Table { |
| explicit Table(const Limits& limits) |
| : limits(limits), func_indexes(limits.initial, kInvalidIndex) {} |
| |
| Limits limits; |
| std::vector<Index> 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<char> data; |
| }; |
| |
| // ValueTypeRep converts from one type to its representation on the |
| // stack. For example, float -> uint32_t. See Value below. |
| template <typename T> |
| struct ValueTypeRepT; |
| |
| template <> struct ValueTypeRepT<int32_t> { typedef uint32_t type; }; |
| template <> struct ValueTypeRepT<uint32_t> { typedef uint32_t type; }; |
| template <> struct ValueTypeRepT<int64_t> { typedef uint64_t type; }; |
| template <> struct ValueTypeRepT<uint64_t> { typedef uint64_t type; }; |
| template <> struct ValueTypeRepT<float> { typedef uint32_t type; }; |
| template <> struct ValueTypeRepT<double> { typedef uint64_t type; }; |
| template <> struct ValueTypeRepT<v128> { typedef v128 type; }; |
| |
| template <typename T> |
| using ValueTypeRep = typename ValueTypeRepT<T>::type; |
| |
| union Value { |
| uint32_t i32; |
| uint64_t i64; |
| ValueTypeRep<float> f32_bits; |
| ValueTypeRep<double> f64_bits; |
| ValueTypeRep<v128> 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<TypedValue> 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<Type> 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<void(const char* msg)> 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<Export> 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<FuncImport> func_imports; |
| std::vector<TableImport> table_imports; |
| std::vector<MemoryImport> memory_imports; |
| std::vector<GlobalImport> global_imports; |
| std::vector<ExceptImport> 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<HostImportDelegate> 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<OutputBuffer> istream) { |
| istream_ = std::move(istream); |
| } |
| std::unique_ptr<OutputBuffer> 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 <typename... Args> |
| FuncSignature* EmplaceBackFuncSignature(Args&&... args) { |
| sigs_.emplace_back(std::forward<Args>(args)...); |
| return &sigs_.back(); |
| } |
| |
| template <typename... Args> |
| Func* EmplaceBackFunc(Args&&... args) { |
| funcs_.emplace_back(std::forward<Args>(args)...); |
| return funcs_.back().get(); |
| } |
| |
| template <typename... Args> |
| Global* EmplaceBackGlobal(Args&&... args) { |
| globals_.emplace_back(std::forward<Args>(args)...); |
| return &globals_.back(); |
| } |
| |
| template <typename... Args> |
| Table* EmplaceBackTable(Args&&... args) { |
| tables_.emplace_back(std::forward<Args>(args)...); |
| return &tables_.back(); |
| } |
| |
| template <typename... Args> |
| Memory* EmplaceBackMemory(Args&&... args) { |
| memories_.emplace_back(std::forward<Args>(args)...); |
| return &memories_.back(); |
| } |
| |
| template <typename... Args> |
| Module* EmplaceBackModule(Args&&... args) { |
| modules_.emplace_back(std::forward<Args>(args)...); |
| return modules_.back().get(); |
| } |
| |
| template <typename... Args> |
| void EmplaceModuleBinding(Args&&... args) { |
| module_bindings_.emplace(std::forward<Args>(args)...); |
| } |
| |
| template <typename... Args> |
| void EmplaceRegisteredModuleBinding(Args&&... args) { |
| registered_module_bindings_.emplace(std::forward<Args>(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<std::unique_ptr<Module>> modules_; |
| std::vector<FuncSignature> sigs_; |
| std::vector<std::unique_ptr<Func>> funcs_; |
| std::vector<Memory> memories_; |
| std::vector<Table> tables_; |
| std::vector<Global> globals_; |
| std::unique_ptr<OutputBuffer> 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 <typename MemType> |
| Result GetAccessAddress(const uint8_t** pc, void** out_address); |
| template <typename MemType> |
| Result GetAtomicAccessAddress(const uint8_t** pc, void** out_address); |
| |
| Value& Top(); |
| Value& Pick(Index depth); |
| |
| // Push/Pop values with conversions, e.g. Push<float> will convert to the |
| // ValueTypeRep (uint32_t) and push that. Similarly, Pop<float> will pop the |
| // value and convert to float. |
| template <typename T> |
| Result Push(T) WABT_WARN_UNUSED; |
| template <typename T> |
| T Pop(); |
| |
| // Push/Pop values without conversions, e.g. PushRep<float> will take a |
| // uint32_t argument which is the integer representation of that float value. |
| // Similarly, PopRep<float> will not convert the value to a float. |
| template <typename T> |
| Result PushRep(ValueTypeRep<T>) WABT_WARN_UNUSED; |
| template <typename T> |
| ValueTypeRep<T> PopRep(); |
| |
| void DropKeep(uint32_t drop_count, uint32_t keep_count); |
| |
| Result PushCall(const uint8_t* pc) WABT_WARN_UNUSED; |
| IstreamOffset PopCall(); |
| |
| template <typename R, typename T> using UnopFunc = R(T); |
| template <typename R, typename T> using UnopTrapFunc = Result(T, R*); |
| template <typename R, typename T> using BinopFunc = R(T, T); |
| template <typename R, typename T> using BinopTrapFunc = Result(T, T, R*); |
| |
| template <typename MemType, typename ResultType = MemType> |
| Result Load(const uint8_t** pc) WABT_WARN_UNUSED; |
| template <typename MemType, typename ResultType = MemType> |
| Result Store(const uint8_t** pc) WABT_WARN_UNUSED; |
| template <typename MemType, typename ResultType = MemType> |
| Result AtomicLoad(const uint8_t** pc) WABT_WARN_UNUSED; |
| template <typename MemType, typename ResultType = MemType> |
| Result AtomicStore(const uint8_t** pc) WABT_WARN_UNUSED; |
| template <typename MemType, typename ResultType = MemType> |
| Result AtomicRmw(BinopFunc<ResultType, ResultType>, |
| const uint8_t** pc) WABT_WARN_UNUSED; |
| template <typename MemType, typename ResultType = MemType> |
| Result AtomicRmwCmpxchg(const uint8_t** pc) WABT_WARN_UNUSED; |
| |
| template <typename R, typename T = R> |
| Result Unop(UnopFunc<R, T> func) WABT_WARN_UNUSED; |
| template <typename R, typename T = R> |
| Result UnopTrap(UnopTrapFunc<R, T> func) WABT_WARN_UNUSED; |
| |
| template <typename T, typename L, typename R, typename P = R> |
| Result SimdUnop(UnopFunc<R, P> func) WABT_WARN_UNUSED; |
| |
| template <typename R, typename T = R> |
| Result Binop(BinopFunc<R, T> func) WABT_WARN_UNUSED; |
| template <typename R, typename T = R> |
| Result BinopTrap(BinopTrapFunc<R, T> func) WABT_WARN_UNUSED; |
| |
| template <typename T, typename L, typename R, typename P = R> |
| Result SimdBinop(BinopFunc<R, P> func) WABT_WARN_UNUSED; |
| |
| Environment* env_ = nullptr; |
| std::vector<Value> value_stack_; |
| std::vector<IstreamOffset> 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_ */ |