[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:
committed by
Commit Queue
parent
7431e885e8
commit
50e0e0d99d
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -2990,7 +2990,9 @@ struct NameFormattingParams {
|
||||
|
||||
enum class FfiCallbackKind : uint8_t {
|
||||
kIsolateLocalStaticCallback,
|
||||
kIsolateGroupSharedStaticCallback,
|
||||
kIsolateLocalClosureCallback,
|
||||
kIsolateGroupSharedClosureCallback,
|
||||
kAsyncCallback,
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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") \
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
.dart_tool
|
||||
.packages
|
||||
pubspec.lock
|
||||
lib/libfake_http.so
|
||||
lib/libfake_http.dylib
|
||||
lib/fake_http.dll
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
@@ -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`.
|
||||
@@ -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()),
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
@@ -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 ]
|
||||
|
||||
@@ -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)>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 :)");
|
||||
}
|
||||
Reference in New Issue
Block a user