Commit b8f88601 authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[wasm] [interpreter] Implement indirect function calls

This CL adds support for indirect function calls to the interpreter. It
can indirectly call other wasm function in the same instance, which are
then executed in the interpreter, or call imported functions.

Implementing this required some refactoring:
- The wasm interpreter now unwraps import wrappers on demand, instead
  of unwrapping all of them on instantiation and storing a vector of
  handles. This also avoids the DeferredHandleScope completely, instead
  we just store two global handles in the code map.
- The interpreter gets the code table, function tables and signature
  tables directly from the attached wasm instance object. This ensures
  that the interpreter sees all updates to tables that might have been
  performed by external code.
- There is now common functionality for calling a code object. This is
  used for direct calls to imported functions and for all indirect
  calls. As these code objects can also be wasm functions which should
  be executed in the interpreter itself, I introduce a struct to hold
  the outcome of calling the code object, or a pointer to
  InterpreterCode to be called in the interpreter.

R=ahaas@chromium.org
BUG=v8:5822

Change-Id: I20fb2ea007e79e5fcff9afb4b1ca31739ebcb83f
Reviewed-on: https://chromium-review.googlesource.com/458417
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44059}
parent 98fcd3e4
...@@ -24,39 +24,16 @@ using namespace v8::internal::wasm; ...@@ -24,39 +24,16 @@ using namespace v8::internal::wasm;
namespace { namespace {
// Unwrap a wasm to js wrapper, return the callable heap object.
// Only call this method for proper wrappers that do not throw a type error.
HeapObject* UnwrapJSWrapper(Code* js_wrapper) {
int mask = RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT);
for (RelocIterator it(js_wrapper, mask);; it.next()) {
DCHECK(!it.done());
HeapObject* obj = it.rinfo()->target_object();
if (!obj->IsCallable()) continue;
#ifdef DEBUG
// There should only be this reference to a callable object.
for (it.next(); !it.done(); it.next()) {
HeapObject* other = it.rinfo()->target_object();
DCHECK(!other->IsCallable());
}
#endif
return obj;
}
UNREACHABLE();
return nullptr;
}
// Forward declaration. // Forward declaration.
class InterpreterHandle; class InterpreterHandle;
InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info); InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info);
class InterpreterHandle { class InterpreterHandle {
AccountingAllocator allocator_;
WasmInstance instance_; WasmInstance instance_;
WasmInterpreter interpreter_; WasmInterpreter interpreter_;
Isolate* isolate_; Isolate* isolate_;
StepAction next_step_action_ = StepNone; StepAction next_step_action_ = StepNone;
int last_step_stack_depth_ = 0; int last_step_stack_depth_ = 0;
DeferredHandles* import_handles_ = nullptr;
std::unordered_map<Address, uint32_t> activations_; std::unordered_map<Address, uint32_t> activations_;
uint32_t StartActivation(Address frame_pointer) { uint32_t StartActivation(Address frame_pointer) {
...@@ -93,10 +70,20 @@ class InterpreterHandle { ...@@ -93,10 +70,20 @@ class InterpreterHandle {
// WasmInterpreter has to be allocated in place, since it is not movable. // WasmInterpreter has to be allocated in place, since it is not movable.
InterpreterHandle(Isolate* isolate, WasmDebugInfo* debug_info) InterpreterHandle(Isolate* isolate, WasmDebugInfo* debug_info)
: instance_(debug_info->wasm_instance()->compiled_module()->module()), : instance_(debug_info->wasm_instance()->compiled_module()->module()),
interpreter_(GetBytesEnv(&instance_, debug_info), &allocator_), interpreter_(isolate, GetBytesEnv(&instance_, debug_info)),
isolate_(isolate) { isolate_(isolate) {
if (debug_info->wasm_instance()->has_memory_buffer()) { DisallowHeapAllocation no_gc;
JSArrayBuffer* mem_buffer = debug_info->wasm_instance()->memory_buffer();
WasmInstanceObject* instance = debug_info->wasm_instance();
// Store a global handle to the wasm instance in the interpreter.
interpreter_.SetInstanceObject(instance);
// Set memory start pointer and size.
// TODO(wasm): Update this on grow_memory in both directions (compiled ->
// interpreter, interpreter -> compiles).
if (instance->has_memory_buffer()) {
JSArrayBuffer* mem_buffer = instance->memory_buffer();
instance_.mem_start = instance_.mem_start =
reinterpret_cast<byte*>(mem_buffer->backing_store()); reinterpret_cast<byte*>(mem_buffer->backing_store());
CHECK(mem_buffer->byte_length()->ToUint32(&instance_.mem_size)); CHECK(mem_buffer->byte_length()->ToUint32(&instance_.mem_size));
...@@ -105,37 +92,16 @@ class InterpreterHandle { ...@@ -105,37 +92,16 @@ class InterpreterHandle {
instance_.mem_start = nullptr; instance_.mem_start = nullptr;
instance_.mem_size = 0; instance_.mem_size = 0;
} }
if (debug_info->wasm_instance()->has_globals_buffer()) {
instance_.globals_start = reinterpret_cast<byte*>( // Set pointer to globals storage.
debug_info->wasm_instance()->globals_buffer()->backing_store()); if (instance->has_globals_buffer()) {
} instance_.globals_start =
if (instance_.module->num_imported_functions > 0) { reinterpret_cast<byte*>(instance->globals_buffer()->backing_store());
int num_imported_functions =
static_cast<int>(instance_.module->num_imported_functions);
Handle<WasmCompiledModule> compiled_module(
debug_info->wasm_instance()->compiled_module(), isolate);
Handle<FixedArray> code_table = compiled_module->code_table();
DeferredHandleScope deferred_scope(isolate_);
instance_.context =
handle(compiled_module->ptr_to_native_context(), isolate_);
for (int i = 0; i < num_imported_functions; ++i) {
Code* code = Code::cast(code_table->get(i));
Handle<HeapObject> called_obj;
if (code->kind() == Code::WASM_FUNCTION) {
called_obj = handle(code, isolate_);
} else if (IsJSCompatibleSignature(
instance_.module->functions[i].sig)) {
called_obj = handle(UnwrapJSWrapper(code), isolate_);
}
interpreter_.AddImportedFunction(called_obj);
}
import_handles_ = deferred_scope.Detach();
} }
} }
~InterpreterHandle() { ~InterpreterHandle() {
DCHECK_EQ(0, activations_.size()); DCHECK_EQ(0, activations_.size());
delete import_handles_; // might be nullptr, which is ok.
} }
static ModuleBytesEnv GetBytesEnv(WasmInstance* instance, static ModuleBytesEnv GetBytesEnv(WasmInstance* instance,
......
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
#include "src/wasm/wasm-interpreter.h" #include "src/wasm/wasm-interpreter.h"
#include "src/assembler-inl.h"
#include "src/conversions.h" #include "src/conversions.h"
#include "src/identity-map.h"
#include "src/objects-inl.h" #include "src/objects-inl.h"
#include "src/utils.h" #include "src/utils.h"
#include "src/wasm/decoder.h" #include "src/wasm/decoder.h"
...@@ -15,6 +17,7 @@ ...@@ -15,6 +17,7 @@
#include "src/wasm/wasm-external-refs.h" #include "src/wasm/wasm-external-refs.h"
#include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module.h" #include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects.h"
#include "src/zone/accounting-allocator.h" #include "src/zone/accounting-allocator.h"
#include "src/zone/zone-containers.h" #include "src/zone/zone-containers.h"
...@@ -170,7 +173,9 @@ namespace wasm { ...@@ -170,7 +173,9 @@ namespace wasm {
V(F32Sqrt, float) \ V(F32Sqrt, float) \
V(F64Sqrt, double) V(F64Sqrt, double)
static inline int32_t ExecuteI32DivS(int32_t a, int32_t b, TrapReason* trap) { namespace {
inline int32_t ExecuteI32DivS(int32_t a, int32_t b, TrapReason* trap) {
if (b == 0) { if (b == 0) {
*trap = kTrapDivByZero; *trap = kTrapDivByZero;
return 0; return 0;
...@@ -182,8 +187,7 @@ static inline int32_t ExecuteI32DivS(int32_t a, int32_t b, TrapReason* trap) { ...@@ -182,8 +187,7 @@ static inline int32_t ExecuteI32DivS(int32_t a, int32_t b, TrapReason* trap) {
return a / b; return a / b;
} }
static inline uint32_t ExecuteI32DivU(uint32_t a, uint32_t b, inline uint32_t ExecuteI32DivU(uint32_t a, uint32_t b, TrapReason* trap) {
TrapReason* trap) {
if (b == 0) { if (b == 0) {
*trap = kTrapDivByZero; *trap = kTrapDivByZero;
return 0; return 0;
...@@ -191,7 +195,7 @@ static inline uint32_t ExecuteI32DivU(uint32_t a, uint32_t b, ...@@ -191,7 +195,7 @@ static inline uint32_t ExecuteI32DivU(uint32_t a, uint32_t b,
return a / b; return a / b;
} }
static inline int32_t ExecuteI32RemS(int32_t a, int32_t b, TrapReason* trap) { inline int32_t ExecuteI32RemS(int32_t a, int32_t b, TrapReason* trap) {
if (b == 0) { if (b == 0) {
*trap = kTrapRemByZero; *trap = kTrapRemByZero;
return 0; return 0;
...@@ -200,8 +204,7 @@ static inline int32_t ExecuteI32RemS(int32_t a, int32_t b, TrapReason* trap) { ...@@ -200,8 +204,7 @@ static inline int32_t ExecuteI32RemS(int32_t a, int32_t b, TrapReason* trap) {
return a % b; return a % b;
} }
static inline uint32_t ExecuteI32RemU(uint32_t a, uint32_t b, inline uint32_t ExecuteI32RemU(uint32_t a, uint32_t b, TrapReason* trap) {
TrapReason* trap) {
if (b == 0) { if (b == 0) {
*trap = kTrapRemByZero; *trap = kTrapRemByZero;
return 0; return 0;
...@@ -209,20 +212,19 @@ static inline uint32_t ExecuteI32RemU(uint32_t a, uint32_t b, ...@@ -209,20 +212,19 @@ static inline uint32_t ExecuteI32RemU(uint32_t a, uint32_t b,
return a % b; return a % b;
} }
static inline uint32_t ExecuteI32Shl(uint32_t a, uint32_t b, TrapReason* trap) { inline uint32_t ExecuteI32Shl(uint32_t a, uint32_t b, TrapReason* trap) {
return a << (b & 0x1f); return a << (b & 0x1f);
} }
static inline uint32_t ExecuteI32ShrU(uint32_t a, uint32_t b, inline uint32_t ExecuteI32ShrU(uint32_t a, uint32_t b, TrapReason* trap) {
TrapReason* trap) {
return a >> (b & 0x1f); return a >> (b & 0x1f);
} }
static inline int32_t ExecuteI32ShrS(int32_t a, int32_t b, TrapReason* trap) { inline int32_t ExecuteI32ShrS(int32_t a, int32_t b, TrapReason* trap) {
return a >> (b & 0x1f); return a >> (b & 0x1f);
} }
static inline int64_t ExecuteI64DivS(int64_t a, int64_t b, TrapReason* trap) { inline int64_t ExecuteI64DivS(int64_t a, int64_t b, TrapReason* trap) {
if (b == 0) { if (b == 0) {
*trap = kTrapDivByZero; *trap = kTrapDivByZero;
return 0; return 0;
...@@ -234,8 +236,7 @@ static inline int64_t ExecuteI64DivS(int64_t a, int64_t b, TrapReason* trap) { ...@@ -234,8 +236,7 @@ static inline int64_t ExecuteI64DivS(int64_t a, int64_t b, TrapReason* trap) {
return a / b; return a / b;
} }
static inline uint64_t ExecuteI64DivU(uint64_t a, uint64_t b, inline uint64_t ExecuteI64DivU(uint64_t a, uint64_t b, TrapReason* trap) {
TrapReason* trap) {
if (b == 0) { if (b == 0) {
*trap = kTrapDivByZero; *trap = kTrapDivByZero;
return 0; return 0;
...@@ -243,7 +244,7 @@ static inline uint64_t ExecuteI64DivU(uint64_t a, uint64_t b, ...@@ -243,7 +244,7 @@ static inline uint64_t ExecuteI64DivU(uint64_t a, uint64_t b,
return a / b; return a / b;
} }
static inline int64_t ExecuteI64RemS(int64_t a, int64_t b, TrapReason* trap) { inline int64_t ExecuteI64RemS(int64_t a, int64_t b, TrapReason* trap) {
if (b == 0) { if (b == 0) {
*trap = kTrapRemByZero; *trap = kTrapRemByZero;
return 0; return 0;
...@@ -252,8 +253,7 @@ static inline int64_t ExecuteI64RemS(int64_t a, int64_t b, TrapReason* trap) { ...@@ -252,8 +253,7 @@ static inline int64_t ExecuteI64RemS(int64_t a, int64_t b, TrapReason* trap) {
return a % b; return a % b;
} }
static inline uint64_t ExecuteI64RemU(uint64_t a, uint64_t b, inline uint64_t ExecuteI64RemU(uint64_t a, uint64_t b, TrapReason* trap) {
TrapReason* trap) {
if (b == 0) { if (b == 0) {
*trap = kTrapRemByZero; *trap = kTrapRemByZero;
return 0; return 0;
...@@ -261,65 +261,63 @@ static inline uint64_t ExecuteI64RemU(uint64_t a, uint64_t b, ...@@ -261,65 +261,63 @@ static inline uint64_t ExecuteI64RemU(uint64_t a, uint64_t b,
return a % b; return a % b;
} }
static inline uint64_t ExecuteI64Shl(uint64_t a, uint64_t b, TrapReason* trap) { inline uint64_t ExecuteI64Shl(uint64_t a, uint64_t b, TrapReason* trap) {
return a << (b & 0x3f); return a << (b & 0x3f);
} }
static inline uint64_t ExecuteI64ShrU(uint64_t a, uint64_t b, inline uint64_t ExecuteI64ShrU(uint64_t a, uint64_t b, TrapReason* trap) {
TrapReason* trap) {
return a >> (b & 0x3f); return a >> (b & 0x3f);
} }
static inline int64_t ExecuteI64ShrS(int64_t a, int64_t b, TrapReason* trap) { inline int64_t ExecuteI64ShrS(int64_t a, int64_t b, TrapReason* trap) {
return a >> (b & 0x3f); return a >> (b & 0x3f);
} }
static inline uint32_t ExecuteI32Ror(uint32_t a, uint32_t b, TrapReason* trap) { inline uint32_t ExecuteI32Ror(uint32_t a, uint32_t b, TrapReason* trap) {
uint32_t shift = (b & 0x1f); uint32_t shift = (b & 0x1f);
return (a >> shift) | (a << (32 - shift)); return (a >> shift) | (a << (32 - shift));
} }
static inline uint32_t ExecuteI32Rol(uint32_t a, uint32_t b, TrapReason* trap) { inline uint32_t ExecuteI32Rol(uint32_t a, uint32_t b, TrapReason* trap) {
uint32_t shift = (b & 0x1f); uint32_t shift = (b & 0x1f);
return (a << shift) | (a >> (32 - shift)); return (a << shift) | (a >> (32 - shift));
} }
static inline uint64_t ExecuteI64Ror(uint64_t a, uint64_t b, TrapReason* trap) { inline uint64_t ExecuteI64Ror(uint64_t a, uint64_t b, TrapReason* trap) {
uint32_t shift = (b & 0x3f); uint32_t shift = (b & 0x3f);
return (a >> shift) | (a << (64 - shift)); return (a >> shift) | (a << (64 - shift));
} }
static inline uint64_t ExecuteI64Rol(uint64_t a, uint64_t b, TrapReason* trap) { inline uint64_t ExecuteI64Rol(uint64_t a, uint64_t b, TrapReason* trap) {
uint32_t shift = (b & 0x3f); uint32_t shift = (b & 0x3f);
return (a << shift) | (a >> (64 - shift)); return (a << shift) | (a >> (64 - shift));
} }
static inline float ExecuteF32Min(float a, float b, TrapReason* trap) { inline float ExecuteF32Min(float a, float b, TrapReason* trap) {
return JSMin(a, b); return JSMin(a, b);
} }
static inline float ExecuteF32Max(float a, float b, TrapReason* trap) { inline float ExecuteF32Max(float a, float b, TrapReason* trap) {
return JSMax(a, b); return JSMax(a, b);
} }
static inline float ExecuteF32CopySign(float a, float b, TrapReason* trap) { inline float ExecuteF32CopySign(float a, float b, TrapReason* trap) {
return copysignf(a, b); return copysignf(a, b);
} }
static inline double ExecuteF64Min(double a, double b, TrapReason* trap) { inline double ExecuteF64Min(double a, double b, TrapReason* trap) {
return JSMin(a, b); return JSMin(a, b);
} }
static inline double ExecuteF64Max(double a, double b, TrapReason* trap) { inline double ExecuteF64Max(double a, double b, TrapReason* trap) {
return JSMax(a, b); return JSMax(a, b);
} }
static inline double ExecuteF64CopySign(double a, double b, TrapReason* trap) { inline double ExecuteF64CopySign(double a, double b, TrapReason* trap) {
return copysign(a, b); return copysign(a, b);
} }
static inline int32_t ExecuteI32AsmjsDivS(int32_t a, int32_t b, inline int32_t ExecuteI32AsmjsDivS(int32_t a, int32_t b, TrapReason* trap) {
TrapReason* trap) {
if (b == 0) return 0; if (b == 0) return 0;
if (b == -1 && a == std::numeric_limits<int32_t>::min()) { if (b == -1 && a == std::numeric_limits<int32_t>::min()) {
return std::numeric_limits<int32_t>::min(); return std::numeric_limits<int32_t>::min();
...@@ -327,131 +325,114 @@ static inline int32_t ExecuteI32AsmjsDivS(int32_t a, int32_t b, ...@@ -327,131 +325,114 @@ static inline int32_t ExecuteI32AsmjsDivS(int32_t a, int32_t b,
return a / b; return a / b;
} }
static inline uint32_t ExecuteI32AsmjsDivU(uint32_t a, uint32_t b, inline uint32_t ExecuteI32AsmjsDivU(uint32_t a, uint32_t b, TrapReason* trap) {
TrapReason* trap) {
if (b == 0) return 0; if (b == 0) return 0;
return a / b; return a / b;
} }
static inline int32_t ExecuteI32AsmjsRemS(int32_t a, int32_t b, inline int32_t ExecuteI32AsmjsRemS(int32_t a, int32_t b, TrapReason* trap) {
TrapReason* trap) {
if (b == 0) return 0; if (b == 0) return 0;
if (b == -1) return 0; if (b == -1) return 0;
return a % b; return a % b;
} }
static inline uint32_t ExecuteI32AsmjsRemU(uint32_t a, uint32_t b, inline uint32_t ExecuteI32AsmjsRemU(uint32_t a, uint32_t b, TrapReason* trap) {
TrapReason* trap) {
if (b == 0) return 0; if (b == 0) return 0;
return a % b; return a % b;
} }
static inline int32_t ExecuteI32AsmjsSConvertF32(float a, TrapReason* trap) { inline int32_t ExecuteI32AsmjsSConvertF32(float a, TrapReason* trap) {
return DoubleToInt32(a); return DoubleToInt32(a);
} }
static inline uint32_t ExecuteI32AsmjsUConvertF32(float a, TrapReason* trap) { inline uint32_t ExecuteI32AsmjsUConvertF32(float a, TrapReason* trap) {
return DoubleToUint32(a); return DoubleToUint32(a);
} }
static inline int32_t ExecuteI32AsmjsSConvertF64(double a, TrapReason* trap) { inline int32_t ExecuteI32AsmjsSConvertF64(double a, TrapReason* trap) {
return DoubleToInt32(a); return DoubleToInt32(a);
} }
static inline uint32_t ExecuteI32AsmjsUConvertF64(double a, TrapReason* trap) { inline uint32_t ExecuteI32AsmjsUConvertF64(double a, TrapReason* trap) {
return DoubleToUint32(a); return DoubleToUint32(a);
} }
static int32_t ExecuteI32Clz(uint32_t val, TrapReason* trap) { int32_t ExecuteI32Clz(uint32_t val, TrapReason* trap) {
return base::bits::CountLeadingZeros32(val); return base::bits::CountLeadingZeros32(val);
} }
static uint32_t ExecuteI32Ctz(uint32_t val, TrapReason* trap) { uint32_t ExecuteI32Ctz(uint32_t val, TrapReason* trap) {
return base::bits::CountTrailingZeros32(val); return base::bits::CountTrailingZeros32(val);
} }
static uint32_t ExecuteI32Popcnt(uint32_t val, TrapReason* trap) { uint32_t ExecuteI32Popcnt(uint32_t val, TrapReason* trap) {
return word32_popcnt_wrapper(&val); return word32_popcnt_wrapper(&val);
} }
static inline uint32_t ExecuteI32Eqz(uint32_t val, TrapReason* trap) { inline uint32_t ExecuteI32Eqz(uint32_t val, TrapReason* trap) {
return val == 0 ? 1 : 0; return val == 0 ? 1 : 0;
} }
static int64_t ExecuteI64Clz(uint64_t val, TrapReason* trap) { int64_t ExecuteI64Clz(uint64_t val, TrapReason* trap) {
return base::bits::CountLeadingZeros64(val); return base::bits::CountLeadingZeros64(val);
} }
static inline uint64_t ExecuteI64Ctz(uint64_t val, TrapReason* trap) { inline uint64_t ExecuteI64Ctz(uint64_t val, TrapReason* trap) {
return base::bits::CountTrailingZeros64(val); return base::bits::CountTrailingZeros64(val);
} }
static inline int64_t ExecuteI64Popcnt(uint64_t val, TrapReason* trap) { inline int64_t ExecuteI64Popcnt(uint64_t val, TrapReason* trap) {
return word64_popcnt_wrapper(&val); return word64_popcnt_wrapper(&val);
} }
static inline int32_t ExecuteI64Eqz(uint64_t val, TrapReason* trap) { inline int32_t ExecuteI64Eqz(uint64_t val, TrapReason* trap) {
return val == 0 ? 1 : 0; return val == 0 ? 1 : 0;
} }
static inline float ExecuteF32Abs(float a, TrapReason* trap) { inline float ExecuteF32Abs(float a, TrapReason* trap) {
return bit_cast<float>(bit_cast<uint32_t>(a) & 0x7fffffff); return bit_cast<float>(bit_cast<uint32_t>(a) & 0x7fffffff);
} }
static inline float ExecuteF32Neg(float a, TrapReason* trap) { inline float ExecuteF32Neg(float a, TrapReason* trap) {
return bit_cast<float>(bit_cast<uint32_t>(a) ^ 0x80000000); return bit_cast<float>(bit_cast<uint32_t>(a) ^ 0x80000000);
} }
static inline float ExecuteF32Ceil(float a, TrapReason* trap) { inline float ExecuteF32Ceil(float a, TrapReason* trap) { return ceilf(a); }
return ceilf(a);
}
static inline float ExecuteF32Floor(float a, TrapReason* trap) { inline float ExecuteF32Floor(float a, TrapReason* trap) { return floorf(a); }
return floorf(a);
}
static inline float ExecuteF32Trunc(float a, TrapReason* trap) { inline float ExecuteF32Trunc(float a, TrapReason* trap) { return truncf(a); }
return truncf(a);
}
static inline float ExecuteF32NearestInt(float a, TrapReason* trap) { inline float ExecuteF32NearestInt(float a, TrapReason* trap) {
return nearbyintf(a); return nearbyintf(a);
} }
static inline float ExecuteF32Sqrt(float a, TrapReason* trap) { inline float ExecuteF32Sqrt(float a, TrapReason* trap) {
float result = sqrtf(a); float result = sqrtf(a);
return result; return result;
} }
static inline double ExecuteF64Abs(double a, TrapReason* trap) { inline double ExecuteF64Abs(double a, TrapReason* trap) {
return bit_cast<double>(bit_cast<uint64_t>(a) & 0x7fffffffffffffff); return bit_cast<double>(bit_cast<uint64_t>(a) & 0x7fffffffffffffff);
} }
static inline double ExecuteF64Neg(double a, TrapReason* trap) { inline double ExecuteF64Neg(double a, TrapReason* trap) {
return bit_cast<double>(bit_cast<uint64_t>(a) ^ 0x8000000000000000); return bit_cast<double>(bit_cast<uint64_t>(a) ^ 0x8000000000000000);
} }
static inline double ExecuteF64Ceil(double a, TrapReason* trap) { inline double ExecuteF64Ceil(double a, TrapReason* trap) { return ceil(a); }
return ceil(a);
}
static inline double ExecuteF64Floor(double a, TrapReason* trap) { inline double ExecuteF64Floor(double a, TrapReason* trap) { return floor(a); }
return floor(a);
}
static inline double ExecuteF64Trunc(double a, TrapReason* trap) { inline double ExecuteF64Trunc(double a, TrapReason* trap) { return trunc(a); }
return trunc(a);
}
static inline double ExecuteF64NearestInt(double a, TrapReason* trap) { inline double ExecuteF64NearestInt(double a, TrapReason* trap) {
return nearbyint(a); return nearbyint(a);
} }
static inline double ExecuteF64Sqrt(double a, TrapReason* trap) { inline double ExecuteF64Sqrt(double a, TrapReason* trap) { return sqrt(a); }
return sqrt(a);
}
static int32_t ExecuteI32SConvertF32(float a, TrapReason* trap) { int32_t ExecuteI32SConvertF32(float a, TrapReason* trap) {
// The upper bound is (INT32_MAX + 1), which is the lowest float-representable // The upper bound is (INT32_MAX + 1), which is the lowest float-representable
// number above INT32_MAX which cannot be represented as int32. // number above INT32_MAX which cannot be represented as int32.
float upper_bound = 2147483648.0f; float upper_bound = 2147483648.0f;
...@@ -466,7 +447,7 @@ static int32_t ExecuteI32SConvertF32(float a, TrapReason* trap) { ...@@ -466,7 +447,7 @@ static int32_t ExecuteI32SConvertF32(float a, TrapReason* trap) {
return 0; return 0;
} }
static int32_t ExecuteI32SConvertF64(double a, TrapReason* trap) { int32_t ExecuteI32SConvertF64(double a, TrapReason* trap) {
// The upper bound is (INT32_MAX + 1), which is the lowest double- // The upper bound is (INT32_MAX + 1), which is the lowest double-
// representable number above INT32_MAX which cannot be represented as int32. // representable number above INT32_MAX which cannot be represented as int32.
double upper_bound = 2147483648.0; double upper_bound = 2147483648.0;
...@@ -480,7 +461,7 @@ static int32_t ExecuteI32SConvertF64(double a, TrapReason* trap) { ...@@ -480,7 +461,7 @@ static int32_t ExecuteI32SConvertF64(double a, TrapReason* trap) {
return 0; return 0;
} }
static uint32_t ExecuteI32UConvertF32(float a, TrapReason* trap) { uint32_t ExecuteI32UConvertF32(float a, TrapReason* trap) {
// The upper bound is (UINT32_MAX + 1), which is the lowest // The upper bound is (UINT32_MAX + 1), which is the lowest
// float-representable number above UINT32_MAX which cannot be represented as // float-representable number above UINT32_MAX which cannot be represented as
// uint32. // uint32.
...@@ -493,7 +474,7 @@ static uint32_t ExecuteI32UConvertF32(float a, TrapReason* trap) { ...@@ -493,7 +474,7 @@ static uint32_t ExecuteI32UConvertF32(float a, TrapReason* trap) {
return 0; return 0;
} }
static uint32_t ExecuteI32UConvertF64(double a, TrapReason* trap) { uint32_t ExecuteI32UConvertF64(double a, TrapReason* trap) {
// The upper bound is (UINT32_MAX + 1), which is the lowest // The upper bound is (UINT32_MAX + 1), which is the lowest
// double-representable number above UINT32_MAX which cannot be represented as // double-representable number above UINT32_MAX which cannot be represented as
// uint32. // uint32.
...@@ -506,11 +487,11 @@ static uint32_t ExecuteI32UConvertF64(double a, TrapReason* trap) { ...@@ -506,11 +487,11 @@ static uint32_t ExecuteI32UConvertF64(double a, TrapReason* trap) {
return 0; return 0;
} }
static inline uint32_t ExecuteI32ConvertI64(int64_t a, TrapReason* trap) { inline uint32_t ExecuteI32ConvertI64(int64_t a, TrapReason* trap) {
return static_cast<uint32_t>(a & 0xFFFFFFFF); return static_cast<uint32_t>(a & 0xFFFFFFFF);
} }
static int64_t ExecuteI64SConvertF32(float a, TrapReason* trap) { int64_t ExecuteI64SConvertF32(float a, TrapReason* trap) {
int64_t output; int64_t output;
if (!float32_to_int64_wrapper(&a, &output)) { if (!float32_to_int64_wrapper(&a, &output)) {
*trap = kTrapFloatUnrepresentable; *trap = kTrapFloatUnrepresentable;
...@@ -518,7 +499,7 @@ static int64_t ExecuteI64SConvertF32(float a, TrapReason* trap) { ...@@ -518,7 +499,7 @@ static int64_t ExecuteI64SConvertF32(float a, TrapReason* trap) {
return output; return output;
} }
static int64_t ExecuteI64SConvertF64(double a, TrapReason* trap) { int64_t ExecuteI64SConvertF64(double a, TrapReason* trap) {
int64_t output; int64_t output;
if (!float64_to_int64_wrapper(&a, &output)) { if (!float64_to_int64_wrapper(&a, &output)) {
*trap = kTrapFloatUnrepresentable; *trap = kTrapFloatUnrepresentable;
...@@ -526,7 +507,7 @@ static int64_t ExecuteI64SConvertF64(double a, TrapReason* trap) { ...@@ -526,7 +507,7 @@ static int64_t ExecuteI64SConvertF64(double a, TrapReason* trap) {
return output; return output;
} }
static uint64_t ExecuteI64UConvertF32(float a, TrapReason* trap) { uint64_t ExecuteI64UConvertF32(float a, TrapReason* trap) {
uint64_t output; uint64_t output;
if (!float32_to_uint64_wrapper(&a, &output)) { if (!float32_to_uint64_wrapper(&a, &output)) {
*trap = kTrapFloatUnrepresentable; *trap = kTrapFloatUnrepresentable;
...@@ -534,7 +515,7 @@ static uint64_t ExecuteI64UConvertF32(float a, TrapReason* trap) { ...@@ -534,7 +515,7 @@ static uint64_t ExecuteI64UConvertF32(float a, TrapReason* trap) {
return output; return output;
} }
static uint64_t ExecuteI64UConvertF64(double a, TrapReason* trap) { uint64_t ExecuteI64UConvertF64(double a, TrapReason* trap) {
uint64_t output; uint64_t output;
if (!float64_to_uint64_wrapper(&a, &output)) { if (!float64_to_uint64_wrapper(&a, &output)) {
*trap = kTrapFloatUnrepresentable; *trap = kTrapFloatUnrepresentable;
...@@ -542,80 +523,79 @@ static uint64_t ExecuteI64UConvertF64(double a, TrapReason* trap) { ...@@ -542,80 +523,79 @@ static uint64_t ExecuteI64UConvertF64(double a, TrapReason* trap) {
return output; return output;
} }
static inline int64_t ExecuteI64SConvertI32(int32_t a, TrapReason* trap) { inline int64_t ExecuteI64SConvertI32(int32_t a, TrapReason* trap) {
return static_cast<int64_t>(a); return static_cast<int64_t>(a);
} }
static inline int64_t ExecuteI64UConvertI32(uint32_t a, TrapReason* trap) { inline int64_t ExecuteI64UConvertI32(uint32_t a, TrapReason* trap) {
return static_cast<uint64_t>(a); return static_cast<uint64_t>(a);
} }
static inline float ExecuteF32SConvertI32(int32_t a, TrapReason* trap) { inline float ExecuteF32SConvertI32(int32_t a, TrapReason* trap) {
return static_cast<float>(a); return static_cast<float>(a);
} }
static inline float ExecuteF32UConvertI32(uint32_t a, TrapReason* trap) { inline float ExecuteF32UConvertI32(uint32_t a, TrapReason* trap) {
return static_cast<float>(a); return static_cast<float>(a);
} }
static inline float ExecuteF32SConvertI64(int64_t a, TrapReason* trap) { inline float ExecuteF32SConvertI64(int64_t a, TrapReason* trap) {
float output; float output;
int64_to_float32_wrapper(&a, &output); int64_to_float32_wrapper(&a, &output);
return output; return output;
} }
static inline float ExecuteF32UConvertI64(uint64_t a, TrapReason* trap) { inline float ExecuteF32UConvertI64(uint64_t a, TrapReason* trap) {
float output; float output;
uint64_to_float32_wrapper(&a, &output); uint64_to_float32_wrapper(&a, &output);
return output; return output;
} }
static inline float ExecuteF32ConvertF64(double a, TrapReason* trap) { inline float ExecuteF32ConvertF64(double a, TrapReason* trap) {
return static_cast<float>(a); return static_cast<float>(a);
} }
static inline float ExecuteF32ReinterpretI32(int32_t a, TrapReason* trap) { inline float ExecuteF32ReinterpretI32(int32_t a, TrapReason* trap) {
return bit_cast<float>(a); return bit_cast<float>(a);
} }
static inline double ExecuteF64SConvertI32(int32_t a, TrapReason* trap) { inline double ExecuteF64SConvertI32(int32_t a, TrapReason* trap) {
return static_cast<double>(a); return static_cast<double>(a);
} }
static inline double ExecuteF64UConvertI32(uint32_t a, TrapReason* trap) { inline double ExecuteF64UConvertI32(uint32_t a, TrapReason* trap) {
return static_cast<double>(a); return static_cast<double>(a);
} }
static inline double ExecuteF64SConvertI64(int64_t a, TrapReason* trap) { inline double ExecuteF64SConvertI64(int64_t a, TrapReason* trap) {
double output; double output;
int64_to_float64_wrapper(&a, &output); int64_to_float64_wrapper(&a, &output);
return output; return output;
} }
static inline double ExecuteF64UConvertI64(uint64_t a, TrapReason* trap) { inline double ExecuteF64UConvertI64(uint64_t a, TrapReason* trap) {
double output; double output;
uint64_to_float64_wrapper(&a, &output); uint64_to_float64_wrapper(&a, &output);
return output; return output;
} }
static inline double ExecuteF64ConvertF32(float a, TrapReason* trap) { inline double ExecuteF64ConvertF32(float a, TrapReason* trap) {
return static_cast<double>(a); return static_cast<double>(a);
} }
static inline double ExecuteF64ReinterpretI64(int64_t a, TrapReason* trap) { inline double ExecuteF64ReinterpretI64(int64_t a, TrapReason* trap) {
return bit_cast<double>(a); return bit_cast<double>(a);
} }
static inline int32_t ExecuteI32ReinterpretF32(WasmVal a) { inline int32_t ExecuteI32ReinterpretF32(WasmVal a) {
return a.to_unchecked<int32_t>(); return a.to_unchecked<int32_t>();
} }
static inline int64_t ExecuteI64ReinterpretF64(WasmVal a) { inline int64_t ExecuteI64ReinterpretF64(WasmVal a) {
return a.to_unchecked<int64_t>(); return a.to_unchecked<int64_t>();
} }
static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, inline int32_t ExecuteGrowMemory(uint32_t delta_pages, WasmInstance* instance) {
WasmInstance* instance) {
// TODO(ahaas): Move memory allocation to wasm-module.cc for better // TODO(ahaas): Move memory allocation to wasm-module.cc for better
// encapsulation. // encapsulation.
if (delta_pages > FLAG_wasm_max_mem_pages || if (delta_pages > FLAG_wasm_max_mem_pages ||
...@@ -664,7 +644,7 @@ enum InternalOpcode { ...@@ -664,7 +644,7 @@ enum InternalOpcode {
#undef DECL_INTERNAL_ENUM #undef DECL_INTERNAL_ENUM
}; };
static const char* OpcodeName(uint32_t val) { const char* OpcodeName(uint32_t val) {
switch (val) { switch (val) {
#define DECL_INTERNAL_CASE(name, value) \ #define DECL_INTERNAL_CASE(name, value) \
case kInternal##name: \ case kInternal##name: \
...@@ -675,7 +655,31 @@ static const char* OpcodeName(uint32_t val) { ...@@ -675,7 +655,31 @@ static const char* OpcodeName(uint32_t val) {
return WasmOpcodes::OpcodeName(static_cast<WasmOpcode>(val)); return WasmOpcodes::OpcodeName(static_cast<WasmOpcode>(val));
} }
static const int kRunSteps = 1000; constexpr int kRunSteps = 1000;
// Unwrap a wasm to js wrapper, return the callable heap object.
// If the wrapper would throw a TypeError, return a null handle.
Handle<HeapObject> UnwrapWasmToJSWrapper(Isolate* isolate,
Handle<Code> js_wrapper) {
DCHECK_EQ(Code::WASM_TO_JS_FUNCTION, js_wrapper->kind());
int mask = RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT);
for (RelocIterator it(*js_wrapper, mask); !it.done(); it.next()) {
HeapObject* obj = it.rinfo()->target_object();
if (!obj->IsCallable()) continue;
#ifdef DEBUG
// There should only be this one reference to a callable object.
for (it.next(); !it.done(); it.next()) {
HeapObject* other = it.rinfo()->target_object();
DCHECK(!other->IsCallable());
}
#endif
return handle(obj, isolate);
}
// If we did not find a callable object, then there must be a reference to
// the WasmThrowTypeError runtime function.
// TODO(clemensh): Check that this is the case.
return Handle<HeapObject>::null();
}
// A helper class to compute the control transfers for each bytecode offset. // A helper class to compute the control transfers for each bytecode offset.
// Control transfers allow Br, BrIf, BrTable, If, Else, and End bytecodes to // Control transfers allow Br, BrIf, BrTable, If, Else, and End bytecodes to
...@@ -845,20 +849,53 @@ struct InterpreterCode { ...@@ -845,20 +849,53 @@ struct InterpreterCode {
const byte* at(pc_t pc) { return start + pc; } const byte* at(pc_t pc) { return start + pc; }
}; };
struct ExternalCallResult {
enum Type {
// The function should be executed inside this interpreter.
INTERNAL,
// For indirect calls: Table or function does not exist.
INVALID_FUNC,
// For indirect calls: Signature does not match expected signature.
SIGNATURE_MISMATCH,
// The function was executed and returned normally.
EXTERNAL_RETURNED,
// The function was executed, threw an exception, and the stack was unwound.
EXTERNAL_UNWOUND
};
Type type;
// If type is INTERNAL, this field holds the function to call internally.
InterpreterCode* interpreter_code;
ExternalCallResult(Type type) : type(type) { // NOLINT
DCHECK_NE(INTERNAL, type);
}
ExternalCallResult(Type type, InterpreterCode* code)
: type(type), interpreter_code(code) {
DCHECK_EQ(INTERNAL, type);
}
};
// The main storage for interpreter code. It maps {WasmFunction} to the // The main storage for interpreter code. It maps {WasmFunction} to the
// metadata needed to execute each function. // metadata needed to execute each function.
class CodeMap { class CodeMap {
public:
Zone* zone_; Zone* zone_;
const WasmModule* module_; const WasmModule* module_;
ZoneVector<InterpreterCode> interpreter_code_; ZoneVector<InterpreterCode> interpreter_code_;
ZoneVector<Handle<HeapObject>> imported_functions_; // callable objects // Global handle to the wasm instance.
Handle<WasmInstanceObject> instance_;
// Global handle to array of unwrapped imports.
Handle<FixedArray> imported_functions_;
// Map from WASM_TO_JS wrappers to unwrapped imports (indexes into
// imported_functions_).
IdentityMap<int, ZoneAllocationPolicy> unwrapped_imports_;
CodeMap(const WasmModule* module, const uint8_t* module_start, Zone* zone) public:
CodeMap(Isolate* isolate, const WasmModule* module,
const uint8_t* module_start, Zone* zone)
: zone_(zone), : zone_(zone),
module_(module), module_(module),
interpreter_code_(zone), interpreter_code_(zone),
imported_functions_(zone) { unwrapped_imports_(isolate->heap(), ZoneAllocationPolicy(zone)) {
if (module == nullptr) return; if (module == nullptr) return;
interpreter_code_.reserve(module->functions.size()); interpreter_code_.reserve(module->functions.size());
for (const WasmFunction& function : module->functions) { for (const WasmFunction& function : module->functions) {
...@@ -871,18 +908,35 @@ class CodeMap { ...@@ -871,18 +908,35 @@ class CodeMap {
AddFunction(&function, code_start, code_end); AddFunction(&function, code_start, code_end);
} }
} }
imported_functions_.reserve(module_->num_imported_functions);
} }
void AddImportedFunction(Handle<HeapObject> function) { ~CodeMap() {
DCHECK_GT(module_->num_imported_functions, imported_functions_.size()); // Destroy the global handles (passing nullptr is ok).
imported_functions_.emplace_back(function); GlobalHandles::Destroy(Handle<Object>::cast(instance_).location());
GlobalHandles::Destroy(
Handle<Object>::cast(imported_functions_).location());
}
const WasmModule* module() const { return module_; }
bool has_instance() const { return !instance_.is_null(); }
Handle<WasmInstanceObject> instance() const {
DCHECK(has_instance());
return instance_;
}
void SetInstanceObject(WasmInstanceObject* instance) {
// Only set the instance once (otherwise we have to destroy the global
// handle first).
DCHECK(instance_.is_null());
DCHECK_EQ(instance->module(), module_);
instance_ = instance->GetIsolate()->global_handles()->Create(instance);
} }
Handle<HeapObject> GetImportedFunction(uint32_t function_index) { Code* GetImportedFunction(uint32_t function_index) {
DCHECK(!instance_.is_null());
DCHECK_GT(module_->num_imported_functions, function_index); DCHECK_GT(module_->num_imported_functions, function_index);
DCHECK_EQ(module_->num_imported_functions, imported_functions_.size()); FixedArray* code_table = instance_->compiled_module()->ptr_to_code_table();
return imported_functions_[function_index]; return Code::cast(code_table->get(static_cast<int>(function_index)));
} }
InterpreterCode* GetCode(const WasmFunction* function) { InterpreterCode* GetCode(const WasmFunction* function) {
...@@ -940,9 +994,49 @@ class CodeMap { ...@@ -940,9 +994,49 @@ class CodeMap {
code->end = const_cast<byte*>(end); code->end = const_cast<byte*>(end);
Preprocess(code); Preprocess(code);
} }
};
namespace { // Returns a callable object if the imported function has a JS-compatible
// signature, or a null handle otherwise.
Handle<HeapObject> GetCallableObjectForJSImport(Isolate* isolate,
Handle<Code> code) {
DCHECK_EQ(Code::WASM_TO_JS_FUNCTION, code->kind());
int* unwrapped_index = unwrapped_imports_.Find(code);
if (unwrapped_index) {
return handle(
HeapObject::cast(imported_functions_->get(*unwrapped_index)),
isolate);
}
Handle<HeapObject> called_obj = UnwrapWasmToJSWrapper(isolate, code);
if (!called_obj.is_null()) {
// Cache the unwrapped callable object.
if (imported_functions_.is_null()) {
// This is the first call to an imported function. Allocate the
// FixedArray to cache unwrapped objects.
constexpr int kInitialCacheSize = 8;
Handle<FixedArray> new_imported_functions =
isolate->factory()->NewFixedArray(kInitialCacheSize, TENURED);
// First entry: Number of occupied slots.
new_imported_functions->set(0, Smi::kZero);
imported_functions_ =
isolate->global_handles()->Create(*new_imported_functions);
}
int this_idx = Smi::cast(imported_functions_->get(0))->value() + 1;
if (this_idx == imported_functions_->length()) {
Handle<FixedArray> new_imported_functions =
isolate->factory()->CopyFixedArrayAndGrow(imported_functions_,
this_idx / 2, TENURED);
// Update the existing global handle:
*imported_functions_.location() = *new_imported_functions;
}
DCHECK_GT(imported_functions_->length(), this_idx);
DCHECK(imported_functions_->get(this_idx)->IsUndefined(isolate));
imported_functions_->set(0, Smi::FromInt(this_idx));
imported_functions_->set(this_idx, *called_obj);
unwrapped_imports_.Set(code, this_idx);
}
return called_obj;
}
};
Handle<Object> WasmValToNumber(Factory* factory, WasmVal val, Handle<Object> WasmValToNumber(Factory* factory, WasmVal val,
wasm::ValueType type) { wasm::ValueType type) {
...@@ -1533,44 +1627,59 @@ class ThreadImpl { ...@@ -1533,44 +1627,59 @@ class ThreadImpl {
InterpreterCode* target = codemap()->GetCode(operand.index); InterpreterCode* target = codemap()->GetCode(operand.index);
if (target->function->imported) { if (target->function->imported) {
CommitPc(pc); CommitPc(pc);
if (!CallImportedFunction(target->function->func_index)) return; ExternalCallResult result =
PAUSE_IF_BREAK_FLAG(AfterCall); CallImportedFunction(target->function->func_index);
len = 1 + operand.length; switch (result.type) {
break; // bump pc case ExternalCallResult::INTERNAL:
// The import is a function of this instance. Call it directly.
target = result.interpreter_code;
DCHECK(!target->function->imported);
break;
case ExternalCallResult::INVALID_FUNC:
case ExternalCallResult::SIGNATURE_MISMATCH:
// Direct calls are checked statically.
UNREACHABLE();
case ExternalCallResult::EXTERNAL_RETURNED:
PAUSE_IF_BREAK_FLAG(AfterCall);
len = 1 + operand.length;
break;
case ExternalCallResult::EXTERNAL_UNWOUND:
return;
}
if (result.type != ExternalCallResult::INTERNAL) break;
} }
// Execute an internal call.
DoCall(&decoder, target, &pc, &limit); DoCall(&decoder, target, &pc, &limit);
code = target; code = target;
PAUSE_IF_BREAK_FLAG(AfterCall); PAUSE_IF_BREAK_FLAG(AfterCall);
continue; // don't bump pc continue; // don't bump pc
} } break;
case kExprCallIndirect: { case kExprCallIndirect: {
CallIndirectOperand operand(&decoder, code->at(pc)); CallIndirectOperand operand(&decoder, code->at(pc));
uint32_t entry_index = Pop().to<uint32_t>(); uint32_t entry_index = Pop().to<uint32_t>();
// Assume only one table for now. // Assume only one table for now.
DCHECK_LE(module()->function_tables.size(), 1u); DCHECK_LE(module()->function_tables.size(), 1u);
InterpreterCode* target = codemap()->GetIndirectCode(0, entry_index); ExternalCallResult result =
if (target == nullptr) return DoTrap(kTrapFuncInvalid, pc); CallIndirectFunction(0, entry_index, operand.index);
if (target->function->sig_index != operand.index) { switch (result.type) {
// If not an exact match, we have to do a canonical check. case ExternalCallResult::INTERNAL:
// TODO(titzer): make this faster with some kind of caching? // The import is a function of this instance. Call it directly.
const WasmIndirectFunctionTable* table = DoCall(&decoder, result.interpreter_code, &pc, &limit);
&module()->function_tables[0]; code = result.interpreter_code;
int function_key = table->map.Find(target->function->sig); PAUSE_IF_BREAK_FLAG(AfterCall);
if (function_key < 0 || continue; // don't bump pc
(function_key != case ExternalCallResult::INVALID_FUNC:
table->map.Find(module()->signatures[operand.index]))) { return DoTrap(kTrapFuncInvalid, pc);
case ExternalCallResult::SIGNATURE_MISMATCH:
return DoTrap(kTrapFuncSigMismatch, pc); return DoTrap(kTrapFuncSigMismatch, pc);
} case ExternalCallResult::EXTERNAL_RETURNED:
PAUSE_IF_BREAK_FLAG(AfterCall);
len = 1 + operand.length;
break;
case ExternalCallResult::EXTERNAL_UNWOUND:
return;
} }
if (target->function->imported) { } break;
// TODO(clemensh): Call imported function.
UNREACHABLE();
}
DoCall(&decoder, target, &pc, &limit);
code = target;
PAUSE_IF_BREAK_FLAG(AfterCall);
continue;
}
case kExprGetGlobal: { case kExprGetGlobal: {
GlobalIndexOperand operand(&decoder, code->at(pc)); GlobalIndexOperand operand(&decoder, code->at(pc));
const WasmGlobal* global = &module()->globals[operand.index]; const WasmGlobal* global = &module()->globals[operand.index];
...@@ -1881,30 +1990,49 @@ class ThreadImpl { ...@@ -1881,30 +1990,49 @@ class ThreadImpl {
#endif // DEBUG #endif // DEBUG
} }
// Call imported function. Return true if the function returned normally or a ExternalCallResult TryHandleException(Isolate* isolate) {
// thrown exception was handled inside this wasm activation. Returns false if if (HandleException(isolate) == WasmInterpreter::Thread::UNWOUND) {
// the stack was fully unwound. A pending exception will be set in the latter return {ExternalCallResult::EXTERNAL_UNWOUND};
// case.
bool CallImportedFunction(uint32_t function_index) {
Handle<HeapObject> target = codemap()->GetImportedFunction(function_index);
if (target.is_null()) {
// The function does not have a js-compatible signature.
// TODO(clemensh): Throw type error.
UNIMPLEMENTED();
} }
if (target->IsCode()) { return {ExternalCallResult::EXTERNAL_RETURNED};
DCHECK_EQ(Code::WASM_FUNCTION, Code::cast(*target)->kind()); }
// TODO(clemensh): Call wasm code directly.
UNIMPLEMENTED(); ExternalCallResult CallCodeObject(Isolate* isolate, Handle<Code> code,
FunctionSig* signature) {
DCHECK(AllowHandleAllocation::IsAllowed());
DCHECK(AllowHeapAllocation::IsAllowed());
if (code->kind() == Code::WASM_FUNCTION) {
FixedArray* deopt_data = code->deoptimization_data();
DCHECK_EQ(2, deopt_data->length());
WasmInstanceObject* target_instance =
WasmInstanceObject::cast(WeakCell::cast(deopt_data->get(0))->value());
if (target_instance != *codemap()->instance()) {
// TODO(wasm): Implement calling functions of other instances/modules.
UNIMPLEMENTED();
}
int target_func_idx = Smi::cast(deopt_data->get(1))->value();
DCHECK_LE(0, target_func_idx);
return {ExternalCallResult::INTERNAL,
codemap()->GetCode(target_func_idx)};
}
Handle<HeapObject> target =
codemap()->GetCallableObjectForJSImport(isolate, code);
if (target.is_null()) {
isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kWasmTrapTypeError));
return TryHandleException(isolate);
} }
#if DEBUG
std::ostringstream oss; std::ostringstream oss;
TRACE(" => CallImportedFunction #%u: %s\n", function_index, target->HeapObjectShortPrint(oss);
(target->HeapObjectShortPrint(oss), oss.str().c_str())); TRACE(" => Calling imported function %s\n", oss.str().c_str());
const WasmFunction* called_fun = #endif
&codemap()->module_->functions[function_index];
int num_args = static_cast<int>(called_fun->sig->parameter_count()); int num_args = static_cast<int>(signature->parameter_count());
DCHECK(!instance()->context.is_null());
Isolate* isolate = instance()->context->GetIsolate();
// Get all arguments as JS values. // Get all arguments as JS values.
std::vector<Handle<Object>> args; std::vector<Handle<Object>> args;
...@@ -1912,26 +2040,110 @@ class ThreadImpl { ...@@ -1912,26 +2040,110 @@ class ThreadImpl {
WasmVal* wasm_args = stack_.data() + (stack_.size() - num_args); WasmVal* wasm_args = stack_.data() + (stack_.size() - num_args);
for (int i = 0; i < num_args; ++i) { for (int i = 0; i < num_args; ++i) {
args.push_back(WasmValToNumber(isolate->factory(), wasm_args[i], args.push_back(WasmValToNumber(isolate->factory(), wasm_args[i],
called_fun->sig->GetParam(i))); signature->GetParam(i)));
} }
MaybeHandle<Object> maybe_retval = Execution::Call( MaybeHandle<Object> maybe_retval = Execution::Call(
isolate, target, isolate->global_proxy(), num_args, args.data()); isolate, target, isolate->global_proxy(), num_args, args.data());
if (maybe_retval.is_null()) { if (maybe_retval.is_null()) return TryHandleException(isolate);
auto result = HandleException(isolate);
return result == WasmInterpreter::Thread::HANDLED;
}
Handle<Object> retval = maybe_retval.ToHandleChecked(); Handle<Object> retval = maybe_retval.ToHandleChecked();
// TODO(clemensh): Call ToNumber on retval. // TODO(clemensh): Call ToNumber on retval.
// Pop arguments of the stack. // Pop arguments off the stack.
stack_.resize(stack_.size() - num_args); stack_.resize(stack_.size() - num_args);
if (called_fun->sig->return_count() > 0) { if (signature->return_count() > 0) {
// TODO(wasm): Handle multiple returns. // TODO(wasm): Handle multiple returns.
DCHECK_EQ(1, called_fun->sig->return_count()); DCHECK_EQ(1, signature->return_count());
stack_.push_back(NumberToWasmVal(retval, called_fun->sig->GetReturn())); stack_.push_back(NumberToWasmVal(retval, signature->GetReturn()));
} }
return true; return {ExternalCallResult::EXTERNAL_RETURNED};
}
ExternalCallResult CallImportedFunction(uint32_t function_index) {
// Use a new HandleScope to avoid leaking / accumulating handles in the
// outer scope.
Isolate* isolate = codemap()->instance()->GetIsolate();
HandleScope handle_scope(isolate);
Handle<Code> target(codemap()->GetImportedFunction(function_index),
isolate);
return CallCodeObject(isolate, target,
codemap()->module()->functions[function_index].sig);
}
ExternalCallResult CallIndirectFunction(uint32_t table_index,
uint32_t entry_index,
uint32_t sig_index) {
if (!codemap()->has_instance()) {
// No instance. Rely on the information stored in the WasmModule.
// TODO(wasm): This is only needed for testing. Refactor testing to use
// the same paths as production.
InterpreterCode* code =
codemap()->GetIndirectCode(table_index, entry_index);
if (!code) return {ExternalCallResult::INVALID_FUNC};
if (code->function->sig_index != sig_index) {
// If not an exact match, we have to do a canonical check.
// TODO(titzer): make this faster with some kind of caching?
const WasmIndirectFunctionTable* table =
&module()->function_tables[table_index];
int function_key = table->map.Find(code->function->sig);
if (function_key < 0 ||
(function_key !=
table->map.Find(module()->signatures[sig_index]))) {
return {ExternalCallResult::SIGNATURE_MISMATCH};
}
}
return {ExternalCallResult::INTERNAL, code};
}
WasmCompiledModule* compiled_module =
codemap()->instance()->compiled_module();
Isolate* isolate = compiled_module->GetIsolate();
Code* target;
{
DisallowHeapAllocation no_gc;
// Get function to be called directly from the live instance to see latest
// changes to the tables.
// Canonicalize signature index.
// TODO(titzer): make this faster with some kind of caching?
const WasmIndirectFunctionTable* table =
&module()->function_tables[table_index];
FunctionSig* sig = module()->signatures[sig_index];
uint32_t canonical_sig_index = table->map.Find(sig);
// Check signature.
FixedArray* sig_tables = compiled_module->ptr_to_signature_tables();
if (table_index >= static_cast<uint32_t>(sig_tables->length())) {
return {ExternalCallResult::INVALID_FUNC};
}
FixedArray* sig_table =
FixedArray::cast(sig_tables->get(static_cast<int>(table_index)));
if (entry_index >= static_cast<uint32_t>(sig_table->length())) {
return {ExternalCallResult::INVALID_FUNC};
}
int found_sig =
Smi::cast(sig_table->get(static_cast<int>(entry_index)))->value();
if (static_cast<uint32_t>(found_sig) != canonical_sig_index) {
return {ExternalCallResult::SIGNATURE_MISMATCH};
}
// Get code object.
FixedArray* fun_tables = compiled_module->ptr_to_function_tables();
DCHECK_EQ(sig_tables->length(), fun_tables->length());
FixedArray* fun_table =
FixedArray::cast(fun_tables->get(static_cast<int>(table_index)));
DCHECK_EQ(sig_table->length(), fun_table->length());
target = Code::cast(fun_table->get(static_cast<int>(entry_index)));
}
// Call the code object. Use a new HandleScope to avoid leaking /
// accumulating handles in the outer scope.
HandleScope handle_scope(isolate);
FunctionSig* signature =
&codemap()->module()->signatures[table_index][sig_index];
return CallCodeObject(isolate, handle(target, isolate), signature);
} }
inline Activation current_activation() { inline Activation current_activation() {
...@@ -1946,9 +2158,10 @@ class ThreadImpl { ...@@ -1946,9 +2158,10 @@ class ThreadImpl {
WasmInterpreter::Thread* ToThread(ThreadImpl* impl) { WasmInterpreter::Thread* ToThread(ThreadImpl* impl) {
return reinterpret_cast<WasmInterpreter::Thread*>(impl); return reinterpret_cast<WasmInterpreter::Thread*>(impl);
} }
static ThreadImpl* ToImpl(WasmInterpreter::Thread* thread) { ThreadImpl* ToImpl(WasmInterpreter::Thread* thread) {
return reinterpret_cast<ThreadImpl*>(thread); return reinterpret_cast<ThreadImpl*>(thread);
} }
} // namespace } // namespace
//============================================================================ //============================================================================
...@@ -2036,10 +2249,12 @@ class WasmInterpreterInternals : public ZoneObject { ...@@ -2036,10 +2249,12 @@ class WasmInterpreterInternals : public ZoneObject {
CodeMap codemap_; CodeMap codemap_;
ZoneVector<ThreadImpl> threads_; ZoneVector<ThreadImpl> threads_;
WasmInterpreterInternals(Zone* zone, const ModuleBytesEnv& env) WasmInterpreterInternals(Isolate* isolate, Zone* zone,
const ModuleBytesEnv& env)
: instance_(env.module_env.instance), : instance_(env.module_env.instance),
module_bytes_(env.wire_bytes.start(), env.wire_bytes.end(), zone), module_bytes_(env.wire_bytes.start(), env.wire_bytes.end(), zone),
codemap_( codemap_(
isolate,
env.module_env.instance ? env.module_env.instance->module : nullptr, env.module_env.instance ? env.module_env.instance->module : nullptr,
module_bytes_.data(), zone), module_bytes_.data(), zone),
threads_(zone) { threads_(zone) {
...@@ -2052,10 +2267,9 @@ class WasmInterpreterInternals : public ZoneObject { ...@@ -2052,10 +2267,9 @@ class WasmInterpreterInternals : public ZoneObject {
//============================================================================ //============================================================================
// Implementation of the public interface of the interpreter. // Implementation of the public interface of the interpreter.
//============================================================================ //============================================================================
WasmInterpreter::WasmInterpreter(const ModuleBytesEnv& env, WasmInterpreter::WasmInterpreter(Isolate* isolate, const ModuleBytesEnv& env)
AccountingAllocator* allocator) : zone_(isolate->allocator(), ZONE_NAME),
: zone_(allocator, ZONE_NAME), internals_(new (&zone_) WasmInterpreterInternals(isolate, &zone_, env)) {}
internals_(new (&zone_) WasmInterpreterInternals(&zone_, env)) {}
WasmInterpreter::~WasmInterpreter() { internals_->Delete(); } WasmInterpreter::~WasmInterpreter() { internals_->Delete(); }
...@@ -2098,8 +2312,8 @@ bool WasmInterpreter::SetTracing(const WasmFunction* function, bool enabled) { ...@@ -2098,8 +2312,8 @@ bool WasmInterpreter::SetTracing(const WasmFunction* function, bool enabled) {
return false; return false;
} }
void WasmInterpreter::AddImportedFunction(Handle<HeapObject> code) { void WasmInterpreter::SetInstanceObject(WasmInstanceObject* instance) {
internals_->codemap_.AddImportedFunction(code); internals_->codemap_.SetInstanceObject(instance);
} }
int WasmInterpreter::GetThreadCount() { int WasmInterpreter::GetThreadCount() {
......
...@@ -14,6 +14,8 @@ class AccountingAllocator; ...@@ -14,6 +14,8 @@ class AccountingAllocator;
} }
namespace internal { namespace internal {
class WasmInstanceObject;
namespace wasm { namespace wasm {
// forward declarations. // forward declarations.
...@@ -185,7 +187,7 @@ class V8_EXPORT_PRIVATE WasmInterpreter { ...@@ -185,7 +187,7 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
uint32_t ActivationFrameBase(uint32_t activation_id); uint32_t ActivationFrameBase(uint32_t activation_id);
}; };
WasmInterpreter(const ModuleBytesEnv& env, AccountingAllocator* allocator); WasmInterpreter(Isolate* isolate, const ModuleBytesEnv& env);
~WasmInterpreter(); ~WasmInterpreter();
//========================================================================== //==========================================================================
...@@ -204,10 +206,12 @@ class V8_EXPORT_PRIVATE WasmInterpreter { ...@@ -204,10 +206,12 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
// Enable or disable tracing for {function}. Return the previous state. // Enable or disable tracing for {function}. Return the previous state.
bool SetTracing(const WasmFunction* function, bool enabled); bool SetTracing(const WasmFunction* function, bool enabled);
// Add an imported function. // Set the associated wasm instance object.
// We store the passed Handle internally, so the caller must ensure that it // If the instance object has been set, some tables stored inside it are used
// stays valid at least as long as the WasmInterpreter. // instead of the tables stored in the WasmModule struct. This allows to call
void AddImportedFunction(Handle<HeapObject>); // back and forth between the interpreter and outside code (JS or wasm
// compiled) without repeatedly copying information.
void SetInstanceObject(WasmInstanceObject*);
//========================================================================== //==========================================================================
// Thread iteration and inspection. // Thread iteration and inspection.
......
...@@ -80,12 +80,12 @@ class TestingModule : public ModuleEnv { ...@@ -80,12 +80,12 @@ class TestingModule : public ModuleEnv {
instance_(&module_), instance_(&module_),
isolate_(CcTest::InitIsolateOnce()), isolate_(CcTest::InitIsolateOnce()),
global_offset(0), global_offset(0),
interpreter_(mode == kExecuteInterpreted interpreter_(
? new WasmInterpreter( mode == kExecuteInterpreted
ModuleBytesEnv(&module_, &instance_, ? new WasmInterpreter(
Vector<const byte>::empty()), isolate_, ModuleBytesEnv(&module_, &instance_,
zone->allocator()) Vector<const byte>::empty()))
: nullptr) { : nullptr) {
WasmJs::Install(isolate_); WasmJs::Install(isolate_);
instance->module = &module_; instance->module = &module_;
instance->globals_start = global_data; instance->globals_start = global_data;
......
...@@ -139,7 +139,7 @@ int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower, ...@@ -139,7 +139,7 @@ int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower,
instance.globals_start = nullptr; instance.globals_start = nullptr;
ModuleBytesEnv env(module, &instance, wire_bytes); ModuleBytesEnv env(module, &instance, wire_bytes);
WasmInterpreter interpreter(env, isolate->allocator()); WasmInterpreter interpreter(isolate, env);
WasmInterpreter::Thread* thread = interpreter.GetThread(0); WasmInterpreter::Thread* thread = interpreter.GetThread(0);
thread->Reset(); thread->Reset();
......
...@@ -225,3 +225,87 @@ function checkStack(stack, expected_lines) { ...@@ -225,3 +225,87 @@ function checkStack(stack, expected_lines) {
} }
} }
})(); })();
(function testIndirectImports() {
var builder = new WasmModuleBuilder();
var sig_i_ii = builder.addType(kSig_i_ii);
var sig_i_i = builder.addType(kSig_i_i);
var mul = builder.addImport('q', 'mul', sig_i_ii);
var add = builder.addFunction('add', sig_i_ii).addBody([
kExprGetLocal, 0, kExprGetLocal, 1, kExprI32Add
]);
var mismatch =
builder.addFunction('sig_mismatch', sig_i_i).addBody([kExprGetLocal, 0]);
var main = builder.addFunction('main', kSig_i_iii)
.addBody([
// Call indirect #0 with args <#1, #2>.
kExprGetLocal, 1, kExprGetLocal, 2, kExprGetLocal, 0,
kExprCallIndirect, sig_i_ii, kTableZero
])
.exportFunc();
builder.appendToTable([mul, add.index, mismatch.index, main.index]);
var instance = builder.instantiate({q: {mul: (a, b) => a * b}});
// Call mul.
assertEquals(-6, instance.exports.main(0, -2, 3));
// Call add.
assertEquals(99, instance.exports.main(1, 22, 77));
// main and sig_mismatch have another signature.
assertTraps(kTrapFuncSigMismatch, () => instance.exports.main(2, 12, 33));
assertTraps(kTrapFuncSigMismatch, () => instance.exports.main(3, 12, 33));
// Function index 4 does not exist.
assertTraps(kTrapFuncInvalid, () => instance.exports.main(4, 12, 33));
})();
(function testIllegalImports() {
var builder = new WasmModuleBuilder();
var sig_l_v = builder.addType(kSig_l_v);
var imp = builder.addImport('q', 'imp', sig_l_v);
var direct = builder.addFunction('direct', kSig_l_v)
.addBody([kExprCallFunction, imp])
.exportFunc();
var indirect = builder.addFunction('indirect', kSig_l_v).addBody([
kExprI32Const, 0, kExprCallIndirect, sig_l_v, kTableZero
]);
var main =
builder.addFunction('main', kSig_v_i)
.addBody([
// Call indirect #0 with arg #0, drop result.
kExprGetLocal, 0, kExprCallIndirect, sig_l_v, kTableZero, kExprDrop
])
.exportFunc();
builder.appendToTable([imp, direct.index, indirect.index]);
var instance = builder.instantiate({q: {imp: () => 1}});
// Calling imported functions with i64 in signature should fail.
try {
// Via direct call.
instance.exports.main(1);
} catch (e) {
if (!(e instanceof TypeError)) throw e;
checkStack(stripPath(e.stack), [
'TypeError: invalid type', // -
' at direct (<WASM>[1]+1)', // -
' at main (<WASM>[3]+3)', // -
/^ at testIllegalImports \(interpreter.js:\d+:22\)$/, // -
/^ at interpreter.js:\d+:3$/
]);
}
try {
// Via indirect call.
instance.exports.main(2);
} catch (e) {
if (!(e instanceof TypeError)) throw e;
checkStack(stripPath(e.stack), [
'TypeError: invalid type', // -
' at indirect (<WASM>[2]+1)', // -
' at main (<WASM>[3]+3)', // -
/^ at testIllegalImports \(interpreter.js:\d+:22\)$/, // -
/^ at interpreter.js:\d+:3$/
]);
}
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment