[wasm] Clarify source of runtime information for interpreter.

This is part of the effort to consolidate the ownership of
wasm instantiation/specialization parameters.

This change is focused solely on the interpreter part of that effort, to
verify we're not regressing performance in interpreter benchmarks.

There are two aspects being addressed:
- dataflow-wise, we always fetch the interpreter's memory view from the
runtime objects (i.e. WasmInstanceObject/WasmCompiledModule). This is
consistent with how other instance-specific information is obtained
(e.g. code, indirect functions).

- representation-wise, we do not reuse ModuleEnv/WasmInstance just for
the memory view, because it is surprising that other instance info isn't
accessed from there. 

Bug: 
Change-Id: I536fbffd8e1f142a315fa1770ba9b08319f56a8e
Reviewed-on: https://chromium-review.googlesource.com/602083
Reviewed-by: Ben Titzer <titzer@chromium.org>
Reviewed-by: Clemens Hammacher <clemensh@chromium.org>
Commit-Queue: Mircea Trofin <mtrofin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47205}
This commit is contained in:
Mircea Trofin 2017-08-07 10:17:06 -07:00 committed by Commit Bot
parent 81778aaf72
commit 3f1e32b336
6 changed files with 175 additions and 125 deletions

View File

@ -95,10 +95,9 @@ InterpreterHandle* GetInterpreterHandle(WasmDebugInfo* debug_info);
class InterpreterHandle {
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(InterpreterHandle);
WasmInstance instance_;
WasmInterpreter interpreter_;
Isolate* isolate_;
const WasmModule* module_;
WasmInterpreter interpreter_;
StepAction next_step_action_ = StepNone;
int last_step_stack_depth_ = 0;
std::unordered_map<Address, uint32_t> activations_;
@ -132,50 +131,53 @@ class InterpreterHandle {
return {frame_base, frame_limit};
}
public:
// Initialize in the right order, using helper methods to make this possible.
// WasmInterpreter has to be allocated in place, since it is not movable.
InterpreterHandle(Isolate* isolate, WasmDebugInfo* debug_info,
WasmInstance* external_instance = nullptr)
: instance_(debug_info->wasm_instance()->compiled_module()->module()),
interpreter_(isolate, GetBytesEnv(external_instance ? external_instance
: &instance_,
debug_info)),
isolate_(isolate) {
DisallowHeapAllocation no_gc;
WasmInstanceObject* instance = debug_info->wasm_instance();
// Set memory start pointer and size.
instance_.mem_start = nullptr;
instance_.mem_size = 0;
if (instance->has_memory_buffer()) {
UpdateMemory(instance->memory_buffer());
} else {
DCHECK_EQ(0, instance_.module->min_mem_pages);
}
// Set pointer to globals storage.
instance_.globals_start =
debug_info->wasm_instance()->compiled_module()->GetGlobalsStartOrNull();
}
~InterpreterHandle() {
DCHECK_EQ(0, activations_.size());
}
static ModuleBytesEnv GetBytesEnv(WasmInstance* instance,
WasmDebugInfo* debug_info) {
static Vector<const byte> GetBytes(WasmDebugInfo* debug_info) {
// Return raw pointer into heap. The WasmInterpreter will make its own copy
// of this data anyway, and there is no heap allocation in-between.
SeqOneByteString* bytes_str =
debug_info->wasm_instance()->compiled_module()->module_bytes();
Vector<const byte> bytes(bytes_str->GetChars(), bytes_str->length());
return {instance->module, instance, bytes};
return {bytes_str->GetChars(), static_cast<size_t>(bytes_str->length())};
}
static uint32_t GetMemSize(WasmDebugInfo* debug_info) {
DisallowHeapAllocation no_gc;
WasmCompiledModule* compiled_module =
debug_info->wasm_instance()->compiled_module();
return compiled_module->has_embedded_mem_size()
? compiled_module->embedded_mem_size()
: 0;
}
static byte* GetMemStart(WasmDebugInfo* debug_info) {
DisallowHeapAllocation no_gc;
WasmCompiledModule* compiled_module =
debug_info->wasm_instance()->compiled_module();
return reinterpret_cast<byte*>(compiled_module->has_embedded_mem_start()
? compiled_module->embedded_mem_start()
: 0);
}
static byte* GetGlobalsStart(WasmDebugInfo* debug_info) {
DisallowHeapAllocation no_gc;
WasmCompiledModule* compiled_module =
debug_info->wasm_instance()->compiled_module();
return reinterpret_cast<byte*>(compiled_module->has_globals_start()
? compiled_module->globals_start()
: 0);
}
public:
InterpreterHandle(Isolate* isolate, WasmDebugInfo* debug_info)
: isolate_(isolate),
module_(debug_info->wasm_instance()->compiled_module()->module()),
interpreter_(isolate, module_, GetBytes(debug_info),
GetGlobalsStart(debug_info), GetMemStart(debug_info),
GetMemSize(debug_info)) {}
~InterpreterHandle() { DCHECK_EQ(0, activations_.size()); }
WasmInterpreter* interpreter() { return &interpreter_; }
const WasmModule* module() { return instance_.module; }
const WasmModule* module() const { return module_; }
void PrepareStep(StepAction step_action) {
next_step_action_ = step_action;
@ -647,11 +649,10 @@ Handle<WasmDebugInfo> WasmDebugInfo::New(Handle<WasmInstanceObject> instance) {
}
WasmInterpreter* WasmDebugInfo::SetupForTesting(
Handle<WasmInstanceObject> instance_obj, WasmInstance* instance) {
Handle<WasmInstanceObject> instance_obj) {
Handle<WasmDebugInfo> debug_info = WasmDebugInfo::New(instance_obj);
Isolate* isolate = instance_obj->GetIsolate();
InterpreterHandle* cpp_handle =
new InterpreterHandle(isolate, *debug_info, instance);
InterpreterHandle* cpp_handle = new InterpreterHandle(isolate, *debug_info);
Handle<Object> handle = Managed<InterpreterHandle>::New(isolate, cpp_handle);
debug_info->set(kInterpreterHandleIndex, *handle);
return cpp_handle->interpreter();

View File

@ -176,6 +176,27 @@ namespace wasm {
namespace {
// CachedInstanceInfo encapsulates globals and memory buffer runtime information
// for a wasm instance. The interpreter caches that information when
// constructed, copying it from the {WasmInstanceObject}. It expects it be
// notified on changes to it, e.g. {GrowMemory}. We cache it because interpreter
// perf is sensitive to accesses to this information.
//
// TODO(wasm): other runtime information, such as indirect function table, or
// code table (incl. imports) is currently handled separately. Consider
// unifying, if possible, with {ModuleEnv}.
struct CachedInstanceInfo {
CachedInstanceInfo(byte* globals, byte* mem, uint32_t size)
: globals_start(globals), mem_start(mem), mem_size(size) {}
// We do not expect the location of the globals buffer to
// change for an instance.
byte* const globals_start = nullptr;
// The memory buffer may change because of GrowMemory
byte* mem_start = nullptr;
uint32_t mem_size = 0;
};
inline int32_t ExecuteI32DivS(int32_t a, int32_t b, TrapReason* trap) {
if (b == 0) {
*trap = kTrapDivByZero;
@ -598,18 +619,29 @@ inline int64_t ExecuteI64ReinterpretF64(WasmValue a) {
inline int32_t ExecuteGrowMemory(uint32_t delta_pages,
MaybeHandle<WasmInstanceObject> instance_obj,
WasmInstance* instance) {
DCHECK_EQ(0, instance->mem_size % WasmModule::kPageSize);
uint32_t old_pages = instance->mem_size / WasmModule::kPageSize;
CachedInstanceInfo* mem_info) {
Handle<WasmInstanceObject> instance = instance_obj.ToHandleChecked();
Isolate* isolate = instance->GetIsolate();
int32_t ret = WasmInstanceObject::GrowMemory(isolate, instance, delta_pages);
Isolate* isolate = instance_obj.ToHandleChecked()->GetIsolate();
int32_t ret = WasmInstanceObject::GrowMemory(
isolate, instance_obj.ToHandleChecked(), delta_pages);
// Some sanity checks.
DCHECK_EQ(ret == -1 ? old_pages : old_pages + delta_pages,
instance->mem_size / WasmModule::kPageSize);
DCHECK(ret == -1 || static_cast<uint32_t>(ret) == old_pages);
USE(old_pages);
#ifdef DEBUG
// Ensure the effects of GrowMemory have been observed by the interpreter.
// See {UpdateMemory}. In all cases, we are in agreement with the runtime
// object's view.
uint32_t cached_size = mem_info->mem_size;
byte* cached_start = mem_info->mem_start;
uint32_t instance_size =
instance->compiled_module()->has_embedded_mem_size()
? instance->compiled_module()->embedded_mem_size()
: 0;
byte* instance_start =
instance->compiled_module()->has_embedded_mem_start()
? reinterpret_cast<byte*>(
instance->compiled_module()->embedded_mem_start())
: nullptr;
CHECK_EQ(cached_size, instance_size);
CHECK_EQ(cached_start, instance_start);
#endif
return ret;
}
@ -1100,9 +1132,10 @@ class ThreadImpl {
};
public:
ThreadImpl(Zone* zone, CodeMap* codemap, WasmInstance* instance)
ThreadImpl(Zone* zone, CodeMap* codemap,
CachedInstanceInfo* cached_instance_info)
: codemap_(codemap),
instance_(instance),
cached_instance_info_(cached_instance_info),
zone_(zone),
frames_(zone),
activations_(zone) {}
@ -1262,7 +1295,7 @@ class ThreadImpl {
friend class InterpretedFrameImpl;
CodeMap* codemap_;
WasmInstance* instance_;
CachedInstanceInfo* const cached_instance_info_;
Zone* zone_;
WasmValue* stack_start_ = nullptr; // Start of allocated stack space.
WasmValue* stack_limit_ = nullptr; // End of allocated stack space.
@ -1278,9 +1311,8 @@ class ThreadImpl {
// inspection).
ZoneVector<Activation> activations_;
CodeMap* codemap() { return codemap_; }
WasmInstance* instance() { return instance_; }
const WasmModule* module() { return instance_->module; }
CodeMap* codemap() const { return codemap_; }
const WasmModule* module() const { return codemap_->module(); }
void DoTrap(TrapReason trap, pc_t pc) {
state_ = WasmInterpreter::TRAPPED;
@ -1426,11 +1458,12 @@ class ThreadImpl {
bool ExecuteLoad(Decoder* decoder, InterpreterCode* code, pc_t pc, int& len) {
MemoryAccessOperand<false> operand(decoder, code->at(pc), sizeof(ctype));
uint32_t index = Pop().to<uint32_t>();
if (!BoundsCheck<mtype>(instance()->mem_size, operand.offset, index)) {
if (!BoundsCheck<mtype>(cached_instance_info_->mem_size, operand.offset,
index)) {
DoTrap(kTrapMemOutOfBounds, pc);
return false;
}
byte* addr = instance()->mem_start + operand.offset + index;
byte* addr = cached_instance_info_->mem_start + operand.offset + index;
WasmValue result(static_cast<ctype>(ReadLittleEndianValue<mtype>(addr)));
Push(result);
@ -1445,11 +1478,12 @@ class ThreadImpl {
WasmValue val = Pop();
uint32_t index = Pop().to<uint32_t>();
if (!BoundsCheck<mtype>(instance()->mem_size, operand.offset, index)) {
if (!BoundsCheck<mtype>(cached_instance_info_->mem_size, operand.offset,
index)) {
DoTrap(kTrapMemOutOfBounds, pc);
return false;
}
byte* addr = instance()->mem_start + operand.offset + index;
byte* addr = cached_instance_info_->mem_start + operand.offset + index;
WriteLittleEndianValue<mtype>(addr, static_cast<mtype>(val.to<ctype>()));
len = 1 + operand.length;
@ -1744,7 +1778,7 @@ class ThreadImpl {
case kExprGetGlobal: {
GlobalIndexOperand<false> operand(&decoder, code->at(pc));
const WasmGlobal* global = &module()->globals[operand.index];
byte* ptr = instance()->globals_start + global->offset;
byte* ptr = cached_instance_info_->globals_start + global->offset;
WasmValue val;
switch (global->type) {
#define CASE_TYPE(wasm, ctype) \
@ -1763,7 +1797,7 @@ class ThreadImpl {
case kExprSetGlobal: {
GlobalIndexOperand<false> operand(&decoder, code->at(pc));
const WasmGlobal* global = &module()->globals[operand.index];
byte* ptr = instance()->globals_start + global->offset;
byte* ptr = cached_instance_info_->globals_start + global->offset;
WasmValue val = Pop();
switch (global->type) {
#define CASE_TYPE(wasm, ctype) \
@ -1818,19 +1852,19 @@ class ThreadImpl {
STORE_CASE(F64StoreMem, double, double);
#undef STORE_CASE
#define ASMJS_LOAD_CASE(name, ctype, mtype, defval) \
case kExpr##name: { \
uint32_t index = Pop().to<uint32_t>(); \
ctype result; \
if (!BoundsCheck<mtype>(instance()->mem_size, 0, index)) { \
result = defval; \
} else { \
byte* addr = instance()->mem_start + index; \
/* TODO(titzer): alignment for asmjs load mem? */ \
result = static_cast<ctype>(*reinterpret_cast<mtype*>(addr)); \
} \
Push(WasmValue(result)); \
break; \
#define ASMJS_LOAD_CASE(name, ctype, mtype, defval) \
case kExpr##name: { \
uint32_t index = Pop().to<uint32_t>(); \
ctype result; \
if (!BoundsCheck<mtype>(cached_instance_info_->mem_size, 0, index)) { \
result = defval; \
} else { \
byte* addr = cached_instance_info_->mem_start + index; \
/* TODO(titzer): alignment for asmjs load mem? */ \
result = static_cast<ctype>(*reinterpret_cast<mtype*>(addr)); \
} \
Push(WasmValue(result)); \
break; \
}
ASMJS_LOAD_CASE(I32AsmjsLoadMem8S, int32_t, int8_t, 0);
ASMJS_LOAD_CASE(I32AsmjsLoadMem8U, int32_t, uint8_t, 0);
@ -1847,8 +1881,8 @@ class ThreadImpl {
case kExpr##name: { \
WasmValue val = Pop(); \
uint32_t index = Pop().to<uint32_t>(); \
if (BoundsCheck<mtype>(instance()->mem_size, 0, index)) { \
byte* addr = instance()->mem_start + index; \
if (BoundsCheck<mtype>(cached_instance_info_->mem_size, 0, index)) { \
byte* addr = cached_instance_info_->mem_start + index; \
/* TODO(titzer): alignment for asmjs store mem? */ \
*(reinterpret_cast<mtype*>(addr)) = static_cast<mtype>(val.to<ctype>()); \
} \
@ -1866,13 +1900,13 @@ class ThreadImpl {
MemoryIndexOperand<false> operand(&decoder, code->at(pc));
uint32_t delta_pages = Pop().to<uint32_t>();
Push(WasmValue(ExecuteGrowMemory(
delta_pages, codemap_->maybe_instance(), instance())));
delta_pages, codemap_->maybe_instance(), cached_instance_info_)));
len = 1 + operand.length;
break;
}
case kExprMemorySize: {
MemoryIndexOperand<false> operand(&decoder, code->at(pc));
Push(WasmValue(static_cast<uint32_t>(instance()->mem_size /
Push(WasmValue(static_cast<uint32_t>(cached_instance_info_->mem_size /
WasmModule::kPageSize)));
len = 1 + operand.length;
break;
@ -2511,7 +2545,9 @@ uint32_t WasmInterpreter::Thread::ActivationFrameBase(uint32_t id) {
//============================================================================
class WasmInterpreterInternals : public ZoneObject {
public:
WasmInstance* instance_;
// We cache the memory information of the debugged instance here, and all
// threads (currently, one) share it and update it in case of {GrowMemory}.
CachedInstanceInfo cached_instance_info_;
// Create a copy of the module bytes for the interpreter, since the passed
// pointer might be invalidated after constructing the interpreter.
const ZoneVector<uint8_t> module_bytes_;
@ -2519,24 +2555,29 @@ class WasmInterpreterInternals : public ZoneObject {
ZoneVector<ThreadImpl> threads_;
WasmInterpreterInternals(Isolate* isolate, Zone* zone,
const ModuleBytesEnv& env)
: instance_(env.module_env.instance),
module_bytes_(env.wire_bytes.start(), env.wire_bytes.end(), zone),
codemap_(
isolate,
env.module_env.instance ? env.module_env.instance->module : nullptr,
module_bytes_.data(), zone),
const WasmModule* module,
const ModuleWireBytes& wire_bytes,
byte* globals_start, byte* mem_start,
uint32_t mem_size)
: cached_instance_info_(globals_start, mem_start, mem_size),
module_bytes_(wire_bytes.start(), wire_bytes.end(), zone),
codemap_(isolate, module, module_bytes_.data(), zone),
threads_(zone) {
threads_.emplace_back(zone, &codemap_, env.module_env.instance);
threads_.emplace_back(zone, &codemap_, &cached_instance_info_);
}
};
//============================================================================
// Implementation of the public interface of the interpreter.
//============================================================================
WasmInterpreter::WasmInterpreter(Isolate* isolate, const ModuleBytesEnv& env)
WasmInterpreter::WasmInterpreter(Isolate* isolate, const WasmModule* module,
const ModuleWireBytes& wire_bytes,
byte* globals_start, byte* mem_start,
uint32_t mem_size)
: zone_(isolate->allocator(), ZONE_NAME),
internals_(new (&zone_) WasmInterpreterInternals(isolate, &zone_, env)) {}
internals_(new (&zone_) WasmInterpreterInternals(
isolate, &zone_, module, wire_bytes, globals_start, mem_start,
mem_size)) {}
WasmInterpreter::~WasmInterpreter() { internals_->~WasmInterpreterInternals(); }
@ -2588,22 +2629,12 @@ WasmInterpreter::Thread* WasmInterpreter::GetThread(int id) {
return ToThread(&internals_->threads_[id]);
}
size_t WasmInterpreter::GetMemorySize() {
return internals_->instance_->mem_size;
}
WasmValue WasmInterpreter::ReadMemory(size_t offset) {
UNIMPLEMENTED();
return WasmValue();
}
void WasmInterpreter::WriteMemory(size_t offset, WasmValue val) {
UNIMPLEMENTED();
}
void WasmInterpreter::UpdateMemory(byte* mem_start, uint32_t mem_size) {
internals_->instance_->mem_start = mem_start;
internals_->instance_->mem_size = mem_size;
// We assume one thread. Things are likely to be more complicated than this
// in a multi-threaded case.
DCHECK_EQ(1, internals_->threads_.size());
internals_->cached_instance_info_.mem_start = mem_start;
internals_->cached_instance_info_.mem_size = mem_size;
}
void WasmInterpreter::AddFunctionForTesting(const WasmFunction* function) {

View File

@ -20,7 +20,7 @@ class WasmInstanceObject;
namespace wasm {
// forward declarations.
struct ModuleBytesEnv;
struct ModuleWireBytes;
struct WasmFunction;
struct WasmModule;
class WasmInterpreterInternals;
@ -171,7 +171,9 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
uint32_t ActivationFrameBase(uint32_t activation_id);
};
WasmInterpreter(Isolate* isolate, const ModuleBytesEnv& env);
WasmInterpreter(Isolate* isolate, const WasmModule* module,
const ModuleWireBytes& wire_bytes, byte* globals_start,
byte* mem_start, uint32_t mem_size);
~WasmInterpreter();
//==========================================================================
@ -197,12 +199,8 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
Thread* GetThread(int id);
//==========================================================================
// Memory access.
// Update the cached module env memory parameters after a grow memory event.
//==========================================================================
size_t GetMemorySize();
WasmValue ReadMemory(size_t offset);
void WriteMemory(size_t offset, WasmValue val);
// Update the memory region, e.g. after external GrowMemory.
void UpdateMemory(byte* mem_start, uint32_t mem_size);
//==========================================================================
@ -222,7 +220,7 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
private:
Zone zone_;
WasmInterpreterInternals* internals_;
WasmInterpreterInternals* const internals_;
};
} // namespace wasm

View File

@ -611,7 +611,7 @@ class WasmDebugInfo : public FixedArray {
// WasmDebugInfo.
// Use for testing only.
V8_EXPORT_PRIVATE static wasm::WasmInterpreter* SetupForTesting(
Handle<WasmInstanceObject>, wasm::WasmInstance*);
Handle<WasmInstanceObject>);
// Set a breakpoint in the given function at the given byte offset within that
// function. This will redirect all future calls to this function to the

View File

@ -69,9 +69,10 @@ using namespace v8::internal::wasm;
const uint32_t kMaxGlobalsSize = 128;
// A helper for module environments that adds the ability to allocate memory
// and global variables. Contains a built-in {WasmModule} and
// {WasmInstance}.
// A buildable ModuleEnv. Globals are pre-set, however, memory and code may be
// progressively added by a test. In turn, we piecemeal update the runtime
// objects, i.e. {WasmInstanceObject}, {WasmCompiledModule} and, if necessary,
// the interpreter.
class TestingModule : public ModuleEnv {
public:
explicit TestingModule(Zone* zone, WasmExecutionMode mode = kExecuteCompiled)
@ -89,8 +90,7 @@ class TestingModule : public ModuleEnv {
memset(global_data, 0, sizeof(global_data));
instance_object_ = InitInstanceObject();
if (mode == kExecuteInterpreted) {
interpreter_ =
WasmDebugInfo::SetupForTesting(instance_object_, &instance_);
interpreter_ = WasmDebugInfo::SetupForTesting(instance_object_);
}
}
@ -113,6 +113,21 @@ class TestingModule : public ModuleEnv {
CHECK(size == 0 || instance->mem_start);
memset(instance->mem_start, 0, size);
instance->mem_size = size;
Handle<WasmCompiledModule> compiled_module =
handle(instance_object_->compiled_module());
Factory* factory = CcTest::i_isolate()->factory();
// It's not really necessary we recreate the Number objects,
// if we happened to have one, but this is a reasonable inefficiencly,
// given this is test.
WasmCompiledModule::recreate_embedded_mem_size(compiled_module, factory,
instance->mem_size);
WasmCompiledModule::recreate_embedded_mem_start(
compiled_module, factory,
reinterpret_cast<size_t>(instance->mem_start));
if (interpreter_) {
interpreter_->UpdateMemory(instance->mem_start, instance->mem_size);
}
return instance->mem_start;
}
@ -346,6 +361,12 @@ class TestingModule : public ModuleEnv {
std::vector<Handle<FixedArray>> empty;
Handle<WasmCompiledModule> compiled_module = WasmCompiledModule::New(
isolate_, shared_module_data, code_table, empty, empty);
// This method is called when we initialize TestEnvironment. We don't
// have a memory yet, so we won't create it here. We'll update the
// interpreter when we get a memory. We do have globals, though.
WasmCompiledModule::recreate_globals_start(
compiled_module, isolate_->factory(),
reinterpret_cast<size_t>(instance->globals_start));
Handle<FixedArray> weak_exported = isolate_->factory()->NewFixedArray(0);
compiled_module->set_weak_exported_functions(weak_exported);
DCHECK(WasmCompiledModule::IsWasmCompiledModule(*compiled_module));

View File

@ -91,8 +91,7 @@ int32_t InterpretWasmModule(Isolate* isolate,
Zone zone(isolate->allocator(), ZONE_NAME);
v8::internal::HandleScope scope(isolate);
WasmInterpreter* interpreter =
WasmDebugInfo::SetupForTesting(instance, nullptr);
WasmInterpreter* interpreter = WasmDebugInfo::SetupForTesting(instance);
WasmInterpreter::HeapObjectsScope heap_objects_scope(interpreter, instance);
WasmInterpreter::Thread* thread = interpreter->GetThread(0);
thread->Reset();