| /* |
| * 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. |
| */ |
| |
| #include "interpreter.h" |
| |
| #include <assert.h> |
| #include <inttypes.h> |
| #include <math.h> |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "stream.h" |
| |
| namespace wabt { |
| |
| static const char* s_interpreter_opcode_name[256] = { |
| |
| #define WABT_OPCODE(rtype, type1, type2, mem_size, code, NAME, text) text, |
| #include "interpreter-opcode.def" |
| #undef WABT_OPCODE |
| |
| }; |
| |
| #define CHECK_RESULT(expr) \ |
| do { \ |
| if (WABT_FAILED(expr)) \ |
| return Result::Error; \ |
| } while (0) |
| |
| static const char* get_interpreter_opcode_name(InterpreterOpcode opcode) { |
| return s_interpreter_opcode_name[static_cast<int>(opcode)]; |
| } |
| |
| InterpreterEnvironment::InterpreterEnvironment() |
| : istream(new OutputBuffer()) {} |
| |
| InterpreterThread::InterpreterThread() |
| : env(nullptr), |
| value_stack_top(nullptr), |
| value_stack_end(nullptr), |
| call_stack_top(nullptr), |
| call_stack_end(nullptr), |
| pc(0) {} |
| |
| InterpreterImport::InterpreterImport() |
| : kind(ExternalKind::Func) { |
| WABT_ZERO_MEMORY(module_name); |
| WABT_ZERO_MEMORY(field_name); |
| WABT_ZERO_MEMORY(func.sig_index); |
| } |
| |
| InterpreterImport::InterpreterImport(InterpreterImport&& other) { |
| *this = std::move(other); |
| } |
| |
| InterpreterImport& InterpreterImport::operator=(InterpreterImport&& other) { |
| kind = other.kind; |
| module_name = other.module_name; |
| WABT_ZERO_MEMORY(other.module_name); |
| field_name = other.field_name; |
| WABT_ZERO_MEMORY(other.field_name); |
| switch (kind) { |
| case ExternalKind::Func: |
| func.sig_index = other.func.sig_index; |
| break; |
| case ExternalKind::Table: |
| table.limits = other.table.limits; |
| break; |
| case ExternalKind::Memory: |
| memory.limits = other.memory.limits; |
| break; |
| case ExternalKind::Global: |
| global.type = other.global.type; |
| global.mutable_ = other.global.mutable_; |
| break; |
| } |
| return *this; |
| } |
| |
| |
| InterpreterImport::~InterpreterImport() { |
| destroy_string_slice(&module_name); |
| destroy_string_slice(&field_name); |
| } |
| |
| InterpreterExport::InterpreterExport(InterpreterExport&& other) |
| : name(other.name), |
| kind(other.kind), |
| index(other.index) { |
| WABT_ZERO_MEMORY(other.name); |
| } |
| |
| InterpreterExport& InterpreterExport::operator=(InterpreterExport&& other) { |
| name = other.name; |
| kind = other.kind; |
| index = other.index; |
| WABT_ZERO_MEMORY(other.name); |
| return *this; |
| } |
| |
| InterpreterExport::~InterpreterExport() { |
| destroy_string_slice(&name); |
| } |
| |
| InterpreterModule::InterpreterModule(bool is_host) |
| : memory_index(WABT_INVALID_INDEX), |
| table_index(WABT_INVALID_INDEX), |
| is_host(is_host) { |
| WABT_ZERO_MEMORY(name); |
| } |
| |
| InterpreterModule::InterpreterModule(const StringSlice& name, bool is_host) |
| : name(name), |
| memory_index(WABT_INVALID_INDEX), |
| table_index(WABT_INVALID_INDEX), |
| is_host(is_host) {} |
| |
| InterpreterModule::~InterpreterModule() { |
| destroy_string_slice(&name); |
| } |
| |
| DefinedInterpreterModule::DefinedInterpreterModule(size_t istream_start) |
| : InterpreterModule(false), |
| start_func_index(WABT_INVALID_INDEX), |
| istream_start(istream_start), |
| istream_end(istream_start) {} |
| |
| HostInterpreterModule::HostInterpreterModule(const StringSlice& name) |
| : InterpreterModule(name, true) {} |
| |
| InterpreterEnvironmentMark mark_interpreter_environment( |
| InterpreterEnvironment* env) { |
| InterpreterEnvironmentMark mark; |
| WABT_ZERO_MEMORY(mark); |
| mark.modules_size = env->modules.size(); |
| mark.sigs_size = env->sigs.size(); |
| mark.funcs_size = env->funcs.size(); |
| mark.memories_size = env->memories.size(); |
| mark.tables_size = env->tables.size(); |
| mark.globals_size = env->globals.size(); |
| mark.istream_size = env->istream->data.size(); |
| return mark; |
| } |
| |
| void reset_interpreter_environment_to_mark(InterpreterEnvironment* env, |
| InterpreterEnvironmentMark mark) { |
| /* Destroy entries in the binding hash. */ |
| for (size_t i = mark.modules_size; i < env->modules.size(); ++i) { |
| const StringSlice* name = &env->modules[i]->name; |
| if (!string_slice_is_empty(name)) |
| env->module_bindings.erase(string_slice_to_string(*name)); |
| } |
| |
| /* registered_module_bindings maps from an arbitrary name to a module index, |
| * so we have to iterate through the entire table to find entries to remove. |
| */ |
| auto iter = env->registered_module_bindings.begin(); |
| while (iter != env->registered_module_bindings.end()) { |
| if (iter->second.index >= static_cast<int>(mark.modules_size)) |
| iter = env->registered_module_bindings.erase(iter); |
| else |
| ++iter; |
| } |
| |
| env->modules.erase(env->modules.begin() + mark.modules_size, |
| env->modules.end()); |
| env->sigs.erase(env->sigs.begin() + mark.sigs_size, env->sigs.end()); |
| env->funcs.erase(env->funcs.begin() + mark.funcs_size, env->funcs.end()); |
| env->memories.erase(env->memories.begin() + mark.memories_size, |
| env->memories.end()); |
| env->tables.erase(env->tables.begin() + mark.tables_size, env->tables.end()); |
| env->globals.erase(env->globals.begin() + mark.globals_size, |
| env->globals.end()); |
| env->istream->data.resize(mark.istream_size); |
| } |
| |
| HostInterpreterModule* append_host_module(InterpreterEnvironment* env, |
| StringSlice name) { |
| HostInterpreterModule* module = |
| new HostInterpreterModule(dup_string_slice(name)); |
| env->modules.emplace_back(module); |
| env->registered_module_bindings.emplace(string_slice_to_string(name), |
| Binding(env->modules.size() - 1)); |
| return module; |
| } |
| |
| void init_interpreter_thread(InterpreterEnvironment* env, |
| InterpreterThread* thread, |
| InterpreterThreadOptions* options) { |
| thread->value_stack.resize(options->value_stack_size); |
| thread->call_stack.resize(options->call_stack_size); |
| thread->env = env; |
| thread->value_stack_top = thread->value_stack.data(); |
| thread->value_stack_end = |
| thread->value_stack.data() + thread->value_stack.size(); |
| thread->call_stack_top = thread->call_stack.data(); |
| thread->call_stack_end = |
| thread->call_stack.data() + thread->call_stack.size(); |
| thread->pc = options->pc; |
| } |
| |
| InterpreterResult push_thread_value(InterpreterThread* thread, |
| InterpreterValue value) { |
| if (thread->value_stack_top >= thread->value_stack_end) |
| return InterpreterResult::TrapValueStackExhausted; |
| *thread->value_stack_top++ = value; |
| return InterpreterResult::Ok; |
| } |
| |
| InterpreterExport* get_interpreter_export_by_name(InterpreterModule* module, |
| const StringSlice* name) { |
| int field_index = module->export_bindings.find_index(*name); |
| if (field_index < 0) |
| return nullptr; |
| return &module->exports[field_index]; |
| } |
| |
| /* 3 32222222 222...00 |
| * 1 09876543 210...10 |
| * ------------------- |
| * 0 00000000 000...00 => 0x00000000 => 0 |
| * 0 10011101 111...11 => 0x4effffff => 2147483520 (~INT32_MAX) |
| * 0 10011110 000...00 => 0x4f000000 => 2147483648 |
| * 0 10011110 111...11 => 0x4f7fffff => 4294967040 (~UINT32_MAX) |
| * 0 10111110 111...11 => 0x5effffff => 9223371487098961920 (~INT64_MAX) |
| * 0 10111110 000...00 => 0x5f000000 => 9223372036854775808 |
| * 0 10111111 111...11 => 0x5f7fffff => 18446742974197923840 (~UINT64_MAX) |
| * 0 10111111 000...00 => 0x5f800000 => 18446744073709551616 |
| * 0 11111111 000...00 => 0x7f800000 => inf |
| * 0 11111111 000...01 => 0x7f800001 => nan(0x1) |
| * 0 11111111 111...11 => 0x7fffffff => nan(0x7fffff) |
| * 1 00000000 000...00 => 0x80000000 => -0 |
| * 1 01111110 111...11 => 0xbf7fffff => -1 + ulp (~UINT32_MIN, ~UINT64_MIN) |
| * 1 01111111 000...00 => 0xbf800000 => -1 |
| * 1 10011110 000...00 => 0xcf000000 => -2147483648 (INT32_MIN) |
| * 1 10111110 000...00 => 0xdf000000 => -9223372036854775808 (INT64_MIN) |
| * 1 11111111 000...00 => 0xff800000 => -inf |
| * 1 11111111 000...01 => 0xff800001 => -nan(0x1) |
| * 1 11111111 111...11 => 0xffffffff => -nan(0x7fffff) |
| */ |
| |
| #define F32_MAX 0x7f7fffffU |
| #define F32_INF 0x7f800000U |
| #define F32_NEG_MAX 0xff7fffffU |
| #define F32_NEG_INF 0xff800000U |
| #define F32_NEG_ONE 0xbf800000U |
| #define F32_NEG_ZERO 0x80000000U |
| #define F32_QUIET_NAN 0x7fc00000U |
| #define F32_QUIET_NEG_NAN 0xffc00000U |
| #define F32_QUIET_NAN_BIT 0x00400000U |
| #define F32_SIG_BITS 23 |
| #define F32_SIG_MASK 0x7fffff |
| #define F32_SIGN_MASK 0x80000000U |
| |
| static bool is_nan_f32(uint32_t f32_bits) { |
| return (f32_bits > F32_INF && f32_bits < F32_NEG_ZERO) || |
| (f32_bits > F32_NEG_INF); |
| } |
| |
| bool is_canonical_nan_f32(uint32_t f32_bits) { |
| return f32_bits == F32_QUIET_NAN || f32_bits == F32_QUIET_NEG_NAN; |
| } |
| |
| bool is_arithmetic_nan_f32(uint32_t f32_bits) { |
| return (f32_bits & F32_QUIET_NAN) == F32_QUIET_NAN; |
| } |
| |
| static WABT_INLINE bool is_zero_f32(uint32_t f32_bits) { |
| return f32_bits == 0 || f32_bits == F32_NEG_ZERO; |
| } |
| |
| static WABT_INLINE bool is_in_range_i32_trunc_s_f32(uint32_t f32_bits) { |
| return (f32_bits < 0x4f000000U) || |
| (f32_bits >= F32_NEG_ZERO && f32_bits <= 0xcf000000U); |
| } |
| |
| static WABT_INLINE bool is_in_range_i64_trunc_s_f32(uint32_t f32_bits) { |
| return (f32_bits < 0x5f000000U) || |
| (f32_bits >= F32_NEG_ZERO && f32_bits <= 0xdf000000U); |
| } |
| |
| static WABT_INLINE bool is_in_range_i32_trunc_u_f32(uint32_t f32_bits) { |
| return (f32_bits < 0x4f800000U) || |
| (f32_bits >= F32_NEG_ZERO && f32_bits < F32_NEG_ONE); |
| } |
| |
| static WABT_INLINE bool is_in_range_i64_trunc_u_f32(uint32_t f32_bits) { |
| return (f32_bits < 0x5f800000U) || |
| (f32_bits >= F32_NEG_ZERO && f32_bits < F32_NEG_ONE); |
| } |
| |
| /* |
| * 6 66655555555 5544..2..222221...000 |
| * 3 21098765432 1098..9..432109...210 |
| * ----------------------------------- |
| * 0 00000000000 0000..0..000000...000 0x0000000000000000 => 0 |
| * 0 10000011101 1111..1..111000...000 0x41dfffffffc00000 => 2147483647 (INT32_MAX) |
| * 0 10000011110 1111..1..111100...000 0x41efffffffe00000 => 4294967295 (UINT32_MAX) |
| * 0 10000111101 1111..1..111111...111 0x43dfffffffffffff => 9223372036854774784 (~INT64_MAX) |
| * 0 10000111110 0000..0..000000...000 0x43e0000000000000 => 9223372036854775808 |
| * 0 10000111110 1111..1..111111...111 0x43efffffffffffff => 18446744073709549568 (~UINT64_MAX) |
| * 0 10000111111 0000..0..000000...000 0x43f0000000000000 => 18446744073709551616 |
| * 0 10001111110 1111..1..000000...000 0x47efffffe0000000 => 3.402823e+38 (FLT_MAX) |
| * 0 11111111111 0000..0..000000...000 0x7ff0000000000000 => inf |
| * 0 11111111111 0000..0..000000...001 0x7ff0000000000001 => nan(0x1) |
| * 0 11111111111 1111..1..111111...111 0x7fffffffffffffff => nan(0xfff...) |
| * 1 00000000000 0000..0..000000...000 0x8000000000000000 => -0 |
| * 1 01111111110 1111..1..111111...111 0xbfefffffffffffff => -1 + ulp (~UINT32_MIN, ~UINT64_MIN) |
| * 1 01111111111 0000..0..000000...000 0xbff0000000000000 => -1 |
| * 1 10000011110 0000..0..000000...000 0xc1e0000000000000 => -2147483648 (INT32_MIN) |
| * 1 10000111110 0000..0..000000...000 0xc3e0000000000000 => -9223372036854775808 (INT64_MIN) |
| * 1 10001111110 1111..1..000000...000 0xc7efffffe0000000 => -3.402823e+38 (-FLT_MAX) |
| * 1 11111111111 0000..0..000000...000 0xfff0000000000000 => -inf |
| * 1 11111111111 0000..0..000000...001 0xfff0000000000001 => -nan(0x1) |
| * 1 11111111111 1111..1..111111...111 0xffffffffffffffff => -nan(0xfff...) |
| */ |
| |
| #define F64_INF 0x7ff0000000000000ULL |
| #define F64_NEG_INF 0xfff0000000000000ULL |
| #define F64_NEG_ONE 0xbff0000000000000ULL |
| #define F64_NEG_ZERO 0x8000000000000000ULL |
| #define F64_QUIET_NAN 0x7ff8000000000000ULL |
| #define F64_QUIET_NEG_NAN 0xfff8000000000000ULL |
| #define F64_QUIET_NAN_BIT 0x0008000000000000ULL |
| #define F64_SIG_BITS 52 |
| #define F64_SIG_MASK 0xfffffffffffffULL |
| #define F64_SIGN_MASK 0x8000000000000000ULL |
| |
| static bool is_nan_f64(uint64_t f64_bits) { |
| return (f64_bits > F64_INF && f64_bits < F64_NEG_ZERO) || |
| (f64_bits > F64_NEG_INF); |
| } |
| |
| bool is_canonical_nan_f64(uint64_t f64_bits) { |
| return f64_bits == F64_QUIET_NAN || f64_bits == F64_QUIET_NEG_NAN; |
| } |
| |
| bool is_arithmetic_nan_f64(uint64_t f64_bits) { |
| return (f64_bits & F64_QUIET_NAN) == F64_QUIET_NAN; |
| } |
| |
| static WABT_INLINE bool is_zero_f64(uint64_t f64_bits) { |
| return f64_bits == 0 || f64_bits == F64_NEG_ZERO; |
| } |
| |
| static WABT_INLINE bool is_in_range_i32_trunc_s_f64(uint64_t f64_bits) { |
| return (f64_bits <= 0x41dfffffffc00000ULL) || |
| (f64_bits >= F64_NEG_ZERO && f64_bits <= 0xc1e0000000000000ULL); |
| } |
| |
| static WABT_INLINE bool is_in_range_i32_trunc_u_f64(uint64_t f64_bits) { |
| return (f64_bits <= 0x41efffffffe00000ULL) || |
| (f64_bits >= F64_NEG_ZERO && f64_bits < F64_NEG_ONE); |
| } |
| |
| static WABT_INLINE bool is_in_range_i64_trunc_s_f64(uint64_t f64_bits) { |
| return (f64_bits < 0x43e0000000000000ULL) || |
| (f64_bits >= F64_NEG_ZERO && f64_bits <= 0xc3e0000000000000ULL); |
| } |
| |
| static WABT_INLINE bool is_in_range_i64_trunc_u_f64(uint64_t f64_bits) { |
| return (f64_bits < 0x43f0000000000000ULL) || |
| (f64_bits >= F64_NEG_ZERO && f64_bits < F64_NEG_ONE); |
| } |
| |
| static WABT_INLINE bool is_in_range_f64_demote_f32(uint64_t f64_bits) { |
| return (f64_bits <= 0x47efffffe0000000ULL) || |
| (f64_bits >= F64_NEG_ZERO && f64_bits <= 0xc7efffffe0000000ULL); |
| } |
| |
| /* The WebAssembly rounding mode means that these values (which are > F32_MAX) |
| * should be rounded to F32_MAX and not set to infinity. Unfortunately, UBSAN |
| * complains that the value is not representable as a float, so we'll special |
| * case them. */ |
| static WABT_INLINE bool is_in_range_f64_demote_f32_round_to_f32_max( |
| uint64_t f64_bits) { |
| return f64_bits > 0x47efffffe0000000ULL && f64_bits < 0x47effffff0000000ULL; |
| } |
| |
| static WABT_INLINE bool is_in_range_f64_demote_f32_round_to_neg_f32_max( |
| uint64_t f64_bits) { |
| return f64_bits > 0xc7efffffe0000000ULL && f64_bits < 0xc7effffff0000000ULL; |
| } |
| |
| #define IS_NAN_F32 is_nan_f32 |
| #define IS_NAN_F64 is_nan_f64 |
| #define IS_ZERO_F32 is_zero_f32 |
| #define IS_ZERO_F64 is_zero_f64 |
| |
| #define DEFINE_BITCAST(name, src, dst) \ |
| static WABT_INLINE dst name(src x) { \ |
| dst result; \ |
| memcpy(&result, &x, sizeof(dst)); \ |
| return result; \ |
| } |
| |
| DEFINE_BITCAST(bitcast_u32_to_i32, uint32_t, int32_t) |
| DEFINE_BITCAST(bitcast_u64_to_i64, uint64_t, int64_t) |
| DEFINE_BITCAST(bitcast_f32_to_u32, float, uint32_t) |
| DEFINE_BITCAST(bitcast_u32_to_f32, uint32_t, float) |
| DEFINE_BITCAST(bitcast_f64_to_u64, double, uint64_t) |
| DEFINE_BITCAST(bitcast_u64_to_f64, uint64_t, double) |
| |
| #define bitcast_i32_to_u32(x) (static_cast<uint32_t>(x)) |
| #define bitcast_i64_to_u64(x) (static_cast<uint64_t>(x)) |
| |
| #define VALUE_TYPE_I32 uint32_t |
| #define VALUE_TYPE_I64 uint64_t |
| #define VALUE_TYPE_F32 uint32_t |
| #define VALUE_TYPE_F64 uint64_t |
| |
| #define VALUE_TYPE_SIGNED_MAX_I32 (0x80000000U) |
| #define VALUE_TYPE_UNSIGNED_MAX_I32 (0xFFFFFFFFU) |
| #define VALUE_TYPE_SIGNED_MAX_I64 static_cast<uint64_t>(0x8000000000000000ULL) |
| #define VALUE_TYPE_UNSIGNED_MAX_I64 static_cast<uint64_t>(0xFFFFFFFFFFFFFFFFULL) |
| |
| #define FLOAT_TYPE_F32 float |
| #define FLOAT_TYPE_F64 double |
| |
| #define MEM_TYPE_I8 int8_t |
| #define MEM_TYPE_U8 uint8_t |
| #define MEM_TYPE_I16 int16_t |
| #define MEM_TYPE_U16 uint16_t |
| #define MEM_TYPE_I32 int32_t |
| #define MEM_TYPE_U32 uint32_t |
| #define MEM_TYPE_I64 int64_t |
| #define MEM_TYPE_U64 uint64_t |
| #define MEM_TYPE_F32 uint32_t |
| #define MEM_TYPE_F64 uint64_t |
| |
| #define MEM_TYPE_EXTEND_I32_I8 int32_t |
| #define MEM_TYPE_EXTEND_I32_U8 uint32_t |
| #define MEM_TYPE_EXTEND_I32_I16 int32_t |
| #define MEM_TYPE_EXTEND_I32_U16 uint32_t |
| #define MEM_TYPE_EXTEND_I32_I32 int32_t |
| #define MEM_TYPE_EXTEND_I32_U32 uint32_t |
| |
| #define MEM_TYPE_EXTEND_I64_I8 int64_t |
| #define MEM_TYPE_EXTEND_I64_U8 uint64_t |
| #define MEM_TYPE_EXTEND_I64_I16 int64_t |
| #define MEM_TYPE_EXTEND_I64_U16 uint64_t |
| #define MEM_TYPE_EXTEND_I64_I32 int64_t |
| #define MEM_TYPE_EXTEND_I64_U32 uint64_t |
| #define MEM_TYPE_EXTEND_I64_I64 int64_t |
| #define MEM_TYPE_EXTEND_I64_U64 uint64_t |
| |
| #define MEM_TYPE_EXTEND_F32_F32 uint32_t |
| #define MEM_TYPE_EXTEND_F64_F64 uint64_t |
| |
| #define BITCAST_I32_TO_SIGNED bitcast_u32_to_i32 |
| #define BITCAST_I64_TO_SIGNED bitcast_u64_to_i64 |
| #define BITCAST_I32_TO_UNSIGNED bitcast_i32_to_u32 |
| #define BITCAST_I64_TO_UNSIGNED bitcast_i64_to_u64 |
| |
| #define BITCAST_TO_F32 bitcast_u32_to_f32 |
| #define BITCAST_TO_F64 bitcast_u64_to_f64 |
| #define BITCAST_FROM_F32 bitcast_f32_to_u32 |
| #define BITCAST_FROM_F64 bitcast_f64_to_u64 |
| |
| #define TYPE_FIELD_NAME_I32 i32 |
| #define TYPE_FIELD_NAME_I64 i64 |
| #define TYPE_FIELD_NAME_F32 f32_bits |
| #define TYPE_FIELD_NAME_F64 f64_bits |
| |
| #define TRAP(type) return InterpreterResult::Trap##type |
| #define TRAP_UNLESS(cond, type) TRAP_IF(!(cond), type) |
| #define TRAP_IF(cond, type) \ |
| do { \ |
| if (WABT_UNLIKELY(cond)) \ |
| TRAP(type); \ |
| } while (0) |
| |
| #define CHECK_STACK() \ |
| TRAP_IF(thread->value_stack_top >= thread->value_stack_end, \ |
| ValueStackExhausted) |
| |
| #define PUSH_NEG_1_AND_BREAK_IF(cond) \ |
| if (WABT_UNLIKELY(cond)) { \ |
| PUSH_I32(-1); \ |
| break; \ |
| } |
| |
| #define PUSH(v) \ |
| do { \ |
| CHECK_STACK(); \ |
| (*thread->value_stack_top++) = (v); \ |
| } while (0) |
| |
| #define PUSH_TYPE(type, v) \ |
| do { \ |
| CHECK_STACK(); \ |
| (*thread->value_stack_top++).TYPE_FIELD_NAME_##type = \ |
| static_cast<VALUE_TYPE_##type>(v); \ |
| } while (0) |
| |
| #define PUSH_I32(v) PUSH_TYPE(I32, (v)) |
| #define PUSH_I64(v) PUSH_TYPE(I64, (v)) |
| #define PUSH_F32(v) PUSH_TYPE(F32, (v)) |
| #define PUSH_F64(v) PUSH_TYPE(F64, (v)) |
| |
| #define PICK(depth) (*(thread->value_stack_top - (depth))) |
| #define TOP() (PICK(1)) |
| #define POP() (*--thread->value_stack_top) |
| #define POP_I32() (POP().i32) |
| #define POP_I64() (POP().i64) |
| #define POP_F32() (POP().f32_bits) |
| #define POP_F64() (POP().f64_bits) |
| #define DROP_KEEP(drop, keep) \ |
| do { \ |
| assert((keep) <= 1); \ |
| if ((keep) == 1) \ |
| PICK((drop) + 1) = TOP(); \ |
| thread->value_stack_top -= (drop); \ |
| } while (0) |
| |
| #define GOTO(offset) pc = &istream[offset] |
| |
| #define PUSH_CALL() \ |
| do { \ |
| TRAP_IF(thread->call_stack_top >= thread->call_stack_end, \ |
| CallStackExhausted); \ |
| (*thread->call_stack_top++) = (pc - istream); \ |
| } while (0) |
| |
| #define POP_CALL() (*--thread->call_stack_top) |
| |
| #define GET_MEMORY(var) \ |
| uint32_t memory_index = read_u32(&pc); \ |
| InterpreterMemory* var = &env->memories[memory_index] |
| |
| #define LOAD(type, mem_type) \ |
| do { \ |
| GET_MEMORY(memory); \ |
| uint64_t offset = static_cast<uint64_t>(POP_I32()) + read_u32(&pc); \ |
| MEM_TYPE_##mem_type value; \ |
| TRAP_IF(offset + sizeof(value) > memory->data.size(), \ |
| MemoryAccessOutOfBounds); \ |
| void* src = memory->data.data() + static_cast<uint32_t>(offset); \ |
| memcpy(&value, src, sizeof(MEM_TYPE_##mem_type)); \ |
| PUSH_##type(static_cast<MEM_TYPE_EXTEND_##type##_##mem_type>(value)); \ |
| } while (0) |
| |
| #define STORE(type, mem_type) \ |
| do { \ |
| GET_MEMORY(memory); \ |
| VALUE_TYPE_##type value = POP_##type(); \ |
| uint64_t offset = static_cast<uint64_t>(POP_I32()) + read_u32(&pc); \ |
| MEM_TYPE_##mem_type src = static_cast<MEM_TYPE_##mem_type>(value); \ |
| TRAP_IF(offset + sizeof(src) > memory->data.size(), \ |
| MemoryAccessOutOfBounds); \ |
| void* dst = memory->data.data() + static_cast<uint32_t>(offset); \ |
| memcpy(dst, &src, sizeof(MEM_TYPE_##mem_type)); \ |
| } while (0) |
| |
| #define BINOP(rtype, type, op) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| PUSH_##rtype(lhs op rhs); \ |
| } while (0) |
| |
| #define BINOP_SIGNED(rtype, type, op) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| PUSH_##rtype(BITCAST_##type##_TO_SIGNED(lhs) \ |
| op BITCAST_##type##_TO_SIGNED(rhs)); \ |
| } while (0) |
| |
| #define SHIFT_MASK_I32 31 |
| #define SHIFT_MASK_I64 63 |
| |
| #define BINOP_SHIFT(type, op, sign) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| PUSH_##type(BITCAST_##type##_TO_##sign(lhs) op(rhs& SHIFT_MASK_##type)); \ |
| } while (0) |
| |
| #define ROT_LEFT_0_SHIFT_OP << |
| #define ROT_LEFT_1_SHIFT_OP >> |
| #define ROT_RIGHT_0_SHIFT_OP >> |
| #define ROT_RIGHT_1_SHIFT_OP << |
| |
| #define BINOP_ROT(type, dir) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| uint32_t amount = rhs & SHIFT_MASK_##type; \ |
| if (WABT_LIKELY(amount != 0)) { \ |
| PUSH_##type( \ |
| (lhs ROT_##dir##_0_SHIFT_OP amount) | \ |
| (lhs ROT_##dir##_1_SHIFT_OP((SHIFT_MASK_##type + 1) - amount))); \ |
| } else { \ |
| PUSH_##type(lhs); \ |
| } \ |
| } while (0) |
| |
| #define BINOP_DIV_REM_U(type, op) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| TRAP_IF(rhs == 0, IntegerDivideByZero); \ |
| PUSH_##type(BITCAST_##type##_TO_UNSIGNED(lhs) \ |
| op BITCAST_##type##_TO_UNSIGNED(rhs)); \ |
| } while (0) |
| |
| /* {i32,i64}.{div,rem}_s are special-cased because they trap when dividing the |
| * max signed value by -1. The modulo operation on x86 uses the same |
| * instruction to generate the quotient and the remainder. */ |
| #define BINOP_DIV_S(type) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| TRAP_IF(rhs == 0, IntegerDivideByZero); \ |
| TRAP_IF(lhs == VALUE_TYPE_SIGNED_MAX_##type && \ |
| rhs == VALUE_TYPE_UNSIGNED_MAX_##type, \ |
| IntegerOverflow); \ |
| PUSH_##type(BITCAST_##type##_TO_SIGNED(lhs) / \ |
| BITCAST_##type##_TO_SIGNED(rhs)); \ |
| } while (0) |
| |
| #define BINOP_REM_S(type) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| TRAP_IF(rhs == 0, IntegerDivideByZero); \ |
| if (WABT_UNLIKELY(lhs == VALUE_TYPE_SIGNED_MAX_##type && \ |
| rhs == VALUE_TYPE_UNSIGNED_MAX_##type)) { \ |
| PUSH_##type(0); \ |
| } else { \ |
| PUSH_##type(BITCAST_##type##_TO_SIGNED(lhs) % \ |
| BITCAST_##type##_TO_SIGNED(rhs)); \ |
| } \ |
| } while (0) |
| |
| #define UNOP_FLOAT(type, func) \ |
| do { \ |
| FLOAT_TYPE_##type value = BITCAST_TO_##type(POP_##type()); \ |
| VALUE_TYPE_##type result = BITCAST_FROM_##type(func(value)); \ |
| if (WABT_UNLIKELY(IS_NAN_##type(result))) { \ |
| result |= type##_QUIET_NAN_BIT; \ |
| } \ |
| PUSH_##type(result); \ |
| break; \ |
| } while (0) |
| |
| #define BINOP_FLOAT(type, op) \ |
| do { \ |
| FLOAT_TYPE_##type rhs = BITCAST_TO_##type(POP_##type()); \ |
| FLOAT_TYPE_##type lhs = BITCAST_TO_##type(POP_##type()); \ |
| PUSH_##type(BITCAST_FROM_##type(lhs op rhs)); \ |
| } while (0) |
| |
| #define BINOP_FLOAT_DIV(type) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| if (WABT_UNLIKELY(IS_ZERO_##type(rhs))) { \ |
| if (IS_NAN_##type(lhs)) { \ |
| PUSH_##type(lhs | type##_QUIET_NAN); \ |
| } else if (IS_ZERO_##type(lhs)) { \ |
| PUSH_##type(type##_QUIET_NAN); \ |
| } else { \ |
| VALUE_TYPE_##type sign = \ |
| (lhs & type##_SIGN_MASK) ^ (rhs & type##_SIGN_MASK); \ |
| PUSH_##type(sign | type##_INF); \ |
| } \ |
| } else { \ |
| PUSH_##type(BITCAST_FROM_##type(BITCAST_TO_##type(lhs) / \ |
| BITCAST_TO_##type(rhs))); \ |
| } \ |
| } while (0) |
| |
| #define BINOP_FLOAT_COMPARE(type, op) \ |
| do { \ |
| FLOAT_TYPE_##type rhs = BITCAST_TO_##type(POP_##type()); \ |
| FLOAT_TYPE_##type lhs = BITCAST_TO_##type(POP_##type()); \ |
| PUSH_I32(lhs op rhs); \ |
| } while (0) |
| |
| #define MIN_OP < |
| #define MAX_OP > |
| |
| #define MINMAX_FLOAT(type, op) \ |
| do { \ |
| VALUE_TYPE_##type rhs = POP_##type(); \ |
| VALUE_TYPE_##type lhs = POP_##type(); \ |
| VALUE_TYPE_##type result; \ |
| if (WABT_UNLIKELY(IS_NAN_##type(lhs))) { \ |
| result = lhs | type##_QUIET_NAN_BIT; \ |
| } else if (WABT_UNLIKELY(IS_NAN_##type(rhs))) { \ |
| result = rhs | type##_QUIET_NAN_BIT; \ |
| } else if ((lhs ^ rhs) & type##_SIGN_MASK) { \ |
| /* min(-0.0, 0.0) => -0.0; since we know the sign bits are different, we \ |
| * can just use the inverse integer comparison (because the sign bit is \ |
| * set when the value is negative) */ \ |
| result = !(lhs op##_OP rhs) ? lhs : rhs; \ |
| } else { \ |
| FLOAT_TYPE_##type float_rhs = BITCAST_TO_##type(rhs); \ |
| FLOAT_TYPE_##type float_lhs = BITCAST_TO_##type(lhs); \ |
| result = BITCAST_FROM_##type(float_lhs op##_OP float_rhs ? float_lhs \ |
| : float_rhs); \ |
| } \ |
| PUSH_##type(result); \ |
| } while (0) |
| |
| static WABT_INLINE uint32_t read_u32_at(const uint8_t* pc) { |
| uint32_t result; |
| memcpy(&result, pc, sizeof(uint32_t)); |
| return result; |
| } |
| |
| static WABT_INLINE uint32_t read_u32(const uint8_t** pc) { |
| uint32_t result = read_u32_at(*pc); |
| *pc += sizeof(uint32_t); |
| return result; |
| } |
| |
| static WABT_INLINE uint64_t read_u64_at(const uint8_t* pc) { |
| uint64_t result; |
| memcpy(&result, pc, sizeof(uint64_t)); |
| return result; |
| } |
| |
| static WABT_INLINE uint64_t read_u64(const uint8_t** pc) { |
| uint64_t result = read_u64_at(*pc); |
| *pc += sizeof(uint64_t); |
| return result; |
| } |
| |
| static WABT_INLINE void read_table_entry_at(const uint8_t* pc, |
| uint32_t* out_offset, |
| uint32_t* out_drop, |
| uint8_t* out_keep) { |
| *out_offset = read_u32_at(pc + WABT_TABLE_ENTRY_OFFSET_OFFSET); |
| *out_drop = read_u32_at(pc + WABT_TABLE_ENTRY_DROP_OFFSET); |
| *out_keep = *(pc + WABT_TABLE_ENTRY_KEEP_OFFSET); |
| } |
| |
| bool func_signatures_are_equal(InterpreterEnvironment* env, |
| uint32_t sig_index_0, |
| uint32_t sig_index_1) { |
| if (sig_index_0 == sig_index_1) |
| return true; |
| InterpreterFuncSignature* sig_0 = &env->sigs[sig_index_0]; |
| InterpreterFuncSignature* sig_1 = &env->sigs[sig_index_1]; |
| return sig_0->param_types == sig_1->param_types && |
| sig_0->result_types == sig_1->result_types; |
| } |
| |
| InterpreterResult call_host(InterpreterThread* thread, |
| HostInterpreterFunc* func) { |
| InterpreterFuncSignature* sig = &thread->env->sigs[func->sig_index]; |
| |
| size_t num_params = sig->param_types.size(); |
| size_t num_results = sig->result_types.size(); |
| // + 1 is a workaround for using data() below; UBSAN doesn't like calling |
| // data() with an empty vector. |
| std::vector<InterpreterTypedValue> params(num_params + 1); |
| std::vector<InterpreterTypedValue> results(num_results + 1); |
| |
| for (size_t i = num_params; i > 0; --i) { |
| params[i - 1].value = POP(); |
| params[i - 1].type = sig->param_types[i - 1]; |
| } |
| |
| Result call_result = |
| func->callback(func, sig, num_params, params.data(), num_results, |
| results.data(), func->user_data); |
| TRAP_IF(call_result != Result::Ok, HostTrapped); |
| |
| for (size_t i = 0; i < num_results; ++i) { |
| TRAP_IF(results[i].type != sig->result_types[i], HostResultTypeMismatch); |
| PUSH(results[i].value); |
| } |
| |
| return InterpreterResult::Ok; |
| } |
| |
| InterpreterResult run_interpreter(InterpreterThread* thread, |
| uint32_t num_instructions, |
| uint32_t* call_stack_return_top) { |
| InterpreterResult result = InterpreterResult::Ok; |
| assert(call_stack_return_top < thread->call_stack_end); |
| |
| InterpreterEnvironment* env = thread->env; |
| |
| const uint8_t* istream = env->istream->data.data(); |
| const uint8_t* pc = &istream[thread->pc]; |
| for (uint32_t i = 0; i < num_instructions; ++i) { |
| InterpreterOpcode opcode = static_cast<InterpreterOpcode>(*pc++); |
| switch (opcode) { |
| case InterpreterOpcode::Select: { |
| VALUE_TYPE_I32 cond = POP_I32(); |
| InterpreterValue false_ = POP(); |
| InterpreterValue true_ = POP(); |
| PUSH(cond ? true_ : false_); |
| break; |
| } |
| |
| case InterpreterOpcode::Br: |
| GOTO(read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::BrIf: { |
| uint32_t new_pc = read_u32(&pc); |
| if (POP_I32()) |
| GOTO(new_pc); |
| break; |
| } |
| |
| case InterpreterOpcode::BrTable: { |
| uint32_t num_targets = read_u32(&pc); |
| uint32_t table_offset = read_u32(&pc); |
| VALUE_TYPE_I32 key = POP_I32(); |
| uint32_t key_offset = |
| (key >= num_targets ? num_targets : key) * WABT_TABLE_ENTRY_SIZE; |
| const uint8_t* entry = istream + table_offset + key_offset; |
| uint32_t new_pc; |
| uint32_t drop_count; |
| uint8_t keep_count; |
| read_table_entry_at(entry, &new_pc, &drop_count, &keep_count); |
| DROP_KEEP(drop_count, keep_count); |
| GOTO(new_pc); |
| break; |
| } |
| |
| case InterpreterOpcode::Return: |
| if (thread->call_stack_top == call_stack_return_top) { |
| result = InterpreterResult::Returned; |
| goto exit_loop; |
| } |
| GOTO(POP_CALL()); |
| break; |
| |
| case InterpreterOpcode::Unreachable: |
| TRAP(Unreachable); |
| break; |
| |
| case InterpreterOpcode::I32Const: |
| PUSH_I32(read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::I64Const: |
| PUSH_I64(read_u64(&pc)); |
| break; |
| |
| case InterpreterOpcode::F32Const: |
| PUSH_F32(read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::F64Const: |
| PUSH_F64(read_u64(&pc)); |
| break; |
| |
| case InterpreterOpcode::GetGlobal: { |
| uint32_t index = read_u32(&pc); |
| assert(index < env->globals.size()); |
| PUSH(env->globals[index].typed_value.value); |
| break; |
| } |
| |
| case InterpreterOpcode::SetGlobal: { |
| uint32_t index = read_u32(&pc); |
| assert(index < env->globals.size()); |
| env->globals[index].typed_value.value = POP(); |
| break; |
| } |
| |
| case InterpreterOpcode::GetLocal: { |
| InterpreterValue value = PICK(read_u32(&pc)); |
| PUSH(value); |
| break; |
| } |
| |
| case InterpreterOpcode::SetLocal: { |
| InterpreterValue value = POP(); |
| PICK(read_u32(&pc)) = value; |
| break; |
| } |
| |
| case InterpreterOpcode::TeeLocal: |
| PICK(read_u32(&pc)) = TOP(); |
| break; |
| |
| case InterpreterOpcode::Call: { |
| uint32_t offset = read_u32(&pc); |
| PUSH_CALL(); |
| GOTO(offset); |
| break; |
| } |
| |
| case InterpreterOpcode::CallIndirect: { |
| uint32_t table_index = read_u32(&pc); |
| InterpreterTable* table = &env->tables[table_index]; |
| uint32_t sig_index = read_u32(&pc); |
| VALUE_TYPE_I32 entry_index = POP_I32(); |
| TRAP_IF(entry_index >= table->func_indexes.size(), UndefinedTableIndex); |
| uint32_t func_index = table->func_indexes[entry_index]; |
| TRAP_IF(func_index == WABT_INVALID_INDEX, UninitializedTableElement); |
| InterpreterFunc* func = env->funcs[func_index].get(); |
| TRAP_UNLESS(func_signatures_are_equal(env, func->sig_index, sig_index), |
| IndirectCallSignatureMismatch); |
| if (func->is_host) { |
| call_host(thread, func->as_host()); |
| } else { |
| PUSH_CALL(); |
| GOTO(func->as_defined()->offset); |
| } |
| break; |
| } |
| |
| case InterpreterOpcode::CallHost: { |
| uint32_t func_index = read_u32(&pc); |
| call_host(thread, env->funcs[func_index]->as_host()); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Load8S: |
| LOAD(I32, I8); |
| break; |
| |
| case InterpreterOpcode::I32Load8U: |
| LOAD(I32, U8); |
| break; |
| |
| case InterpreterOpcode::I32Load16S: |
| LOAD(I32, I16); |
| break; |
| |
| case InterpreterOpcode::I32Load16U: |
| LOAD(I32, U16); |
| break; |
| |
| case InterpreterOpcode::I64Load8S: |
| LOAD(I64, I8); |
| break; |
| |
| case InterpreterOpcode::I64Load8U: |
| LOAD(I64, U8); |
| break; |
| |
| case InterpreterOpcode::I64Load16S: |
| LOAD(I64, I16); |
| break; |
| |
| case InterpreterOpcode::I64Load16U: |
| LOAD(I64, U16); |
| break; |
| |
| case InterpreterOpcode::I64Load32S: |
| LOAD(I64, I32); |
| break; |
| |
| case InterpreterOpcode::I64Load32U: |
| LOAD(I64, U32); |
| break; |
| |
| case InterpreterOpcode::I32Load: |
| LOAD(I32, U32); |
| break; |
| |
| case InterpreterOpcode::I64Load: |
| LOAD(I64, U64); |
| break; |
| |
| case InterpreterOpcode::F32Load: |
| LOAD(F32, F32); |
| break; |
| |
| case InterpreterOpcode::F64Load: |
| LOAD(F64, F64); |
| break; |
| |
| case InterpreterOpcode::I32Store8: |
| STORE(I32, U8); |
| break; |
| |
| case InterpreterOpcode::I32Store16: |
| STORE(I32, U16); |
| break; |
| |
| case InterpreterOpcode::I64Store8: |
| STORE(I64, U8); |
| break; |
| |
| case InterpreterOpcode::I64Store16: |
| STORE(I64, U16); |
| break; |
| |
| case InterpreterOpcode::I64Store32: |
| STORE(I64, U32); |
| break; |
| |
| case InterpreterOpcode::I32Store: |
| STORE(I32, U32); |
| break; |
| |
| case InterpreterOpcode::I64Store: |
| STORE(I64, U64); |
| break; |
| |
| case InterpreterOpcode::F32Store: |
| STORE(F32, F32); |
| break; |
| |
| case InterpreterOpcode::F64Store: |
| STORE(F64, F64); |
| break; |
| |
| case InterpreterOpcode::CurrentMemory: { |
| GET_MEMORY(memory); |
| PUSH_I32(memory->page_limits.initial); |
| break; |
| } |
| |
| case InterpreterOpcode::GrowMemory: { |
| GET_MEMORY(memory); |
| uint32_t old_page_size = memory->page_limits.initial; |
| VALUE_TYPE_I32 grow_pages = POP_I32(); |
| uint32_t new_page_size = old_page_size + grow_pages; |
| uint32_t max_page_size = memory->page_limits.has_max |
| ? memory->page_limits.max |
| : WABT_MAX_PAGES; |
| PUSH_NEG_1_AND_BREAK_IF(new_page_size > max_page_size); |
| PUSH_NEG_1_AND_BREAK_IF( |
| static_cast<uint64_t>(new_page_size) * WABT_PAGE_SIZE > UINT32_MAX); |
| memory->data.resize(new_page_size * WABT_PAGE_SIZE); |
| memory->page_limits.initial = new_page_size; |
| PUSH_I32(old_page_size); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Add: |
| BINOP(I32, I32, +); |
| break; |
| |
| case InterpreterOpcode::I32Sub: |
| BINOP(I32, I32, -); |
| break; |
| |
| case InterpreterOpcode::I32Mul: |
| BINOP(I32, I32, *); |
| break; |
| |
| case InterpreterOpcode::I32DivS: |
| BINOP_DIV_S(I32); |
| break; |
| |
| case InterpreterOpcode::I32DivU: |
| BINOP_DIV_REM_U(I32, /); |
| break; |
| |
| case InterpreterOpcode::I32RemS: |
| BINOP_REM_S(I32); |
| break; |
| |
| case InterpreterOpcode::I32RemU: |
| BINOP_DIV_REM_U(I32, %); |
| break; |
| |
| case InterpreterOpcode::I32And: |
| BINOP(I32, I32, &); |
| break; |
| |
| case InterpreterOpcode::I32Or: |
| BINOP(I32, I32, |); |
| break; |
| |
| case InterpreterOpcode::I32Xor: |
| BINOP(I32, I32, ^); |
| break; |
| |
| case InterpreterOpcode::I32Shl: |
| BINOP_SHIFT(I32, <<, UNSIGNED); |
| break; |
| |
| case InterpreterOpcode::I32ShrU: |
| BINOP_SHIFT(I32, >>, UNSIGNED); |
| break; |
| |
| case InterpreterOpcode::I32ShrS: |
| BINOP_SHIFT(I32, >>, SIGNED); |
| break; |
| |
| case InterpreterOpcode::I32Eq: |
| BINOP(I32, I32, ==); |
| break; |
| |
| case InterpreterOpcode::I32Ne: |
| BINOP(I32, I32, !=); |
| break; |
| |
| case InterpreterOpcode::I32LtS: |
| BINOP_SIGNED(I32, I32, <); |
| break; |
| |
| case InterpreterOpcode::I32LeS: |
| BINOP_SIGNED(I32, I32, <=); |
| break; |
| |
| case InterpreterOpcode::I32LtU: |
| BINOP(I32, I32, <); |
| break; |
| |
| case InterpreterOpcode::I32LeU: |
| BINOP(I32, I32, <=); |
| break; |
| |
| case InterpreterOpcode::I32GtS: |
| BINOP_SIGNED(I32, I32, >); |
| break; |
| |
| case InterpreterOpcode::I32GeS: |
| BINOP_SIGNED(I32, I32, >=); |
| break; |
| |
| case InterpreterOpcode::I32GtU: |
| BINOP(I32, I32, >); |
| break; |
| |
| case InterpreterOpcode::I32GeU: |
| BINOP(I32, I32, >=); |
| break; |
| |
| case InterpreterOpcode::I32Clz: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_I32(value != 0 ? wabt_clz_u32(value) : 32); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Ctz: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_I32(value != 0 ? wabt_ctz_u32(value) : 32); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Popcnt: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_I32(wabt_popcount_u32(value)); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Eqz: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_I32(value == 0); |
| break; |
| } |
| |
| case InterpreterOpcode::I64Add: |
| BINOP(I64, I64, +); |
| break; |
| |
| case InterpreterOpcode::I64Sub: |
| BINOP(I64, I64, -); |
| break; |
| |
| case InterpreterOpcode::I64Mul: |
| BINOP(I64, I64, *); |
| break; |
| |
| case InterpreterOpcode::I64DivS: |
| BINOP_DIV_S(I64); |
| break; |
| |
| case InterpreterOpcode::I64DivU: |
| BINOP_DIV_REM_U(I64, /); |
| break; |
| |
| case InterpreterOpcode::I64RemS: |
| BINOP_REM_S(I64); |
| break; |
| |
| case InterpreterOpcode::I64RemU: |
| BINOP_DIV_REM_U(I64, %); |
| break; |
| |
| case InterpreterOpcode::I64And: |
| BINOP(I64, I64, &); |
| break; |
| |
| case InterpreterOpcode::I64Or: |
| BINOP(I64, I64, |); |
| break; |
| |
| case InterpreterOpcode::I64Xor: |
| BINOP(I64, I64, ^); |
| break; |
| |
| case InterpreterOpcode::I64Shl: |
| BINOP_SHIFT(I64, <<, UNSIGNED); |
| break; |
| |
| case InterpreterOpcode::I64ShrU: |
| BINOP_SHIFT(I64, >>, UNSIGNED); |
| break; |
| |
| case InterpreterOpcode::I64ShrS: |
| BINOP_SHIFT(I64, >>, SIGNED); |
| break; |
| |
| case InterpreterOpcode::I64Eq: |
| BINOP(I32, I64, ==); |
| break; |
| |
| case InterpreterOpcode::I64Ne: |
| BINOP(I32, I64, !=); |
| break; |
| |
| case InterpreterOpcode::I64LtS: |
| BINOP_SIGNED(I32, I64, <); |
| break; |
| |
| case InterpreterOpcode::I64LeS: |
| BINOP_SIGNED(I32, I64, <=); |
| break; |
| |
| case InterpreterOpcode::I64LtU: |
| BINOP(I32, I64, <); |
| break; |
| |
| case InterpreterOpcode::I64LeU: |
| BINOP(I32, I64, <=); |
| break; |
| |
| case InterpreterOpcode::I64GtS: |
| BINOP_SIGNED(I32, I64, >); |
| break; |
| |
| case InterpreterOpcode::I64GeS: |
| BINOP_SIGNED(I32, I64, >=); |
| break; |
| |
| case InterpreterOpcode::I64GtU: |
| BINOP(I32, I64, >); |
| break; |
| |
| case InterpreterOpcode::I64GeU: |
| BINOP(I32, I64, >=); |
| break; |
| |
| case InterpreterOpcode::I64Clz: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_I64(value != 0 ? wabt_clz_u64(value) : 64); |
| break; |
| } |
| |
| case InterpreterOpcode::I64Ctz: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_I64(value != 0 ? wabt_ctz_u64(value) : 64); |
| break; |
| } |
| |
| case InterpreterOpcode::I64Popcnt: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_I64(wabt_popcount_u64(value)); |
| break; |
| } |
| |
| case InterpreterOpcode::F32Add: |
| BINOP_FLOAT(F32, +); |
| break; |
| |
| case InterpreterOpcode::F32Sub: |
| BINOP_FLOAT(F32, -); |
| break; |
| |
| case InterpreterOpcode::F32Mul: |
| BINOP_FLOAT(F32, *); |
| break; |
| |
| case InterpreterOpcode::F32Div: |
| BINOP_FLOAT_DIV(F32); |
| break; |
| |
| case InterpreterOpcode::F32Min: |
| MINMAX_FLOAT(F32, MIN); |
| break; |
| |
| case InterpreterOpcode::F32Max: |
| MINMAX_FLOAT(F32, MAX); |
| break; |
| |
| case InterpreterOpcode::F32Abs: |
| TOP().f32_bits &= ~F32_SIGN_MASK; |
| break; |
| |
| case InterpreterOpcode::F32Neg: |
| TOP().f32_bits ^= F32_SIGN_MASK; |
| break; |
| |
| case InterpreterOpcode::F32Copysign: { |
| VALUE_TYPE_F32 rhs = POP_F32(); |
| VALUE_TYPE_F32 lhs = POP_F32(); |
| PUSH_F32((lhs & ~F32_SIGN_MASK) | (rhs & F32_SIGN_MASK)); |
| break; |
| } |
| |
| case InterpreterOpcode::F32Ceil: |
| UNOP_FLOAT(F32, ceilf); |
| break; |
| |
| case InterpreterOpcode::F32Floor: |
| UNOP_FLOAT(F32, floorf); |
| break; |
| |
| case InterpreterOpcode::F32Trunc: |
| UNOP_FLOAT(F32, truncf); |
| break; |
| |
| case InterpreterOpcode::F32Nearest: |
| UNOP_FLOAT(F32, nearbyintf); |
| break; |
| |
| case InterpreterOpcode::F32Sqrt: |
| UNOP_FLOAT(F32, sqrtf); |
| break; |
| |
| case InterpreterOpcode::F32Eq: |
| BINOP_FLOAT_COMPARE(F32, ==); |
| break; |
| |
| case InterpreterOpcode::F32Ne: |
| BINOP_FLOAT_COMPARE(F32, !=); |
| break; |
| |
| case InterpreterOpcode::F32Lt: |
| BINOP_FLOAT_COMPARE(F32, <); |
| break; |
| |
| case InterpreterOpcode::F32Le: |
| BINOP_FLOAT_COMPARE(F32, <=); |
| break; |
| |
| case InterpreterOpcode::F32Gt: |
| BINOP_FLOAT_COMPARE(F32, >); |
| break; |
| |
| case InterpreterOpcode::F32Ge: |
| BINOP_FLOAT_COMPARE(F32, >=); |
| break; |
| |
| case InterpreterOpcode::F64Add: |
| BINOP_FLOAT(F64, +); |
| break; |
| |
| case InterpreterOpcode::F64Sub: |
| BINOP_FLOAT(F64, -); |
| break; |
| |
| case InterpreterOpcode::F64Mul: |
| BINOP_FLOAT(F64, *); |
| break; |
| |
| case InterpreterOpcode::F64Div: |
| BINOP_FLOAT_DIV(F64); |
| break; |
| |
| case InterpreterOpcode::F64Min: |
| MINMAX_FLOAT(F64, MIN); |
| break; |
| |
| case InterpreterOpcode::F64Max: |
| MINMAX_FLOAT(F64, MAX); |
| break; |
| |
| case InterpreterOpcode::F64Abs: |
| TOP().f64_bits &= ~F64_SIGN_MASK; |
| break; |
| |
| case InterpreterOpcode::F64Neg: |
| TOP().f64_bits ^= F64_SIGN_MASK; |
| break; |
| |
| case InterpreterOpcode::F64Copysign: { |
| VALUE_TYPE_F64 rhs = POP_F64(); |
| VALUE_TYPE_F64 lhs = POP_F64(); |
| PUSH_F64((lhs & ~F64_SIGN_MASK) | (rhs & F64_SIGN_MASK)); |
| break; |
| } |
| |
| case InterpreterOpcode::F64Ceil: |
| UNOP_FLOAT(F64, ceil); |
| break; |
| |
| case InterpreterOpcode::F64Floor: |
| UNOP_FLOAT(F64, floor); |
| break; |
| |
| case InterpreterOpcode::F64Trunc: |
| UNOP_FLOAT(F64, trunc); |
| break; |
| |
| case InterpreterOpcode::F64Nearest: |
| UNOP_FLOAT(F64, nearbyint); |
| break; |
| |
| case InterpreterOpcode::F64Sqrt: |
| UNOP_FLOAT(F64, sqrt); |
| break; |
| |
| case InterpreterOpcode::F64Eq: |
| BINOP_FLOAT_COMPARE(F64, ==); |
| break; |
| |
| case InterpreterOpcode::F64Ne: |
| BINOP_FLOAT_COMPARE(F64, !=); |
| break; |
| |
| case InterpreterOpcode::F64Lt: |
| BINOP_FLOAT_COMPARE(F64, <); |
| break; |
| |
| case InterpreterOpcode::F64Le: |
| BINOP_FLOAT_COMPARE(F64, <=); |
| break; |
| |
| case InterpreterOpcode::F64Gt: |
| BINOP_FLOAT_COMPARE(F64, >); |
| break; |
| |
| case InterpreterOpcode::F64Ge: |
| BINOP_FLOAT_COMPARE(F64, >=); |
| break; |
| |
| case InterpreterOpcode::I32TruncSF32: { |
| VALUE_TYPE_F32 value = POP_F32(); |
| TRAP_IF(is_nan_f32(value), InvalidConversionToInteger); |
| TRAP_UNLESS(is_in_range_i32_trunc_s_f32(value), IntegerOverflow); |
| PUSH_I32(static_cast<int32_t>(BITCAST_TO_F32(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I32TruncSF64: { |
| VALUE_TYPE_F64 value = POP_F64(); |
| TRAP_IF(is_nan_f64(value), InvalidConversionToInteger); |
| TRAP_UNLESS(is_in_range_i32_trunc_s_f64(value), IntegerOverflow); |
| PUSH_I32(static_cast<int32_t>(BITCAST_TO_F64(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I32TruncUF32: { |
| VALUE_TYPE_F32 value = POP_F32(); |
| TRAP_IF(is_nan_f32(value), InvalidConversionToInteger); |
| TRAP_UNLESS(is_in_range_i32_trunc_u_f32(value), IntegerOverflow); |
| PUSH_I32(static_cast<uint32_t>(BITCAST_TO_F32(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I32TruncUF64: { |
| VALUE_TYPE_F64 value = POP_F64(); |
| TRAP_IF(is_nan_f64(value), InvalidConversionToInteger); |
| TRAP_UNLESS(is_in_range_i32_trunc_u_f64(value), IntegerOverflow); |
| PUSH_I32(static_cast<uint32_t>(BITCAST_TO_F64(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I32WrapI64: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_I32(static_cast<uint32_t>(value)); |
| break; |
| } |
| |
| case InterpreterOpcode::I64TruncSF32: { |
| VALUE_TYPE_F32 value = POP_F32(); |
| TRAP_IF(is_nan_f32(value), InvalidConversionToInteger); |
| TRAP_UNLESS(is_in_range_i64_trunc_s_f32(value), IntegerOverflow); |
| PUSH_I64(static_cast<int64_t>(BITCAST_TO_F32(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I64TruncSF64: { |
| VALUE_TYPE_F64 value = POP_F64(); |
| TRAP_IF(is_nan_f64(value), InvalidConversionToInteger); |
| TRAP_UNLESS(is_in_range_i64_trunc_s_f64(value), IntegerOverflow); |
| PUSH_I64(static_cast<int64_t>(BITCAST_TO_F64(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I64TruncUF32: { |
| VALUE_TYPE_F32 value = POP_F32(); |
| TRAP_IF(is_nan_f32(value), InvalidConversionToInteger); |
| TRAP_UNLESS(is_in_range_i64_trunc_u_f32(value), IntegerOverflow); |
| PUSH_I64(static_cast<uint64_t>(BITCAST_TO_F32(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I64TruncUF64: { |
| VALUE_TYPE_F64 value = POP_F64(); |
| TRAP_IF(is_nan_f64(value), InvalidConversionToInteger); |
| TRAP_UNLESS(is_in_range_i64_trunc_u_f64(value), IntegerOverflow); |
| PUSH_I64(static_cast<uint64_t>(BITCAST_TO_F64(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I64ExtendSI32: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_I64(static_cast<int64_t>(BITCAST_I32_TO_SIGNED(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::I64ExtendUI32: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_I64(static_cast<uint64_t>(value)); |
| break; |
| } |
| |
| case InterpreterOpcode::F32ConvertSI32: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_F32( |
| BITCAST_FROM_F32(static_cast<float>(BITCAST_I32_TO_SIGNED(value)))); |
| break; |
| } |
| |
| case InterpreterOpcode::F32ConvertUI32: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_F32(BITCAST_FROM_F32(static_cast<float>(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::F32ConvertSI64: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_F32( |
| BITCAST_FROM_F32(static_cast<float>(BITCAST_I64_TO_SIGNED(value)))); |
| break; |
| } |
| |
| case InterpreterOpcode::F32ConvertUI64: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_F32(BITCAST_FROM_F32(wabt_convert_uint64_to_float(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::F32DemoteF64: { |
| VALUE_TYPE_F64 value = POP_F64(); |
| if (WABT_LIKELY(is_in_range_f64_demote_f32(value))) { |
| PUSH_F32(BITCAST_FROM_F32(static_cast<float>(BITCAST_TO_F64(value)))); |
| } else if (is_in_range_f64_demote_f32_round_to_f32_max(value)) { |
| PUSH_F32(F32_MAX); |
| } else if (is_in_range_f64_demote_f32_round_to_neg_f32_max(value)) { |
| PUSH_F32(F32_NEG_MAX); |
| } else { |
| uint32_t sign = (value >> 32) & F32_SIGN_MASK; |
| uint32_t tag = 0; |
| if (IS_NAN_F64(value)) { |
| tag = F32_QUIET_NAN_BIT | |
| ((value >> (F64_SIG_BITS - F32_SIG_BITS)) & F32_SIG_MASK); |
| } |
| PUSH_F32(sign | F32_INF | tag); |
| } |
| break; |
| } |
| |
| case InterpreterOpcode::F32ReinterpretI32: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_F32(value); |
| break; |
| } |
| |
| case InterpreterOpcode::F64ConvertSI32: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_F64(BITCAST_FROM_F64( |
| static_cast<double>(BITCAST_I32_TO_SIGNED(value)))); |
| break; |
| } |
| |
| case InterpreterOpcode::F64ConvertUI32: { |
| VALUE_TYPE_I32 value = POP_I32(); |
| PUSH_F64(BITCAST_FROM_F64(static_cast<double>(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::F64ConvertSI64: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_F64(BITCAST_FROM_F64( |
| static_cast<double>(BITCAST_I64_TO_SIGNED(value)))); |
| break; |
| } |
| |
| case InterpreterOpcode::F64ConvertUI64: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_F64(BITCAST_FROM_F64(wabt_convert_uint64_to_double(value))); |
| break; |
| } |
| |
| case InterpreterOpcode::F64PromoteF32: { |
| VALUE_TYPE_F32 value = POP_F32(); |
| PUSH_F64(BITCAST_FROM_F64(static_cast<double>(BITCAST_TO_F32(value)))); |
| break; |
| } |
| |
| case InterpreterOpcode::F64ReinterpretI64: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_F64(value); |
| break; |
| } |
| |
| case InterpreterOpcode::I32ReinterpretF32: { |
| VALUE_TYPE_F32 value = POP_F32(); |
| PUSH_I32(value); |
| break; |
| } |
| |
| case InterpreterOpcode::I64ReinterpretF64: { |
| VALUE_TYPE_F64 value = POP_F64(); |
| PUSH_I64(value); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Rotr: |
| BINOP_ROT(I32, RIGHT); |
| break; |
| |
| case InterpreterOpcode::I32Rotl: |
| BINOP_ROT(I32, LEFT); |
| break; |
| |
| case InterpreterOpcode::I64Rotr: |
| BINOP_ROT(I64, RIGHT); |
| break; |
| |
| case InterpreterOpcode::I64Rotl: |
| BINOP_ROT(I64, LEFT); |
| break; |
| |
| case InterpreterOpcode::I64Eqz: { |
| VALUE_TYPE_I64 value = POP_I64(); |
| PUSH_I64(value == 0); |
| break; |
| } |
| |
| case InterpreterOpcode::Alloca: { |
| InterpreterValue* old_value_stack_top = thread->value_stack_top; |
| thread->value_stack_top += read_u32(&pc); |
| CHECK_STACK(); |
| memset(old_value_stack_top, 0, |
| (thread->value_stack_top - old_value_stack_top) * |
| sizeof(InterpreterValue)); |
| break; |
| } |
| |
| case InterpreterOpcode::BrUnless: { |
| uint32_t new_pc = read_u32(&pc); |
| if (!POP_I32()) |
| GOTO(new_pc); |
| break; |
| } |
| |
| case InterpreterOpcode::Drop: |
| (void)POP(); |
| break; |
| |
| case InterpreterOpcode::DropKeep: { |
| uint32_t drop_count = read_u32(&pc); |
| uint8_t keep_count = *pc++; |
| DROP_KEEP(drop_count, keep_count); |
| break; |
| } |
| |
| case InterpreterOpcode::Data: |
| /* shouldn't ever execute this */ |
| assert(0); |
| break; |
| |
| case InterpreterOpcode::Nop: |
| break; |
| |
| default: |
| assert(0); |
| break; |
| } |
| } |
| |
| exit_loop: |
| thread->pc = pc - istream; |
| return result; |
| } |
| |
| void trace_pc(InterpreterThread* thread, Stream* stream) { |
| const uint8_t* istream = thread->env->istream->data.data(); |
| const uint8_t* pc = &istream[thread->pc]; |
| size_t value_stack_depth = |
| thread->value_stack_top - thread->value_stack.data(); |
| size_t call_stack_depth = thread->call_stack_top - thread->call_stack.data(); |
| |
| stream->Writef("#%" PRIzd ". %4" PRIzd ": V:%-3" PRIzd "| ", call_stack_depth, |
| pc - thread->env->istream->data.data(), value_stack_depth); |
| |
| InterpreterOpcode opcode = static_cast<InterpreterOpcode>(*pc++); |
| switch (opcode) { |
| case InterpreterOpcode::Select: |
| stream->Writef("%s %u, %" PRIu64 ", %" PRIu64 "\n", |
| get_interpreter_opcode_name(opcode), PICK(3).i32, |
| PICK(2).i64, PICK(1).i64); |
| break; |
| |
| case InterpreterOpcode::Br: |
| stream->Writef("%s @%u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc)); |
| break; |
| |
| case InterpreterOpcode::BrIf: |
| stream->Writef("%s @%u, %u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc), TOP().i32); |
| break; |
| |
| case InterpreterOpcode::BrTable: { |
| uint32_t num_targets = read_u32_at(pc); |
| uint32_t table_offset = read_u32_at(pc + 4); |
| VALUE_TYPE_I32 key = TOP().i32; |
| stream->Writef("%s %u, $#%u, table:$%u\n", |
| get_interpreter_opcode_name(opcode), key, num_targets, |
| table_offset); |
| break; |
| } |
| |
| case InterpreterOpcode::Nop: |
| case InterpreterOpcode::Return: |
| case InterpreterOpcode::Unreachable: |
| case InterpreterOpcode::Drop: |
| stream->Writef("%s\n", get_interpreter_opcode_name(opcode)); |
| break; |
| |
| case InterpreterOpcode::CurrentMemory: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| memory_index); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Const: |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc)); |
| break; |
| |
| case InterpreterOpcode::I64Const: |
| stream->Writef("%s $%" PRIu64 "\n", get_interpreter_opcode_name(opcode), |
| read_u64_at(pc)); |
| break; |
| |
| case InterpreterOpcode::F32Const: |
| stream->Writef("%s $%g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u32_to_f32(read_u32_at(pc))); |
| break; |
| |
| case InterpreterOpcode::F64Const: |
| stream->Writef("%s $%g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u64_to_f64(read_u64_at(pc))); |
| break; |
| |
| case InterpreterOpcode::GetLocal: |
| case InterpreterOpcode::GetGlobal: |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc)); |
| break; |
| |
| case InterpreterOpcode::SetLocal: |
| case InterpreterOpcode::SetGlobal: |
| case InterpreterOpcode::TeeLocal: |
| stream->Writef("%s $%u, %u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc), TOP().i32); |
| break; |
| |
| case InterpreterOpcode::Call: |
| stream->Writef("%s @%u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc)); |
| break; |
| |
| case InterpreterOpcode::CallIndirect: |
| stream->Writef("%s $%u, %u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc), TOP().i32); |
| break; |
| |
| case InterpreterOpcode::CallHost: |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc)); |
| break; |
| |
| case InterpreterOpcode::I32Load8S: |
| case InterpreterOpcode::I32Load8U: |
| case InterpreterOpcode::I32Load16S: |
| case InterpreterOpcode::I32Load16U: |
| case InterpreterOpcode::I64Load8S: |
| case InterpreterOpcode::I64Load8U: |
| case InterpreterOpcode::I64Load16S: |
| case InterpreterOpcode::I64Load16U: |
| case InterpreterOpcode::I64Load32S: |
| case InterpreterOpcode::I64Load32U: |
| case InterpreterOpcode::I32Load: |
| case InterpreterOpcode::I64Load: |
| case InterpreterOpcode::F32Load: |
| case InterpreterOpcode::F64Load: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u:%u+$%u\n", get_interpreter_opcode_name(opcode), |
| memory_index, TOP().i32, read_u32_at(pc)); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Store8: |
| case InterpreterOpcode::I32Store16: |
| case InterpreterOpcode::I32Store: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u:%u+$%u, %u\n", get_interpreter_opcode_name(opcode), |
| memory_index, PICK(2).i32, read_u32_at(pc), PICK(1).i32); |
| break; |
| } |
| |
| case InterpreterOpcode::I64Store8: |
| case InterpreterOpcode::I64Store16: |
| case InterpreterOpcode::I64Store32: |
| case InterpreterOpcode::I64Store: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u:%u+$%u, %" PRIu64 "\n", |
| get_interpreter_opcode_name(opcode), memory_index, |
| PICK(2).i32, read_u32_at(pc), PICK(1).i64); |
| break; |
| } |
| |
| case InterpreterOpcode::F32Store: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u:%u+$%u, %g\n", get_interpreter_opcode_name(opcode), |
| memory_index, PICK(2).i32, read_u32_at(pc), |
| bitcast_u32_to_f32(PICK(1).f32_bits)); |
| break; |
| } |
| |
| case InterpreterOpcode::F64Store: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u:%u+$%u, %g\n", get_interpreter_opcode_name(opcode), |
| memory_index, PICK(2).i32, read_u32_at(pc), |
| bitcast_u64_to_f64(PICK(1).f64_bits)); |
| break; |
| } |
| |
| case InterpreterOpcode::GrowMemory: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u:%u\n", get_interpreter_opcode_name(opcode), |
| memory_index, TOP().i32); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Add: |
| case InterpreterOpcode::I32Sub: |
| case InterpreterOpcode::I32Mul: |
| case InterpreterOpcode::I32DivS: |
| case InterpreterOpcode::I32DivU: |
| case InterpreterOpcode::I32RemS: |
| case InterpreterOpcode::I32RemU: |
| case InterpreterOpcode::I32And: |
| case InterpreterOpcode::I32Or: |
| case InterpreterOpcode::I32Xor: |
| case InterpreterOpcode::I32Shl: |
| case InterpreterOpcode::I32ShrU: |
| case InterpreterOpcode::I32ShrS: |
| case InterpreterOpcode::I32Eq: |
| case InterpreterOpcode::I32Ne: |
| case InterpreterOpcode::I32LtS: |
| case InterpreterOpcode::I32LeS: |
| case InterpreterOpcode::I32LtU: |
| case InterpreterOpcode::I32LeU: |
| case InterpreterOpcode::I32GtS: |
| case InterpreterOpcode::I32GeS: |
| case InterpreterOpcode::I32GtU: |
| case InterpreterOpcode::I32GeU: |
| case InterpreterOpcode::I32Rotr: |
| case InterpreterOpcode::I32Rotl: |
| stream->Writef("%s %u, %u\n", get_interpreter_opcode_name(opcode), |
| PICK(2).i32, PICK(1).i32); |
| break; |
| |
| case InterpreterOpcode::I32Clz: |
| case InterpreterOpcode::I32Ctz: |
| case InterpreterOpcode::I32Popcnt: |
| case InterpreterOpcode::I32Eqz: |
| stream->Writef("%s %u\n", get_interpreter_opcode_name(opcode), TOP().i32); |
| break; |
| |
| case InterpreterOpcode::I64Add: |
| case InterpreterOpcode::I64Sub: |
| case InterpreterOpcode::I64Mul: |
| case InterpreterOpcode::I64DivS: |
| case InterpreterOpcode::I64DivU: |
| case InterpreterOpcode::I64RemS: |
| case InterpreterOpcode::I64RemU: |
| case InterpreterOpcode::I64And: |
| case InterpreterOpcode::I64Or: |
| case InterpreterOpcode::I64Xor: |
| case InterpreterOpcode::I64Shl: |
| case InterpreterOpcode::I64ShrU: |
| case InterpreterOpcode::I64ShrS: |
| case InterpreterOpcode::I64Eq: |
| case InterpreterOpcode::I64Ne: |
| case InterpreterOpcode::I64LtS: |
| case InterpreterOpcode::I64LeS: |
| case InterpreterOpcode::I64LtU: |
| case InterpreterOpcode::I64LeU: |
| case InterpreterOpcode::I64GtS: |
| case InterpreterOpcode::I64GeS: |
| case InterpreterOpcode::I64GtU: |
| case InterpreterOpcode::I64GeU: |
| case InterpreterOpcode::I64Rotr: |
| case InterpreterOpcode::I64Rotl: |
| stream->Writef("%s %" PRIu64 ", %" PRIu64 "\n", |
| get_interpreter_opcode_name(opcode), PICK(2).i64, |
| PICK(1).i64); |
| break; |
| |
| case InterpreterOpcode::I64Clz: |
| case InterpreterOpcode::I64Ctz: |
| case InterpreterOpcode::I64Popcnt: |
| case InterpreterOpcode::I64Eqz: |
| stream->Writef("%s %" PRIu64 "\n", get_interpreter_opcode_name(opcode), |
| TOP().i64); |
| break; |
| |
| case InterpreterOpcode::F32Add: |
| case InterpreterOpcode::F32Sub: |
| case InterpreterOpcode::F32Mul: |
| case InterpreterOpcode::F32Div: |
| case InterpreterOpcode::F32Min: |
| case InterpreterOpcode::F32Max: |
| case InterpreterOpcode::F32Copysign: |
| case InterpreterOpcode::F32Eq: |
| case InterpreterOpcode::F32Ne: |
| case InterpreterOpcode::F32Lt: |
| case InterpreterOpcode::F32Le: |
| case InterpreterOpcode::F32Gt: |
| case InterpreterOpcode::F32Ge: |
| stream->Writef("%s %g, %g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u32_to_f32(PICK(2).i32), |
| bitcast_u32_to_f32(PICK(1).i32)); |
| break; |
| |
| case InterpreterOpcode::F32Abs: |
| case InterpreterOpcode::F32Neg: |
| case InterpreterOpcode::F32Ceil: |
| case InterpreterOpcode::F32Floor: |
| case InterpreterOpcode::F32Trunc: |
| case InterpreterOpcode::F32Nearest: |
| case InterpreterOpcode::F32Sqrt: |
| stream->Writef("%s %g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u32_to_f32(TOP().i32)); |
| break; |
| |
| case InterpreterOpcode::F64Add: |
| case InterpreterOpcode::F64Sub: |
| case InterpreterOpcode::F64Mul: |
| case InterpreterOpcode::F64Div: |
| case InterpreterOpcode::F64Min: |
| case InterpreterOpcode::F64Max: |
| case InterpreterOpcode::F64Copysign: |
| case InterpreterOpcode::F64Eq: |
| case InterpreterOpcode::F64Ne: |
| case InterpreterOpcode::F64Lt: |
| case InterpreterOpcode::F64Le: |
| case InterpreterOpcode::F64Gt: |
| case InterpreterOpcode::F64Ge: |
| stream->Writef("%s %g, %g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u64_to_f64(PICK(2).i64), |
| bitcast_u64_to_f64(PICK(1).i64)); |
| break; |
| |
| case InterpreterOpcode::F64Abs: |
| case InterpreterOpcode::F64Neg: |
| case InterpreterOpcode::F64Ceil: |
| case InterpreterOpcode::F64Floor: |
| case InterpreterOpcode::F64Trunc: |
| case InterpreterOpcode::F64Nearest: |
| case InterpreterOpcode::F64Sqrt: |
| stream->Writef("%s %g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u64_to_f64(TOP().i64)); |
| break; |
| |
| case InterpreterOpcode::I32TruncSF32: |
| case InterpreterOpcode::I32TruncUF32: |
| case InterpreterOpcode::I64TruncSF32: |
| case InterpreterOpcode::I64TruncUF32: |
| case InterpreterOpcode::F64PromoteF32: |
| case InterpreterOpcode::I32ReinterpretF32: |
| stream->Writef("%s %g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u32_to_f32(TOP().i32)); |
| break; |
| |
| case InterpreterOpcode::I32TruncSF64: |
| case InterpreterOpcode::I32TruncUF64: |
| case InterpreterOpcode::I64TruncSF64: |
| case InterpreterOpcode::I64TruncUF64: |
| case InterpreterOpcode::F32DemoteF64: |
| case InterpreterOpcode::I64ReinterpretF64: |
| stream->Writef("%s %g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u64_to_f64(TOP().i64)); |
| break; |
| |
| case InterpreterOpcode::I32WrapI64: |
| case InterpreterOpcode::F32ConvertSI64: |
| case InterpreterOpcode::F32ConvertUI64: |
| case InterpreterOpcode::F64ConvertSI64: |
| case InterpreterOpcode::F64ConvertUI64: |
| case InterpreterOpcode::F64ReinterpretI64: |
| stream->Writef("%s %" PRIu64 "\n", get_interpreter_opcode_name(opcode), |
| TOP().i64); |
| break; |
| |
| case InterpreterOpcode::I64ExtendSI32: |
| case InterpreterOpcode::I64ExtendUI32: |
| case InterpreterOpcode::F32ConvertSI32: |
| case InterpreterOpcode::F32ConvertUI32: |
| case InterpreterOpcode::F32ReinterpretI32: |
| case InterpreterOpcode::F64ConvertSI32: |
| case InterpreterOpcode::F64ConvertUI32: |
| stream->Writef("%s %u\n", get_interpreter_opcode_name(opcode), TOP().i32); |
| break; |
| |
| case InterpreterOpcode::Alloca: |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc)); |
| break; |
| |
| case InterpreterOpcode::BrUnless: |
| stream->Writef("%s @%u, %u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc), TOP().i32); |
| break; |
| |
| case InterpreterOpcode::DropKeep: |
| stream->Writef("%s $%u $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32_at(pc), *(pc + 4)); |
| break; |
| |
| case InterpreterOpcode::Data: |
| /* shouldn't ever execute this */ |
| assert(0); |
| break; |
| |
| default: |
| assert(0); |
| break; |
| } |
| } |
| |
| void disassemble(InterpreterEnvironment* env, |
| Stream* stream, |
| uint32_t from, |
| uint32_t to) { |
| /* TODO(binji): mark function entries */ |
| /* TODO(binji): track value stack size */ |
| if (from >= env->istream->data.size()) |
| return; |
| to = std::min<uint32_t>(to, env->istream->data.size()); |
| const uint8_t* istream = env->istream->data.data(); |
| const uint8_t* pc = &istream[from]; |
| |
| while (static_cast<uint32_t>(pc - istream) < to) { |
| stream->Writef("%4" PRIzd "| ", pc - istream); |
| |
| InterpreterOpcode opcode = static_cast<InterpreterOpcode>(*pc++); |
| switch (opcode) { |
| case InterpreterOpcode::Select: |
| stream->Writef("%s %%[-3], %%[-2], %%[-1]\n", |
| get_interpreter_opcode_name(opcode)); |
| break; |
| |
| case InterpreterOpcode::Br: |
| stream->Writef("%s @%u\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::BrIf: |
| stream->Writef("%s @%u, %%[-1]\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::BrTable: { |
| uint32_t num_targets = read_u32(&pc); |
| uint32_t table_offset = read_u32(&pc); |
| stream->Writef("%s %%[-1], $#%u, table:$%u\n", |
| get_interpreter_opcode_name(opcode), num_targets, |
| table_offset); |
| break; |
| } |
| |
| case InterpreterOpcode::Nop: |
| case InterpreterOpcode::Return: |
| case InterpreterOpcode::Unreachable: |
| case InterpreterOpcode::Drop: |
| stream->Writef("%s\n", get_interpreter_opcode_name(opcode)); |
| break; |
| |
| case InterpreterOpcode::CurrentMemory: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| memory_index); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Const: |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::I64Const: |
| stream->Writef("%s $%" PRIu64 "\n", get_interpreter_opcode_name(opcode), |
| read_u64(&pc)); |
| break; |
| |
| case InterpreterOpcode::F32Const: |
| stream->Writef("%s $%g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u32_to_f32(read_u32(&pc))); |
| break; |
| |
| case InterpreterOpcode::F64Const: |
| stream->Writef("%s $%g\n", get_interpreter_opcode_name(opcode), |
| bitcast_u64_to_f64(read_u64(&pc))); |
| break; |
| |
| case InterpreterOpcode::GetLocal: |
| case InterpreterOpcode::GetGlobal: |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::SetLocal: |
| case InterpreterOpcode::SetGlobal: |
| case InterpreterOpcode::TeeLocal: |
| stream->Writef("%s $%u, %%[-1]\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::Call: |
| stream->Writef("%s @%u\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::CallIndirect: { |
| uint32_t table_index = read_u32(&pc); |
| stream->Writef("%s $%u:%u, %%[-1]\n", |
| get_interpreter_opcode_name(opcode), table_index, |
| read_u32(&pc)); |
| break; |
| } |
| |
| case InterpreterOpcode::CallHost: |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::I32Load8S: |
| case InterpreterOpcode::I32Load8U: |
| case InterpreterOpcode::I32Load16S: |
| case InterpreterOpcode::I32Load16U: |
| case InterpreterOpcode::I64Load8S: |
| case InterpreterOpcode::I64Load8U: |
| case InterpreterOpcode::I64Load16S: |
| case InterpreterOpcode::I64Load16U: |
| case InterpreterOpcode::I64Load32S: |
| case InterpreterOpcode::I64Load32U: |
| case InterpreterOpcode::I32Load: |
| case InterpreterOpcode::I64Load: |
| case InterpreterOpcode::F32Load: |
| case InterpreterOpcode::F64Load: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u:%%[-1]+$%u\n", |
| get_interpreter_opcode_name(opcode), memory_index, |
| read_u32(&pc)); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Store8: |
| case InterpreterOpcode::I32Store16: |
| case InterpreterOpcode::I32Store: |
| case InterpreterOpcode::I64Store8: |
| case InterpreterOpcode::I64Store16: |
| case InterpreterOpcode::I64Store32: |
| case InterpreterOpcode::I64Store: |
| case InterpreterOpcode::F32Store: |
| case InterpreterOpcode::F64Store: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s %%[-2]+$%u, $%u:%%[-1]\n", |
| get_interpreter_opcode_name(opcode), memory_index, |
| read_u32(&pc)); |
| break; |
| } |
| |
| case InterpreterOpcode::I32Add: |
| case InterpreterOpcode::I32Sub: |
| case InterpreterOpcode::I32Mul: |
| case InterpreterOpcode::I32DivS: |
| case InterpreterOpcode::I32DivU: |
| case InterpreterOpcode::I32RemS: |
| case InterpreterOpcode::I32RemU: |
| case InterpreterOpcode::I32And: |
| case InterpreterOpcode::I32Or: |
| case InterpreterOpcode::I32Xor: |
| case InterpreterOpcode::I32Shl: |
| case InterpreterOpcode::I32ShrU: |
| case InterpreterOpcode::I32ShrS: |
| case InterpreterOpcode::I32Eq: |
| case InterpreterOpcode::I32Ne: |
| case InterpreterOpcode::I32LtS: |
| case InterpreterOpcode::I32LeS: |
| case InterpreterOpcode::I32LtU: |
| case InterpreterOpcode::I32LeU: |
| case InterpreterOpcode::I32GtS: |
| case InterpreterOpcode::I32GeS: |
| case InterpreterOpcode::I32GtU: |
| case InterpreterOpcode::I32GeU: |
| case InterpreterOpcode::I32Rotr: |
| case InterpreterOpcode::I32Rotl: |
| case InterpreterOpcode::F32Add: |
| case InterpreterOpcode::F32Sub: |
| case InterpreterOpcode::F32Mul: |
| case InterpreterOpcode::F32Div: |
| case InterpreterOpcode::F32Min: |
| case InterpreterOpcode::F32Max: |
| case InterpreterOpcode::F32Copysign: |
| case InterpreterOpcode::F32Eq: |
| case InterpreterOpcode::F32Ne: |
| case InterpreterOpcode::F32Lt: |
| case InterpreterOpcode::F32Le: |
| case InterpreterOpcode::F32Gt: |
| case InterpreterOpcode::F32Ge: |
| case InterpreterOpcode::I64Add: |
| case InterpreterOpcode::I64Sub: |
| case InterpreterOpcode::I64Mul: |
| case InterpreterOpcode::I64DivS: |
| case InterpreterOpcode::I64DivU: |
| case InterpreterOpcode::I64RemS: |
| case InterpreterOpcode::I64RemU: |
| case InterpreterOpcode::I64And: |
| case InterpreterOpcode::I64Or: |
| case InterpreterOpcode::I64Xor: |
| case InterpreterOpcode::I64Shl: |
| case InterpreterOpcode::I64ShrU: |
| case InterpreterOpcode::I64ShrS: |
| case InterpreterOpcode::I64Eq: |
| case InterpreterOpcode::I64Ne: |
| case InterpreterOpcode::I64LtS: |
| case InterpreterOpcode::I64LeS: |
| case InterpreterOpcode::I64LtU: |
| case InterpreterOpcode::I64LeU: |
| case InterpreterOpcode::I64GtS: |
| case InterpreterOpcode::I64GeS: |
| case InterpreterOpcode::I64GtU: |
| case InterpreterOpcode::I64GeU: |
| case InterpreterOpcode::I64Rotr: |
| case InterpreterOpcode::I64Rotl: |
| case InterpreterOpcode::F64Add: |
| case InterpreterOpcode::F64Sub: |
| case InterpreterOpcode::F64Mul: |
| case InterpreterOpcode::F64Div: |
| case InterpreterOpcode::F64Min: |
| case InterpreterOpcode::F64Max: |
| case InterpreterOpcode::F64Copysign: |
| case InterpreterOpcode::F64Eq: |
| case InterpreterOpcode::F64Ne: |
| case InterpreterOpcode::F64Lt: |
| case InterpreterOpcode::F64Le: |
| case InterpreterOpcode::F64Gt: |
| case InterpreterOpcode::F64Ge: |
| stream->Writef("%s %%[-2], %%[-1]\n", |
| get_interpreter_opcode_name(opcode)); |
| break; |
| |
| case InterpreterOpcode::I32Clz: |
| case InterpreterOpcode::I32Ctz: |
| case InterpreterOpcode::I32Popcnt: |
| case InterpreterOpcode::I32Eqz: |
| case InterpreterOpcode::I64Clz: |
| case InterpreterOpcode::I64Ctz: |
| case InterpreterOpcode::I64Popcnt: |
| case InterpreterOpcode::I64Eqz: |
| case InterpreterOpcode::F32Abs: |
| case InterpreterOpcode::F32Neg: |
| case InterpreterOpcode::F32Ceil: |
| case InterpreterOpcode::F32Floor: |
| case InterpreterOpcode::F32Trunc: |
| case InterpreterOpcode::F32Nearest: |
| case InterpreterOpcode::F32Sqrt: |
| case InterpreterOpcode::F64Abs: |
| case InterpreterOpcode::F64Neg: |
| case InterpreterOpcode::F64Ceil: |
| case InterpreterOpcode::F64Floor: |
| case InterpreterOpcode::F64Trunc: |
| case InterpreterOpcode::F64Nearest: |
| case InterpreterOpcode::F64Sqrt: |
| case InterpreterOpcode::I32TruncSF32: |
| case InterpreterOpcode::I32TruncUF32: |
| case InterpreterOpcode::I64TruncSF32: |
| case InterpreterOpcode::I64TruncUF32: |
| case InterpreterOpcode::F64PromoteF32: |
| case InterpreterOpcode::I32ReinterpretF32: |
| case InterpreterOpcode::I32TruncSF64: |
| case InterpreterOpcode::I32TruncUF64: |
| case InterpreterOpcode::I64TruncSF64: |
| case InterpreterOpcode::I64TruncUF64: |
| case InterpreterOpcode::F32DemoteF64: |
| case InterpreterOpcode::I64ReinterpretF64: |
| case InterpreterOpcode::I32WrapI64: |
| case InterpreterOpcode::F32ConvertSI64: |
| case InterpreterOpcode::F32ConvertUI64: |
| case InterpreterOpcode::F64ConvertSI64: |
| case InterpreterOpcode::F64ConvertUI64: |
| case InterpreterOpcode::F64ReinterpretI64: |
| case InterpreterOpcode::I64ExtendSI32: |
| case InterpreterOpcode::I64ExtendUI32: |
| case InterpreterOpcode::F32ConvertSI32: |
| case InterpreterOpcode::F32ConvertUI32: |
| case InterpreterOpcode::F32ReinterpretI32: |
| case InterpreterOpcode::F64ConvertSI32: |
| case InterpreterOpcode::F64ConvertUI32: |
| stream->Writef("%s %%[-1]\n", get_interpreter_opcode_name(opcode)); |
| break; |
| |
| case InterpreterOpcode::GrowMemory: { |
| uint32_t memory_index = read_u32(&pc); |
| stream->Writef("%s $%u:%%[-1]\n", get_interpreter_opcode_name(opcode), |
| memory_index); |
| break; |
| } |
| |
| case InterpreterOpcode::Alloca: |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::BrUnless: |
| stream->Writef("%s @%u, %%[-1]\n", get_interpreter_opcode_name(opcode), |
| read_u32(&pc)); |
| break; |
| |
| case InterpreterOpcode::DropKeep: { |
| uint32_t drop = read_u32(&pc); |
| uint32_t keep = *pc++; |
| stream->Writef("%s $%u $%u\n", get_interpreter_opcode_name(opcode), |
| drop, keep); |
| break; |
| } |
| |
| case InterpreterOpcode::Data: { |
| uint32_t num_bytes = read_u32(&pc); |
| stream->Writef("%s $%u\n", get_interpreter_opcode_name(opcode), |
| num_bytes); |
| /* for now, the only reason this is emitted is for br_table, so display |
| * it as a list of table entries */ |
| if (num_bytes % WABT_TABLE_ENTRY_SIZE == 0) { |
| uint32_t num_entries = num_bytes / WABT_TABLE_ENTRY_SIZE; |
| for (uint32_t i = 0; i < num_entries; ++i) { |
| stream->Writef("%4" PRIzd "| ", pc - istream); |
| uint32_t offset; |
| uint32_t drop; |
| uint8_t keep; |
| read_table_entry_at(pc, &offset, &drop, &keep); |
| stream->Writef(" entry %d: offset: %u drop: %u keep: %u\n", i, |
| offset, drop, keep); |
| pc += WABT_TABLE_ENTRY_SIZE; |
| } |
| } else { |
| /* just skip those data bytes */ |
| pc += num_bytes; |
| } |
| |
| break; |
| } |
| |
| default: |
| assert(0); |
| break; |
| } |
| } |
| } |
| |
| void disassemble_module(InterpreterEnvironment* env, |
| Stream* stream, |
| InterpreterModule* module) { |
| assert(!module->is_host); |
| disassemble(env, stream, module->as_defined()->istream_start, |
| module->as_defined()->istream_end); |
| } |
| |
| } // namespace wabt |