Commit 30dd7b46 authored by Thibaud Michaud's avatar Thibaud Michaud Committed by Commit Bot

Reland "[wasm][liftoff][eh] Implement catch_all"

This is a reland of 6e234e9d
Fix CFI by adding a BTI instruction at the start of the handler.

Original change's description:
> [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/+/2726497
> Reviewed-by: Clemens Backes <clemensb@chromium.org>
> Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#73129}

Bug: v8:11453
Change-Id: I84d90877e6227a1966b6347877a9c18e213d9419
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2732023
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73184}
parent 29bac13e
......@@ -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);
}
......@@ -3619,6 +3655,34 @@ 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());
__ ExceptionHandler();
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;
......@@ -3695,6 +3759,7 @@ class LiftoffCompiler {
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallRuntimeStub(WasmCode::kWasmThrow);
EmitLandingPad(decoder);
DefineSafepoint();
}
......@@ -4982,6 +5047,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.
......@@ -4999,6 +5065,7 @@ class LiftoffCompiler {
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallNativeWasmCode(addr);
EmitLandingPad(decoder);
}
}
......@@ -5131,6 +5198,7 @@ class LiftoffCompiler {
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallIndirect(sig, call_descriptor, target);
EmitLandingPad(decoder);
}
DefineSafepoint();
......@@ -5341,6 +5409,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);
......@@ -5502,6 +5571,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