Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit afc54e2

Browse files
sjindel-googlecommit-bot@chromium.org
authored andcommitted
[vm/ffi] FFI callbacks on ARM64.
For design context and motivation, see go/dart-ffi-callbacks Change-Id: Ie463a462c8676c4a1973f377acee067253aca9f0 Cq-Include-Trybots: luci.dart.try:vm-kernel-linux-debug-simdbc64-try, vm-kernel-linux-release-simdbc64-try, vm-kernel-mac-debug-simdbc64-try, vm-kernel-mac-release-simdbc64-try, vm-kernel-reload-mac-debug-simdbc64-try, vm-kernel-reload-mac-release-simdbc64-try, vm-kernel-linux-debug-ia32-try, vm-dartkb-linux-debug-simarm64-try, vm-kernel-win-debug-ia32-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/101825 Commit-Queue: Samir Jindel <[email protected]> Reviewed-by: Aart Bik <[email protected]> Reviewed-by: Daco Harkes <[email protected]>
1 parent 0a5e599 commit afc54e2

File tree

14 files changed

+291
-50
lines changed

14 files changed

+291
-50
lines changed

runtime/lib/ffi.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,12 @@ static uword CompileNativeCallback(const Function& c_signature,
552552
const Function& dart_target) {
553553
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
554554
UNREACHABLE();
555-
#elif !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_IA32)
555+
#elif !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_IA32) && \
556+
!defined(TARGET_ARCH_ARM64)
556557
// https://github.com/dart-lang/sdk/issues/35774
557558
// FFI is supported, but callbacks are not.
558559
Exceptions::ThrowUnsupportedError(
559-
"FFI callbacks are currently supported on Intel only.");
560+
"FFI callbacks are currently supported on Intel and 64-bit ARM only.");
560561
#else
561562
Thread* const thread = Thread::Current();
562563
const int32_t callback_id = thread->AllocateFfiCallbackId();

runtime/vm/compiler/assembler/assembler_arm64.cc

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,23 @@ void Assembler::ReserveAlignedFrameSpace(intptr_t frame_space) {
11641164
}
11651165
}
11661166

1167+
void Assembler::EmitEntryFrameVerification() {
1168+
#if defined(DEBUG)
1169+
Label done;
1170+
ASSERT(!constant_pool_allowed());
1171+
LoadImmediate(TMP,
1172+
compiler::target::frame_layout.exit_link_slot_from_entry_fp *
1173+
compiler::target::kWordSize);
1174+
add(TMP, TMP, Operand(FPREG));
1175+
cmp(TMP, Operand(SPREG));
1176+
b(&done, EQ);
1177+
1178+
Breakpoint();
1179+
1180+
Bind(&done);
1181+
#endif
1182+
}
1183+
11671184
void Assembler::RestoreCodePointer() {
11681185
ldr(CODE_REG, Address(FP, compiler::target::frame_layout.code_from_fp *
11691186
target::kWordSize));
@@ -1295,11 +1312,12 @@ void Assembler::LeaveDartFrame(RestorePP restore_pp) {
12951312
}
12961313

12971314
void Assembler::TransitionGeneratedToNative(Register destination,
1315+
Register new_exit_rame,
12981316
Register state) {
12991317
Register addr = TMP2;
13001318

13011319
// Save exit frame information to enable stack walking.
1302-
StoreToOffset(FPREG, THR,
1320+
StoreToOffset(new_exit_rame, THR,
13031321
compiler::target::Thread::top_exit_frame_info_offset());
13041322

13051323
// Mark that the thread is executing native code.
@@ -1826,6 +1844,41 @@ void Assembler::PopRegisters(const RegisterSet& regs) {
18261844
}
18271845
}
18281846

1847+
void Assembler::PushNativeCalleeSavedRegisters() {
1848+
// Save the callee-saved registers.
1849+
for (int i = kAbiFirstPreservedCpuReg; i <= kAbiLastPreservedCpuReg; i++) {
1850+
const Register r = static_cast<Register>(i);
1851+
// We use str instead of the Push macro because we will be pushing the PP
1852+
// register when it is not holding a pool-pointer since we are coming from
1853+
// C++ code.
1854+
str(r, Address(SP, -1 * target::kWordSize, Address::PreIndex));
1855+
}
1856+
1857+
// Save the bottom 64-bits of callee-saved V registers.
1858+
for (int i = kAbiFirstPreservedFpuReg; i <= kAbiLastPreservedFpuReg; i++) {
1859+
const VRegister r = static_cast<VRegister>(i);
1860+
PushDouble(r);
1861+
}
1862+
}
1863+
1864+
void Assembler::PopNativeCalleeSavedRegisters() {
1865+
// Restore the bottom 64-bits of callee-saved V registers.
1866+
for (int i = kAbiLastPreservedFpuReg; i >= kAbiFirstPreservedFpuReg; i--) {
1867+
const VRegister r = static_cast<VRegister>(i);
1868+
PopDouble(r);
1869+
}
1870+
1871+
// Restore C++ ABI callee-saved registers.
1872+
for (int i = kAbiLastPreservedCpuReg; i >= kAbiFirstPreservedCpuReg; i--) {
1873+
Register r = static_cast<Register>(i);
1874+
// We use ldr instead of the Pop macro because we will be popping the PP
1875+
// register when it is not holding a pool-pointer since we are returning to
1876+
// C++ code. We also skip the dart stack pointer SP, since we are still
1877+
// using it as the stack pointer.
1878+
ldr(r, Address(SP, 1 * target::kWordSize, Address::PostIndex));
1879+
}
1880+
}
1881+
18291882
} // namespace compiler
18301883

18311884
} // namespace dart

