[vm] Fix JumpToFrame execution state transition

Instead of handling FFI related execution state and safepoint
in assembly handle it in runtime code.

The transition needs to be done before JumpToFrame unwinds
stack because unwinding destroys exit frame and this can't
be done at safepoint as GC might be traversing the stack.

An incorrect order of operation was manifesting as crashes in
GC when one isolate in a group was encountering a lot of
exceptions thrown from an FFI call and another isolate is
triggering GCs.

To catch this in the future added a bit of validation to
ExitSafepoint runtime call which triggers when --use-slow-path
is enabled. Though after refactoring this code does not
trigger this code path anymore because it was completely
removed - but it is better than nothing.

This CL also removes a lot of unnecessary complexity which
was associated with handling this transition in the stub
itself.

TEST=ffi/vmspecific_handle_test

Bug: b/408377905
Cq-Include-Trybots: dart/try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-asan-linux-release-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-msan-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-ubsan-linux-release-x64-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-x64c-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-arm64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-mac-debug-simarm64_arm64-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-arm64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-tsan-linux-release-arm64-try,vm-tsan-linux-release-x64-try,vm-ubsan-linux-release-arm64-try,vm-ubsan-linux-release-x64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try
Change-Id: Ia073cb6bb9e1b5a0ea8514c7e048cee6019b84d6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420324
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Vyacheslav Egorov
2025-04-07 05:22:56 -07:00
committed by Commit Queue
parent d33c6c3ef1
commit 8085a97a63
33 changed files with 2230 additions and 2450 deletions
+3 -15
View File
@@ -634,9 +634,7 @@ void Assembler::TransitionGeneratedToNative(Register destination_address,
}
}
void Assembler::ExitFullSafepoint(Register tmp1,
Register tmp2,
bool ignore_unwind_in_progress) {
void Assembler::ExitFullSafepoint(Register tmp1, Register tmp2) {
Register addr = tmp1;
Register state = tmp2;
@@ -664,14 +662,7 @@ void Assembler::ExitFullSafepoint(Register tmp1,
}
Bind(&slow_path);
if (ignore_unwind_in_progress) {
ldr(TMP,
Address(THR,
target::Thread::
exit_safepoint_ignore_unwind_in_progress_stub_offset()));
} else {
ldr(TMP, Address(THR, target::Thread::exit_safepoint_stub_offset()));
}
ldr(TMP, Address(THR, target::Thread::exit_safepoint_stub_offset()));
ldr(TMP, FieldAddress(TMP, target::Code::entry_point_offset()));
blx(TMP);
@@ -681,13 +672,10 @@ void Assembler::ExitFullSafepoint(Register tmp1,
void Assembler::TransitionNativeToGenerated(Register addr,
Register state,
bool exit_safepoint,
bool ignore_unwind_in_progress,
bool set_tag) {
if (exit_safepoint) {
ExitFullSafepoint(addr, state, ignore_unwind_in_progress);
ExitFullSafepoint(addr, state);
} else {
// flag only makes sense if we are leaving safepoint
ASSERT(!ignore_unwind_in_progress);
#if defined(DEBUG)
// Ensure we've already left the safepoint.
ASSERT(target::Thread::native_safepoint_state_acquired() != 0);
@@ -621,14 +621,11 @@ class Assembler : public AssemblerBase {
void TransitionNativeToGenerated(Register scratch0,
Register scratch1,
bool exit_safepoint,
bool ignore_unwind_in_progress = false,
bool set_tag = true);
void VerifyInGenerated(Register scratch);
void VerifyNotInGenerated(Register scratch);
void EnterFullSafepoint(Register scratch0, Register scratch1);
void ExitFullSafepoint(Register scratch0,
Register scratch1,
bool ignore_unwind_in_progress);
void ExitFullSafepoint(Register scratch0, Register scratch1);
// Miscellaneous instructions.
void clrex();
@@ -1619,8 +1619,7 @@ void Assembler::TransitionGeneratedToNative(Register destination,
}
}
void Assembler::ExitFullSafepoint(Register state,
bool ignore_unwind_in_progress) {
void Assembler::ExitFullSafepoint(Register state) {
// We generate the same number of instructions whether or not the slow-path is
// forced, for consistency with EnterFullSafepoint.
// For TSAN, we always go to the runtime so TSAN is aware of the acquire
@@ -1650,14 +1649,7 @@ void Assembler::ExitFullSafepoint(Register state,
}
Bind(&slow_path);
if (ignore_unwind_in_progress) {
ldr(addr,
Address(THR,
target::Thread::
exit_safepoint_ignore_unwind_in_progress_stub_offset()));
} else {
ldr(addr, Address(THR, target::Thread::exit_safepoint_stub_offset()));
}
ldr(addr, Address(THR, target::Thread::exit_safepoint_stub_offset()));
ldr(addr, FieldAddress(addr, target::Code::entry_point_offset()));
blr(addr);
@@ -1666,13 +1658,10 @@ void Assembler::ExitFullSafepoint(Register state,
void Assembler::TransitionNativeToGenerated(Register state,
bool exit_safepoint,
bool ignore_unwind_in_progress,
bool set_tag) {
if (exit_safepoint) {
ExitFullSafepoint(state, ignore_unwind_in_progress);
ExitFullSafepoint(state);
} else {
// flag only makes sense if we are leaving safepoint
ASSERT(!ignore_unwind_in_progress);
#if defined(DEBUG)
// Ensure we've already left the safepoint.
ASSERT(target::Thread::native_safepoint_state_acquired() != 0);
@@ -2111,12 +2111,11 @@ class Assembler : public AssemblerBase {
bool enter_safepoint);
void TransitionNativeToGenerated(Register scratch,
bool exit_safepoint,
bool ignore_unwind_in_progress = false,
bool set_tag = true);
void VerifyInGenerated(Register scratch);
void VerifyNotInGenerated(Register scratch);
void EnterFullSafepoint(Register scratch);
void ExitFullSafepoint(Register scratch, bool ignore_unwind_in_progress);
void ExitFullSafepoint(Register scratch);
void CheckCodePointer();
void RestoreCodePointer();
@@ -2537,8 +2537,7 @@ void Assembler::TransitionGeneratedToNative(Register destination_address,
}
}
void Assembler::ExitFullSafepoint(Register scratch,
bool ignore_unwind_in_progress) {
void Assembler::ExitFullSafepoint(Register scratch) {
ASSERT(scratch != EAX);
// We generate the same number of instructions whether or not the slow-path is
// forced, for consistency with EnterFullSafepoint.
@@ -2563,14 +2562,7 @@ void Assembler::ExitFullSafepoint(Register scratch,
}
Bind(&slow_path);
if (ignore_unwind_in_progress) {
movl(scratch,
Address(THR,
target::Thread::
exit_safepoint_ignore_unwind_in_progress_stub_offset()));
} else {
movl(scratch, Address(THR, target::Thread::exit_safepoint_stub_offset()));
}
movl(scratch, Address(THR, target::Thread::exit_safepoint_stub_offset()));
movl(scratch, FieldAddress(scratch, target::Code::entry_point_offset()));
call(scratch);
@@ -2579,13 +2571,10 @@ void Assembler::ExitFullSafepoint(Register scratch,
void Assembler::TransitionNativeToGenerated(Register scratch,
bool exit_safepoint,
bool ignore_unwind_in_progress,
bool set_tag) {
if (exit_safepoint) {
ExitFullSafepoint(scratch, ignore_unwind_in_progress);
ExitFullSafepoint(scratch);
} else {
// flag only makes sense if we are leaving safepoint
ASSERT(!ignore_unwind_in_progress);
#if defined(DEBUG)
// Ensure we've already left the safepoint.
movl(scratch, Address(THR, target::Thread::safepoint_state_offset()));
@@ -901,10 +901,9 @@ class Assembler : public AssemblerBase {
bool enter_safepoint);
void TransitionNativeToGenerated(Register scratch,
bool exit_safepoint,
bool ignore_unwind_in_progress = false,
bool set_tag = true);
void EnterFullSafepoint(Register scratch);
void ExitFullSafepoint(Register scratch, bool ignore_unwind_in_progress);
void ExitFullSafepoint(Register scratch);
// For non-leaf runtime calls. For leaf runtime calls, use LeafRuntimeScope,
void CallRuntime(const RuntimeEntry& entry, intptr_t argument_count);
@@ -4399,13 +4399,10 @@ void Assembler::TransitionGeneratedToNative(Register destination,
void Assembler::TransitionNativeToGenerated(Register state,
bool exit_safepoint,
bool ignore_unwind_in_progress,
bool set_tag) {
if (exit_safepoint) {
ExitFullSafepoint(state, ignore_unwind_in_progress);
ExitFullSafepoint(state);
} else {
// flag only makes sense if we are leaving safepoint
ASSERT(!ignore_unwind_in_progress);
#if defined(DEBUG)
// Ensure we've already left the safepoint.
ASSERT(target::Thread::native_safepoint_state_acquired() != 0);
@@ -4495,8 +4492,7 @@ void Assembler::EnterFullSafepoint(Register state) {
Bind(&done);
}
void Assembler::ExitFullSafepoint(Register state,
bool ignore_unwind_in_progress) {
void Assembler::ExitFullSafepoint(Register state) {
// We generate the same number of instructions whether or not the slow-path is
// forced, for consistency with EnterFullSafepoint.
// For TSAN, we always go to the runtime so TSAN is aware of the acquire
@@ -4524,14 +4520,7 @@ void Assembler::ExitFullSafepoint(Register state,
}
Bind(&slow_path);
if (ignore_unwind_in_progress) {
lx(addr,
Address(THR,
target::Thread::
exit_safepoint_ignore_unwind_in_progress_stub_offset()));
} else {
lx(addr, Address(THR, target::Thread::exit_safepoint_stub_offset()));
}
lx(addr, Address(THR, target::Thread::exit_safepoint_stub_offset()));
lx(addr, FieldAddress(addr, target::Code::entry_point_offset()));
jalr(addr);
@@ -1452,12 +1452,11 @@ class Assembler : public MicroAssembler {
bool enter_safepoint);
void TransitionNativeToGenerated(Register scratch,
bool exit_safepoint,
bool ignore_unwind_in_progress = false,
bool set_tag = true);
void VerifyInGenerated(Register scratch);
void VerifyNotInGenerated(Register scratch);
void EnterFullSafepoint(Register scratch);
void ExitFullSafepoint(Register scratch, bool ignore_unwind_in_progress);
void ExitFullSafepoint(Register scratch);
void CheckFpSpDist(intptr_t fp_sp_dist);
+4 -15
View File
@@ -216,7 +216,7 @@ void Assembler::TransitionGeneratedToNative(Register destination_address,
}
}
void Assembler::ExitFullSafepoint(bool ignore_unwind_in_progress) {
void Assembler::ExitFullSafepoint() {
// We generate the same number of instructions whether or not the slow-path is
// forced, for consistency with EnterFullSafepoint.
// For TSAN, we always go to the runtime so TSAN is aware of the acquire
@@ -243,14 +243,7 @@ void Assembler::ExitFullSafepoint(bool ignore_unwind_in_progress) {
}
Bind(&slow_path);
if (ignore_unwind_in_progress) {
movq(TMP,
Address(THR,
target::Thread::
exit_safepoint_ignore_unwind_in_progress_stub_offset()));
} else {
movq(TMP, Address(THR, target::Thread::exit_safepoint_stub_offset()));
}
movq(TMP, Address(THR, target::Thread::exit_safepoint_stub_offset()));
movq(TMP, FieldAddress(TMP, target::Code::entry_point_offset()));
// Use call instead of CallCFunction to avoid having to clean up shadow space
@@ -261,14 +254,10 @@ void Assembler::ExitFullSafepoint(bool ignore_unwind_in_progress) {
Bind(&done);
}
void Assembler::TransitionNativeToGenerated(bool exit_safepoint,
bool ignore_unwind_in_progress,
bool set_tag) {
void Assembler::TransitionNativeToGenerated(bool exit_safepoint, bool set_tag) {
if (exit_safepoint) {
ExitFullSafepoint(ignore_unwind_in_progress);
ExitFullSafepoint();
} else {
// flag only makes sense if we are leaving safepoint
ASSERT(!ignore_unwind_in_progress);
#if defined(DEBUG)
// Ensure we've already left the safepoint.
movq(TMP, Address(THR, target::Thread::safepoint_state_offset()));
@@ -319,14 +319,12 @@ class Assembler : public AssemblerBase {
void setcc(Condition condition, ByteRegister dst);
void EnterFullSafepoint();
void ExitFullSafepoint(bool ignore_unwind_in_progress);
void ExitFullSafepoint();
void TransitionGeneratedToNative(Register destination_address,
Register new_exit_frame,
Register new_exit_through_ffi,
bool enter_safepoint);
void TransitionNativeToGenerated(bool exit_safepoint,
bool ignore_unwind_in_progress = false,
bool set_tag = true);
void TransitionNativeToGenerated(bool exit_safepoint, bool set_tag = true);
void VerifyInGenerated(Register scratch);
void VerifyNotInGenerated(Register scratch);
-1
View File
@@ -1876,7 +1876,6 @@ void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// The callback trampoline (caller) has already left the safepoint for us.
__ TransitionNativeToGenerated(/*scratch0=*/R0, /*scratch1=*/R1,
/*exit_safepoint=*/false,
/*ignore_unwind_in_progress=*/false,
/*set_tag=*/false);
// Now that the safepoint has ended, we can touch Dart objects without
-1
View File
@@ -1639,7 +1639,6 @@ void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// The callback trampoline (caller) has already left the safepoint for us.
__ TransitionNativeToGenerated(R0, /*exit_safepoint=*/false,
/*ignore_unwind_in_progress=*/false,
/*set_tag=*/false);
// Now that the safepoint has ended, we can touch Dart objects without
-1
View File
@@ -1294,7 +1294,6 @@ void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// The callback trampoline (caller) has already left the safepoint for us.
__ TransitionNativeToGenerated(EAX, /*exit_safepoint=*/false,
/*ignore_unwind_in_progress=*/false,
/*set_tag=*/false);
// Now that the safepoint has ended, we can hold Dart objects with bare hands.
-1
View File
@@ -1729,7 +1729,6 @@ void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// The callback trampoline (caller) has already left the safepoint for us.
__ TransitionNativeToGenerated(A0, /*exit_safepoint=*/false,
/*ignore_unwind_in_progress=*/false,
/*set_tag=*/false);
// Now that the safepoint has ended, we can touch Dart objects without
-1
View File
@@ -1474,7 +1474,6 @@ void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// The callback trampoline (caller) has already left the safepoint for us.
__ TransitionNativeToGenerated(/*exit_safepoint=*/false,
/*ignore_unwind_in_progress=*/false,
/*set_tag=*/false);
// Load the code object.
-1
View File
@@ -1258,7 +1258,6 @@ class Thread : public AllStatic {
static word deoptimize_stub_offset();
static word enter_safepoint_stub_offset();
static word exit_safepoint_stub_offset();
static word exit_safepoint_ignore_unwind_in_progress_stub_offset();
static word call_native_through_safepoint_stub_offset();
static word call_native_through_safepoint_entry_point_offset();
File diff suppressed because it is too large Load Diff
@@ -271,7 +271,6 @@
FIELD(Thread, enter_safepoint_stub_offset) \
FIELD(Thread, execution_state_offset) \
FIELD(Thread, exit_safepoint_stub_offset) \
FIELD(Thread, exit_safepoint_ignore_unwind_in_progress_stub_offset) \
FIELD(Thread, call_native_through_safepoint_stub_offset) \
FIELD(Thread, call_native_through_safepoint_entry_point_offset) \
FIELD(Thread, fix_allocation_stub_code_offset) \
+3 -34
View File
@@ -222,8 +222,7 @@ void StubCodeCompiler::GenerateEnterSafepointStub() {
__ Ret();
}
static void GenerateExitSafepointStubCommon(Assembler* assembler,
uword runtime_entry_offset) {
void StubCodeCompiler::GenerateExitSafepointStub() {
RegisterSet all_registers;
all_registers.AddAllGeneralRegisters();
__ PushRegisters(all_registers);
@@ -233,7 +232,7 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ VerifyNotInGenerated(R0);
__ ldr(R0, Address(THR, runtime_entry_offset));
__ ldr(R0, Address(THR, kExitSafepointRuntimeEntry.OffsetFromThread()));
__ blx(R0);
RESTORES_LR_FROM_FRAME(__ LeaveFrame((1 << FP) | (1 << LR), 0));
@@ -241,17 +240,6 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ Ret();
}
void StubCodeCompiler::GenerateExitSafepointStub() {
GenerateExitSafepointStubCommon(
assembler, kExitSafepointRuntimeEntry.OffsetFromThread());
}
void StubCodeCompiler::GenerateExitSafepointIgnoreUnwindInProgressStub() {
GenerateExitSafepointStubCommon(
assembler,
kExitSafepointIgnoreUnwindInProgressRuntimeEntry.OffsetFromThread());
}
// Call a native function within a safepoint.
//
// On entry:
@@ -3055,32 +3043,13 @@ void StubCodeCompiler::GenerateJumpToFrameStub() {
COMPILE_ASSERT(kStackTraceObjectReg == R1);
COMPILE_ASSERT(IsAbiPreservedRegister(R4));
COMPILE_ASSERT(IsAbiPreservedRegister(THR));
__ mov(IP, Operand(R1)); // Copy Stack pointer into IP.
// TransitionGeneratedToNative might clobber LR if it takes the slow path.
__ mov(R4, Operand(R0)); // Program counter.
__ mov(THR, Operand(R3)); // Thread.
__ mov(FP, Operand(R2)); // Frame_pointer.
__ mov(SP, Operand(IP)); // Set Stack pointer.
__ mov(SP, Operand(R1)); // Set Stack pointer.
#if defined(USING_SHADOW_CALL_STACK)
#error Unimplemented
#endif
Label exit_through_non_ffi;
Register tmp1 = R0, tmp2 = R1;
// Check if we exited generated from FFI. If so do transition - this is needed
// because normally runtime calls transition back to generated via destructor
// of TransitionGeneratedToVM/Native that is part of runtime boilerplate
// code (see DEFINE_RUNTIME_ENTRY_IMPL in runtime_entry.h). Ffi calls don't
// have this boilerplate, don't have this stack resource, have to transition
// explicitly.
__ LoadFromOffset(tmp1, THR,
compiler::target::Thread::exit_through_ffi_offset());
__ LoadImmediate(tmp2, target::Thread::exit_through_ffi());
__ cmp(tmp1, Operand(tmp2));
__ b(&exit_through_non_ffi, NE);
__ TransitionNativeToGenerated(tmp1, tmp2,
/*exit_safepoint=*/true,
/*ignore_unwind_in_progress=*/true);
__ Bind(&exit_through_non_ffi);
// Set the tag.
__ LoadImmediate(R2, VMTag::kDartTagId);
@@ -360,8 +360,7 @@ void StubCodeCompiler::GenerateEnterSafepointStub() {
__ Ret();
}
static void GenerateExitSafepointStubCommon(Assembler* assembler,
uword runtime_entry_offset) {
void StubCodeCompiler::GenerateExitSafepointStub() {
RegisterSet all_registers;
all_registers.AddAllGeneralRegisters();
@@ -375,7 +374,7 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ VerifyNotInGenerated(R0);
__ ldr(R0, Address(THR, runtime_entry_offset));
__ ldr(R0, Address(THR, kExitSafepointRuntimeEntry.OffsetFromThread()));
__ blr(R0);
__ mov(SP, CALLEE_SAVED_TEMP2);
@@ -387,17 +386,6 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ Ret();
}
void StubCodeCompiler::GenerateExitSafepointStub() {
GenerateExitSafepointStubCommon(
assembler, kExitSafepointRuntimeEntry.OffsetFromThread());
}
void StubCodeCompiler::GenerateExitSafepointIgnoreUnwindInProgressStub() {
GenerateExitSafepointStubCommon(
assembler,
kExitSafepointIgnoreUnwindInProgressRuntimeEntry.OffsetFromThread());
}
// Calls native code within a safepoint.
//
// On entry:
@@ -3453,22 +3441,6 @@ void StubCodeCompiler::GenerateJumpToFrameStub() {
#elif defined(USING_SHADOW_CALL_STACK)
#error Unimplemented
#endif
Label exit_through_non_ffi;
Register tmp1 = R0, tmp2 = R1;
// Check if we exited generated from FFI. If so do transition - this is needed
// because normally runtime calls transition back to generated via destructor
// of TransitionGeneratedToVM/Native that is part of runtime boilerplate
// code (see DEFINE_RUNTIME_ENTRY_IMPL in runtime_entry.h). Ffi calls don't
// have this boilerplate, don't have this stack resource, have to transition
// explicitly.
__ LoadFromOffset(tmp1, THR,
compiler::target::Thread::exit_through_ffi_offset());
__ LoadImmediate(tmp2, target::Thread::exit_through_ffi());
__ cmp(tmp1, Operand(tmp2));
__ b(&exit_through_non_ffi, NE);
__ TransitionNativeToGenerated(tmp1, /*exit_safepoint=*/true,
/*ignore_unwind_in_progress=*/true);
__ Bind(&exit_through_non_ffi);
// Refresh pinned registers (write barrier mask, null, dispatch table, etc).
__ RestorePinnedRegisters();
+2 -29
View File
@@ -155,8 +155,7 @@ void StubCodeCompiler::GenerateEnterSafepointStub() {
__ ret();
}
static void GenerateExitSafepointStubCommon(Assembler* assembler,
uword runtime_entry_offset) {
void StubCodeCompiler::GenerateExitSafepointStub() {
__ pushal();
__ subl(SPREG, Immediate(8));
__ movsd(Address(SPREG, 0), XMM0);
@@ -164,7 +163,7 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ EnterFrame(0);
__ ReserveAlignedFrameSpace(0);
__ movl(EAX, Address(THR, runtime_entry_offset));
__ movl(EAX, Address(THR, kExitSafepointRuntimeEntry.OffsetFromThread()));
__ call(EAX);
__ LeaveFrame();
@@ -174,17 +173,6 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ ret();
}
void StubCodeCompiler::GenerateExitSafepointStub() {
GenerateExitSafepointStubCommon(
assembler, kExitSafepointRuntimeEntry.OffsetFromThread());
}
void StubCodeCompiler::GenerateExitSafepointIgnoreUnwindInProgressStub() {
GenerateExitSafepointStubCommon(
assembler,
kExitSafepointIgnoreUnwindInProgressRuntimeEntry.OffsetFromThread());
}
void StubCodeCompiler::GenerateLoadBSSEntry(BSS::Relocation relocation,
Register dst,
Register tmp) {
@@ -2960,21 +2948,6 @@ void StubCodeCompiler::GenerateJumpToFrameStub() {
#error Unimplemented
#endif
Label exit_through_non_ffi;
// Check if we exited generated from FFI. If so do transition - this is needed
// because normally runtime calls transition back to generated via destructor
// of TransitionGeneratedToVM/Native that is part of runtime boilerplate
// code (see DEFINE_RUNTIME_ENTRY_IMPL in runtime_entry.h). Ffi calls don't
// have this boilerplate, don't have this stack resource, have to transition
// explicitly.
__ cmpl(compiler::Address(
THR, compiler::target::Thread::exit_through_ffi_offset()),
compiler::Immediate(target::Thread::exit_through_ffi()));
__ j(NOT_EQUAL, &exit_through_non_ffi, compiler::Assembler::kNearJump);
__ TransitionNativeToGenerated(ECX, /*exit_safepoint=*/true,
/*ignore_unwind_in_progress=*/true);
__ Bind(&exit_through_non_ffi);
// Set tag.
__ movl(Assembler::VMTagAddress(), Immediate(VMTag::kDartTagId));
// Clear top exit frame.
@@ -229,8 +229,7 @@ void StubCodeCompiler::GenerateEnterSafepointStub() {
__ ret();
}
static void GenerateExitSafepointStubCommon(Assembler* assembler,
uword runtime_entry_offset) {
void StubCodeCompiler::GenerateExitSafepointStub() {
RegisterSet all_registers;
all_registers.AddAllGeneralRegisters();
@@ -241,7 +240,7 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ VerifyNotInGenerated(TMP);
__ lx(TMP, Address(THR, runtime_entry_offset));
__ lx(TMP, Address(THR, kExitSafepointRuntimeEntry.OffsetFromThread()));
__ jalr(TMP);
__ LeaveFrame();
@@ -249,17 +248,6 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ ret();
}
void StubCodeCompiler::GenerateExitSafepointStub() {
GenerateExitSafepointStubCommon(
assembler, kExitSafepointRuntimeEntry.OffsetFromThread());
}
void StubCodeCompiler::GenerateExitSafepointIgnoreUnwindInProgressStub() {
GenerateExitSafepointStubCommon(
assembler,
kExitSafepointIgnoreUnwindInProgressRuntimeEntry.OffsetFromThread());
}
// Calls native code within a safepoint.
//
// On entry:
@@ -2926,20 +2914,6 @@ void StubCodeCompiler::GenerateJumpToFrameStub() {
#elif defined(USING_SHADOW_CALL_STACK)
#error Unimplemented
#endif
Label exit_through_non_ffi;
// Check if we exited generated from FFI. If so do transition - this is needed
// because normally runtime calls transition back to generated via destructor
// of TransitionGeneratedToVM/Native that is part of runtime boilerplate
// code (see DEFINE_RUNTIME_ENTRY_IMPL in runtime_entry.h). Ffi calls don't
// have this boilerplate, don't have this stack resource, have to transition
// explicitly.
__ LoadFromOffset(TMP, THR,
compiler::target::Thread::exit_through_ffi_offset());
__ LoadImmediate(TMP2, target::Thread::exit_through_ffi());
__ bne(TMP, TMP2, &exit_through_non_ffi);
__ TransitionNativeToGenerated(TMP, /*exit_safepoint=*/true,
/*ignore_unwind_in_progress=*/true);
__ Bind(&exit_through_non_ffi);
// Refresh pinned registers values (inc. write barrier mask and null object).
__ RestorePinnedRegisters();
+2 -28
View File
@@ -339,8 +339,7 @@ void StubCodeCompiler::GenerateEnterSafepointStub() {
__ ret();
}
static void GenerateExitSafepointStubCommon(Assembler* assembler,
uword runtime_entry_offset) {
void StubCodeCompiler::GenerateExitSafepointStub() {
RegisterSet all_registers;
all_registers.AddAllGeneralRegisters();
__ PushRegisters(all_registers);
@@ -350,7 +349,7 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ VerifyNotInGenerated(RAX);
__ movq(RAX, Address(THR, runtime_entry_offset));
__ movq(RAX, Address(THR, kExitSafepointRuntimeEntry.OffsetFromThread()));
__ CallCFunction(RAX);
__ LeaveFrame();
@@ -358,17 +357,6 @@ static void GenerateExitSafepointStubCommon(Assembler* assembler,
__ ret();
}
void StubCodeCompiler::GenerateExitSafepointStub() {
GenerateExitSafepointStubCommon(
assembler, kExitSafepointRuntimeEntry.OffsetFromThread());
}
void StubCodeCompiler::GenerateExitSafepointIgnoreUnwindInProgressStub() {
GenerateExitSafepointStubCommon(
assembler,
kExitSafepointIgnoreUnwindInProgressRuntimeEntry.OffsetFromThread());
}
// Calls native code within a safepoint.
//
// On entry:
@@ -3356,20 +3344,6 @@ void StubCodeCompiler::GenerateJumpToFrameStub() {
#if defined(USING_SHADOW_CALL_STACK)
#error Unimplemented
#endif
Label exit_through_non_ffi;
// Check if we exited generated from FFI. If so do transition - this is needed
// because normally runtime calls transition back to generated via destructor
// of TransitionGeneratedToVM/Native that is part of runtime boilerplate
// code (see DEFINE_RUNTIME_ENTRY_IMPL in runtime_entry.h). Ffi calls don't
// have this boilerplate, don't have this stack resource, have to transition
// explicitly.
__ cmpq(compiler::Address(
THR, compiler::target::Thread::exit_through_ffi_offset()),
compiler::Immediate(target::Thread::exit_through_ffi()));
__ j(NOT_EQUAL, &exit_through_non_ffi, compiler::Assembler::kNearJump);
__ TransitionNativeToGenerated(/*exit_safepoint=*/true,
/*ignore_unwind_in_progress=*/true);
__ Bind(&exit_through_non_ffi);
// Set the tag.
__ movq(Assembler::VMTagAddress(), Immediate(VMTag::kDartTagId));
+27 -4
View File
@@ -619,6 +619,33 @@ NO_SANITIZE_SAFE_STACK // This function manipulates the safestack pointer.
(clear_deopt_at_target ? frame_pointer + 1 : frame_pointer);
ClearLazyDeopts(thread, fp_for_clearing);
// Prepare for unwinding frames by destroying all the stack resources
// in the previous frames.
StackResource::Unwind(thread);
// If execution exited generated code through FFI then exit the safepoint
// and transition back to kThreadInGenerated execution state. JumpToFrame
// stub will transfer control directly to the exception handler and bypass
// inlined transition code which follows the FFI callsite.
//
// For contrast, runtime calls perform transition by entering
// the |TransitionGeneratedToVM| scope in the runtime entry itself
// (see DEFINE_RUNTIME_ENTRY_IMPL boilerplate in runtime_entry.h). This scope
// will be destroyed by |StackResource::Unwind| above and execution state
// will transition to kThreadInGenerated as a side-effect of that.
//
// Important: thread must exit safepoint before |JumpToFrame| is called
// because the stub will unwind the stack and thus destroy the exit frame,
// which can only happen outside of safepoint - as GC otherwise might try
// to use it to traverse the stack.
if (thread->exit_through_ffi() == Thread::kExitThroughFfi) {
// StackResource::Unwind above should have left us in the Native state by
// destroying appropriate TransitionNativeToVM.
ASSERT(thread->execution_state() == Thread::kThreadInNative);
thread->ExitSafepointFromNative();
thread->set_execution_state(Thread::kThreadInGenerated);
}
#if defined(USING_SIMULATOR)
// Unwinding of the C++ frames and destroying of their stack resources is done
// by the simulator, because the target stack_pointer is a simulated stack
@@ -632,10 +659,6 @@ NO_SANITIZE_SAFE_STACK // This function manipulates the safestack pointer.
frame_pointer, thread);
#else
// Prepare for unwinding frames by destroying all the stack resources
// in the previous frames.
StackResource::Unwind(thread);
// Unpoison the stack before we tear it down in the generated stub code.
uword current_sp = OSThread::GetCurrentStackPointer() - 1024;
ASAN_UNPOISON(reinterpret_cast<void*>(current_sp),
-5
View File
@@ -3928,11 +3928,6 @@ void Interpreter::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) {
ASSERT(buf != nullptr);
ASSERT(last_setjmp_buffer() == buf);
// The C++ caller has not cleaned up the stack memory of C++ frames.
// Prepare for unwinding frames by destroying all the stack resources
// in the previous C++ frames.
StackResource::Unwind(thread);
fp_ = reinterpret_cast<ObjectPtr*>(fp);
if (pc == StubCode::RunExceptionHandler().EntryPoint()) {
-27
View File
@@ -4377,33 +4377,6 @@ DEFINE_RAW_LEAF_RUNTIME_ENTRY(ExitSafepoint,
/*is_float=*/false,
DFLRT_ExitSafepoint);
// This is expected to be invoked when jumping to destination frame,
// during exception handling.
extern "C" void DFLRT_ExitSafepointIgnoreUnwindInProgress(
NativeArguments __unusable_) {
CHECK_STACK_ALIGNMENT;
TRACE_RUNTIME_CALL("%s", "ExitSafepointIgnoreUnwindInProgress");
Thread* thread = Thread::Current();
ASSERT(thread->top_exit_frame_info() != 0);
// Compared to ExitSafepoint above we are going to ignore
// is_unwind_in_progress flag because this is called as part of JumpToFrame
// exception handler - we want this transition to complete so that the next
// safepoint check does error propagation.
if (thread->execution_state() == Thread::kThreadInNative) {
thread->ExitSafepointFromNative();
} else {
ASSERT(thread->execution_state() == Thread::kThreadInVM);
thread->ExitSafepoint();
}
TRACE_RUNTIME_CALL("%s", "ExitSafepointIgnoreUnwindInProgress done");
}
DEFINE_RAW_LEAF_RUNTIME_ENTRY(ExitSafepointIgnoreUnwindInProgress,
/*argument_count=*/0,
/*is_float*/ false,
DFLRT_ExitSafepointIgnoreUnwindInProgress);
// This is called by a native callback trampoline
// (see StubCodeCompiler::GenerateFfiCallbackTrampolineStub). Not registered as
// a runtime entry because we can't use Thread to look it up.
-1
View File
@@ -115,7 +115,6 @@ namespace dart {
uword /*SmiPtr*/, uword /*SmiPtr*/, uword /*SmiPtr*/) \
V(void, EnterSafepoint) \
V(void, ExitSafepoint) \
V(void, ExitSafepointIgnoreUnwindInProgress) \
V(ApiLocalScope*, EnterHandleScope, Thread*) \
V(void, ExitHandleScope, Thread*) \
V(LocalHandle*, AllocateHandle, ApiLocalScope*) \
-5
View File
@@ -3680,11 +3680,6 @@ void Simulator::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) {
}
ASSERT(buf != nullptr);
// The C++ caller has not cleaned up the stack memory of C++ frames.
// Prepare for unwinding frames by destroying all the stack resources
// in the previous C++ frames.
StackResource::Unwind(thread);
// Keep the following code in sync with `StubCode::JumpToFrameStub()`.
// Unwind the C++ stack and continue simulation in the target frame.
-11
View File
@@ -4088,17 +4088,6 @@ void Simulator::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) {
// Keep the following code in sync with `StubCode::JumpToFrameStub()`.
// Check if we exited generated from FFI. If so do transition - this is needed
// because normally runtime calls transition back to generated via destructor
// of TransitionGeneratedToVM/Native that is part of runtime boilerplate
// code (see DEFINE_RUNTIME_ENTRY_IMPL in runtime_entry.h). Ffi calls don't
// have this boilerplate, don't have this stack resource, have to transition
// explicitly.
if (thread->exit_through_ffi() == dart::Thread::kExitThroughFfi) {
thread->ExitSafepointFromNative();
thread->set_execution_state(Thread::kThreadInGenerated);
}
// Unwind the C++ stack and continue simulation in the target frame.
set_pc(static_cast<int64_t>(pc));
set_register(nullptr, SP, static_cast<int64_t>(sp));
-1
View File
@@ -152,7 +152,6 @@ namespace dart {
V(OneArgOptimizedCheckInlineCacheWithExactnessCheck) \
V(EnterSafepoint) \
V(ExitSafepoint) \
V(ExitSafepointIgnoreUnwindInProgress) \
V(CallNativeThroughSafepoint) \
V(FfiCallbackTrampoline) \
V(InitStaticField) \
+55
View File
@@ -1381,6 +1381,61 @@ void Thread::HandleStolen() {
/*was_stolen=*/true);
}
#ifndef PRODUCT
namespace {
// This visitor simply dereferences every non-Smi |ObjectPtr| it visits and
// checks that its class id is valid.
//
// It is used for fast validation of pointers on the stack: if a pointer is
// invalid it is likely to either cause a crash when dereferenced or have
// a garbage class id.
class FastPointerValidator : public ObjectPointerVisitor {
public:
explicit FastPointerValidator(IsolateGroup* isolate_group)
: ObjectPointerVisitor(isolate_group) {}
void VisitPointers(ObjectPtr* from, ObjectPtr* to) override {
for (ObjectPtr* ptr = from; ptr <= to; ptr++) {
const auto cid = (*ptr)->GetClassId();
RELEASE_ASSERT(class_table()->IsValidIndex(cid));
}
}
#if defined(DART_COMPRESSED_POINTERS)
void VisitCompressedPointers(uword heap_base,
CompressedObjectPtr* first,
CompressedObjectPtr* last) override {
// We are not expecting compressed pointers on the stack.
UNREACHABLE();
}
#endif
private:
DISALLOW_COPY_AND_ASSIGN(FastPointerValidator);
};
} // namespace
void Thread::ValidateExitFrameState() {
if (top_exit_frame_info() == 0) {
return;
}
FastPointerValidator fast_pointers_validator(isolate_group());
StackFrameIterator frames_iterator(
top_exit_frame_info(), ValidationPolicy::kValidateFrames, this,
StackFrameIterator::kNoCrossThreadIteration);
StackFrame* frame = frames_iterator.NextFrame();
while (frame != nullptr) {
frame->VisitObjectPointers(&fast_pointers_validator);
frame = frames_iterator.NextFrame();
}
}
#endif
void Thread::EnterSafepointUsingLock() {
isolate_group()->safepoint_handler()->EnterSafepointUsingLock(this);
}
+21 -2
View File
@@ -175,8 +175,6 @@ class Thread;
StubCode::LazySpecializeTypeTest().ptr(), nullptr) \
V(CodePtr, enter_safepoint_stub_, StubCode::EnterSafepoint().ptr(), nullptr) \
V(CodePtr, exit_safepoint_stub_, StubCode::ExitSafepoint().ptr(), nullptr) \
V(CodePtr, exit_safepoint_ignore_unwind_in_progress_stub_, \
StubCode::ExitSafepointIgnoreUnwindInProgress().ptr(), nullptr) \
V(CodePtr, call_native_through_safepoint_stub_, \
StubCode::CallNativeThroughSafepoint().ptr(), nullptr)
@@ -1170,6 +1168,14 @@ class Thread : public ThreadState {
// of a safepoint operation.
ExitSafepointUsingLock();
}
#ifndef PRODUCT
// Exit frame must have remained valid for the whole duration of the
// safepoint. Do some quick checks to validate that.
if (FLAG_use_slow_path) {
ValidateExitFrameState();
}
#endif
}
bool TryExitSafepointFromNative() {
@@ -1195,9 +1201,22 @@ class Thread : public ThreadState {
ASSERT(!ActiveMutatorStealableField::decode(safepoint_state_));
ASSERT(!ActiveMutatorStolenField::decode(safepoint_state_));
#ifndef PRODUCT
// Exit frame must have remained valid for the whole duration of the
// safepoint. Do some quick checks to validate that.
if (FLAG_use_slow_path) {
ValidateExitFrameState();
}
#endif
}
void HandleStolen();
#ifndef PRODUCT
void ValidateExitFrameState();
#endif
void CheckForSafepoint() {
// If we are in a runtime call that doesn't support lazy deopt, we will only
// respond to gc safepointing requests.
+2
View File
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
//
// SharedObjects=ffi_test_functions
// VMOptions=
// VMOptions=--use_slow_path
import 'dart:ffi';