[wasm] [interpreter] Handle stack unwinding
If an exception is thrown and the wasm interpreter entry frame is unwound, also the internal frames in the interpreter need to be unwound. We did not do so before, leaving a corrupted internal state of the wasm interpreter. Thus reusing it would fail. This CL fixes this and adds a test which reenters a previously unwound wasm interpreter. It checks that this works and the correct stack is returned. This test also requires support for calling an imported function which throws, so this change is also included here. R=ahaas@chromium.org, titzer@chromium.org BUG=v8:5822 Change-Id: I12fb843f7a371a4e618b4ac63ed3299667a03a82 Reviewed-on: https://chromium-review.googlesource.com/453938 Commit-Queue: Clemens Hammacher <clemensh@chromium.org> Reviewed-by: Andreas Haas <ahaas@chromium.org> Cr-Commit-Position: refs/heads/master@{#43937}
This commit is contained in:
parent
4f3e168cf9
commit
91852dffaa
@ -1341,13 +1341,14 @@ Object* Isolate::UnwindAndFindHandler() {
|
||||
}
|
||||
break;
|
||||
|
||||
case StackFrame::WASM_INTERPRETER_ENTRY:
|
||||
// TODO(clemensh): Handle unwinding interpreted wasm frames (stored in
|
||||
// the WasmInterpreter C++ object).
|
||||
case StackFrame::WASM_INTERPRETER_ENTRY: {
|
||||
if (trap_handler::IsThreadInWasm()) {
|
||||
trap_handler::ClearThreadInWasm();
|
||||
}
|
||||
break;
|
||||
WasmInterpreterEntryFrame* interpreter_frame =
|
||||
WasmInterpreterEntryFrame::cast(frame);
|
||||
interpreter_frame->wasm_instance()->debug_info()->Unwind(frame->fp());
|
||||
} break;
|
||||
|
||||
default:
|
||||
// All other types can not handle exception.
|
||||
|
@ -154,9 +154,7 @@ class InterpreterHandle {
|
||||
WasmInterpreter::Thread* thread = interpreter_.GetThread(0);
|
||||
// We do not support reentering an already running interpreter at the moment
|
||||
// (like INTERPRETER -> JS -> WASM -> INTERPRETER).
|
||||
DCHECK(thread->state() == WasmInterpreter::STOPPED ||
|
||||
thread->state() == WasmInterpreter::FINISHED ||
|
||||
thread->state() == WasmInterpreter::TRAPPED);
|
||||
DCHECK_EQ(0, thread->GetFrameCount());
|
||||
thread->Reset();
|
||||
thread->InitFrame(&module()->functions[func_index], wasm_args.start());
|
||||
bool finished = false;
|
||||
@ -180,8 +178,11 @@ class InterpreterHandle {
|
||||
// And hard exit the execution.
|
||||
return false;
|
||||
} break;
|
||||
// STOPPED and RUNNING should never occur here.
|
||||
case WasmInterpreter::State::STOPPED:
|
||||
// Then an exception happened, and the stack was unwound.
|
||||
DCHECK_EQ(0, thread->GetFrameCount());
|
||||
return false;
|
||||
// RUNNING should never occur here.
|
||||
case WasmInterpreter::State::RUNNING:
|
||||
default:
|
||||
UNREACHABLE();
|
||||
@ -329,6 +330,17 @@ class InterpreterHandle {
|
||||
new wasm::InterpretedFrame(thread->GetMutableFrame(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);
|
||||
}
|
||||
|
||||
uint64_t NumInterpretedCalls() {
|
||||
DCHECK_EQ(1, interpreter()->GetThreadCount());
|
||||
return interpreter()->GetThread(0)->NumInterpretedCalls();
|
||||
@ -498,6 +510,10 @@ std::unique_ptr<wasm::InterpretedFrame> WasmDebugInfo::GetInterpretedFrame(
|
||||
return GetInterpreterHandle(this)->GetInterpretedFrame(frame_pointer, idx);
|
||||
}
|
||||
|
||||
void WasmDebugInfo::Unwind(Address frame_pointer) {
|
||||
return GetInterpreterHandle(this)->Unwind(frame_pointer);
|
||||
}
|
||||
|
||||
uint64_t WasmDebugInfo::NumInterpretedCalls() {
|
||||
auto handle = GetInterpreterHandleOrNull(this);
|
||||
return handle ? handle->NumInterpretedCalls() : 0;
|
||||
|
@ -1006,11 +1006,13 @@ class ThreadImpl {
|
||||
WasmInterpreter::State Run() {
|
||||
DCHECK(state_ == WasmInterpreter::STOPPED ||
|
||||
state_ == WasmInterpreter::PAUSED);
|
||||
// Execute in chunks of {kRunSteps} steps as long as we did not trap or
|
||||
// unwind.
|
||||
do {
|
||||
TRACE(" => Run()\n");
|
||||
state_ = WasmInterpreter::RUNNING;
|
||||
Execute(frames_.back().code, frames_.back().pc, kRunSteps);
|
||||
} while (state_ == WasmInterpreter::STOPPED);
|
||||
} while (state_ == WasmInterpreter::STOPPED && !frames_.empty());
|
||||
return state_;
|
||||
}
|
||||
|
||||
@ -1071,6 +1073,22 @@ class ThreadImpl {
|
||||
|
||||
void ClearBreakFlags() { break_flags_ = WasmInterpreter::BreakFlag::None; }
|
||||
|
||||
// Handle a thrown exception. Returns whether the exception was handled inside
|
||||
// wasm. Unwinds the interpreted stack accordingly.
|
||||
WasmInterpreter::Thread::ExceptionHandlingResult HandleException(
|
||||
Isolate* isolate) {
|
||||
DCHECK(isolate->has_pending_exception());
|
||||
// TODO(wasm): Add wasm exception handling.
|
||||
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();
|
||||
state_ = WasmInterpreter::STOPPED;
|
||||
return WasmInterpreter::Thread::UNWOUND;
|
||||
}
|
||||
|
||||
private:
|
||||
// Entries on the stack of functions being evaluated.
|
||||
struct Frame {
|
||||
@ -1466,7 +1484,7 @@ class ThreadImpl {
|
||||
InterpreterCode* target = codemap()->GetCode(operand.index);
|
||||
if (target->function->imported) {
|
||||
CommitPc(pc);
|
||||
CallImportedFunction(operand.index);
|
||||
if (!CallImportedFunction(operand.index)) return;
|
||||
PAUSE_IF_BREAK_FLAG(AfterCall);
|
||||
len = 1 + operand.length;
|
||||
break; // bump pc
|
||||
@ -1814,7 +1832,11 @@ class ThreadImpl {
|
||||
#endif // DEBUG
|
||||
}
|
||||
|
||||
void CallImportedFunction(uint32_t function_index) {
|
||||
// Call imported function. Return true if the function returned normally or a
|
||||
// thrown exception was handled inside this wasm activation. Returns false if
|
||||
// the stack was fully unwound. A pending exception will be set in the latter
|
||||
// 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.
|
||||
@ -1847,9 +1869,10 @@ class ThreadImpl {
|
||||
MaybeHandle<Object> maybe_retval = Execution::Call(
|
||||
isolate, target, isolate->global_proxy(), num_args, args.data());
|
||||
if (maybe_retval.is_null()) {
|
||||
// TODO(clemensh): Exception handling here.
|
||||
UNIMPLEMENTED();
|
||||
auto result = HandleException(isolate);
|
||||
return result == WasmInterpreter::Thread::HANDLED;
|
||||
}
|
||||
|
||||
Handle<Object> retval = maybe_retval.ToHandleChecked();
|
||||
// TODO(clemensh): Call ToNumber on retval.
|
||||
// Pop arguments of the stack.
|
||||
@ -1859,6 +1882,7 @@ class ThreadImpl {
|
||||
DCHECK_EQ(1, called_fun->sig->return_count());
|
||||
stack_.push_back(NumberToWasmVal(retval, called_fun->sig->GetReturn()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1895,6 +1919,10 @@ WasmInterpreter::State WasmInterpreter::Thread::Step() {
|
||||
}
|
||||
void WasmInterpreter::Thread::Pause() { return ToImpl(this)->Pause(); }
|
||||
void WasmInterpreter::Thread::Reset() { return ToImpl(this)->Reset(); }
|
||||
WasmInterpreter::Thread::ExceptionHandlingResult
|
||||
WasmInterpreter::Thread::HandleException(Isolate* isolate) {
|
||||
return ToImpl(this)->HandleException(isolate);
|
||||
}
|
||||
pc_t WasmInterpreter::Thread::GetBreakpointPc() {
|
||||
return ToImpl(this)->GetBreakpointPc();
|
||||
}
|
||||
|
@ -113,11 +113,11 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
|
||||
// +---------------Run()-----------+
|
||||
// V |
|
||||
// STOPPED ---Run()--> RUNNING ------Pause()-----+-> PAUSED <------+
|
||||
// | | | / | |
|
||||
// | | +---- Breakpoint ---+ +-- Step() --+
|
||||
// | |
|
||||
// | +------------ Trap --------------> TRAPPED
|
||||
// +------------- Finish -------------> FINISHED
|
||||
// ^ | | | | / | |
|
||||
// +- HandleException -+ | | +--- Breakpoint ---+ +-- Step() --+
|
||||
// | |
|
||||
// | +---------- Trap --------------> TRAPPED
|
||||
// +----------- Finish -------------> FINISHED
|
||||
enum State { STOPPED, RUNNING, PAUSED, FINISHED, TRAPPED };
|
||||
|
||||
// Tells a thread to pause after certain instructions.
|
||||
@ -134,6 +134,8 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
|
||||
Thread() = delete;
|
||||
|
||||
public:
|
||||
enum ExceptionHandlingResult { HANDLED, UNWOUND };
|
||||
|
||||
// Execution control.
|
||||
State state();
|
||||
void InitFrame(const WasmFunction* function, WasmVal* args);
|
||||
@ -141,6 +143,9 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
|
||||
State Step();
|
||||
void Pause();
|
||||
void Reset();
|
||||
// Handle the pending exception in the passed isolate. Unwind the stack
|
||||
// accordingly. Return whether the exception was handled inside wasm.
|
||||
ExceptionHandlingResult HandleException(Isolate* isolate);
|
||||
|
||||
// Stack inspection and modification.
|
||||
pc_t GetBreakpointPc();
|
||||
|
@ -487,6 +487,10 @@ class WasmDebugInfo : public FixedArray {
|
||||
std::unique_ptr<wasm::InterpretedFrame> GetInterpretedFrame(
|
||||
Address frame_pointer, int idx);
|
||||
|
||||
// Unwind the interpreted stack belonging to the passed interpreter entry
|
||||
// frame.
|
||||
void Unwind(Address frame_pointer);
|
||||
|
||||
// Returns the number of calls / function frames executed in the interpreter.
|
||||
uint64_t NumInterpretedCalls();
|
||||
|
||||
|
@ -108,3 +108,34 @@ function checkStack(stack, expected_lines) {
|
||||
]);
|
||||
}
|
||||
})();
|
||||
|
||||
(function testThrowFromImport() {
|
||||
function func() {
|
||||
throw new Error('thrown from imported function');
|
||||
}
|
||||
var builder = new WasmModuleBuilder();
|
||||
builder.addImport("mod", "func", kSig_v_v);
|
||||
builder.addFunction('main', kSig_v_v)
|
||||
.addBody([kExprCallFunction, 0])
|
||||
.exportFunc();
|
||||
var 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);
|
||||
var stack;
|
||||
try {
|
||||
instance.exports.main();
|
||||
assertUnreachable();
|
||||
} catch (e) {
|
||||
stack = e.stack;
|
||||
}
|
||||
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\)/, // -
|
||||
/^ at interpreter.js:\d+:3$/
|
||||
]);
|
||||
}
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user