From 50e0e0d99df1f1a581882c86eaaf785766945d29 Mon Sep 17 00:00:00 2001 From: Alexander Aprelev Date: Tue, 6 May 2025 13:28:44 -0700 Subject: [PATCH] [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 Commit-Queue: Alexander Aprelev --- BUILD.gn | 2 + .../modular/transformations/ffi/common.dart | 30 +++ .../transformations/ffi/use_sites.dart | 116 +++++++-- pubspec.yaml | 1 + runtime/bin/ffi_test/ffi_test_functions.cc | 6 + runtime/lib/ffi.cc | 8 + runtime/vm/bootstrap_natives.h | 1 + runtime/vm/bss_relocs.cc | 3 + runtime/vm/bss_relocs.h | 1 + runtime/vm/compiler/ffi/callback.cc | 6 + .../frontend/kernel_binary_flowgraph.cc | 17 +- runtime/vm/compiler/frontend/kernel_to_il.cc | 15 +- runtime/vm/compiler/recognized_methods_list.h | 4 + runtime/vm/compiler/stub_code_compiler_arm.cc | 33 +++ .../vm/compiler/stub_code_compiler_arm64.cc | 53 +++++ .../vm/compiler/stub_code_compiler_ia32.cc | 38 ++- .../vm/compiler/stub_code_compiler_riscv.cc | 66 +++++- runtime/vm/compiler/stub_code_compiler_x64.cc | 51 +++- runtime/vm/ffi_callback_metadata.cc | 81 ++++--- runtime/vm/ffi_callback_metadata.h | 80 +++++-- runtime/vm/ffi_callback_metadata_test.cc | 10 +- runtime/vm/isolate.cc | 15 +- runtime/vm/isolate.h | 4 + runtime/vm/kernel_loader.cc | 25 +- runtime/vm/kernel_loader.h | 3 +- runtime/vm/object.h | 2 + runtime/vm/runtime_entry.cc | 90 +++++-- runtime/vm/runtime_entry.h | 2 +- runtime/vm/symbols.h | 1 + runtime/vm/thread.cc | 2 - runtime/vm/thread.h | 7 +- samples/ffi/httpIG/.gitignore | 6 + samples/ffi/httpIG/BUILD.gn | 11 + samples/ffi/httpIG/README.md | 12 + samples/ffi/httpIG/lib/dylib_utils.dart | 22 ++ samples/ffi/httpIG/lib/fake_http.cc | 60 +++++ samples/ffi/httpIG/lib/http.dart | 142 +++++++++++ samples/ffi/httpIG/pubspec.yaml | 14 ++ samples/ffi/httpIG/test/http_test.dart | 31 +++ samples/samples.status | 1 + sdk/lib/_internal/vm/lib/ffi_patch.dart | 42 ++++ sdk/lib/ffi/ffi.dart | 8 + sdk/lib/io/platform.dart | 3 +- .../isolate_group_shared_callback_test.dart | 221 ++++++++++++++++++ 44 files changed, 1225 insertions(+), 121 deletions(-) create mode 100644 samples/ffi/httpIG/.gitignore create mode 100644 samples/ffi/httpIG/BUILD.gn create mode 100644 samples/ffi/httpIG/README.md create mode 100644 samples/ffi/httpIG/lib/dylib_utils.dart create mode 100644 samples/ffi/httpIG/lib/fake_http.cc create mode 100644 samples/ffi/httpIG/lib/http.dart create mode 100644 samples/ffi/httpIG/pubspec.yaml create mode 100644 samples/ffi/httpIG/test/http_test.dart create mode 100644 tests/ffi/isolate_group_shared_callback_test.dart diff --git a/BUILD.gn b/BUILD.gn index 64d472f8a46..17791774432 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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", diff --git a/pkg/vm/lib/modular/transformations/ffi/common.dart b/pkg/vm/lib/modular/transformations/ffi/common.dart index 287532c67f2..7def1f88256 100644 --- a/pkg/vm/lib/modular/transformations/ffi/common.dart +++ b/pkg/vm/lib/modular/transformations/ffi/common.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 loadMethods; final Map loadUnalignedMethods; final Map 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', diff --git a/pkg/vm/lib/modular/transformations/ffi/use_sites.dart b/pkg/vm/lib/modular/transformations/ffi/use_sites.dart index 6d5fbbd9b22..b900c7dffbf 100644 --- a/pkg/vm/lib/modular/transformations/ffi/use_sites.dart +++ b/pkg/vm/lib/modular/transformations/ffi/use_sites.dart @@ -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.isolateLocal(target, exceptionalReturn) calls become: - // isStaticFunction is false: - // _NativeCallableIsolateLocal( - // _createNativeCallableIsolateLocal>( - // _nativeIsolateLocalCallbackFunction(exceptionalReturn), - // target, - // true)); // isStaticFunction is true: // _NativeCallableIsolateLocal( // _createNativeCallableIsolateLocal>( // _nativeCallbackFunction(target, exceptionalReturn), // null, // true); + // isStaticFunction is false: + // _NativeCallableIsolateLocal( + // _createNativeCallableIsolateLocal>( + // _nativeIsolateLocalCallbackFunction(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(_handler, debugName); // _callback._pointer = _createNativeCallableListener>( - // _nativeAsyncCallbackFunction(), _callback._rawPort); + // _nativeAsyncCallbackFunction(), _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>( - // _nativeAsyncCallbackFunction(), _callback._rawPort); + // _nativeAsyncCallbackFunction(), _callback._port); final pointerValue = StaticInvocation( createNativeCallableListenerProcedure, Arguments( @@ -1054,9 +1059,73 @@ mixin _FfiUseSiteTransformer on FfiTransformer { ); } - Expression _verifyAndReplaceNativeCallableIsolateLocal( + // NativeCallable.isolateGroupShared(target, exceptionalReturn) calls become: + // isStaticFunction is true: + // _NativeCallableIsolateGroupShared( + // _createNativeCallableIsolateGroupShared>( + // _nativeIsolateGroupSharedCallbackFunction(target, exceptionalReturn), + // null); + // isStaticFunction is false: + // _NativeCallableIsolateGroupShared( + // _createNativeCallableIsolateGroupShared>( + // _nativeIsolateGroupSharedClosureFunction(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() @@ -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) { diff --git a/pubspec.yaml b/pubspec.yaml index f7e48105aae..9af99e01468 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/runtime/bin/ffi_test/ffi_test_functions.cc b/runtime/bin/ffi_test/ffi_test_functions.cc index cdc33e0f66b..8b98079d7fc 100644 --- a/runtime/bin/ffi_test/ffi_test_functions.cc +++ b/runtime/bin/ffi_test/ffi_test_functions.cc @@ -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 diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc index ac1ede8e1c9..d2703cfdc4f 100644 --- a/runtime/lib/ffi.cc +++ b/runtime/lib/ffi.cc @@ -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()); diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 4fe3599decd..9f292f960ff 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -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) \ diff --git a/runtime/vm/bss_relocs.cc b/runtime/vm/bss_relocs.cc index 56d8456051d..b4062e17655 100644 --- a/runtime/vm/bss_relocs.cc +++ b/runtime/vm/bss_relocs.cc @@ -35,6 +35,9 @@ void BSS::Initialize(Thread* current, uword* bss_start, bool vm) { InitializeBSSEntry(Relocation::DLRT_ExitTemporaryIsolate, reinterpret_cast(DLRT_ExitTemporaryIsolate), bss_start); + InitializeBSSEntry( + Relocation::DLRT_ExitIsolateGroupSharedIsolate, + reinterpret_cast(DLRT_ExitIsolateGroupSharedIsolate), bss_start); } } // namespace dart diff --git a/runtime/vm/bss_relocs.h b/runtime/vm/bss_relocs.h index 97647e214c6..67ab37f5520 100644 --- a/runtime/vm/bss_relocs.h +++ b/runtime/vm/bss_relocs.h @@ -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. diff --git a/runtime/vm/compiler/ffi/callback.cc b/runtime/vm/compiler/ffi/callback.cc index 984b2a0596d..691595cdce2 100644 --- a/runtime/vm/compiler/ffi/callback.cc +++ b/runtime/vm/compiler/ffi/callback.cc @@ -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(); } diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index d7dcf336075..bfe9492435d 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -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(target, exceptionalReturn) // + // FfiCallbackKind::kIsolateGroupSharedStaticCallback: + // _nativeCallbackFunction(target, exceptionalReturn) + // // FfiCallbackKind::kAsyncCallback: // _nativeAsyncCallbackFunction() // @@ -6217,9 +6226,15 @@ Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction( // _nativeIsolateLocalCallbackFunction( // exceptionalReturn) // + // FfiCallbackKind::kIsolateGroupSharedClosureCallback: + // _nativeIsolateGroupSharedCallbackFunction( + // 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(has_target) + static_cast(has_exceptional_return); diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index aedb4992424..e29d16c8960 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -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); diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h index 9a8a205347c..e0a966c23bf 100644 --- a/runtime/vm/compiler/recognized_methods_list.h +++ b/runtime/vm/compiler/recognized_methods_list.h @@ -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) \ diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc index 74702c021cd..3535e571dbf 100644 --- a/runtime/vm/compiler/stub_code_compiler_arm.cc +++ b/runtime/vm/compiler/stub_code_compiler_arm.cc @@ -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(FfiCallbackMetadata::TrampolineType::kAsync))); __ b(&async_callback, EQ); + __ cmp(R4, + Operand(static_cast( + 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 diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc index ff164eff5bb..69459f20007 100644 --- a/runtime/vm/compiler/stub_code_compiler_arm64.cc +++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc @@ -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(FfiCallbackMetadata::TrampolineType::kAsync))); __ b(&async_callback, EQ); + __ cmp(R9, + Operand(static_cast( + 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(&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 diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc index 421b7bd3cac..59bd7fbb80e 100644 --- a/runtime/vm/compiler/stub_code_compiler_ia32.cc +++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc @@ -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( FfiCallbackMetadata::TrampolineType::kAsync))); __ j(EQUAL, &async_callback, Assembler::kNearJump); + __ cmpl(EBX, + Immediate(static_cast( + 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( + 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 diff --git a/runtime/vm/compiler/stub_code_compiler_riscv.cc b/runtime/vm/compiler/stub_code_compiler_riscv.cc index 6379382a037..b3b4744ca28 100644 --- a/runtime/vm/compiler/stub_code_compiler_riscv.cc +++ b/runtime/vm/compiler/stub_code_compiler_riscv.cc @@ -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(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(FfiCallbackMetadata::TrampolineType::kAsync) == 2); + __ subi(T3, T3, 2); + __ beqz(T3, &async_callback, Assembler::kNearJump); + + COMPILE_ASSERT( + static_cast( + 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(&DLRT_ExitIsolateGroupSharedIsolate)); +#else + __ Emit64(reinterpret_cast(&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 diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc index 6dfdbe68035..48b198488f3 100644 --- a/runtime/vm/compiler/stub_code_compiler_x64.cc +++ b/runtime/vm/compiler/stub_code_compiler_x64.cc @@ -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( FfiCallbackMetadata::TrampolineType::kAsync))); __ j(EQUAL, &async_callback, Assembler::kNearJump); + __ cmpq(RAX, + Immediate(static_cast( + 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( + 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 diff --git a/runtime/vm/ffi_callback_metadata.cc b/runtime/vm/ffi_callback_metadata.cc index 8d25f1542ec..34ec4958571 100644 --- a/runtime/vm/ffi_callback_metadata.cc +++ b/runtime/vm/ffi_callback_metadata.cc @@ -170,6 +170,9 @@ void FfiCallbackMetadata::EnsureFreeListNotEmptyLocked() { reinterpret_cast(DLRT_GetFfiCallbackMetadata)); FillRuntimeFunction(new_page, kExitTemporaryIsolate, reinterpret_cast(DLRT_ExitTemporaryIsolate)); + FillRuntimeFunction( + new_page, kExitIsolateGroupSharedIsolate, + reinterpret_cast(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; } - *entry = Metadata(target_isolate, trampoline_type, target_entry_point, - context, nullptr, next_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, - Zone* zone, - const Function& function, - const Closure& closure, - Metadata** list_head) { +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(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(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 diff --git a/runtime/vm/ffi_callback_metadata.h b/runtime/vm/ffi_callback_metadata.h index 7fb7c724721..a48e22c771e 100644 --- a/runtime/vm/ffi_callback_metadata.h +++ b/runtime/vm/ffi_callback_metadata.h @@ -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,12 +70,14 @@ class FfiCallbackMetadata { Dart_Port send_port, Metadata** list_head); - // Creates an isolate local callback trampoline for the given function. - Trampoline CreateIsolateLocalFfiCallback(Isolate* isolate, - Zone* zone, - const Function& function, - const Closure& closure, - Metadata** list_head); + // 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, + Metadata** list_head); // Deletes a single trampoline. void DeleteCallback(Trampoline trampoline, Metadata** list_head); @@ -81,7 +87,10 @@ class FfiCallbackMetadata { // FFI callback metadata for any sync or async trampoline. class Metadata { - Isolate* target_isolate_; + 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(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_; diff --git a/runtime/vm/ffi_callback_metadata_test.cc b/runtime/vm/ffi_callback_metadata_test.cc index b09896b3950..95dc590af42 100644 --- a/runtime/vm/ffi_callback_metadata_test.cc +++ b/runtime/vm/ffi_callback_metadata_test.cc @@ -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. diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc index f7aef569049..92576e38e84 100644 --- a/runtime/vm/isolate.cc +++ b/runtime/vm/isolate.cc @@ -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() { diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h index 2c22d0b52c4..6e1b25a4948 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -1284,6 +1284,10 @@ class Isolate : public IntrusiveDListEntry { 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(); diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc index 5edd7ae5794..4dcf4e3e28a 100644 --- a/runtime/vm/kernel_loader.cc +++ b/runtime/vm/kernel_loader.cc @@ -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,9 +1769,11 @@ void KernelLoader::ReadVMAnnotations(intptr_t annotation_count, } if (constant_reader.IsStringConstant(name_index, "vm:shared")) { if (!FLAG_experimental_shared_data) { - FATAL( - "Encountered vm:shared when functionality is disabled. " - "Pass --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); } @@ -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); diff --git a/runtime/vm/kernel_loader.h b/runtime/vm/kernel_loader.h index 6a309cce8df..bcd9e1a3f2c 100644 --- a/runtime/vm/kernel_loader.h +++ b/runtime/vm/kernel_loader.h @@ -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); diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 0c09e10f358..1160c94601f 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -2990,7 +2990,9 @@ struct NameFormattingParams { enum class FfiCallbackKind : uint8_t { kIsolateLocalStaticCallback, + kIsolateGroupSharedStaticCallback, kIsolateLocalClosureCallback, + kIsolateGroupSharedClosureCallback, kAsyncCallback, }; diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc index 44f66ebc5c7..1f0aaeb8cec 100644 --- a/runtime/vm/runtime_entry.cc +++ b/runtime/vm/runtime_entry.cc @@ -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,30 +4460,57 @@ extern "C" Thread* DLRT_GetFfiCallbackMetadata( if (!metadata.IsLive()) { FATAL("Callback invoked after it has been deleted."); } - Isolate* target_isolate = metadata.target_isolate(); - *out_entry_point = metadata.target_entry_point(); - *out_trampoline_type = static_cast(metadata.trampoline_type()); - if (current_thread == nullptr) { - FATAL("Cannot invoke native callback outside an isolate."); - } - if (current_thread->no_callback_scope_depth() != 0) { - FATAL("Cannot invoke native callback when API callbacks are prohibited."); - } - if (current_thread->is_unwind_in_progress()) { - FATAL("Cannot invoke native callback while unwind error propagates."); - } - if (!current_thread->IsDartMutatorThread()) { - FATAL("Native callbacks must be invoked on the mutator thread."); - } - if (current_thread->isolate() != target_isolate) { - FATAL("Cannot invoke native callback from a different isolate."); - } - if (current_thread->execution_state() != Thread::kThreadInNative) { - FATAL("Cannot invoke native callback from a leaf call."); + if (metadata.is_isolate_group_shared()) { + *out_entry_point = metadata.target_entry_point(); + *out_trampoline_type = static_cast(metadata.trampoline_type()); + } else { + Isolate* target_isolate = metadata.target_isolate(); + *out_entry_point = metadata.target_entry_point(); + *out_trampoline_type = static_cast(metadata.trampoline_type()); + if (current_thread == nullptr) { + FATAL("Cannot invoke native callback outside an isolate."); + } + if (current_thread->no_callback_scope_depth() != 0) { + FATAL("Cannot invoke native callback when API callbacks are prohibited."); + } + if (current_thread->is_unwind_in_progress()) { + FATAL("Cannot invoke native callback while unwind error propagates."); + } + if (!current_thread->IsDartMutatorThread()) { + FATAL("Native callbacks must be invoked on the mutator thread."); + } + if (current_thread->isolate() != target_isolate) { + FATAL("Cannot invoke native callback from a different isolate."); + } + if (current_thread->execution_state() != Thread::kThreadInNative) { + FATAL("Cannot invoke native callback from a leaf call."); + } } - current_thread->ExitSafepointFromNative(); - current_thread->set_execution_state(Thread::kThreadInVM); + 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(current_thread)); + new_thread->set_unboxed_int64_runtime_second_arg( + reinterpret_cast(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(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(); diff --git a/runtime/vm/runtime_entry.h b/runtime/vm/runtime_entry.h index e2b582cdb60..42e3efe3b17 100644 --- a/runtime/vm/runtime_entry.h +++ b/runtime/vm/runtime_entry.h @@ -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); diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 519315a9158..a835be51c03 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -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") \ diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc index 2a0ceab0b0a..648d0b7740b 100644 --- a/runtime/vm/thread.cc +++ b/runtime/vm/thread.cc @@ -1552,8 +1552,6 @@ void Thread::SetupMutatorState() { DeferredMarkingStackAcquire(); } - // TODO(koda): Use StoreBufferAcquire once we properly flush - // before Scavenge. if (task_kind_ == kMutatorTask) { StoreBufferAcquire(); } else { diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h index f7801ca851b..ce341587cc2 100644 --- a/runtime/vm/thread.h +++ b/runtime/vm/thread.h @@ -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(); diff --git a/samples/ffi/httpIG/.gitignore b/samples/ffi/httpIG/.gitignore new file mode 100644 index 00000000000..a18c42e5cee --- /dev/null +++ b/samples/ffi/httpIG/.gitignore @@ -0,0 +1,6 @@ +.dart_tool +.packages +pubspec.lock +lib/libfake_http.so +lib/libfake_http.dylib +lib/fake_http.dll diff --git a/samples/ffi/httpIG/BUILD.gn b/samples/ffi/httpIG/BUILD.gn new file mode 100644 index 00000000000..df76fc70e9b --- /dev/null +++ b/samples/ffi/httpIG/BUILD.gn @@ -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" ] + } +} diff --git a/samples/ffi/httpIG/README.md b/samples/ffi/httpIG/README.md new file mode 100644 index 00000000000..023a324632e --- /dev/null +++ b/samples/ffi/httpIG/README.md @@ -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`. diff --git a/samples/ffi/httpIG/lib/dylib_utils.dart b/samples/ffi/httpIG/lib/dylib_utils.dart new file mode 100644 index 00000000000..844c1faf707 --- /dev/null +++ b/samples/ffi/httpIG/lib/dylib_utils.dart @@ -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? paths}) => + DynamicLibrary.open( + (paths ?? [Uri()]) + .map((path) => dylibPath(name, path).toFilePath()) + .firstWhere((lib) => File(lib).existsSync()), + ); diff --git a/samples/ffi/httpIG/lib/fake_http.cc b/samples/ffi/httpIG/lib/fake_http.cc new file mode 100644 index 00000000000..ed39a9eea83 --- /dev/null +++ b/samples/ffi/httpIG/lib/fake_http.cc @@ -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 +#include +#include +#include + +#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 + + + + Hello world! + + +)"; + +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 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; + } +} diff --git a/samples/ffi/httpIG/lib/http.dart b/samples/ffi/httpIG/lib/http.dart new file mode 100644 index 00000000000..492451d0628 --- /dev/null +++ b/samples/ffi/httpIG/lib/http.dart @@ -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 httpGet(String uri) async { + // Create the NativeCallable.listener. + final completer = Completer(); + 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.isolateGroupShared(( + Pointer responsePointer, + ) { + final typedList = responsePointer.cast().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.isolateGroupShared(( + Pointer requestPointer, + ) { + counter++; + final typedList = requestPointer.cast().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); + +typedef HttpGetFunction = void Function( + Pointer, Pointer>); +typedef HttpGetNativeFunction = Void Function( + Pointer, Pointer>); +final nativeHttpGet = + dylib.lookupFunction('http_get'); + +typedef HttpStartServingFunction = bool Function( + Pointer>); +typedef HttpStartServingNativeFunction = Bool Function( + Pointer>); +final nativeHttpStartServing = dylib + .lookupFunction( + 'http_start_serving', +); + +typedef HttpStopServingFunction = void Function(); +typedef HttpStopServingNativeFunction = Void Function(); +final nativeHttpStopServing = dylib + .lookupFunction( + 'http_stop_serving', +); + +Future 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(); +} diff --git a/samples/ffi/httpIG/pubspec.yaml b/samples/ffi/httpIG/pubspec.yaml new file mode 100644 index 00000000000..03c6ad100d3 --- /dev/null +++ b/samples/ffi/httpIG/pubspec.yaml @@ -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 diff --git a/samples/ffi/httpIG/test/http_test.dart b/samples/ffi/httpIG/test/http_test.dart new file mode 100644 index 00000000000..0e1da4667d9 --- /dev/null +++ b/samples/ffi/httpIG/test/http_test.dart @@ -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 main() async { + test('httpGet', () async { + final response = await httpGet('http://example.com'); + expect(response, contains('Hello world!')); + }); + + test('httpServe', () async { + final completer = Completer(); + final receivePort = httpServe((request) { + if (!completer.isCompleted) { + completer.complete(request); + } + }); + final request = await completer.future; + expect(request, contains('www.example.com')); + receivePort.close(); + }); +} diff --git a/samples/samples.status b/samples/samples.status index 1124deee938..26d9087668d 100644 --- a/samples/samples.status +++ b/samples/samples.status @@ -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 ] diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart index cc3b5a7016d..2b3ce606bdd 100644 --- a/sdk/lib/_internal/vm/lib/ffi_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart @@ -193,6 +193,11 @@ external Pointer _createNativeCallableIsolateLocal< NS extends NativeFunction >(dynamic trampoline, dynamic target, bool keepIsolateAlive); +@pragma("vm:external-name", "Ffi_createNativeCallableIsolateGroupShared") +external Pointer _createNativeCallableIsolateGroupShared< + NS extends NativeFunction +>(dynamic trampoline, dynamic target); + @pragma("vm:external-name", "Ffi_deleteNativeCallable") external void _deleteNativeCallable( Pointer pointer, @@ -209,6 +214,19 @@ external dynamic _nativeIsolateLocalCallbackFunction( dynamic exceptionalReturn, ); +@pragma("vm:recognized", "other") +@pragma("vm:external-name", "Ffi_nativeIsolateGroupSharedCallbackFunction") +external dynamic _nativeIsolateGroupSharedCallbackFunction( + Function target, + dynamic exceptionalReturn, +); + +@pragma("vm:recognized", "other") +@pragma("vm:external-name", "Ffi_nativeIsolateGroupSharedClosureFunction") +external dynamic _nativeIsolateGroupSharedClosureFunction( + dynamic exceptionalReturn, +); + @patch @pragma('vm:deeply-immutable') @pragma("vm:entry-point") @@ -329,6 +347,29 @@ final class _NativeCallableListener bool get _keepIsolateAlive => _port.keepIsolateAlive; } +final class _NativeCallableIsolateGroupShared + extends _NativeCallableBase { + 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 extends _Compound { @@ -1884,6 +1925,7 @@ class Native { // Resolver for FFI Native C function pointers. @pragma('vm:entry-point') + @pragma('vm:shared') static final _ffi_resolver = _get_ffi_native_resolver< NativeFunction diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index cd5003350bb..8b872ff1ba9 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -34,6 +34,7 @@ external int sizeOf(); /// Represents a pointer into the native C memory corresponding to 'NULL', e.g. /// a pointer with address 0. +@pragma('vm:shared') final Pointer nullptr = Pointer.fromAddress(0); /// Represents a pointer into the native C memory. Cannot be extended. @@ -395,6 +396,13 @@ abstract final class NativeCallable { 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 diff --git a/sdk/lib/io/platform.dart b/sdk/lib/io/platform.dart index 0a532f21013..6e36cacb0b3 100644 --- a/sdk/lib/io/platform.dart +++ b/sdk/lib/io/platform.dart @@ -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). diff --git a/tests/ffi/isolate_group_shared_callback_test.dart b/tests/ffi/isolate_group_shared_callback_test.dart new file mode 100644 index 00000000000..20b399e5afa --- /dev/null +++ b/tests/ffi/isolate_group_shared_callback_test.dart @@ -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( + "CallFunctionOnSameThread", + ); + callFunctionOnNewThreadBlocking = ffiTestFunctions + .lookupFunction( + "CallFunctionOnNewThreadBlocking", + ); + callFunctionOnNewThreadNonBlocking = ffiTestFunctions + .lookupFunction( + "CallFunctionOnNewThreadNonBlocking", + ); + callTwoIntFunction = ffiTestFunctions + .lookupFunction("CallTwoIntFunction"); + sleep = ffiTestFunctions.lookupFunction( + "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 testNativeCallableHelloWorld() async { + mutexCondvar = Mutex(); + conditionVariable = ConditionVariable(); + // final callback = NativeCallable.isolateGroupShared(simpleFunction); + final callback = NativeCallable.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 testNativeCallableHelloWorldClosure() async { + mutexCondvar = Mutex(); + conditionVariable = ConditionVariable(); + // final callback = NativeCallable.isolateGroupShared(simpleFunction); + final callback = NativeCallable.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.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.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.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 :)"); +}