Commit 6e234e9d authored by Thibaud Michaud's avatar Thibaud Michaud Committed by Commit Bot

[wasm][liftoff][eh] Implement catch_all

Inline a catch handler after each potentially throwing call. The handler
just merges values into the actual catch environment and then jumps to
the catch body.

This automatically adds support for unwind, which also uses the
"CatchAll" interface method.

Many tests can be written either with "catch" or with "catch_all".
Duplicate them to get coverage for both.

R=clemensb@chromium.org

Bug: v8:11453
Change-Id: I789ad44b8d1e496f026157d5c37a12004a8b37e3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2726497Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73129}
parent 1b11278e
......@@ -347,7 +347,7 @@ void CheckBailoutAllowed(LiftoffBailoutReason reason, const char* detail,
return;
}
// TODO(v8:8091): Implement exception handling in Liftoff.
// TODO(v8:11453): Implement exception handling in Liftoff.
if (reason == kExceptionHandling) {
DCHECK(env->enabled_features.has_eh());
return;
......@@ -369,10 +369,19 @@ class LiftoffCompiler {
LiftoffAssembler::CacheState state;
};
struct TryInfo {
TryInfo() = default;
LiftoffAssembler::CacheState catch_state;
MovableLabel catch_label;
bool catch_reached = false;
int32_t previous_catch = -1;
};
struct Control : public ControlBase<Value, validate> {
std::unique_ptr<ElseState> else_state;
LiftoffAssembler::CacheState label_state;
MovableLabel label;
TryInfo* try_info = nullptr;
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(Control);
......@@ -474,7 +483,8 @@ class LiftoffCompiler {
safepoint_table_builder_(compilation_zone_),
next_breakpoint_ptr_(breakpoints.begin()),
next_breakpoint_end_(breakpoints.end()),
dead_breakpoint_(dead_breakpoint) {
dead_breakpoint_(dead_breakpoint),
handlers_(compilation_zone) {
if (breakpoints.empty()) {
next_breakpoint_ptr_ = next_breakpoint_end_ = nullptr;
}
......@@ -485,7 +495,7 @@ class LiftoffCompiler {
void GetCode(CodeDesc* desc) {
asm_.GetCode(nullptr, desc, &safepoint_table_builder_,
Assembler::kNoHandlerTable);
handler_table_offset_);
}
OwnedVector<uint8_t> GetSourcePositionTable() {
......@@ -905,6 +915,14 @@ class LiftoffCompiler {
__ PatchPrepareStackFrame(pc_offset_stack_frame_construction_);
__ FinishCode();
safepoint_table_builder_.Emit(&asm_, __ GetTotalFrameSlotCountForGC());
// Emit the handler table.
if (!handlers_.empty()) {
handler_table_offset_ = HandlerTable::EmitReturnTableStart(&asm_);
for (auto& handler : handlers_) {
HandlerTable::EmitReturnEntry(&asm_, handler.pc_offset,
handler.handler.get()->pos());
}
}
__ MaybeEmitOutOfLineConstantPool();
// The previous calls may have also generated a bailout.
DidAssemblerBailout(decoder);
......@@ -1031,7 +1049,9 @@ class LiftoffCompiler {
}
void Try(FullDecoder* decoder, Control* block) {
unsupported(decoder, kExceptionHandling, "try");
block->try_info = compilation_zone_->New<TryInfo>();
block->try_info->previous_catch = current_catch_;
current_catch_ = static_cast<int32_t>(decoder->control_depth() - 1);
}
void CatchException(FullDecoder* decoder,
......@@ -1049,7 +1069,22 @@ class LiftoffCompiler {
}
void CatchAll(FullDecoder* decoder, Control* block) {
unsupported(decoder, kExceptionHandling, "catch-all");
DCHECK(block->is_try_catchall() || block->is_try_catch() ||
block->is_try_unwind());
DCHECK_EQ(decoder->control_at(0), block);
current_catch_ = block->try_info->previous_catch; // Pop try scope.
// The catch block is unreachable if no possible throws in the try block
// exist. We only build a landing pad if some node in the try block can
// (possibly) throw. Otherwise the catch environments remain empty.
if (!block->try_info->catch_reached) {
decoder->SetSucceedingCodeDynamicallyUnreachable();
return;
}
__ bind(block->try_info->catch_label.get());
__ cache_state()->Steal(block->try_info->catch_state);
}
void If(FullDecoder* decoder, const Value& cond, Control* if_block) {
......@@ -1068,11 +1103,12 @@ class LiftoffCompiler {
}
void FallThruTo(FullDecoder* decoder, Control* c) {
if (c->end_merge.reached) {
__ MergeFullStackWith(c->label_state, *__ cache_state());
} else {
c->label_state.Split(*__ cache_state());
if (!c->end_merge.reached) {
c->label_state.InitMerge(*__ cache_state(), __ num_locals(),
c->end_merge.arity, c->stack_depth);
}
__ MergeFullStackWith(c->label_state, *__ cache_state());
__ emit_jump(c->label.get());
TraceCacheState(decoder);
}
......@@ -3617,6 +3653,33 @@ class LiftoffCompiler {
Store32BitExceptionValue(values_array, index_in_array, value.gp(), pinned);
}
void EmitLandingPad(FullDecoder* decoder) {
if (current_catch_ == -1) return;
MovableLabel handler;
int handler_offset = __ pc_offset();
// If we return from the throwing code normally, just skip over the handler.
Label skip_handler;
__ emit_jump(&skip_handler);
// Handler: merge into the catch state, and jump to the catch body.
__ bind(handler.get());
handlers_.push_back({std::move(handler), handler_offset});
Control* current_try =
decoder->control_at(decoder->control_depth() - 1 - current_catch_);
DCHECK_NOT_NULL(current_try->try_info);
if (!current_try->try_info->catch_reached) {
current_try->try_info->catch_state.InitMerge(
*__ cache_state(), __ num_locals(), 0,
current_try->try_info->catch_state.stack_height());
current_try->try_info->catch_reached = true;
}
__ MergeStackWith(current_try->try_info->catch_state, 0);
__ emit_jump(current_try->try_info->catch_label.get());
__ bind(&skip_handler);
}
void Throw(FullDecoder* decoder, const ExceptionIndexImmediate<validate>& imm,
const Vector<Value>& /* args */) {
LiftoffRegList pinned;
......@@ -3693,6 +3756,7 @@ class LiftoffCompiler {
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallRuntimeStub(WasmCode::kWasmThrow);
EmitLandingPad(decoder);
DefineSafepoint();
}
......@@ -4980,6 +5044,7 @@ class LiftoffCompiler {
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallIndirect(sig, call_descriptor, target);
EmitLandingPad(decoder);
}
} else {
// A direct call within this module just gets the current instance.
......@@ -4997,6 +5062,7 @@ class LiftoffCompiler {
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallNativeWasmCode(addr);
EmitLandingPad(decoder);
}
}
......@@ -5129,6 +5195,7 @@ class LiftoffCompiler {
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallIndirect(sig, call_descriptor, target);
EmitLandingPad(decoder);
}
DefineSafepoint();
......@@ -5339,6 +5406,7 @@ class LiftoffCompiler {
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallIndirect(sig, call_descriptor, target_reg);
EmitLandingPad(decoder);
}
DefineSafepoint();
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
......@@ -5500,6 +5568,17 @@ class LiftoffCompiler {
// at the first breakable opcode in the function (if compiling for debugging).
bool did_function_entry_break_checks_ = false;
// Depth of the current try block.
int32_t current_catch_ = -1;
struct HandlerInfo {
MovableLabel handler;
int pc_offset;
};
ZoneVector<HandlerInfo> handlers_;
int handler_table_offset_ = Assembler::kNoHandlerTable;
bool has_outstanding_op() const {
return outstanding_op_ != kNoOutstandingOp;
}
......
......@@ -96,6 +96,30 @@ WASM_EXEC_TEST(TryMultiCatchThrow) {
}
WASM_EXEC_TEST(TryCatchAllThrow) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint32_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build the main test function.
BUILD(r, kExprTry, static_cast<byte>((kWasmI32).value_type_code()),
WASM_STMTS(WASM_I32V(kResult1), WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_THROW(except))),
kExprCatchAll, WASM_I32V(kResult0), kExprEnd);
if (execution_tier != TestExecutionTier::kInterpreter) {
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
} else {
CHECK_EQ(kResult0, r.CallInterpreter(0));
CHECK_EQ(kResult1, r.CallInterpreter(1));
}
}
WASM_EXEC_TEST(TryCatchCatchAllThrow) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
WasmRunner<uint32_t, uint32_t> r(execution_tier);
......@@ -112,8 +136,8 @@ WASM_EXEC_TEST(TryCatchAllThrow) {
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)), WASM_THROW(except1)),
WASM_IF(WASM_I32_EQ(WASM_LOCAL_GET(0), WASM_I32V(1)),
WASM_THROW(except2))),
kExprCatch, except1, WASM_STMTS(WASM_I32V(kResult0)), kExprCatchAll,
WASM_STMTS(WASM_I32V(kResult1)), kExprEnd);
kExprCatch, except1, WASM_I32V(kResult0), kExprCatchAll,
WASM_I32V(kResult1), kExprEnd);
if (execution_tier != TestExecutionTier::kInterpreter) {
// Need to call through JS to allow for creation of stack traces.
......@@ -318,6 +342,39 @@ WASM_EXEC_TEST(TryCatchCallDirect) {
}
}
WASM_EXEC_TEST(TryCatchAllCallDirect) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint32_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build a throwing helper function.
WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
BUILD(throw_func, WASM_THROW(except));
// Build the main test function.
BUILD(r, WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_FUNCTION(
throw_func.function_index(),
WASM_I32V(7), WASM_I32V(9)),
WASM_DROP))),
WASM_STMTS(WASM_I32V(kResult0))));
if (execution_tier != TestExecutionTier::kInterpreter) {
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
} else {
CHECK_EQ(kResult0, r.CallInterpreter(0));
CHECK_EQ(kResult1, r.CallInterpreter(1));
}
}
WASM_EXEC_TEST(TryCatchCallIndirect) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
......@@ -348,7 +405,49 @@ WASM_EXEC_TEST(TryCatchCallIndirect) {
sig_index, WASM_I32V(7),
WASM_I32V(9), WASM_LOCAL_GET(0)),
WASM_DROP))),
WASM_STMTS(WASM_I32V(kResult0)), except));
WASM_I32V(kResult0), except));
if (execution_tier != TestExecutionTier::kInterpreter) {
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
} else {
CHECK_EQ(kResult0, r.CallInterpreter(0));
CHECK_EQ(kResult1, r.CallInterpreter(1));
}
}
WASM_EXEC_TEST(TryCatchAllCallIndirect) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint32_t except = r.builder().AddException(sigs.v_v());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build a throwing helper function.
WasmFunctionCompiler& throw_func = r.NewFunction(sigs.i_ii());
BUILD(throw_func, WASM_THROW(except));
byte sig_index = r.builder().AddSignature(sigs.i_ii());
throw_func.SetSigIndex(0);
// Add an indirect function table.
uint16_t indirect_function_table[] = {
static_cast<uint16_t>(throw_func.function_index())};
r.builder().AddIndirectFunctionTable(indirect_function_table,
arraysize(indirect_function_table));
// Build the main test function.
BUILD(r,
WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_INDIRECT(
sig_index, WASM_I32V(7),
WASM_I32V(9), WASM_LOCAL_GET(0)),
WASM_DROP))),
WASM_I32V(kResult0)));
if (execution_tier != TestExecutionTier::kInterpreter) {
// Need to call through JS to allow for creation of stack traces.
......@@ -383,7 +482,37 @@ WASM_COMPILED_EXEC_TEST(TryCatchCallExternal) {
WASM_STMTS(WASM_CALL_FUNCTION(kJSFunc, WASM_I32V(7),
WASM_I32V(9)),
WASM_DROP))),
WASM_STMTS(WASM_I32V(kResult0))));
WASM_I32V(kResult0)));
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
}
WASM_COMPILED_EXEC_TEST(TryCatchAllCallExternal) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
HandleScope scope(CcTest::InitIsolateOnce());
const char* source = "(function() { throw 'ball'; })";
Handle<JSFunction> js_function =
Handle<JSFunction>::cast(v8::Utils::OpenHandle(
*v8::Local<v8::Function>::Cast(CompileRun(source))));
ManuallyImportedJSFunction import = {sigs.i_ii(), js_function};
WasmRunner<uint32_t, uint32_t> r(execution_tier, &import);
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
constexpr uint32_t kJSFunc = 0;
// Build the main test function.
BUILD(r, WASM_TRY_CATCH_ALL_T(
kWasmI32,
WASM_STMTS(
WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_STMTS(WASM_CALL_FUNCTION(kJSFunc, WASM_I32V(7),
WASM_I32V(9)),
WASM_DROP))),
WASM_I32V(kResult0)));
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
......
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