runtime/vm/compiler/assembler/assembler_arm64.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,12 @@ class Assembler : public AssemblerBase {
438438
void PushRegisters(const RegisterSet& registers);
439439
void PopRegisters(const RegisterSet& registers);
440440

441+
// Push all registers which are callee-saved according to the ARM64 ABI.
442+
void PushNativeCalleeSavedRegisters();
443+
444+
// Pop all registers which are callee-saved according to the ARM64 ABI.
445+
void PopNativeCalleeSavedRegisters();
446+
441447
void MoveRegister(Register rd, Register rn) {
442448
if (rd != rn) {
443449
mov(rd, rn);
@@ -481,6 +487,11 @@ class Assembler : public AssemblerBase {
481487

482488
void ReserveAlignedFrameSpace(intptr_t frame_space);
483489

490+
// In debug mode, this generates code to check that:
491+
// FP + kExitLinkSlotFromEntryFp == SP
492+
// or triggers breakpoint otherwise.
493+
void EmitEntryFrameVerification();
494+
484495
// Instruction pattern from entrypoint is used in Dart frame prologs
485496
// to set up the frame and save a PC which can be used to figure out the
486497
// RawInstruction object corresponding to the code running in the frame.
@@ -1524,9 +1535,13 @@ class Assembler : public AssemblerBase {
15241535
void LeaveFrame();
15251536
void Ret() { ret(LR); }
15261537

1527-
// These require that CSP and SP are equal and aligned.
1528-
// These require a scratch register (in addition to TMP/TMP2).
1538+
// Emit code to transition between generated mode and native mode.
1539+
//
1540+
// These require that CSP and SP are equal and aligned and require a scratch
1541+
// register (in addition to TMP/TMP2).
1542+
15291543
void TransitionGeneratedToNative(Register destination_address,
1544+
Register new_exit_frame,
15301545
Register scratch);
15311546
void TransitionNativeToGenerated(Register scratch);
15321547

runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,8 +1160,8 @@ void FlowGraphCompiler::EmitOptimizedStaticCall(
11601160
}
11611161
// Do not use the code from the function, but let the code be patched so that
11621162
// we can record the outgoing edges to other code.
1163-
GenerateStaticDartCall(deopt_id, token_pos,
1164-
RawPcDescriptors::kOther, locs, function);
1163+
GenerateStaticDartCall(deopt_id, token_pos, RawPcDescriptors::kOther, locs,
1164+
function);
11651165
__ Drop(count_with_type_args);
11661166
}
11671167

@@ -1311,6 +1311,10 @@ void FlowGraphCompiler::EmitMove(Location destination,
13111311
if (destination.IsRegister()) {
13121312
const intptr_t source_offset = source.ToStackSlotOffset();
13131313
__ LoadFromOffset(destination.reg(), source.base_reg(), source_offset);
1314+
} else if (destination.IsFpuRegister()) {
1315+
const intptr_t src_offset = source.ToStackSlotOffset();
1316+
VRegister dst = destination.fpu_reg();
1317+
__ LoadDFromOffset(dst, source.base_reg(), src_offset);
13141318
} else {
13151319
ASSERT(destination.IsStackSlot());
13161320
const intptr_t source_offset = source.ToStackSlotOffset();

runtime/vm/compiler/backend/il.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3960,7 +3960,8 @@ LocationSummary* NativeEntryInstr::MakeLocationSummary(Zone* zone,
39603960
UNREACHABLE();
39613961
}
39623962

3963-
#if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_IA32)
3963+
#if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_IA32) && \
3964+
!defined(TARGET_ARCH_ARM64)
39643965
void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
39653966
UNREACHABLE();
39663967
}
@@ -4079,8 +4080,8 @@ void NativeParameterInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
40794080
#if !defined(TARGET_ARCH_DBC)
40804081
// The native entry frame has size -kExitLinkSlotFromFp. In order to access
40814082
// the top of stack from above the entry frame, we add a constant to account
4082-
// for the the two frame pointers and return address of the entry frame.
4083-
constexpr intptr_t kEntryFramePadding = 3;
4083+
// for the the two frame pointers and two return addresses of the entry frame.
4084+
constexpr intptr_t kEntryFramePadding = 4;
40844085
FrameRebase rebase(/*old_base=*/SPREG, /*new_base=*/FPREG,
40854086
-kExitLinkSlotFromEntryFp + kEntryFramePadding);
40864087
const Location dst = locs()->out(0);

runtime/vm/compiler/backend/il_arm64.cc

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,7 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
916916
__ mov(CSP, SP);
917917

918918
// Update information in the thread object and enter a safepoint.
919-
__ TransitionGeneratedToNative(branch, temp);
919+
__ TransitionGeneratedToNative(branch, FPREG, temp);
920920

921921
__ blr(branch);
922922

@@ -944,6 +944,177 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
944944
__ set_constant_pool_allowed(true);
945945
}
946946

947+
void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
948+
__ LeaveDartFrame();
949+
950+
// The dummy return address is in LR, no need to pop it as on Intel.
951+
952+
// These can be anything besides the return register (R0).
953+
const Register vm_tag_reg = R1, old_exit_frame_reg = R2, tmp = R3;
954+
955+
__ Pop(old_exit_frame_reg);
956+
957+
// Restore top_resource.
958+
__ Pop(tmp);
959+
__ StoreToOffset(tmp, THR, compiler::target::Thread::top_resource_offset());
960+
961+
__ Pop(vm_tag_reg);
962+
963+
// Reset the exit frame info to
964+
// old_exit_frame_reg *before* entering the safepoint.
965+
__ TransitionGeneratedToNative(vm_tag_reg, old_exit_frame_reg, tmp);
966+
967+
__ PopNativeCalleeSavedRegisters();
968+
969+
// Leave the entry frame.
970+
__ LeaveFrame();
971+
972+
// Leave the dummy frame holding the pushed arguments.
973+
__ LeaveFrame();
974+
975+
// Restore the actual stack pointer from SPREG.
976+
__ RestoreCSP();
977+
978+
__ Ret();
979+
980+
// For following blocks.
981+
__ set_constant_pool_allowed(true);
982+
}
983+
984+
void NativeEntryInstr::SaveArgument(FlowGraphCompiler* compiler,
985+
Location loc) const {
986+
ASSERT(!loc.IsPairLocation());
987+
988+
if (loc.HasStackIndex()) return;
989+
990+
if (loc.IsRegister()) {
991+
__ Push(loc.reg());
992+
} else if (loc.IsFpuRegister()) {
993+
__ PushDouble(loc.fpu_reg());
994+
} else {
995+
UNREACHABLE();
996+
}
997+
}
998+
999+
void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
1000+
if (FLAG_precompiled_mode) {
1001+
UNREACHABLE();
1002+
}
1003+
1004+
// Constant pool cannot be used until we enter the actual Dart frame.
1005+
__ set_constant_pool_allowed(false);
1006+
1007+
__ Bind(compiler->GetJumpLabel(this));
1008+
1009+
// We don't use the regular stack pointer in ARM64, so we have to copy the
1010+
// native stack pointer into the Dart stack pointer.
1011+
__ SetupDartSP();
1012+
1013+
// Create a dummy frame holding the pushed arguments. This simplifies
1014+
// NativeReturnInstr::EmitNativeCode.
1015+
__ EnterFrame(0);
1016+
1017+
// Save the argument registers, in reverse order.
1018+
for (intptr_t i = argument_locations_->length(); i-- > 0;) {
1019+
SaveArgument(compiler, argument_locations_->At(i));
1020+
}
1021+
1022+
// Enter the entry frame.
1023+
__ EnterFrame(0);
1024+
1025+
// Save a space for the code object.
1026+
__ PushImmediate(0);
1027+
1028+
__ PushNativeCalleeSavedRegisters();
1029+
1030+
// Load the thread object.
1031+
// TODO(35765): Fix linking issue on AOT.
1032+
// TOOD(35934): Exclude native callbacks from snapshots.
1033+
//
1034+
// Create another frame to align the frame before continuing in "native" code.
1035+
{
1036+
__ EnterFrame(0);
1037+
__ ReserveAlignedFrameSpace(0);
1038+
1039+
__ LoadImmediate(
1040+
R0, reinterpret_cast<int64_t>(DLRT_GetThreadForNativeCallback));
1041+
__ blr(R0);
1042+
__ mov(THR, R0);
1043+
1044+
__ LeaveFrame();
1045+
}
1046+
1047+
// Refresh write barrier mask.
1048+
__ ldr(BARRIER_MASK,
1049+
Address(THR, compiler::target::Thread::write_barrier_mask_offset()));
1050+
1051+
// Save the current VMTag on the stack.
1052+
__ LoadFromOffset(R0, THR, compiler::target::Thread::vm_tag_offset());
1053+
__ Push(R0);
1054+
1055+
// Save the top resource.
1056+
__ LoadFromOffset(R0, THR, compiler::target::Thread::top_resource_offset());
1057+
__ Push(R0);
1058+
__ StoreToOffset(ZR, THR, compiler::target::Thread::top_resource_offset());
1059+
1060+
// Save the top exit frame info. We don't set it to 0 yet in Thread because we
1061+
// need to leave the safepoint first.
1062+
__ LoadFromOffset(R0, THR,
1063+
compiler::target::Thread::top_exit_frame_info_offset());
1064+
__ Push(R0);
1065+
1066+
// In debug mode, verify that we've pushed the top exit frame info at the
1067+
// correct offset from FP.
1068+
__ EmitEntryFrameVerification();
1069+
1070+
// TransitionNativeToGenerated will reset top exit frame info to 0 *after*
1071+
// leaving the safepoint.
1072+
__ TransitionNativeToGenerated(R0);
1073+
1074+
// Now that the safepoint has ended, we can touch Dart objects without
1075+
// handles.
1076+
1077+
// Otherwise we'll clobber the argument sent from the caller.
1078+
ASSERT(CallingConventions::ArgumentRegisters[0] != TMP &&
1079+
CallingConventions::ArgumentRegisters[0] != TMP2 &&
1080+
CallingConventions::ArgumentRegisters[0] != R1);
1081+
__ LoadImmediate(CallingConventions::ArgumentRegisters[0], callback_id_);
1082+
__ LoadFromOffset(
1083+
R1, THR,
1084+
compiler::target::Thread::verify_callback_isolate_entry_point_offset());
1085+
__ blr(R1);
1086+
1087+
// Load the code object.
1088+
__ LoadFromOffset(R0, THR, compiler::target::Thread::callback_code_offset());
1089+
__ LoadFieldFromOffset(R0, R0,
1090+
compiler::target::GrowableObjectArray::data_offset());
1091+
__ LoadFieldFromOffset(CODE_REG, R0,
1092+
compiler::target::Array::data_offset() +
1093+
callback_id_ * compiler::target::kWordSize);
1094+
1095+
// Put the code object in the reserved slot.
1096+
__ StoreToOffset(CODE_REG, FPREG,
1097+
kPcMarkerSlotFromFp * compiler::target::kWordSize);
1098+
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
1099+
__ ldr(PP,
1100+
Address(THR, compiler::target::Thread::global_object_pool_offset()));
1101+
__ sub(PP, PP, Operand(kHeapObjectTag)); // Pool in PP is untagged!
1102+
} else {
1103+
// We now load the pool pointer (PP) with a GC safe value as we are about to
1104+
// invoke dart code. We don't need a real object pool here.
1105+
// Smi zero does not work because ARM64 assumes PP to be untagged.
1106+
__ LoadObject(PP, compiler::NullObject());
1107+
}
1108+
1109+
// Load a dummy return address which suggests that we are inside of
1110+
// InvokeDartCodeStub. This is how the stack walker detects an entry frame.
1111+
__ LoadFromOffset(LR, THR,
1112+
compiler::target::Thread::invoke_dart_code_stub_offset());
1113+
__ LoadFieldFromOffset(LR, LR, compiler::target::Code::entry_point_offset());
1114+
1115+
FunctionEntryInstr::EmitNativeCode(compiler);
1116+
}
1117+
9471118
LocationSummary* OneByteStringFromCharCodeInstr::MakeLocationSummary(
9481119
Zone* zone,
9491120
bool opt) const {

runtime/vm/compiler/backend/il_x64.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,9 @@ void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
10121012
SaveArgument(compiler, argument_locations_->At(i));
10131013
}
10141014

1015-
// Enter the entry frame.
1015+
// Enter the entry frame. Push a dummy return address for consistency with
1016+
// EnterFrame on ARM(64).
1017+
__ PushImmediate(Immediate(0));
10161018
__ EnterFrame(0);
10171019

10181020
// Save a space for the code object.

runtime/vm/compiler/frontend/scope_builder.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
392392
// Callbacks need try/catch variables.
393393
if (function.IsFfiTrampoline() &&
394394
function.FfiCallbackTarget() != Function::null()) {
395+
current_function_async_marker_ = FunctionNodeHelper::kSync;
395396
++depth_.try_;
396397
AddTryVariables();
397398
--depth_.try_;

0 commit comments

Comments
 (0)