[vm/shared] Introduce NativeCallable.isolateGroupShared

This method allows for synchronous execution of dart callbacks from native code. The execution happens on dart mutator thread, from which dart code can only access isolate-group variables - those which are tagged with .

Bug: https://github.com/dart-lang/sdk/issues/54530
Bug: https://github.com/dart-lang/sdk/issues/56841
Change-Id: Ia1a6b01327be493f003f1eea82e558bb6b147dd3
CoreLibraryReviewExempt: only internal library change
TEST=isolate_group_shared_callback_test
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422920
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Alexander Aprelev
2025-05-06 13:28:44 -07:00
committed by Commit Queue
parent 7431e885e8
commit 50e0e0d99d
44 changed files with 1225 additions and 121 deletions
+2
View File
@@ -49,6 +49,7 @@ group("runtime") {
"runtime/vm:kernel_platform_files($host_toolchain)",
"samples/embedder:kernel",
"samples/ffi/http:fake_http",
"samples/ffi/httpIG:fake_httpIG",
"utils/dartdev:dartdev",
"utils/kernel-service:kernel-service",
]
@@ -308,6 +309,7 @@ if (is_fuchsia) {
"tests/ffi/inline_array_test.dart",
"tests/ffi/inline_array_variable_length_test.dart",
"tests/ffi/invoke_callback_after_suspension_test.dart",
"tests/ffi/isolate_group_shared_callback_test.dart",
"tests/ffi/isolate_local_function_callbacks_test.dart",
"tests/ffi/msan_test.dart",
"tests/ffi/native_assets/asset_absolute_test.dart",
@@ -313,7 +313,10 @@ class FfiTransformer extends Transformer {
final Procedure nativeCallbackFunctionProcedure;
final Procedure nativeAsyncCallbackFunctionProcedure;
final Procedure createNativeCallableIsolateLocalProcedure;
final Procedure createNativeCallableIsolateGroupSharedProcedure;
final Procedure nativeIsolateLocalCallbackFunctionProcedure;
final Procedure nativeIsolateGroupSharedCallbackFunctionProcedure;
final Procedure nativeIsolateGroupSharedClosureFunctionProcedure;
final Map<NativeType, Procedure> loadMethods;
final Map<NativeType, Procedure> loadUnalignedMethods;
final Map<NativeType, Procedure> storeMethods;
@@ -337,7 +340,9 @@ class FfiTransformer extends Transformer {
final Class rawRecvPortClass;
final Class nativeCallableClass;
final Procedure nativeCallableIsolateLocalConstructor;
final Procedure nativeCallableIsolateGroupSharedConstructor;
final Constructor nativeCallablePrivateIsolateLocalConstructor;
final Constructor nativeCallablePrivateIsolateGroupSharedConstructor;
final Procedure nativeCallableListenerConstructor;
final Constructor nativeCallablePrivateListenerConstructor;
final Field nativeCallablePortField;
@@ -821,6 +826,11 @@ class FfiTransformer extends Transformer {
'dart:ffi',
'_createNativeCallableIsolateLocal',
),
createNativeCallableIsolateGroupSharedProcedure = index
.getTopLevelProcedure(
'dart:ffi',
'_createNativeCallableIsolateGroupShared',
),
nativeCallbackFunctionProcedure = index.getTopLevelProcedure(
'dart:ffi',
'_nativeCallbackFunction',
@@ -833,6 +843,16 @@ class FfiTransformer extends Transformer {
'dart:ffi',
'_nativeIsolateLocalCallbackFunction',
),
nativeIsolateGroupSharedCallbackFunctionProcedure = index
.getTopLevelProcedure(
'dart:ffi',
'_nativeIsolateGroupSharedCallbackFunction',
),
nativeIsolateGroupSharedClosureFunctionProcedure = index
.getTopLevelProcedure(
'dart:ffi',
'_nativeIsolateGroupSharedClosureFunction',
),
nativeTypesClasses = nativeTypeClassNames.map(
(nativeType, name) =>
MapEntry(nativeType, index.getClass('dart:ffi', name)),
@@ -937,6 +957,11 @@ class FfiTransformer extends Transformer {
'NativeCallable',
'isolateLocal',
),
nativeCallableIsolateGroupSharedConstructor = index.getProcedure(
'dart:ffi',
'NativeCallable',
'isolateGroupShared',
),
nativeCallablePrivateIsolateLocalConstructor = index.getConstructor(
'dart:ffi',
'_NativeCallableIsolateLocal',
@@ -952,6 +977,11 @@ class FfiTransformer extends Transformer {
'_NativeCallableListener',
'',
),
nativeCallablePrivateIsolateGroupSharedConstructor = index.getConstructor(
'dart:ffi',
'_NativeCallableIsolateGroupShared',
'',
),
nativeCallablePortField = index.getField(
'dart:ffi',
'_NativeCallableListener',
@@ -570,6 +570,11 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
);
} else if (target == nativeCallableIsolateLocalConstructor) {
return _verifyAndReplaceNativeCallableIsolateLocal(node);
} else if (target == nativeCallableIsolateGroupSharedConstructor) {
return _verifyAndReplaceNativeCallable(
node,
replacement: _replaceNativeCallableIsolateGroupSharedConstructor,
);
} else if (target == nativeCallableListenerConstructor) {
final DartType nativeType = InterfaceType(
nativeFunctionClass,
@@ -881,18 +886,18 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
}
// NativeCallable<T>.isolateLocal(target, exceptionalReturn) calls become:
// isStaticFunction is false:
// _NativeCallableIsolateLocal<T>(
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeIsolateLocalCallbackFunction<T>(exceptionalReturn),
// target,
// true));
// isStaticFunction is true:
// _NativeCallableIsolateLocal<T>(
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeCallbackFunction<T>(target, exceptionalReturn),
// null,
// true);
// isStaticFunction is false:
// _NativeCallableIsolateLocal<T>(
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeIsolateLocalCallbackFunction<T>(exceptionalReturn),
// target,
// true));
Expression _replaceNativeCallableIsolateLocalConstructor(
StaticInvocation node,
Expression exceptionalReturn,
@@ -949,7 +954,7 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
// void _handler(List args) => target(args[0], args[1], ...)
// final _callback = _NativeCallableListener<T>(_handler, debugName);
// _callback._pointer = _createNativeCallableListener<NativeFunction<T>>(
// _nativeAsyncCallbackFunction<T>(), _callback._rawPort);
// _nativeAsyncCallbackFunction<T>(), _callback._port);
// expression result: _callback;
Expression _replaceNativeCallableListenerConstructor(StaticInvocation node) {
final nativeFunctionType = InterfaceType(
@@ -1017,7 +1022,7 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
)..fileOffset = node.fileOffset;
// _callback._pointer = _createNativeCallableListener<NativeFunction<T>>(
// _nativeAsyncCallbackFunction<T>(), _callback._rawPort);
// _nativeAsyncCallbackFunction<T>(), _callback._port);
final pointerValue = StaticInvocation(
createNativeCallableListenerProcedure,
Arguments(
@@ -1054,9 +1059,73 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
);
}
Expression _verifyAndReplaceNativeCallableIsolateLocal(
// NativeCallable<T>.isolateGroupShared(target, exceptionalReturn) calls become:
// isStaticFunction is true:
// _NativeCallableIsolateGroupShared<T>(
// _createNativeCallableIsolateGroupShared<NativeFunction<T>>(
// _nativeIsolateGroupSharedCallbackFunction<T>(target, exceptionalReturn),
// null);
// isStaticFunction is false:
// _NativeCallableIsolateGroupShared<T>(
// _createNativeCallableIsolateGroupShared<NativeFunction<T>>(
// _nativeIsolateGroupSharedClosureFunction<T>(exceptionalReturn),
// target));
Expression _replaceNativeCallableIsolateGroupSharedConstructor(
StaticInvocation node,
Expression exceptionalReturn,
bool isStaticFunction,
) {
final nativeFunctionType = InterfaceType(
nativeFunctionClass,
currentLibrary.nonNullable,
node.arguments.types,
);
final target = node.arguments.positional[0];
late StaticInvocation pointerValue;
if (isStaticFunction) {
pointerValue = StaticInvocation(
createNativeCallableIsolateGroupSharedProcedure,
Arguments(
[
StaticInvocation(
nativeIsolateGroupSharedCallbackFunctionProcedure,
Arguments([
target,
exceptionalReturn,
], types: node.arguments.types),
),
NullLiteral(),
],
types: [nativeFunctionType],
),
);
} else {
pointerValue = StaticInvocation(
createNativeCallableIsolateGroupSharedProcedure,
Arguments(
[
StaticInvocation(
nativeIsolateGroupSharedClosureFunctionProcedure,
Arguments([exceptionalReturn], types: node.arguments.types),
),
target,
],
types: [nativeFunctionType],
),
);
}
return ConstructorInvocation(
nativeCallablePrivateIsolateGroupSharedConstructor,
Arguments([pointerValue], types: node.arguments.types),
);
}
Expression _verifyAndReplaceNativeCallable(
StaticInvocation node, {
bool fromFunction = false,
required Expression Function(StaticInvocation, Expression, bool)
replacement,
}) {
final DartType nativeType = InterfaceType(
nativeFunctionClass,
@@ -1188,15 +1257,6 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
}
}
final replacement =
fromFunction
? _replaceFromFunction(node, exceptionalReturn)
: _replaceNativeCallableIsolateLocalConstructor(
node,
exceptionalReturn,
isStaticFunction,
);
final compoundClasses =
funcType.positionalParameters
.whereType<InterfaceType>()
@@ -1205,7 +1265,25 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
(c) => c.superclass == structClass || c.superclass == unionClass,
)
.toList();
return invokeCompoundConstructors(replacement, compoundClasses);
return invokeCompoundConstructors(
replacement(node, exceptionalReturn, isStaticFunction),
compoundClasses,
);
}
Expression _verifyAndReplaceNativeCallableIsolateLocal(
StaticInvocation node, {
bool fromFunction = false,
}) {
return _verifyAndReplaceNativeCallable(
node,
fromFunction: fromFunction,
replacement:
fromFunction
? (node, exceptionalReturn, _) =>
_replaceFromFunction(node, exceptionalReturn)
: _replaceNativeCallableIsolateLocalConstructor,
);
}
Expression _replaceGetRef(StaticInvocation node) {
+1
View File
@@ -78,6 +78,7 @@ workspace:
- pkg/wasm_builder
- runtime/tools/profiling
- samples/ffi/http
- samples/ffi/httpIG
# dap and language_server_protocol are checked in to and
# developed in the SDK repo, though they are located in `third_party/`.
- third_party/pkg/dap
@@ -66,6 +66,12 @@ DART_EXPORT Coord GetGlobalStruct() {
return globalStruct;
}
DART_EXPORT void SleepFor(int32_t ms) {
std::cout << "Sleeping for " << ms << " milliseconds...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
std::cout << "done\n";
}
// Sums two ints and adds 42.
// Simple function to test trampolines.
// Also used for testing argument exception on passing null instead of a Dart
+8
View File
@@ -46,6 +46,14 @@ DEFINE_NATIVE_ENTRY(Ffi_createNativeCallableIsolateLocal, 1, 3) {
zone, trampoline, target, keep_isolate_alive));
}
DEFINE_NATIVE_ENTRY(Ffi_createNativeCallableIsolateGroupShared, 1, 2) {
const auto& trampoline =
Function::CheckedHandle(zone, arguments->NativeArg0());
const auto& target = Closure::CheckedHandle(zone, arguments->NativeArgAt(1));
return Pointer::New(
isolate->CreateIsolateGroupSharedFfiCallback(zone, trampoline, target));
}
DEFINE_NATIVE_ENTRY(Ffi_deleteNativeCallable, 1, 1) {
const auto& pointer = Pointer::CheckedHandle(zone, arguments->NativeArg0());
isolate->DeleteFfiCallback(pointer.NativeAddress());
+1
View File
@@ -300,6 +300,7 @@ namespace dart {
V(VMService_RemoveUserTagsFromStreamableSampleList, 1) \
V(Ffi_createNativeCallableListener, 2) \
V(Ffi_createNativeCallableIsolateLocal, 3) \
V(Ffi_createNativeCallableIsolateGroupShared, 2) \
V(Ffi_deleteNativeCallable, 1) \
V(Ffi_updateNativeCallableKeepIsolateAliveCounter, 1) \
V(Ffi_dl_open, 1) \
+3
View File
@@ -35,6 +35,9 @@ void BSS::Initialize(Thread* current, uword* bss_start, bool vm) {
InitializeBSSEntry(Relocation::DLRT_ExitTemporaryIsolate,
reinterpret_cast<uword>(DLRT_ExitTemporaryIsolate),
bss_start);
InitializeBSSEntry(
Relocation::DLRT_ExitIsolateGroupSharedIsolate,
reinterpret_cast<uword>(DLRT_ExitIsolateGroupSharedIsolate), bss_start);
}
} // namespace dart
+1
View File
@@ -18,6 +18,7 @@ class BSS : public AllStatic {
enum class Relocation : intptr_t {
DLRT_GetFfiCallbackMetadata, // TODO(https://dartbug.com/52579): Remove.
DLRT_ExitTemporaryIsolate, // TODO(https://dartbug.com/52579): Remove.
DLRT_ExitIsolateGroupSharedIsolate, // TODO(https://dartbug.com/52579)
EndOfVmEntries,
// We don't have any isolate group specific entries at the moment.
+6
View File
@@ -28,6 +28,12 @@ const String& NativeCallbackFunctionName(Thread* thread,
return String::Handle(
zone, Symbols::FromConcat(thread, Symbols::FfiCallback(),
String::Handle(zone, dart_target.name())));
case FfiCallbackKind::kIsolateGroupSharedClosureCallback:
return Symbols::FfiIsolateGroupSharedCallback();
case FfiCallbackKind::kIsolateGroupSharedStaticCallback:
return String::Handle(
zone, Symbols::FromConcat(thread, Symbols::FfiCallback(),
String::Handle(zone, dart_target.name())));
default:
UNREACHABLE();
}
@@ -3390,6 +3390,12 @@ Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) {
case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction:
return BuildFfiNativeCallbackFunction(
FfiCallbackKind::kIsolateLocalClosureCallback);
case MethodRecognizer::kFfiNativeIsolateGroupSharedCallbackFunction:
return BuildFfiNativeCallbackFunction(
FfiCallbackKind::kIsolateGroupSharedStaticCallback);
case MethodRecognizer::kFfiNativeIsolateGroupSharedClosureFunction:
return BuildFfiNativeCallbackFunction(
FfiCallbackKind::kIsolateGroupSharedClosureCallback);
case MethodRecognizer::kFfiNativeAsyncCallbackFunction:
return BuildFfiNativeCallbackFunction(FfiCallbackKind::kAsyncCallback);
case MethodRecognizer::kFfiLoadAbiSpecificInt:
@@ -6210,6 +6216,9 @@ Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction(
// FfiCallbackKind::kIsolateLocalStaticCallback:
// _nativeCallbackFunction<NativeSignatureType>(target, exceptionalReturn)
//
// FfiCallbackKind::kIsolateGroupSharedStaticCallback:
// _nativeCallbackFunction<NativeSignatureType>(target, exceptionalReturn)
//
// FfiCallbackKind::kAsyncCallback:
// _nativeAsyncCallbackFunction<NativeSignatureType>()
//
@@ -6217,9 +6226,15 @@ Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction(
// _nativeIsolateLocalCallbackFunction<NativeSignatureType>(
// exceptionalReturn)
//
// FfiCallbackKind::kIsolateGroupSharedClosureCallback:
// _nativeIsolateGroupSharedCallbackFunction<NativeSignatureType>(
// exceptionalReturn)
//
// The FE also guarantees that the arguments are constants.
const bool has_target = kind == FfiCallbackKind::kIsolateLocalStaticCallback;
const bool has_target =
kind == FfiCallbackKind::kIsolateLocalStaticCallback ||
kind == FfiCallbackKind::kIsolateGroupSharedStaticCallback;
const bool has_exceptional_return = kind != FfiCallbackKind::kAsyncCallback;
const intptr_t expected_argc =
static_cast<int>(has_target) + static_cast<int>(has_exceptional_return);
+12 -3
View File
@@ -1059,6 +1059,8 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
case MethodRecognizer::kFfiNativeCallbackFunction:
case MethodRecognizer::kFfiNativeAsyncCallbackFunction:
case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction:
case MethodRecognizer::kFfiNativeIsolateGroupSharedCallbackFunction:
case MethodRecognizer::kFfiNativeIsolateGroupSharedClosureFunction:
case MethodRecognizer::kFfiStoreInt8:
case MethodRecognizer::kFfiStoreInt16:
case MethodRecognizer::kFfiStoreInt32:
@@ -1549,7 +1551,9 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
break;
case MethodRecognizer::kFfiNativeCallbackFunction:
case MethodRecognizer::kFfiNativeAsyncCallbackFunction:
case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction: {
case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction:
case MethodRecognizer::kFfiNativeIsolateGroupSharedCallbackFunction:
case MethodRecognizer::kFfiNativeIsolateGroupSharedClosureFunction: {
const auto& error = String::ZoneHandle(
Z, Symbols::New(thread_,
"This function should be handled on call site."));
@@ -5258,7 +5262,9 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline(
const Function& function) {
switch (function.GetFfiCallbackKind()) {
case FfiCallbackKind::kIsolateLocalStaticCallback:
case FfiCallbackKind::kIsolateGroupSharedStaticCallback:
case FfiCallbackKind::kIsolateLocalClosureCallback:
case FfiCallbackKind::kIsolateGroupSharedClosureCallback:
return BuildGraphOfSyncFfiCallback(function);
case FfiCallbackKind::kAsyncCallback:
return BuildGraphOfAsyncFfiCallback(function);
@@ -5569,8 +5575,11 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfSyncFfiCallback(
RELEASE_ASSERT(error == nullptr);
RELEASE_ASSERT(marshaller_ptr != nullptr);
const auto& marshaller = *marshaller_ptr;
const bool is_closure = function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateLocalClosureCallback;
const bool is_closure =
function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateLocalClosureCallback ||
function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateGroupSharedClosureCallback;
graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
@@ -120,6 +120,10 @@ namespace dart {
FfiNativeAsyncCallbackFunction, 0xbdd1a333) \
V(FfiLibrary, ::, _nativeIsolateLocalCallbackFunction, \
FfiNativeIsolateLocalCallbackFunction, 0x21b66eba) \
V(FfiLibrary, ::, _nativeIsolateGroupSharedCallbackFunction, \
FfiNativeIsolateGroupSharedCallbackFunction, 0x8882d3ca) \
V(FfiLibrary, ::, _nativeIsolateGroupSharedClosureFunction, \
FfiNativeIsolateGroupSharedClosureFunction, 0x2c93f675) \
V(FfiLibrary, ::, _loadAbiSpecificInt, FfiLoadAbiSpecificInt, 0x6abf70a6) \
V(FfiLibrary, ::, _loadAbiSpecificIntAtIndex, FfiLoadAbiSpecificIntAtIndex, \
0xc188dd75) \
@@ -391,6 +391,7 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
__ PopRegisters(argument_registers);
Label async_callback;
Label sync_isolate_group_shared_callback;
Label done;
// If GetFfiCallbackMetadata returned a null thread, it means that the async
@@ -404,6 +405,11 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
Operand(static_cast<uword>(FfiCallbackMetadata::TrampolineType::kAsync)));
__ b(&async_callback, EQ);
__ cmp(R4,
Operand(static_cast<uword>(
FfiCallbackMetadata::TrampolineType::kSyncIsolateGroupShared)));
__ b(&sync_isolate_group_shared_callback, EQ);
// Sync callback. The entry point contains the target function, so just call
// it. DLRT_GetThreadForNativeCallbackTrampoline exited the safepoint, so
// re-enter it afterwards.
@@ -417,6 +423,33 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
__ EnterFullSafepoint(R4, R5);
__ b(&done);
__ Bind(&sync_isolate_group_shared_callback);
__ blx(R5);
// Exit isolate group shared isolate.
{
__ EnterFrame(1 << FP, 0);
__ ReserveAlignedFrameSpace(0);
const RegisterSet return_registers(
(1 << CallingConventions::kReturnReg) |
(1 << CallingConventions::kSecondReturnReg),
1 << CallingConventions::kReturnFpuReg);
__ PushRegisters(return_registers);
GenerateLoadFfiCallbackMetadataRuntimeFunction(
FfiCallbackMetadata::kExitIsolateGroupSharedIsolate, R4);
__ blx(R4);
__ PopRegisters(return_registers);
__ LeaveFrame(1 << FP);
}
__ b(&done);
__ Bind(&async_callback);
// Async callback. The entrypoint marshals the arguments into a message and
@@ -571,6 +571,7 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
}
Label async_callback;
Label sync_isolate_group_shared_callback;
Label done;
// If GetFfiCallbackMetadata returned a null thread, it means that the async
@@ -584,6 +585,11 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
Operand(static_cast<uword>(FfiCallbackMetadata::TrampolineType::kAsync)));
__ b(&async_callback, EQ);
__ cmp(R9,
Operand(static_cast<uword>(
FfiCallbackMetadata::TrampolineType::kSyncIsolateGroupShared)));
__ b(&sync_isolate_group_shared_callback, EQ);
// Sync callback. The entry point contains the target function, so just call
// it. DLRT_GetThreadForNativeCallbackTrampoline exited the safepoint, so
// re-enter it afterwards.
@@ -596,6 +602,53 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
__ EnterFullSafepoint(/*scratch=*/R9);
__ b(&done);
__ Bind(&sync_isolate_group_shared_callback);
__ blr(R10);
// Exit isolate group shared isolate.
{
__ SetupDartSP();
__ EnterFrame(0);
__ ReserveAlignedFrameSpace(0);
const RegisterSet return_registers(
(1 << CallingConventions::kReturnReg) |
(1 << CallingConventions::kSecondReturnReg),
1 << CallingConventions::kReturnFpuReg);
__ PushRegisters(return_registers);
#if defined(DART_TARGET_OS_FUCHSIA)
// TODO(https://dartbug.com/52579): Remove.
if (FLAG_precompiled_mode) {
GenerateLoadBSSEntry(BSS::Relocation::DRT_ExitIsolateGroupSharedIsolate,
R4, R9);
} else {
Label call;
__ ldr(R4, compiler::Address::PC(2 * Instr::kInstrSize));
__ b(&call);
__ Emit64(reinterpret_cast<int64_t>(&DLRT_ExitIsolateGroupSharedIsolate));
__ Bind(&call);
}
#else
GenerateLoadFfiCallbackMetadataRuntimeFunction(
FfiCallbackMetadata::kExitIsolateGroupSharedIsolate, R4);
#endif
__ mov(CSP, SP);
__ blr(R4);
__ mov(SP, CSP);
__ mov(THR, R0);
__ PopRegisters(return_registers);
__ LeaveFrame();
__ RestoreCSP();
}
__ b(&done);
__ Bind(&async_callback);
// Async callback. The entrypoint marshals the arguments into a message and
+36 -2
View File
@@ -247,7 +247,7 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
// Load the thread, verify the callback ID and exit the safepoint.
//
// We exit the safepoint inside DLRT_GetFfiCallbackMetadata in order to safe
// We exit the safepoint inside DLRT_GetFfiCallbackMetadata in order to save
// code size on this shared stub.
{
__ EnterFrame(0);
@@ -287,18 +287,24 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
COMPILE_ASSERT(ECX != THR);
Label async_callback;
Label sync_isolate_group_shared_callback;
Label done;
// If GetFfiCallbackMetadata returned a null thread, it means that the async
// callback was invoked after it was deleted. In this case, do nothing.
__ cmpl(THR, Immediate(0));
__ j(EQUAL, &done, Assembler::kNearJump);
__ j(EQUAL, &done, Assembler::kFarJump);
// Check the trampoline type to see how the callback should be invoked.
__ cmpl(EBX, Immediate(static_cast<uword>(
FfiCallbackMetadata::TrampolineType::kAsync)));
__ j(EQUAL, &async_callback, Assembler::kNearJump);
__ cmpl(EBX,
Immediate(static_cast<uword>(
FfiCallbackMetadata::TrampolineType::kSyncIsolateGroupShared)));
__ j(EQUAL, &sync_isolate_group_shared_callback, Assembler::kNearJump);
// Sync callback. The entry point contains the target function, so just call
// it. DLRT_GetThreadForNativeCallbackTrampoline exited the safepoint, so
// re-enter it afterwards.
@@ -325,6 +331,34 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
__ Bind(&ret_4);
__ ret(Immediate(4));
__ Bind(&sync_isolate_group_shared_callback);
__ call(ECX);
// Exit isolate group shared isolate.
{
__ pushl(CallingConventions::kReturnReg);
__ pushl(CallingConventions::kSecondReturnReg);
__ subl(ESP, Immediate(kFpuRegisterSize));
__ movups(Address(ESP, 0), CallingConventions::kReturnFpuReg);
__ EnterFrame(0);
__ ReserveAlignedFrameSpace(0);
__ movl(EAX, Immediate(reinterpret_cast<int64_t>(
DLRT_ExitIsolateGroupSharedIsolate)));
__ CallCFunction(EAX);
__ LeaveFrame();
__ movups(Address(ESP, 0), CallingConventions::kReturnFpuReg);
__ addl(ESP, Immediate(kFpuRegisterSize));
__ popl(CallingConventions::kSecondReturnReg);
__ popl(CallingConventions::kReturnReg);
}
__ jmp(&done, Assembler::kNearJump);
__ Bind(&async_callback);
// Async callback. The entrypoint marshals the arguments into a message and
@@ -428,17 +428,19 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
COMPILE_ASSERT(!IsCalleeSavedRegister(T2) && !IsArgumentRegister(T2));
COMPILE_ASSERT(!IsCalleeSavedRegister(T3) && !IsArgumentRegister(T3));
Label something_other_than_sync_callback;
Label async_callback;
Label done;
// If GetFfiCallbackMetadata returned a null thread, it means that the
// callback was invoked after it was deleted. In this case, do nothing.
__ beqz(THR, &done, Assembler::kNearJump);
__ beqz(THR, &done, Assembler::kFarJump);
// Check the trampoline type to see how the callback should be invoked.
COMPILE_ASSERT(
static_cast<uword>(FfiCallbackMetadata::TrampolineType::kSync) == 0);
__ bnez(T3, &async_callback, Assembler::kNearJump);
__ bnez(T3, &something_other_than_sync_callback, Assembler::kNearJump);
// Sync callback. The entry point contains the target function, so just call
// it. DLRT_GetThreadForNativeCallbackTrampoline exited the safepoint, so
@@ -451,6 +453,66 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
__ EnterFullSafepoint(/*scratch=*/T1);
__ j(&done, Assembler::kNearJump);
__ Bind(&something_other_than_sync_callback);
COMPILE_ASSERT(
static_cast<uword>(FfiCallbackMetadata::TrampolineType::kAsync) == 2);
__ subi(T3, T3, 2);
__ beqz(T3, &async_callback, Assembler::kNearJump);
COMPILE_ASSERT(
static_cast<uword>(
FfiCallbackMetadata::TrampolineType::kSyncIsolateGroupShared) == 3);
// isolate-group-shared callback
__ jalr(T2);
// Exit isolate group shared isolate.
{
__ EnterFrame(0);
__ ReserveAlignedFrameSpace(0);
const RegisterSet return_registers(
(1 << CallingConventions::kReturnReg) |
(1 << CallingConventions::kSecondReturnReg),
1 << CallingConventions::kReturnFpuReg);
__ PushRegisters(return_registers);
Label call;
#if defined(DART_TARGET_OS_FUCHSIA)
// TODO(https://dartbug.com/52579): Remove.
if (FLAG_precompiled_mode) {
GenerateLoadBSSEntry(BSS::Relocation::DRT_ExitIsolateGroupSharedIsolate,
T1, T2);
} else {
const intptr_t kPCRelativeLoadOffset = 12;
intptr_t start = __ CodeSize();
__ auipc(T1, 0);
__ lx(T1, Address(T1, kPCRelativeLoadOffset));
__ j(&call);
ASSERT_EQUAL(__ CodeSize() - start, kPCRelativeLoadOffset);
#if XLEN == 32
__ Emit32(reinterpret_cast<int32_t>(&DLRT_ExitIsolateGroupSharedIsolate));
#else
__ Emit64(reinterpret_cast<int64_t>(&DLRT_ExitIsolateGroupSharedIsolate));
#endif
}
#else
GenerateLoadFfiCallbackMetadataRuntimeFunction(
FfiCallbackMetadata::kExitIsolateGroupSharedIsolate, T1);
#endif // defined(DART_TARGET_OS_FUCHSIA)
__ Bind(&call);
__ jalr(T1);
__ PopRegisters(return_registers);
__ LeaveFrame();
}
__ j(&done, Assembler::kNearJump);
__ Bind(&async_callback);
// Async callback. The entrypoint marshals the arguments into a message and
+48 -3
View File
@@ -468,8 +468,8 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
// Load the thread, verify the callback ID and exit the safepoint.
//
// We exit the safepoint inside DLRT_GetFfiCallbackMetadata in order to safe
// code size on this shared stub.
// We exit the safepoint inside DLRT_GetFfiCallbackMetadata in order to save
// code size of this shared stub.
{
COMPILE_ASSERT(RAX != CallingConventions::kArg1Reg);
__ movq(CallingConventions::kArg1Reg, RAX);
@@ -530,18 +530,24 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
// All argument registers are untouched.
Label async_callback;
Label sync_isolate_group_shared_callback;
Label done;
// If GetFfiCallbackMetadata returned a null thread, it means that the
// callback was invoked after it was deleted. In this case, do nothing.
__ cmpq(THR, Immediate(0));
__ j(EQUAL, &done, Assembler::kNearJump);
__ j(EQUAL, &done, Assembler::kFarJump);
// Check the trampoline type to see how the callback should be invoked.
__ cmpq(RAX, Immediate(static_cast<uword>(
FfiCallbackMetadata::TrampolineType::kAsync)));
__ j(EQUAL, &async_callback, Assembler::kNearJump);
__ cmpq(RAX,
Immediate(static_cast<uword>(
FfiCallbackMetadata::TrampolineType::kSyncIsolateGroupShared)));
__ j(EQUAL, &sync_isolate_group_shared_callback, Assembler::kNearJump);
// Sync callback. The entry point contains the target function, so just call
// it. DLRT_GetThreadForNativeCallbackTrampoline exited the safepoint, so
// re-enter it afterwards.
@@ -554,6 +560,45 @@ void StubCodeCompiler::GenerateFfiCallbackTrampolineStub() {
__ EnterFullSafepoint();
__ jmp(&done, Assembler::kNearJump);
__ Bind(&sync_isolate_group_shared_callback);
__ call(TMP);
// Exit isolate group shared isolate.
{
const RegisterSet return_registers(
(1 << CallingConventions::kReturnReg) |
(1 << CallingConventions::kSecondReturnReg),
1 << CallingConventions::kReturnFpuReg);
__ PushRegisters(return_registers);
#if defined(DART_TARGET_OS_FUCHSIA)
// TODO(https://dartbug.com/52579): Remove.
if (FLAG_precompiled_mode) {
GenerateLoadBSSEntry(BSS::Relocation::DRT_ExitIsolateGroupSharedIsolate,
RAX, TMP);
} else {
__ movq(RAX, Immediate(reinterpret_cast<int64_t>(
DLRT_ExitIsolateGroupSharedIsolate)));
}
#else
GenerateLoadFfiCallbackMetadataRuntimeFunction(
FfiCallbackMetadata::kExitIsolateGroupSharedIsolate, RAX);
#endif // defined(DART_TARGET_OS_FUCHSIA)
__ EnterFrame(0);
__ ReserveAlignedFrameSpace(0);
__ CallCFunction(RAX);
__ LeaveFrame();
__ PopRegisters(return_registers);
}
__ jmp(&done, Assembler::kNearJump);
__ Bind(&async_callback);
// Async callback. The entrypoint marshals the arguments into a message and
+49 -20
View File
@@ -170,6 +170,9 @@ void FfiCallbackMetadata::EnsureFreeListNotEmptyLocked() {
reinterpret_cast<void*>(DLRT_GetFfiCallbackMetadata));
FillRuntimeFunction(new_page, kExitTemporaryIsolate,
reinterpret_cast<void*>(DLRT_ExitTemporaryIsolate));
FillRuntimeFunction(
new_page, kExitIsolateGroupSharedIsolate,
reinterpret_cast<void*>(DLRT_ExitIsolateGroupSharedIsolate));
// Add all the trampolines to the free list.
const intptr_t trampolines_per_page = NumCallbackTrampolinesPerPage();
@@ -182,6 +185,7 @@ void FfiCallbackMetadata::EnsureFreeListNotEmptyLocked() {
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateMetadataEntry(
Isolate* target_isolate,
IsolateGroup* target_isolate_group,
TrampolineType trampoline_type,
uword target_entry_point,
uint64_t context,
@@ -200,8 +204,14 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateMetadataEntry(
ASSERT(next_entry->list_prev_ == nullptr);
next_entry->list_prev_ = entry;
}
if (target_isolate != nullptr) {
*entry = Metadata(target_isolate, trampoline_type, target_entry_point,
context, nullptr, next_entry);
} else {
ASSERT(target_isolate_group != nullptr);
*entry = Metadata(target_isolate_group, trampoline_type, target_entry_point,
context, nullptr, next_entry);
}
*list_head = entry;
return TrampolineOfMetadata(entry);
}
@@ -227,9 +237,7 @@ void FfiCallbackMetadata::DeleteCallbackLocked(Metadata* entry) {
if (entry->trampoline_type_ != TrampolineType::kAsync &&
entry->context_ != 0) {
ASSERT(entry->target_isolate_ != nullptr);
auto* api_state = entry->target_isolate_->group()->api_state();
ASSERT(api_state != nullptr);
api_state->FreePersistentHandle(entry->closure_handle());
entry->api_state()->FreePersistentHandle(entry->closure_handle());
}
AddToFreeListLocked(entry);
}
@@ -272,44 +280,56 @@ uword FfiCallbackMetadata::GetEntryPoint(Zone* zone, const Function& function) {
}
PersistentHandle* FfiCallbackMetadata::CreatePersistentHandle(
Isolate* isolate,
IsolateGroup* isolate_group,
const Closure& closure) {
auto* api_state = isolate->group()->api_state();
auto* api_state = isolate_group->api_state();
ASSERT(api_state != nullptr);
auto* handle = api_state->AllocatePersistentHandle();
handle->set_ptr(closure);
return handle;
}
FfiCallbackMetadata::Trampoline
FfiCallbackMetadata::CreateIsolateLocalFfiCallback(Isolate* isolate,
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateLocalFfiCallback(
Isolate* isolate,
IsolateGroup* isolate_group,
Zone* zone,
const Function& function,
const Closure& closure,
Metadata** list_head) {
PersistentHandle* handle = nullptr;
if (closure.IsNull()) {
// If the closure is null, it means the target is a static function, so is
// baked into the trampoline and is an ordinary sync callback.
ASSERT(function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateLocalStaticCallback);
return CreateSyncFfiCallbackImpl(isolate, zone, function, nullptr,
list_head);
ASSERT((isolate != nullptr && isolate_group == nullptr &&
function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateLocalStaticCallback) ||
(isolate == nullptr && isolate_group != nullptr &&
function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateGroupSharedStaticCallback));
} else {
ASSERT(function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateLocalClosureCallback);
return CreateSyncFfiCallbackImpl(isolate, zone, function,
CreatePersistentHandle(isolate, closure),
list_head);
ASSERT((isolate != nullptr && isolate_group == nullptr &&
function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateLocalClosureCallback) ||
(isolate == nullptr && isolate_group != nullptr &&
function.GetFfiCallbackKind() ==
FfiCallbackKind::kIsolateGroupSharedClosureCallback));
handle = CreatePersistentHandle(
isolate != nullptr ? isolate->group() : isolate_group, closure);
}
return CreateSyncFfiCallbackImpl(isolate, isolate_group, zone, function,
handle, list_head);
}
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateSyncFfiCallbackImpl(
Isolate* isolate,
IsolateGroup* isolate_group,
Zone* zone,
const Function& function,
PersistentHandle* closure,
Metadata** list_head) {
TrampolineType trampoline_type = TrampolineType::kSync;
TrampolineType trampoline_type =
isolate != nullptr ? TrampolineType::kSync
: TrampolineType::kSyncIsolateGroupShared;
#if defined(TARGET_ARCH_IA32)
// On ia32, store the stack delta that we need to use when returning.
@@ -319,11 +339,13 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateSyncFfiCallbackImpl(
: 0;
if (stack_return_delta != 0) {
ASSERT(stack_return_delta == 4);
trampoline_type = TrampolineType::kSyncStackDelta4;
trampoline_type = isolate != nullptr
? TrampolineType::kSyncStackDelta4
: TrampolineType::kSyncIsolateGroupSharedStackDelta4;
}
#endif
return CreateMetadataEntry(isolate, trampoline_type,
return CreateMetadataEntry(isolate, isolate_group, trampoline_type,
GetEntryPoint(zone, function),
reinterpret_cast<uint64_t>(closure), list_head);
}
@@ -335,7 +357,8 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateAsyncFfiCallback(
Dart_Port send_port,
Metadata** list_head) {
ASSERT(send_function.GetFfiCallbackKind() == FfiCallbackKind::kAsyncCallback);
return CreateMetadataEntry(isolate, TrampolineType::kAsync,
return CreateMetadataEntry(isolate, /*isolate_group=*/nullptr,
TrampolineType::kAsync,
GetEntryPoint(zone, send_function),
static_cast<uint64_t>(send_port), list_head);
}
@@ -390,4 +413,10 @@ FfiCallbackMetadata::Metadata FfiCallbackMetadata::LookupMetadataForTrampoline(
FfiCallbackMetadata* FfiCallbackMetadata::singleton_ = nullptr;
ApiState* FfiCallbackMetadata::Metadata::api_state() const {
return (is_isolate_group_shared() ? target_isolate_group_
: target_isolate_->group())
->api_state();
}
} // namespace dart
+59 -11
View File
@@ -12,6 +12,7 @@
namespace dart {
class ApiState;
class Closure;
class Function;
class Isolate;
@@ -44,11 +45,14 @@ class FfiCallbackMetadata {
kSync = 0,
kSyncStackDelta4 = 1, // Only used by TARGET_ARCH_IA32
kAsync = 2,
kSyncIsolateGroupShared = 3,
kSyncIsolateGroupSharedStackDelta4 = 4, // Only used by TARGET_ARCH_IA32
};
enum RuntimeFunctions {
kGetFfiCallbackMetadata,
kExitTemporaryIsolate,
kExitIsolateGroupSharedIsolate,
kNumRuntimeFunctions,
};
@@ -66,8 +70,10 @@ class FfiCallbackMetadata {
Dart_Port send_port,
Metadata** list_head);
// Creates an isolate local callback trampoline for the given function.
Trampoline CreateIsolateLocalFfiCallback(Isolate* isolate,
// Creates an isolate- or isolategroup- local callback trampoline for
// the given function.
Trampoline CreateLocalFfiCallback(Isolate* isolate,
IsolateGroup* isolate_group,
Zone* zone,
const Function& function,
const Closure& closure,
@@ -81,7 +87,10 @@ class FfiCallbackMetadata {
// FFI callback metadata for any sync or async trampoline.
class Metadata {
union {
Isolate* target_isolate_;
IsolateGroup* target_isolate_group_;
};
TrampolineType trampoline_type_;
union {
@@ -117,6 +126,19 @@ class FfiCallbackMetadata {
list_prev_(list_prev),
list_next_(list_next) {}
Metadata(IsolateGroup* target_isolate_group,
TrampolineType trampoline_type,
uword target_entry_point,
uint64_t context,
Metadata* list_prev,
Metadata* list_next)
: target_isolate_group_(target_isolate_group),
trampoline_type_(trampoline_type),
target_entry_point_(target_entry_point),
context_(context),
list_prev_(list_prev),
list_next_(list_next) {}
public:
friend class FfiCallbackMetadata;
bool IsSameCallback(const Metadata& other) const {
@@ -129,7 +151,9 @@ class FfiCallbackMetadata {
}
// Whether the callback is still alive.
bool IsLive() const { return target_isolate_ != 0; }
bool IsLive() const {
return target_isolate_ != 0 || target_isolate_group_ != 0;
}
// The target isolate. The isolate that owns the callback. Sync callbacks
// must be invoked on this isolate. Async callbacks will send a message to
@@ -139,6 +163,11 @@ class FfiCallbackMetadata {
return target_isolate_;
}
IsolateGroup* target_isolate_group() const {
ASSERT(IsLive());
return target_isolate_group_;
}
// The Dart entrypoint for the callback, which the trampoline invokes.
uword target_entry_point() const {
ASSERT(IsLive());
@@ -150,10 +179,21 @@ class FfiCallbackMetadata {
PersistentHandle* closure_handle() const {
ASSERT(IsLive());
ASSERT(trampoline_type_ == TrampolineType::kSync ||
trampoline_type_ == TrampolineType::kSyncStackDelta4);
trampoline_type_ == TrampolineType::kSyncStackDelta4 ||
trampoline_type_ == TrampolineType::kSyncIsolateGroupShared ||
trampoline_type_ ==
TrampolineType::kSyncIsolateGroupSharedStackDelta4);
return reinterpret_cast<PersistentHandle*>(context_);
}
bool is_isolate_group_shared() const {
return trampoline_type_ == TrampolineType::kSyncIsolateGroupShared ||
trampoline_type_ ==
TrampolineType::kSyncIsolateGroupSharedStackDelta4;
}
// ApiState associated with an isolate group associated with this metadata.
ApiState* api_state() const;
// For async callbacks, this is the send port. For sync callbacks this is a
// persistent handle to the callback's closure, or null.
uint64_t context() const {
@@ -251,27 +291,27 @@ class FfiCallbackMetadata {
#if defined(TARGET_ARCH_X64)
static constexpr intptr_t kNativeCallbackTrampolineSize = 12;
static constexpr intptr_t kNativeCallbackSharedStubSize = 289;
static constexpr intptr_t kNativeCallbackSharedStubSize = 338;
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
#elif defined(TARGET_ARCH_IA32)
static constexpr intptr_t kNativeCallbackTrampolineSize = 10;
static constexpr intptr_t kNativeCallbackSharedStubSize = 146;
static constexpr intptr_t kNativeCallbackSharedStubSize = 193;
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 4;
#elif defined(TARGET_ARCH_ARM)
static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
static constexpr intptr_t kNativeCallbackSharedStubSize = 232;
static constexpr intptr_t kNativeCallbackSharedStubSize = 328;
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 4;
#elif defined(TARGET_ARCH_ARM64)
static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
static constexpr intptr_t kNativeCallbackSharedStubSize = 332;
static constexpr intptr_t kNativeCallbackSharedStubSize = 428;
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
#elif defined(TARGET_ARCH_RISCV32)
static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
static constexpr intptr_t kNativeCallbackSharedStubSize = 284;
static constexpr intptr_t kNativeCallbackSharedStubSize = 302;
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
#elif defined(TARGET_ARCH_RISCV64)
static constexpr intptr_t kNativeCallbackTrampolineSize = 8;
static constexpr intptr_t kNativeCallbackSharedStubSize = 252;
static constexpr intptr_t kNativeCallbackSharedStubSize = 302;
static constexpr intptr_t kNativeCallbackTrampolineStackDelta = 2;
#else
#error What architecture?
@@ -291,18 +331,26 @@ class FfiCallbackMetadata {
VirtualMemory* AllocateTrampolinePage();
void EnsureFreeListNotEmptyLocked();
Trampoline CreateMetadataEntry(Isolate* target_isolate,
IsolateGroup* target_isolate_group,
TrampolineType trampoline_type,
uword target_entry_point,
uint64_t context,
Metadata** list_head);
Trampoline CreateSyncFfiCallbackImpl(Isolate* isolate,
IsolateGroup* isolate_group,
Zone* zone,
const Function& function,
PersistentHandle* closure,
Metadata** list_head);
Trampoline CreateIsolateGroupSharedFfiCallbackImpl(
IsolateGroup* isolate_group,
Zone* zone,
const Function& function,
PersistentHandle* closure,
Metadata** list_head);
Trampoline TryAllocateFromFreeListLocked();
static uword GetEntryPoint(Zone* zone, const Function& function);
static PersistentHandle* CreatePersistentHandle(Isolate* isolate,
static PersistentHandle* CreatePersistentHandle(IsolateGroup* isolate_group,
const Closure& closure);
static FfiCallbackMetadata* singleton_;
+5 -5
View File
@@ -446,9 +446,9 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_DeleteTrampolines) {
// Create some callbacks.
for (int itr = 0; itr < kCreations; ++itr) {
tramps.insert(fcm->CreateIsolateLocalFfiCallback(
isolate, thread->zone(), sync_func, Closure::Handle(Closure::null()),
&list_head));
tramps.insert(fcm->CreateLocalFfiCallback(
isolate, /*isolate_group=*/nullptr, thread->zone(), sync_func,
Closure::Handle(Closure::null()), &list_head));
}
// Delete some of the callbacks.
@@ -559,8 +559,8 @@ static void RunBigRandomMultithreadedTest(uint64_t seed) {
if ((random.NextUInt32() % 2) == 0) {
// 50% chance of creating a sync callback.
tramp.port = ILLEGAL_PORT;
tramp.tramp = fcm->CreateIsolateLocalFfiCallback(
isolate, thread->zone(), sync_func,
tramp.tramp = fcm->CreateLocalFfiCallback(
isolate, /*isolate_group=*/nullptr, thread->zone(), sync_func,
Closure::Handle(Closure::null()), &list_head);
} else {
// 50% chance of creating an async callback.
+13 -2
View File
@@ -3828,8 +3828,19 @@ FfiCallbackMetadata::Trampoline Isolate::CreateIsolateLocalFfiCallback(
if (keep_isolate_alive) {
UpdateNativeCallableKeepIsolateAliveCounter(1);
}
return FfiCallbackMetadata::Instance()->CreateIsolateLocalFfiCallback(
this, zone, trampoline, target, &ffi_callback_list_head_);
return FfiCallbackMetadata::Instance()->CreateLocalFfiCallback(
this, /*isolate_group=*/nullptr, zone, trampoline, target,
&ffi_callback_list_head_);
}
// TODO(aam): Should this be in IsolateGroup?
FfiCallbackMetadata::Trampoline Isolate::CreateIsolateGroupSharedFfiCallback(
Zone* zone,
const Function& trampoline,
const Closure& target) {
return FfiCallbackMetadata::Instance()->CreateLocalFfiCallback(
/*isolate=*/nullptr, group(), zone, trampoline, target,
&ffi_callback_list_head_);
}
bool Isolate::HasLivePorts() {
+4
View File
@@ -1284,6 +1284,10 @@ class Isolate : public IntrusiveDListEntry<Isolate> {
const Function& trampoline,
const Closure& target,
bool keep_isolate_alive);
FfiCallbackMetadata::Trampoline CreateIsolateGroupSharedFfiCallback(
Zone* zone,
const Function& trampoline,
const Closure& target);
void DeleteFfiCallback(FfiCallbackMetadata::Trampoline callback);
void UpdateNativeCallableKeepIsolateAliveCounter(intptr_t delta);
bool HasOpenNativeCallables();
+12 -7
View File
@@ -1016,7 +1016,7 @@ void KernelLoader::FinishTopLevelClassLoading(
field_helper.ReadUntilExcluding(FieldHelper::kAnnotations);
intptr_t annotation_count = helper_.ReadListLength();
uint32_t pragma_bits = 0;
ReadVMAnnotations(annotation_count, &pragma_bits);
ReadVMAnnotations(library, annotation_count, &pragma_bits);
field_helper.SetJustRead(FieldHelper::kAnnotations);
field_helper.ReadUntilExcluding(FieldHelper::kType);
@@ -1342,7 +1342,7 @@ void KernelLoader::LoadClass(const Library& library,
class_helper.ReadUntilExcluding(ClassHelper::kAnnotations);
intptr_t annotation_count = helper_.ReadListLength();
uint32_t pragma_bits = 0;
ReadVMAnnotations(annotation_count, &pragma_bits);
ReadVMAnnotations(library, annotation_count, &pragma_bits);
if (IsolateUnsendablePragma::decode(pragma_bits)) {
out_class->set_is_isolate_unsendable_due_to_pragma(true);
}
@@ -1436,7 +1436,7 @@ void KernelLoader::FinishClassLoading(const Class& klass,
field_helper.ReadUntilExcluding(FieldHelper::kAnnotations);
const intptr_t annotation_count = helper_.ReadListLength();
uint32_t pragma_bits = 0;
ReadVMAnnotations(annotation_count, &pragma_bits);
ReadVMAnnotations(library, annotation_count, &pragma_bits);
field_helper.SetJustRead(FieldHelper::kAnnotations);
field_helper.ReadUntilExcluding(FieldHelper::kType);
@@ -1566,7 +1566,7 @@ void KernelLoader::FinishClassLoading(const Class& klass,
constructor_helper.ReadUntilExcluding(ConstructorHelper::kAnnotations);
const intptr_t annotation_count = helper_.ReadListLength();
uint32_t pragma_bits = 0;
ReadVMAnnotations(annotation_count, &pragma_bits);
ReadVMAnnotations(library, annotation_count, &pragma_bits);
constructor_helper.SetJustRead(ConstructorHelper::kAnnotations);
constructor_helper.ReadUntilExcluding(ConstructorHelper::kFunction);
@@ -1720,7 +1720,8 @@ void KernelLoader::FinishLoading(const Class& klass) {
//
// `pragma_bits`: any recognized pragma that was found
//
void KernelLoader::ReadVMAnnotations(intptr_t annotation_count,
void KernelLoader::ReadVMAnnotations(const Library& library,
intptr_t annotation_count,
uint32_t* pragma_bits,
String* native_name) {
*pragma_bits = 0;
@@ -1768,10 +1769,12 @@ void KernelLoader::ReadVMAnnotations(intptr_t annotation_count,
}
if (constant_reader.IsStringConstant(name_index, "vm:shared")) {
if (!FLAG_experimental_shared_data) {
if (!library.IsAnyCoreLibrary()) {
FATAL(
"Encountered vm:shared when functionality is disabled. "
"Pass --experimental-shared-data");
}
}
*pragma_bits = SharedPragma::update(true, *pragma_bits);
}
if (constant_reader.IsStringConstant(name_index,
@@ -1831,7 +1834,7 @@ void KernelLoader::LoadProcedure(const Library& library,
String& native_name = String::Handle(Z);
uint32_t pragma_bits = 0;
const intptr_t annotation_count = helper_.ReadListLength();
ReadVMAnnotations(annotation_count, &pragma_bits, &native_name);
ReadVMAnnotations(library, annotation_count, &pragma_bits, &native_name);
is_external = is_external && native_name.IsNull();
procedure_helper.SetJustRead(ProcedureHelper::kAnnotations);
const Object& script_class =
@@ -2245,7 +2248,9 @@ FunctionPtr KernelLoader::LoadClosureFunction(const Function& parent_function,
variable_helper.ReadUntilExcluding(VariableDeclarationHelper::kAnnotations);
const intptr_t annotation_count = helper_.ReadListLength();
ReadVMAnnotations(annotation_count, &pragma_bits);
const auto& library =
Library::Handle(Z, Class::Handle(Z, parent_function.Owner()).library());
ReadVMAnnotations(library, annotation_count, &pragma_bits);
variable_helper.SetJustRead(VariableDeclarationHelper::kAnnotations);
variable_helper.ReadUntilExcluding(VariableDeclarationHelper::kEnd);
+2 -1
View File
@@ -251,7 +251,8 @@ class KernelLoader : public ValueObject {
bool IsClassName(NameIndex name, const String& library, const String& klass);
void ReadVMAnnotations(intptr_t annotation_count,
void ReadVMAnnotations(const Library& library,
intptr_t annotation_count,
uint32_t* pragma_bits,
String* native_name = nullptr);
+2
View File
@@ -2990,7 +2990,9 @@ struct NameFormattingParams {
enum class FfiCallbackKind : uint8_t {
kIsolateLocalStaticCallback,
kIsolateGroupSharedStaticCallback,
kIsolateLocalClosureCallback,
kIsolateGroupSharedClosureCallback,
kAsyncCallback,
};
+44 -2
View File
@@ -4354,8 +4354,8 @@ DEFINE_RAW_LEAF_RUNTIME_ENTRY(EnterSafepoint,
extern "C" void DFLRT_ExitSafepoint(NativeArguments __unusable_) {
CHECK_STACK_ALIGNMENT;
TRACE_RUNTIME_CALL("%s", "ExitSafepoint");
Thread* thread = Thread::Current();
TRACE_RUNTIME_CALL("ExitSafepoint thread %p", thread);
ASSERT(thread->top_exit_frame_info() != 0);
if (thread->is_unwind_in_progress()) {
@@ -4400,7 +4400,7 @@ extern "C" Thread* DLRT_GetFfiCallbackMetadata(
return nullptr;
}
Thread* const current_thread = Thread::Current();
Thread* current_thread = Thread::Current();
auto* fcm = FfiCallbackMetadata::Instance();
auto metadata = fcm->LookupMetadataForTrampoline(trampoline);
@@ -4460,6 +4460,10 @@ extern "C" Thread* DLRT_GetFfiCallbackMetadata(
if (!metadata.IsLive()) {
FATAL("Callback invoked after it has been deleted.");
}
if (metadata.is_isolate_group_shared()) {
*out_entry_point = metadata.target_entry_point();
*out_trampoline_type = static_cast<uword>(metadata.trampoline_type());
} else {
Isolate* target_isolate = metadata.target_isolate();
*out_entry_point = metadata.target_entry_point();
*out_trampoline_type = static_cast<uword>(metadata.trampoline_type());
@@ -4481,9 +4485,32 @@ extern "C" Thread* DLRT_GetFfiCallbackMetadata(
if (current_thread->execution_state() != Thread::kThreadInNative) {
FATAL("Cannot invoke native callback from a leaf call.");
}
}
if (current_thread != nullptr) {
current_thread->ExitSafepointFromNative();
current_thread->set_execution_state(Thread::kThreadInVM);
}
if (metadata.is_isolate_group_shared()) {
Isolate* current_isolate =
current_thread != nullptr ? current_thread->isolate() : nullptr;
if (current_thread != nullptr) {
Thread::ExitIsolate(/*isolate_shutdown=*/false);
}
Thread::EnterIsolateGroupAsMutator(metadata.target_isolate_group(),
/*bypass_safepoint=*/false);
auto new_thread = Thread::Current();
new_thread->set_execution_state(Thread::kThreadInVM);
// We need to go back to current thread after we come back from
// the callback.
new_thread->set_unboxed_int64_runtime_arg(
reinterpret_cast<intptr_t>(current_thread));
new_thread->set_unboxed_int64_runtime_second_arg(
reinterpret_cast<intptr_t>(current_isolate));
current_thread = new_thread;
}
current_thread->set_unboxed_int64_runtime_arg(metadata.context());
@@ -4495,6 +4522,21 @@ extern "C" Thread* DLRT_GetFfiCallbackMetadata(
return current_thread;
}
extern "C" void DLRT_ExitIsolateGroupSharedIsolate() {
TRACE_RUNTIME_CALL("ExitIsolateGroupSharedIsolate%s", "");
Thread* thread = Thread::Current();
ASSERT(thread != nullptr);
Isolate* source_isolate =
reinterpret_cast<Isolate*>(thread->unboxed_int64_runtime_second_arg());
// Need to accommodate ExitIsolateGroupAsHelper assumptions.
thread->set_execution_state(Thread::kThreadInVM);
Thread::ExitIsolateGroupAsMutator(/*bypass_safepoint=*/false);
if (source_isolate != nullptr) {
Thread::EnterIsolate(source_isolate);
Thread::Current()->EnterSafepoint();
}
}
extern "C" void DLRT_ExitTemporaryIsolate() {
TRACE_RUNTIME_CALL("ExitTemporaryIsolate%s", "");
Thread* thread = Thread::Current();
+1 -1
View File
@@ -168,7 +168,7 @@ extern "C" Thread* DLRT_GetFfiCallbackMetadata(uword trampoline,
uword* out_callback_kind);
extern "C" void DLRT_ExitTemporaryIsolate();
extern "C" void DLRT_ExitIsolateGroupSharedIsolate();
// For creating scoped handles in FFI trampolines.
extern "C" ApiLocalScope* DLRT_EnterHandleScope(Thread* thread);
extern "C" void DLRT_ExitHandleScope(Thread* thread);
+1
View File
@@ -123,6 +123,7 @@ class ObjectPointerVisitor;
V(FfiInt8, "Int8") \
V(FfiIntPtr, "IntPtr") \
V(FfiIsolateLocalCallback, "_FfiIsolateLocalCallback") \
V(FfiIsolateGroupSharedCallback, "_FfiIsolateGroupSharedCallback") \
V(FfiNative, "Native") \
V(FfiNativeFunction, "NativeFunction") \
V(FfiNativeType, "NativeType") \
-2
View File
@@ -1552,8 +1552,6 @@ void Thread::SetupMutatorState() {
DeferredMarkingStackAcquire();
}
// TODO(koda): Use StoreBufferAcquire once we properly flush
// before Scavenge.
if (task_kind_ == kMutatorTask) {
StoreBufferAcquire();
} else {
+4 -3
View File
@@ -364,9 +364,9 @@ class MutatorThreadVisitor {
// A VM thread; may be executing Dart code or performing helper tasks like
// garbage collection or compilation. The Thread structure associated with
// a thread is allocated by EnsureInit before entering an isolate, and destroyed
// automatically when the underlying OS thread exits. NOTE: On Windows, CleanUp
// must currently be called manually (issue 23474).
// a thread is allocated by ThreadRegistry::GetFromFreelistLocked either
// before entering an isolate or entering an isolate group, and destroyed
// automatically when the underlying OS thread exits.
class Thread : public ThreadState {
public:
// The kind of task this thread is performing. Sampled by the profiler.
@@ -381,6 +381,7 @@ class Thread : public ThreadState {
kSampleBlockTask,
kIncrementalCompactorTask,
kSpawnTask,
kIsolateGroupSharedCallbackTask,
};
~Thread();
+6
View File
@@ -0,0 +1,6 @@
.dart_tool
.packages
pubspec.lock
lib/libfake_http.so
lib/libfake_http.dylib
lib/fake_http.dll
+11
View File
@@ -0,0 +1,11 @@
# Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
shared_library("fake_httpIG") {
sources = [ "lib/fake_http.cc" ]
include_dirs = [ "." ]
if (!is_win) {
ldflags = [ "-rdynamic" ]
}
}
+12
View File
@@ -0,0 +1,12 @@
This is an example that shows how to use `NativeCallable.listener` to interact
with a multi threaded native API.
The native API is a fake HTTP library with some hard coded requests and
responses. To build the dynamic library, run this command:
```bash
c++ -shared -fpic lib/fake_http.cc -lstdc++ -o lib/libfake_http.so
```
On Windows the output library should be `lib/fake_http.dll` and on Mac it should
be `lib/libfake_http.dylib`.
+22
View File
@@ -0,0 +1,22 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:ffi';
import 'dart:io' show File, Platform;
Uri dylibPath(String name, Uri path) {
if (Platform.isLinux || Platform.isAndroid || Platform.isFuchsia) {
return path.resolve("lib$name.so");
}
if (Platform.isMacOS) return path.resolve("lib$name.dylib");
if (Platform.isWindows) return path.resolve("$name.dll");
throw Exception("Platform not implemented");
}
DynamicLibrary dlopenPlatformSpecific(String name, {List<Uri>? paths}) =>
DynamicLibrary.open(
(paths ?? [Uri()])
.map((path) => dylibPath(name, path).toFilePath())
.firstWhere((lib) => File(lib).existsSync()),
);
+60
View File
@@ -0,0 +1,60 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include <atomic>
#include <chrono>
#include <cstring>
#include <thread>
#if defined(_WIN32)
#define DART_EXPORT extern "C" __declspec(dllexport)
#else
#define DART_EXPORT \
extern "C" __attribute__((visibility("default"))) __attribute((used))
#endif
constexpr char kExampleRequest[] = R"(
GET / HTTP/1.1
Host: www.example.com
)";
constexpr char kExampleResponse[] = R"(
HTTP/1.1 200 OK
Content-Length: 54
Content-Type: text/html; charset=UTF-8
<html>
<body>
Hello world!
</body>
</html>
)";
DART_EXPORT void http_get(const char* uri, void (*onResponse)(const char*)) {
std::thread([onResponse]() {
std::this_thread::sleep_for(std::chrono::seconds(3));
onResponse(kExampleResponse);
}).detach();
}
std::atomic<bool> stop_requested = false;
std::thread* server = nullptr;
DART_EXPORT void http_start_serving(void (*onRequest)(const char*)) {
server = new std::thread([onRequest]() {
while (!stop_requested) {
std::this_thread::sleep_for(std::chrono::seconds(1));
onRequest(kExampleRequest);
}
});
}
DART_EXPORT void http_stop_serving() {
if (server != nullptr) {
stop_requested = true;
server->join();
delete server;
server = nullptr;
}
}
+142
View File
@@ -0,0 +1,142 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:isolate';
import 'dart:io';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'dylib_utils.dart';
// Runs a simple HTTP GET request using a native HTTP library that runs
// the request on a background thread.
Future<String> httpGet(String uri) async {
// Create the NativeCallable.listener.
final completer = Completer<String>();
final ReceivePort rp = ReceivePort()
..listen(
(string) {
completer.complete(string);
},
onError: (e, st) {
print('httpGet receiver get error $e $st');
},
);
final sendPort = rp.sendPort;
final callback = NativeCallable<HttpCallback>.isolateGroupShared((
Pointer<Utf8> responsePointer,
) {
final typedList = responsePointer.cast<Uint8>().asTypedList(
responsePointer.length,
);
final s = utf8.decode(typedList);
sendPort.send(s);
});
// Invoke the native HTTP API. Our example HTTP library runs our GET
// request on a background thread, and calls the callback on that same
// thread when it receives the response.
final uriPointer = uri.toNativeUtf8();
nativeHttpGet(uriPointer, callback.nativeFunction);
calloc.free(uriPointer);
// Wait for the response.
final response = await completer.future;
rp.close();
return response;
}
@pragma('vm:shared')
late int counter;
// Start a HTTP server on a background thread.
ReceivePort httpServe(void Function(String) onRequest) {
counter = 0;
final rp = ReceivePort()
..listen(
(s) {
print('httpServe counter: $counter');
onRequest(s);
},
onError: (e, st) {
print('httpServe receiver get error $e $st');
},
onDone: () {
nativeHttpStopServing();
},
);
final callback = NativeCallable<HttpCallback>.isolateGroupShared((
Pointer<Utf8> requestPointer,
) {
counter++;
final typedList = requestPointer.cast<Uint8>().asTypedList(
requestPointer.length,
);
final s = utf8.decode(typedList);
rp.sendPort.send(s);
});
// Invoke the native function to start the HTTP server. Our example
// HTTP library will start a server on a background thread, and pass
// any requests it receives to out callback.
nativeHttpStartServing(callback.nativeFunction);
return rp;
}
// Load the native functions from a DynamicLibrary.
late final DynamicLibrary dylib = dlopenPlatformSpecific(
'fake_httpIG',
paths: [
Platform.script.resolve('../lib/'),
Uri.file(Platform.resolvedExecutable),
],
);
typedef HttpCallback = Void Function(Pointer<Utf8>);
typedef HttpGetFunction = void Function(
Pointer<Utf8>, Pointer<NativeFunction<HttpCallback>>);
typedef HttpGetNativeFunction = Void Function(
Pointer<Utf8>, Pointer<NativeFunction<HttpCallback>>);
final nativeHttpGet =
dylib.lookupFunction<HttpGetNativeFunction, HttpGetFunction>('http_get');
typedef HttpStartServingFunction = bool Function(
Pointer<NativeFunction<HttpCallback>>);
typedef HttpStartServingNativeFunction = Bool Function(
Pointer<NativeFunction<HttpCallback>>);
final nativeHttpStartServing = dylib
.lookupFunction<HttpStartServingNativeFunction, HttpStartServingFunction>(
'http_start_serving',
);
typedef HttpStopServingFunction = void Function();
typedef HttpStopServingNativeFunction = Void Function();
final nativeHttpStopServing = dylib
.lookupFunction<HttpStopServingNativeFunction, HttpStopServingFunction>(
'http_stop_serving',
);
Future<void> main() async {
print('Sending GET request...');
final response = await httpGet('http://example.com');
print('Received a response: $response');
print('Starting HTTP server...');
final rpServer = httpServe((String request) {
print('Received a request: $request');
});
await Future.delayed(Duration(seconds: 10));
print('All done');
rpServer.close();
}
+14
View File
@@ -0,0 +1,14 @@
name: httpIG_sample
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: ^3.5.0
dependencies:
ffi: ^2.1.0
dev_dependencies:
test: ^1.21.1
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// VMOptions=--experimental-shared-data
// SharedObjects=fake_httpIG
import 'dart:async';
import 'package:test/test.dart';
import 'package:httpIG_sample/http.dart';
Future<void> main() async {
test('httpGet', () async {
final response = await httpGet('http://example.com');
expect(response, contains('Hello world!'));
});
test('httpServe', () async {
final completer = Completer<String>();
final receivePort = httpServe((request) {
if (!completer.isCompleted) {
completer.complete(request);
}
});
final request = await completer.future;
expect(request, contains('www.example.com'));
receivePort.close();
});
}
+1
View File
@@ -16,6 +16,7 @@ ffi/*: SkipByDesign # FFI skips, see ffi.status
[ $arch != x64 || $compiler != dartk || $system != linux || $hot_reload || $hot_reload_rollback ]
ffi/http/test/http_test: SkipByDesign
ffi/httpIG/test/http_test: SkipByDesign
ffi/sqlite/test/sqlite_test: SkipByDesign # FFI not supported or libsqlite3.so not available.
[ $runtime == d8 || $browser ]
+42
View File
@@ -193,6 +193,11 @@ external Pointer<NS> _createNativeCallableIsolateLocal<
NS extends NativeFunction
>(dynamic trampoline, dynamic target, bool keepIsolateAlive);
@pragma("vm:external-name", "Ffi_createNativeCallableIsolateGroupShared")
external Pointer<NS> _createNativeCallableIsolateGroupShared<
NS extends NativeFunction
>(dynamic trampoline, dynamic target);
@pragma("vm:external-name", "Ffi_deleteNativeCallable")
external void _deleteNativeCallable<NS extends NativeFunction>(
Pointer<NS> pointer,
@@ -209,6 +214,19 @@ external dynamic _nativeIsolateLocalCallbackFunction<NS extends Function>(
dynamic exceptionalReturn,
);
@pragma("vm:recognized", "other")
@pragma("vm:external-name", "Ffi_nativeIsolateGroupSharedCallbackFunction")
external dynamic _nativeIsolateGroupSharedCallbackFunction<NS extends Function>(
Function target,
dynamic exceptionalReturn,
);
@pragma("vm:recognized", "other")
@pragma("vm:external-name", "Ffi_nativeIsolateGroupSharedClosureFunction")
external dynamic _nativeIsolateGroupSharedClosureFunction<NS extends Function>(
dynamic exceptionalReturn,
);
@patch
@pragma('vm:deeply-immutable')
@pragma("vm:entry-point")
@@ -329,6 +347,29 @@ final class _NativeCallableListener<T extends Function>
bool get _keepIsolateAlive => _port.keepIsolateAlive;
}
final class _NativeCallableIsolateGroupShared<T extends Function>
extends _NativeCallableBase<T> {
bool _isKeepingIsolateAlive = false;
_NativeCallableIsolateGroupShared(super._pointer);
@override
void _close() {
_keepIsolateAlive = false;
}
@override
void set _keepIsolateAlive(bool value) {
if (_isKeepingIsolateAlive != value) {
_isKeepingIsolateAlive = value;
_updateNativeCallableKeepIsolateAliveCounter(value ? 1 : -1);
}
}
@override
bool get _keepIsolateAlive => false;
}
@patch
@pragma("vm:entry-point")
final class Array<T extends NativeType> extends _Compound {
@@ -1884,6 +1925,7 @@ class Native<T> {
// Resolver for FFI Native C function pointers.
@pragma('vm:entry-point')
@pragma('vm:shared')
static final _ffi_resolver =
_get_ffi_native_resolver<
NativeFunction<IntPtr Function(Handle, Handle, IntPtr)>
+8
View File
@@ -34,6 +34,7 @@ external int sizeOf<T extends SizedNativeType>();
/// Represents a pointer into the native C memory corresponding to 'NULL', e.g.
/// a pointer with address 0.
@pragma('vm:shared')
final Pointer<Never> nullptr = Pointer.fromAddress(0);
/// Represents a pointer into the native C memory. Cannot be extended.
@@ -395,6 +396,13 @@ abstract final class NativeCallable<T extends Function> {
throw UnsupportedError("NativeCallable cannot be constructed dynamically.");
}
factory NativeCallable.isolateGroupShared(
@DartRepresentationOf("T") Function callback, {
Object? exceptionalReturn,
}) {
throw UnsupportedError("NativeCallable cannot be constructed dynamically.");
}
/// Constructs a [NativeCallable] that can be invoked from any thread.
///
/// When the native code invokes the function [nativeFunction], the arguments
+2 -1
View File
@@ -148,7 +148,8 @@ abstract final class Platform {
/// Whether the operating system is a version of
/// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows).
@pragma("vm:platform-const")
static final bool isWindows = (operatingSystem == "windows");
@pragma("vm:shared")
static bool isWindows = (operatingSystem == "windows");
/// Whether the operating system is a version of
/// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29).
@@ -0,0 +1,221 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Dart test program for testing dart:ffi async callbacks.
//
// VMOptions=--experimental-shared-data
// VMOptions=--experimental-shared-data --use-slow-path
// VMOptions=--experimental-shared-data --use-slow-path --stacktrace-every=100
// VMOptions=--experimental-shared-data --dwarf_stack_traces --no-retain_function_objects --no-retain_code_objects
// VMOptions=--experimental-shared-data --test_il_serialization
// VMOptions=--experimental-shared-data --profiler --profile_vm=true
// VMOptions=--experimental-shared-data --profiler --profile_vm=false
// SharedObjects=ffi_test_functions
import 'dart:async';
import 'dart:concurrent';
import 'dart:ffi';
import 'dart:isolate';
import 'dart:io';
import "package:expect/expect.dart";
import 'dylib_utils.dart';
typedef CallbackNativeType = Void Function(Int64, Int32);
typedef CallbackReturningIntNativeType = Int32 Function(Int32, Int32);
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
typedef FnRunnerNativeType = Void Function(Int64, Pointer);
typedef FnRunnerType = void Function(int, Pointer);
typedef FnSleepNativeType = Void Function(Int32);
typedef FnSleepType = void Function(int);
typedef TwoIntFnNativeType = Int32 Function(Pointer, Int32, Int32);
typedef TwoIntFnType = int Function(Pointer, int, int);
class NativeLibrary {
late final FnRunnerType callFunctionOnSameThread;
late final FnRunnerType callFunctionOnNewThreadBlocking;
late final FnRunnerType callFunctionOnNewThreadNonBlocking;
late final TwoIntFnType callTwoIntFunction;
late final FnSleepType sleep;
NativeLibrary() {
callFunctionOnSameThread = ffiTestFunctions
.lookupFunction<FnRunnerNativeType, FnRunnerType>(
"CallFunctionOnSameThread",
);
callFunctionOnNewThreadBlocking = ffiTestFunctions
.lookupFunction<FnRunnerNativeType, FnRunnerType>(
"CallFunctionOnNewThreadBlocking",
);
callFunctionOnNewThreadNonBlocking = ffiTestFunctions
.lookupFunction<FnRunnerNativeType, FnRunnerType>(
"CallFunctionOnNewThreadNonBlocking",
);
callTwoIntFunction = ffiTestFunctions
.lookupFunction<TwoIntFnNativeType, TwoIntFnType>("CallTwoIntFunction");
sleep = ffiTestFunctions.lookupFunction<FnSleepNativeType, FnSleepType>(
"SleepFor",
);
}
}
@pragma('vm:shared')
late Mutex mutexCondvar;
@pragma('vm:shared')
late ConditionVariable conditionVariable;
@pragma('vm:shared')
int result = 0;
@pragma('vm:shared')
bool resultIsReady = false;
@pragma('vm:shared')
late NativeLibrary lib;
const int sleepForMs = 1000;
void simpleFunction(int a, int b) {
result += (a * b);
lib.sleep(sleepForMs);
mutexCondvar.runLocked(() {
resultIsReady = true;
conditionVariable.notify();
});
}
Future<void> testNativeCallableHelloWorld() async {
mutexCondvar = Mutex();
conditionVariable = ConditionVariable();
// final callback = NativeCallable<CallbackNativeType>.isolateGroupShared(simpleFunction);
final callback = NativeCallable<CallbackNativeType>.isolateGroupShared(
simpleFunction,
);
result = 42;
resultIsReady = false;
lib.callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
mutexCondvar.runLocked(() {
while (!resultIsReady) {
conditionVariable.wait(mutexCondvar);
}
});
Expect.equals(42 + (1001 * 123), result);
resultIsReady = false;
lib.callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
mutexCondvar.runLocked(() {
while (!resultIsReady) {
conditionVariable.wait(mutexCondvar);
}
});
Expect.equals(42 + (1001 * 123) * 2, result);
}
Future<void> testNativeCallableHelloWorldClosure() async {
mutexCondvar = Mutex();
conditionVariable = ConditionVariable();
// final callback = NativeCallable<CallbackNativeType>.isolateGroupShared(simpleFunction);
final callback = NativeCallable<CallbackNativeType>.isolateGroupShared((
int a,
int b,
) {
result += (a * b);
lib.sleep(sleepForMs);
mutexCondvar.runLocked(() {
resultIsReady = true;
conditionVariable.notify();
});
});
result = 42;
resultIsReady = false;
lib.callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
mutexCondvar.runLocked(() {
while (!resultIsReady) {
conditionVariable.wait(mutexCondvar);
}
});
Expect.equals(42 + (1001 * 123), result);
resultIsReady = false;
lib.callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
mutexCondvar.runLocked(() {
while (!resultIsReady) {
conditionVariable.wait(mutexCondvar);
}
});
Expect.equals(42 + (1001 * 123) * 2, result);
}
void testNativeCallableSync() {
final callback =
NativeCallable<CallbackReturningIntNativeType>.isolateGroupShared((
int a,
int b,
) {
return a + b;
}, exceptionalReturn: 1111);
Expect.equals(
1234,
lib.callTwoIntFunction(callback.nativeFunction, 1000, 234),
);
callback.close();
}
void testNativeCallableSyncThrows() {
final callback =
NativeCallable<CallbackReturningIntNativeType>.isolateGroupShared((
int a,
int b,
) {
throw "foo";
return a + b;
}, exceptionalReturn: 1111);
Expect.equals(
1111,
lib.callTwoIntFunction(callback.nativeFunction, 1000, 234),
);
callback.close();
}
int isolateVar = 10;
void testNativeCallableAccessNonSharedVar() {
final callback =
NativeCallable<CallbackReturningIntNativeType>.isolateGroupShared((
int a,
int b,
) {
return isolateVar - a + b;
}, exceptionalReturn: 1111);
isolateVar = 42;
Expect.equals(
1111,
lib.callTwoIntFunction(callback.nativeFunction, 1000, 234),
);
callback.close();
}
main(args, message) async {
lib = NativeLibrary();
// Simple tests.
await testNativeCallableHelloWorld();
await testNativeCallableHelloWorldClosure();
testNativeCallableSync();
testNativeCallableSyncThrows();
testNativeCallableAccessNonSharedVar();
print("All tests completed :)");
}