Commit 3214ccf3 authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[wasm] [interpreter] Allow different activations

This CL makes the interpreter reentrant by allowing different
activations to be live at the same time. The wasm interpreter keeps a
list of activations and stores the stack height at the start of each
activation. This information is used to unwind just one activation, or
show the right portion of the interpreter stack for each interpreter
entry frame.
The WasmDebugInfo object stores a mapping from frame pointer (of the
interpreter entry) to the activation id in order to identify the
activation based on the physical interpreter entry frame.

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

Change-Id: Ibbf93f077f907213173a92e0a2f7f3556515e8eb
Reviewed-on: https://chromium-review.googlesource.com/453958
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#43976}
parent f8c70730
......@@ -1347,6 +1347,7 @@ Object* Isolate::UnwindAndFindHandler() {
}
WasmInterpreterEntryFrame* interpreter_frame =
WasmInterpreterEntryFrame::cast(frame);
// TODO(wasm): Implement try-catch in the interpreter.
interpreter_frame->wasm_instance()->debug_info()->Unwind(frame->fp());
} break;
......
......@@ -200,8 +200,21 @@ RUNTIME_FUNCTION(Runtime_WasmRunInterpreter) {
DCHECK_NULL(isolate->context());
isolate->set_context(instance->compiled_module()->ptr_to_native_context());
// Find the frame pointer of the interpreter entry.
Address frame_pointer = 0;
{
StackFrameIterator it(isolate, isolate->thread_local_top());
// On top: C entry stub.
DCHECK_EQ(StackFrame::EXIT, it.frame()->type());
it.Advance();
// Next: the wasm interpreter entry.
DCHECK_EQ(StackFrame::WASM_INTERPRETER_ENTRY, it.frame()->type());
frame_pointer = it.frame()->fp();
}
trap_handler::ClearThreadInWasm();
bool success = instance->debug_info()->RunInterpreter(func_index, arg_buffer);
bool success = instance->debug_info()->RunInterpreter(frame_pointer,
func_index, arg_buffer);
trap_handler::SetThreadInWasm();
if (!success) {
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <unordered_map>
#include "src/assembler-inl.h"
#include "src/assert-scope.h"
#include "src/compiler/wasm-compiler.h"
......@@ -55,6 +57,36 @@ class InterpreterHandle {
StepAction next_step_action_ = StepNone;
int last_step_stack_depth_ = 0;
DeferredHandles* import_handles_ = nullptr;
std::unordered_map<Address, uint32_t> activations_;
uint32_t StartActivation(Address frame_pointer) {
WasmInterpreter::Thread* thread = interpreter_.GetThread(0);
uint32_t activation_id = thread->StartActivation();
DCHECK_EQ(0, activations_.count(frame_pointer));
activations_.insert(std::make_pair(frame_pointer, activation_id));
return activation_id;
}
void FinishActivation(Address frame_pointer, uint32_t activation_id) {
WasmInterpreter::Thread* thread = interpreter_.GetThread(0);
thread->FinishActivation(activation_id);
DCHECK_EQ(1, activations_.count(frame_pointer));
activations_.erase(frame_pointer);
}
std::pair<uint32_t, uint32_t> GetActivationFrameRange(
WasmInterpreter::Thread* thread, Address frame_pointer) {
DCHECK_EQ(1, activations_.count(frame_pointer));
uint32_t activation_id = activations_.find(frame_pointer)->second;
uint32_t num_activations = static_cast<uint32_t>(activations_.size() - 1);
uint32_t frame_base = thread->ActivationFrameBase(activation_id);
uint32_t frame_limit = activation_id == num_activations
? thread->GetFrameCount()
: thread->ActivationFrameBase(activation_id + 1);
DCHECK_LE(frame_base, frame_limit);
DCHECK_LE(frame_limit, thread->GetFrameCount());
return {frame_base, frame_limit};
}
public:
// Initialize in the right order, using helper methods to make this possible.
......@@ -102,6 +134,7 @@ class InterpreterHandle {
}
~InterpreterHandle() {
DCHECK_EQ(0, activations_.size());
delete import_handles_; // might be nullptr, which is ok.
}
......@@ -130,7 +163,11 @@ class InterpreterHandle {
return interpreter()->GetThread(0)->GetFrameCount();
}
bool Execute(uint32_t func_index, uint8_t* arg_buffer) {
// Returns true if exited regularly, false if a trap/exception occured and was
// not handled inside this activation. In the latter case, a pending exception
// will have been set on the isolate.
bool Execute(Address frame_pointer, uint32_t func_index,
uint8_t* arg_buffer) {
DCHECK_GE(module()->functions.size(), func_index);
FunctionSig* sig = module()->functions[func_index].sig;
DCHECK_GE(kMaxInt, sig->parameter_count());
......@@ -156,11 +193,9 @@ class InterpreterHandle {
arg_buf_ptr += param_size;
}
uint32_t activation_id = StartActivation(frame_pointer);
WasmInterpreter::Thread* thread = interpreter_.GetThread(0);
// We do not support reentering an already running interpreter at the moment
// (like INTERPRETER -> JS -> WASM -> INTERPRETER).
DCHECK_EQ(0, thread->GetFrameCount());
thread->Reset();
thread->InitFrame(&module()->functions[func_index], wasm_args.start());
bool finished = false;
while (!finished) {
......@@ -180,8 +215,10 @@ class InterpreterHandle {
Handle<Object> exception = isolate_->factory()->NewWasmRuntimeError(
static_cast<MessageTemplate::Template>(message_id));
isolate_->Throw(*exception);
// And hard exit the execution.
return false;
// Handle this exception. Return without trying to read back the
// return value.
auto result = thread->HandleException(isolate_);
return result == WasmInterpreter::Thread::HANDLED;
} break;
case WasmInterpreter::State::STOPPED:
// Then an exception happened, and the stack was unwound.
......@@ -215,6 +252,9 @@ class InterpreterHandle {
UNREACHABLE();
}
}
FinishActivation(frame_pointer, activation_id);
return true;
}
......@@ -311,39 +351,53 @@ class InterpreterHandle {
std::vector<std::pair<uint32_t, int>> GetInterpretedStack(
Address frame_pointer) {
// TODO(clemensh): Use frame_pointer.
USE(frame_pointer);
DCHECK_EQ(1, interpreter()->GetThreadCount());
WasmInterpreter::Thread* thread = interpreter()->GetThread(0);
std::vector<std::pair<uint32_t, int>> stack(thread->GetFrameCount());
for (int i = 0, e = thread->GetFrameCount(); i < e; ++i) {
wasm::InterpretedFrame frame = thread->GetFrame(i);
stack[i] = {frame.function()->func_index, frame.pc()};
std::pair<uint32_t, uint32_t> frame_range =
GetActivationFrameRange(thread, frame_pointer);
std::vector<std::pair<uint32_t, int>> stack;
stack.reserve(frame_range.second - frame_range.first);
for (uint32_t fp = frame_range.first; fp < frame_range.second; ++fp) {
wasm::InterpretedFrame frame = thread->GetFrame(fp);
stack.emplace_back(frame.function()->func_index, frame.pc());
}
return stack;
}
std::unique_ptr<wasm::InterpretedFrame> GetInterpretedFrame(
Address frame_pointer, int idx) {
// TODO(clemensh): Use frame_pointer.
USE(frame_pointer);
DCHECK_EQ(1, interpreter()->GetThreadCount());
WasmInterpreter::Thread* thread = interpreter()->GetThread(0);
return std::unique_ptr<wasm::InterpretedFrame>(
new wasm::InterpretedFrame(thread->GetMutableFrame(idx)));
std::pair<uint32_t, uint32_t> frame_range =
GetActivationFrameRange(thread, frame_pointer);
DCHECK_LE(0, idx);
DCHECK_GT(frame_range.second - frame_range.first, idx);
return std::unique_ptr<wasm::InterpretedFrame>(new wasm::InterpretedFrame(
thread->GetMutableFrame(frame_range.first + idx)));
}
void Unwind(Address frame_pointer) {
// TODO(clemensh): Use frame_pointer.
USE(frame_pointer);
using ExceptionResult = WasmInterpreter::Thread::ExceptionHandlingResult;
ExceptionResult result =
interpreter()->GetThread(0)->HandleException(isolate_);
// TODO(wasm): Handle exceptions caught in wasm land.
CHECK_EQ(ExceptionResult::UNWOUND, result);
// Find the current activation.
DCHECK_EQ(1, activations_.count(frame_pointer));
// Activations must be properly stacked:
DCHECK_EQ(activations_.size() - 1, activations_[frame_pointer]);
uint32_t activation_id = static_cast<uint32_t>(activations_.size() - 1);
// Unwind the frames of the current activation if not already unwound.
WasmInterpreter::Thread* thread = interpreter()->GetThread(0);
if (static_cast<uint32_t>(thread->GetFrameCount()) >
thread->ActivationFrameBase(activation_id)) {
using ExceptionResult = WasmInterpreter::Thread::ExceptionHandlingResult;
ExceptionResult result = thread->HandleException(isolate_);
// TODO(wasm): Handle exceptions caught in wasm land.
CHECK_EQ(ExceptionResult::UNWOUND, result);
}
FinishActivation(frame_pointer, activation_id);
}
uint64_t NumInterpretedCalls() {
......@@ -505,10 +559,11 @@ void WasmDebugInfo::PrepareStep(StepAction step_action) {
GetInterpreterHandle(this)->PrepareStep(step_action);
}
bool WasmDebugInfo::RunInterpreter(int func_index, uint8_t* arg_buffer) {
bool WasmDebugInfo::RunInterpreter(Address frame_pointer, int func_index,
uint8_t* arg_buffer) {
DCHECK_LE(0, func_index);
return GetInterpreterHandle(this)->Execute(static_cast<uint32_t>(func_index),
arg_buffer);
return GetInterpreterHandle(this)->Execute(
frame_pointer, static_cast<uint32_t>(func_index), arg_buffer);
}
std::vector<std::pair<uint32_t, int>> WasmDebugInfo::GetInterpretedStack(
......
......@@ -981,13 +981,20 @@ WasmVal NumberToWasmVal(Handle<Object> number, wasm::ValueType type) {
// Responsible for executing code directly.
class ThreadImpl {
struct Activation {
uint32_t fp;
uint32_t sp;
Activation(uint32_t fp, uint32_t sp) : fp(fp), sp(sp) {}
};
public:
ThreadImpl(Zone* zone, CodeMap* codemap, WasmInstance* instance)
: codemap_(codemap),
instance_(instance),
stack_(zone),
frames_(zone),
blocks_(zone) {}
blocks_(zone),
activations_(zone) {}
//==========================================================================
// Implementation of public interface for WasmInterpreter::Thread.
......@@ -996,6 +1003,7 @@ class ThreadImpl {
WasmInterpreter::State state() { return state_; }
void InitFrame(const WasmFunction* function, WasmVal* args) {
DCHECK_EQ(current_activation().fp, frames_.size());
InterpreterCode* code = codemap()->GetCode(function);
for (size_t i = 0; i < function->sig->parameter_count(); ++i) {
stack_.push_back(args[i]);
......@@ -1054,11 +1062,14 @@ class ThreadImpl {
static_cast<int>(frame->llimit()));
}
WasmVal GetReturnValue(int index) {
WasmVal GetReturnValue(uint32_t index) {
if (state_ == WasmInterpreter::TRAPPED) return WasmVal(0xdeadbeef);
CHECK_EQ(WasmInterpreter::FINISHED, state_);
CHECK_LT(static_cast<size_t>(index), stack_.size());
return stack_[index];
DCHECK_EQ(WasmInterpreter::FINISHED, state_);
Activation act = current_activation();
// Current activation must be finished.
DCHECK_EQ(act.fp, frames_.size());
DCHECK_GT(stack_.size(), act.sp + index);
return stack_[act.sp + index];
}
TrapReason GetTrapReason() { return trap_reason_; }
......@@ -1073,18 +1084,53 @@ class ThreadImpl {
void ClearBreakFlags() { break_flags_ = WasmInterpreter::BreakFlag::None; }
uint32_t NumActivations() {
return static_cast<uint32_t>(activations_.size());
}
uint32_t StartActivation() {
TRACE("----- START ACTIVATION %zu -----\n", activations_.size());
// If you use activations, use them consistently:
DCHECK_IMPLIES(activations_.empty(), frames_.empty());
DCHECK_IMPLIES(activations_.empty(), stack_.empty());
uint32_t activation_id = static_cast<uint32_t>(activations_.size());
activations_.emplace_back(static_cast<uint32_t>(frames_.size()),
static_cast<uint32_t>(stack_.size()));
state_ = WasmInterpreter::STOPPED;
return activation_id;
}
void FinishActivation(uint32_t id) {
TRACE("----- FINISH ACTIVATION %zu -----\n", activations_.size() - 1);
DCHECK_LT(0, activations_.size());
DCHECK_EQ(activations_.size() - 1, id);
// Stack height must match the start of this activation (otherwise unwind
// first).
DCHECK_EQ(activations_.back().fp, frames_.size());
DCHECK_LE(activations_.back().sp, stack_.size());
stack_.resize(activations_.back().sp);
activations_.pop_back();
}
uint32_t ActivationFrameBase(uint32_t id) {
DCHECK_GT(activations_.size(), id);
return activations_[id].fp;
}
// Handle a thrown exception. Returns whether the exception was handled inside
// wasm. Unwinds the interpreted stack accordingly.
// the current activation. Unwinds the interpreted stack accordingly.
WasmInterpreter::Thread::ExceptionHandlingResult HandleException(
Isolate* isolate) {
DCHECK(isolate->has_pending_exception());
// TODO(wasm): Add wasm exception handling.
// TODO(wasm): Add wasm exception handling (would return true).
USE(isolate->pending_exception());
TRACE("----- UNWIND -----\n");
// TODO(clemensh): Only clear the portion of the stack belonging to the
// current activation of the interpreter.
stack_.clear();
frames_.clear();
DCHECK_LT(0, activations_.size());
Activation& act = activations_.back();
DCHECK_LE(act.fp, frames_.size());
frames_.resize(act.fp);
DCHECK_LE(act.sp, stack_.size());
stack_.resize(act.sp);
state_ = WasmInterpreter::STOPPED;
return WasmInterpreter::Thread::UNWOUND;
}
......@@ -1120,6 +1166,9 @@ class ThreadImpl {
bool possible_nondeterminism_ = false;
uint8_t break_flags_ = 0; // a combination of WasmInterpreter::BreakFlag
uint64_t num_interpreted_calls_ = 0;
// Store the stack height of each activation (for unwind and frame
// inspection).
ZoneVector<Activation> activations_;
CodeMap* codemap() { return codemap_; }
WasmInstance* instance() { return instance_; }
......@@ -1218,10 +1267,10 @@ class ThreadImpl {
sp_t dest = frames_.back().sp;
frames_.pop_back();
if (frames_.size() == 0) {
if (frames_.size() == current_activation().fp) {
// A return from the last frame terminates the execution.
state_ = WasmInterpreter::FINISHED;
DoStackTransfer(0, arity);
DoStackTransfer(dest, arity);
TRACE(" => finish\n");
return false;
} else {
......@@ -1484,7 +1533,7 @@ class ThreadImpl {
InterpreterCode* target = codemap()->GetCode(operand.index);
if (target->function->imported) {
CommitPc(pc);
if (!CallImportedFunction(operand.index)) return;
if (!CallImportedFunction(target->function->func_index)) return;
PAUSE_IF_BREAK_FLAG(AfterCall);
len = 1 + operand.length;
break; // bump pc
......@@ -1884,6 +1933,10 @@ class ThreadImpl {
}
return true;
}
inline Activation current_activation() {
return activations_.empty() ? Activation(0, 0) : activations_.back();
}
};
// Converters between WasmInterpreter::Thread and WasmInterpreter::ThreadImpl.
......@@ -1958,6 +2011,18 @@ void WasmInterpreter::Thread::AddBreakFlags(uint8_t flags) {
void WasmInterpreter::Thread::ClearBreakFlags() {
ToImpl(this)->ClearBreakFlags();
}
uint32_t WasmInterpreter::Thread::NumActivations() {
return ToImpl(this)->NumActivations();
}
uint32_t WasmInterpreter::Thread::StartActivation() {
return ToImpl(this)->StartActivation();
}
void WasmInterpreter::Thread::FinishActivation(uint32_t id) {
ToImpl(this)->FinishActivation(id);
}
uint32_t WasmInterpreter::Thread::ActivationFrameBase(uint32_t id) {
return ToImpl(this)->ActivationFrameBase(id);
}
//============================================================================
// The implementation details of the interpreter.
......
......@@ -149,6 +149,7 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
// Stack inspection and modification.
pc_t GetBreakpointPc();
// TODO(clemensh): Make this uint32_t.
int GetFrameCount();
const InterpretedFrame GetFrame(int index);
InterpretedFrame GetMutableFrame(int index);
......@@ -170,6 +171,18 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
void AddBreakFlags(uint8_t flags);
void ClearBreakFlags();
// Each thread can have multiple activations, each represented by a portion
// of the stack frames of this thread. StartActivation returns the id
// (counting from 0 up) of the started activation.
// Activations must be properly stacked, i.e. if FinishActivation is called,
// the given id must the the latest activation on the stack.
uint32_t NumActivations();
uint32_t StartActivation();
void FinishActivation(uint32_t activation_id);
// Return the frame base of the given activation, i.e. the number of frames
// when this activation was started.
uint32_t ActivationFrameBase(uint32_t activation_id);
};
WasmInterpreter(const ModuleBytesEnv& env, AccountingAllocator* allocator);
......
......@@ -476,9 +476,12 @@ class WasmDebugInfo : public FixedArray {
// Execute the specified funtion in the interpreter. Read arguments from
// arg_buffer.
// The frame_pointer will be used to identify the new activation of the
// interpreter for unwinding and frame inspection.
// Returns true if exited regularly, false if a trap occured. In the latter
// case, a pending exception will have been set on the isolate.
bool RunInterpreter(int func_index, uint8_t* arg_buffer);
bool RunInterpreter(Address frame_pointer, int func_index,
uint8_t* arg_buffer);
// Get the stack of the wasm interpreter as pairs of <function index, byte
// offset>. The list is ordered bottom-to-top, i.e. caller before callee.
......
......@@ -398,6 +398,37 @@ TEST(TestPossibleNondeterminism) {
CHECK(r.possible_nondeterminism());
}
}
TEST(WasmInterpreterActivations) {
WasmRunner<void> r(kExecuteInterpreted);
Isolate* isolate = r.main_isolate();
BUILD(r, WASM_NOP);
WasmInterpreter* interpreter = r.interpreter();
WasmInterpreter::Thread* thread = interpreter->GetThread(0);
CHECK_EQ(0, thread->NumActivations());
uint32_t act0 = thread->StartActivation();
CHECK_EQ(0, act0);
thread->InitFrame(r.function(), nullptr);
uint32_t act1 = thread->StartActivation();
CHECK_EQ(1, act1);
thread->InitFrame(r.function(), nullptr);
CHECK_EQ(2, thread->NumActivations());
CHECK_EQ(2, thread->GetFrameCount());
isolate->set_pending_exception(Smi::kZero);
thread->HandleException(isolate);
CHECK_EQ(1, thread->GetFrameCount());
CHECK_EQ(2, thread->NumActivations());
thread->FinishActivation(act1);
CHECK_EQ(1, thread->GetFrameCount());
CHECK_EQ(1, thread->NumActivations());
thread->HandleException(isolate);
CHECK_EQ(0, thread->GetFrameCount());
CHECK_EQ(1, thread->NumActivations());
thread->FinishActivation(act0);
CHECK_EQ(0, thread->NumActivations());
}
} // namespace wasm
} // namespace internal
} // namespace v8
......@@ -131,10 +131,10 @@ function checkStack(stack, expected_lines) {
}
assertEquals(interpreted_before + 1, % WasmNumInterpretedCalls(instance));
checkStack(stripPath(stack), [
'Error: thrown from imported function', // -
/^ at func \(interpreter.js:\d+:11\)$/, // -
' at main (<WASM>[1]+1)', // -
/^ at testThrowFromImport \(interpreter.js:\d+:24\)/, // -
'Error: thrown from imported function', // -
/^ at func \(interpreter.js:\d+:11\)$/, // -
' at main (<WASM>[1]+1)', // -
/^ at testThrowFromImport \(interpreter.js:\d+:24\)$/, // -
/^ at interpreter.js:\d+:3$/
]);
}
......@@ -188,3 +188,40 @@ function checkStack(stack, expected_lines) {
assertEqualsDelta(values[2], instance.exports.get_f32(), 2**-23);
assertEquals(values[3], instance.exports.get_f64());
})();
(function testReentrantInterpreter() {
var stacks;
var instance;
function func(i) {
stacks.push(new Error('reentrant interpreter test #' + i).stack);
if (i < 2) instance.exports.main(i + 1);
}
var builder = new WasmModuleBuilder();
builder.addImport('mod', 'func', kSig_v_i);
builder.addFunction('main', kSig_v_i)
.addBody([kExprGetLocal, 0, kExprCallFunction, 0])
.exportFunc();
instance = builder.instantiate({mod: {func: func}});
// Test that this does not mess up internal state by executing it three times.
for (var i = 0; i < 3; ++i) {
var interpreted_before = % WasmNumInterpretedCalls(instance);
stacks = [];
instance.exports.main(0);
assertEquals(interpreted_before + 3, % WasmNumInterpretedCalls(instance));
assertEquals(3, stacks.length);
for (var e = 0; e < stacks.length; ++e) {
expected = ['Error: reentrant interpreter test #' + e];
expected.push(/^ at func \(interpreter.js:\d+:17\)$/);
expected.push(' at main (<WASM>[1]+3)');
for (var k = e; k > 0; --k) {
expected.push(/^ at func \(interpreter.js:\d+:33\)$/);
expected.push(' at main (<WASM>[1]+3)');
}
expected.push(
/^ at testReentrantInterpreter \(interpreter.js:\d+:22\)$/);
expected.push(/ at interpreter.js:\d+:3$/);
checkStack(stripPath(stacks[e]), expected);
}
}
})();
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