From 7eac9f355ed54cb0aab018814f776c08d20cef44 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Thu, 18 Jun 2020 07:30:43 +0000 Subject: [PATCH] [vm/ffi] Expose a subset of dart_(native_)api.h for dynamic linking. This CL introduces dart_api_dl.h which exposes a subset of dart_api.h and dart_native_api.h for dynamic linking at runtime through the FFI. Dynamic linking is done through including dart_api_dl.cc in a shared library and passing NativeApi.initializeApiDLData to the init function. This CL also includes Native API versioning to deal with possible version skew between native api version against which native libraries are compiled and the version in the DartVM the code is run on. The subset of symbols in the CL includes handle related symbols, error related symbols, handle scope symbols, and native port sumbols. Design: http://go/dart-ffi-expose-dart-api Closes: https://github.com/dart-lang/sdk/issues/40607 Closes: https://github.com/dart-lang/sdk/issues/36858 Closes: https://github.com/dart-lang/sdk/issues/41319 Closes: https://github.com/flutter/flutter/issues/46887 Closes: https://github.com/flutter/flutter/issues/47061 Misc: Closes: https://github.com/dart-lang/sdk/issues/42260 Change-Id: I9e557808dbc99b341f23964cbddbb05f26d7a6c5 Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,app-kernel-linux-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-precomp-linux-debug-x64-try,vm-dartkb-linux-release-x64-abi-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-msan-linux-release-x64-try,vm-kernel-precomp-msan-linux-release-x64-try,vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,dart-sdk-linux-try,analyzer-analysis-server-linux-try,analyzer-linux-release-try,front-end-linux-release-x64-try,vm-kernel-precomp-win-release-x64-try,vm-kernel-mac-debug-x64-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-nnbd-linux-debug-x64-try,analyzer-nnbd-linux-release-try,front-end-nnbd-linux-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/145592 Commit-Queue: Daco Harkes Reviewed-by: Martin Kustermann Reviewed-by: Alexander Markov --- pkg/kernel/lib/library_index.dart | 3 +- pkg/vm/lib/transformations/ffi.dart | 10 +- pkg/vm/lib/transformations/ffi_use_sites.dart | 14 +- runtime/BUILD.gn | 3 + runtime/bin/BUILD.gn | 11 +- .../ffi_test/ffi_test_functions_vmspecific.cc | 87 +++++++----- runtime/include/dart_api.h | 2 + runtime/include/dart_api_dl.cc | 59 ++++++++ runtime/include/dart_api_dl.h | 126 ++++++++++++++++++ runtime/include/dart_native_api.h | 2 + runtime/include/dart_version.h | 16 +++ runtime/include/internal/dart_api_dl_impl.h | 67 ++++++++++ runtime/lib/ffi.cc | 36 ++++- runtime/vm/bootstrap_natives.h | 5 +- samples/ffi/async/sample_async_callback.dart | 14 +- .../ffi/async/sample_native_port_call.dart | 41 +----- ...mple_ffi_functions_callbacks_closures.dart | 67 ++++++++++ samples/ffi/samples_test.dart | 31 +++-- sdk/BUILD.gn | 3 + sdk/lib/_internal/vm/lib/ffi_patch.dart | 19 ++- sdk/lib/ffi/ffi.dart | 17 ++- ...ecific_handle_dynamically_linked_test.dart | 45 +++++++ ...ecific_handle_dynamically_linked_test.dart | 45 +++++++ 23 files changed, 613 insertions(+), 110 deletions(-) create mode 100644 runtime/include/dart_api_dl.cc create mode 100644 runtime/include/dart_api_dl.h create mode 100644 runtime/include/dart_version.h create mode 100644 runtime/include/internal/dart_api_dl_impl.h create mode 100644 samples/ffi/sample_ffi_functions_callbacks_closures.dart create mode 100644 tests/ffi/vmspecific_handle_dynamically_linked_test.dart create mode 100644 tests/ffi_2/vmspecific_handle_dynamically_linked_test.dart diff --git a/pkg/kernel/lib/library_index.dart b/pkg/kernel/lib/library_index.dart index 3a05805fdc2..94db880b2d6 100644 --- a/pkg/kernel/lib/library_index.dart +++ b/pkg/kernel/lib/library_index.dart @@ -12,6 +12,7 @@ import 'ast.dart'; class LibraryIndex { static const String getterPrefix = 'get:'; static const String setterPrefix = 'set:'; + static const String tearoffPrefix = 'get#'; /// A special class name that can be used to access the top-level members /// of a library. @@ -237,7 +238,7 @@ class _MemberTable { String getDisambiguatedExtensionName( ExtensionMemberDescriptor extensionMember) { if (extensionMember.kind == ExtensionMemberKind.TearOff) - return 'get#' + extensionMember.name.name; + return LibraryIndex.tearoffPrefix + extensionMember.name.name; if (extensionMember.kind == ExtensionMemberKind.Getter) return LibraryIndex.getterPrefix + extensionMember.name.name; if (extensionMember.kind == ExtensionMemberKind.Setter) diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart index e8ad4420d51..cc02953a55a 100644 --- a/pkg/vm/lib/transformations/ffi.dart +++ b/pkg/vm/lib/transformations/ffi.dart @@ -216,6 +216,8 @@ class FfiTransformer extends Transformer { final Map storeMethods; final Map elementAtMethods; final Procedure loadStructMethod; + final Procedure asFunctionTearoff; + final Procedure lookupFunctionTearoff; /// Classes corresponding to [NativeType], indexed by [NativeType]. final List nativeTypesClasses; @@ -274,7 +276,13 @@ class FfiTransformer extends Transformer { final name = nativeTypeClassNames[t.index]; return index.getTopLevelMember('dart:ffi', "_elementAt$name"); }), - loadStructMethod = index.getTopLevelMember('dart:ffi', '_loadStruct'); + loadStructMethod = index.getTopLevelMember('dart:ffi', '_loadStruct'), + asFunctionTearoff = index.getMember('dart:ffi', 'NativeFunctionPointer', + LibraryIndex.tearoffPrefix + 'asFunction'), + lookupFunctionTearoff = index.getMember( + 'dart:ffi', + 'DynamicLibraryExtension', + LibraryIndex.tearoffPrefix + 'lookupFunction'); /// Computes the Dart type corresponding to a ffi.[NativeType], returns null /// if it is not a valid NativeType. diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart index 96830bfb68c..f23c6696dc8 100644 --- a/pkg/vm/lib/transformations/ffi_use_sites.dart +++ b/pkg/vm/lib/transformations/ffi_use_sites.dart @@ -122,8 +122,15 @@ class _FfiUseSiteTransformer extends FfiTransformer { @override visitProcedure(Procedure node) { + if (isFfiLibrary && node.isExtensionMember) { + if (node == asFunctionTearoff || node == lookupFunctionTearoff) { + // Skip static checks and transformation for the tearoffs. + return node; + } + } + _staticTypeContext = new StaticTypeContext(node, env); - var result = super.visitProcedure(node); + final result = super.visitProcedure(node); _staticTypeContext = null; return result; } @@ -159,15 +166,16 @@ class _FfiUseSiteTransformer extends FfiTransformer { final Member target = node.target; try { - if (target == lookupFunctionMethod && !isFfiLibrary) { + if (target == lookupFunctionMethod) { final DartType nativeType = InterfaceType( nativeFunctionClass, Nullability.legacy, [node.arguments.types[0]]); final DartType dartType = node.arguments.types[1]; _ensureNativeTypeValid(nativeType, node); _ensureNativeTypeToDartType(nativeType, dartType, node); + return _replaceLookupFunction(node); - } else if (target == asFunctionMethod && !isFfiLibrary) { + } else if (target == asFunctionMethod) { final DartType dartType = node.arguments.types[1]; final DartType nativeType = InterfaceType( nativeFunctionClass, Nullability.legacy, [node.arguments.types[0]]); diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index da22c445203..6bf8c3996d1 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -210,8 +210,11 @@ source_set("dart_api") { public_configs = [ ":dart_public_config" ] sources = [ "include/dart_api.h", + "include/dart_api_dl.h", "include/dart_native_api.h", "include/dart_tools_api.h", + "include/dart_version.h", + "include/internal/dart_api_dl_impl.h", ] } diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn index ce8ec7afb6a..0fd5acd5ab0 100644 --- a/runtime/bin/BUILD.gn +++ b/runtime/bin/BUILD.gn @@ -1139,11 +1139,14 @@ shared_library("ffi_test_dynamic_library") { shared_library("ffi_test_functions") { deps = [ ":dart" ] - # The two files here do not depend on each other. - # flutter/flutter integration tests will only use `ffi_test_functions.cc` - - # any test functionality using `dart_api.h` has to go into - # `ffi_test_functions_vmspecific.cc`. sources = [ + # This file must be compiled in for dynamic linking. + "../include/dart_api_dl.cc", + + # The two files here do not depend on each other. + # flutter/flutter integration tests will only use `ffi_test_functions.cc` - + # any test functionality using `dart_api.h` has to go into + # `ffi_test_functions_vmspecific.cc`. "ffi_test/ffi_test_functions.cc", "ffi_test/ffi_test_functions_vmspecific.cc", ] diff --git a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc index a80882b4dc1..05b8952354c 100644 --- a/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc +++ b/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc @@ -35,6 +35,8 @@ #include "include/dart_api.h" #include "include/dart_native_api.h" +#include "include/dart_api_dl.h" + namespace dart { #define CHECK(X) \ @@ -270,31 +272,9 @@ DART_EXPORT intptr_t TestCallbackWrongIsolate(void (*fn)()) { #endif // defined(TARGET_OS_LINUX) //////////////////////////////////////////////////////////////////////////////// -// Dynamic linking of dart_native_api.h for the next two samples. -typedef bool (*Dart_PostCObjectType)(Dart_Port port_id, Dart_CObject* message); -Dart_PostCObjectType Dart_PostCObject_ = nullptr; - -DART_EXPORT void RegisterDart_PostCObject( - Dart_PostCObjectType function_pointer) { - Dart_PostCObject_ = function_pointer; -} - -typedef Dart_Port (*Dart_NewNativePortType)(const char* name, - Dart_NativeMessageHandler handler, - bool handle_concurrently); -Dart_NewNativePortType Dart_NewNativePort_ = nullptr; - -DART_EXPORT void RegisterDart_NewNativePort( - Dart_NewNativePortType function_pointer) { - Dart_NewNativePort_ = function_pointer; -} - -typedef bool (*Dart_CloseNativePortType)(Dart_Port native_port_id); -Dart_CloseNativePortType Dart_CloseNativePort_ = nullptr; - -DART_EXPORT void RegisterDart_CloseNativePort( - Dart_CloseNativePortType function_pointer) { - Dart_CloseNativePort_ = function_pointer; +// Initialize `dart_api_dl.h` +DART_EXPORT intptr_t InitDartApiDL(void* data) { + return Dart_InitializeApiDL(data); } //////////////////////////////////////////////////////////////////////////////// @@ -342,7 +322,7 @@ void NotifyDart(Dart_Port send_port, const Work* work) { dart_object.type = Dart_CObject_kInt64; dart_object.value.as_int64 = work_addr; - const bool result = Dart_PostCObject_(send_port, &dart_object); + const bool result = Dart_PostCObject_DL(send_port, &dart_object); if (!result) { FATAL("C : Posting message to port failed."); } @@ -504,16 +484,16 @@ class PendingCall { PendingCall(void** buffer, size_t* length) : response_buffer_(buffer), response_length_(length) { receive_port_ = - Dart_NewNativePort_("cpp-response", &PendingCall::HandleResponse, - /*handle_concurrently=*/false); + Dart_NewNativePort_DL("cpp-response", &PendingCall::HandleResponse, + /*handle_concurrently=*/false); } - ~PendingCall() { Dart_CloseNativePort_(receive_port_); } + ~PendingCall() { Dart_CloseNativePort_DL(receive_port_); } Dart_Port port() const { return receive_port_; } void PostAndWait(Dart_Port port, Dart_CObject* object) { std::unique_lock lock(mutex); - const bool success = Dart_PostCObject_(send_port_, object); + const bool success = Dart_PostCObject_DL(send_port_, object); if (!success) FATAL("Failed to send message, invalid port or isolate died"); printf("C : Waiting for result.\n"); @@ -668,7 +648,7 @@ void MyCallback2(uint8_t a) { printf("C : Dart_PostCObject_(request: %" Px ", call: %" Px ").\n", reinterpret_cast(&c_request), reinterpret_cast(&c_pending_call)); - Dart_PostCObject_(send_port_, &c_request); + Dart_PostCObject_DL(send_port_, &c_request); } // Simulated work for Thread #1. @@ -793,7 +773,8 @@ DART_EXPORT void ThreadPoolTest_BarrierSync( //////////////////////////////////////////////////////////////////////////////// // Functions for handle tests. // -// vmspecific_handle_test.dart +// vmspecific_handle_test.dart (statically linked). +// vmspecific_handle_dynamically_linked_test.dart (dynamically linked). static void RunFinalizer(void* isolate_callback_data, Dart_WeakPersistentHandle handle, @@ -912,4 +893,46 @@ DART_EXPORT Dart_Handle TrueHandle() { return Dart_True(); } +DART_EXPORT Dart_Handle PassObjectToCUseDynamicLinking(Dart_Handle h) { + auto persistent_handle = Dart_NewPersistentHandle_DL(h); + + Dart_Handle handle_2 = Dart_HandleFromPersistent_DL(persistent_handle); + Dart_SetPersistentHandle_DL(persistent_handle, h); + Dart_DeletePersistentHandle_DL(persistent_handle); + + auto weak_handle = Dart_NewWeakPersistentHandle_DL( + handle_2, reinterpret_cast(0x1234), 64, RunFinalizer); + Dart_Handle return_value = Dart_HandleFromWeakPersistent_DL(weak_handle); + + Dart_DeleteWeakPersistentHandle_DL(weak_handle); + + return return_value; +} + +//////////////////////////////////////////////////////////////////////////////// +// Example for doing closure callbacks with help of `dart_api.h`. +// +// sample_ffi_functions_callbacks_closures.dart + +void (*callback_)(Dart_Handle); +Dart_PersistentHandle closure_to_callback_; + +DART_EXPORT void RegisterClosureCallbackFP(void (*callback)(Dart_Handle)) { + callback_ = callback; +} + +DART_EXPORT void RegisterClosureCallback(Dart_Handle h) { + closure_to_callback_ = Dart_NewPersistentHandle_DL(h); +} + +DART_EXPORT void InvokeClosureCallback() { + Dart_Handle closure_handle = + Dart_HandleFromPersistent_DL(closure_to_callback_); + callback_(closure_handle); +} + +DART_EXPORT void ReleaseClosureCallback() { + Dart_DeletePersistentHandle_DL(closure_to_callback_); +} + } // namespace dart diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h index 56120e78fa4..a58095cf306 100644 --- a/runtime/include/dart_api.h +++ b/runtime/include/dart_api.h @@ -243,6 +243,8 @@ typedef struct _Dart_IsolateGroup* Dart_IsolateGroup; typedef struct _Dart_Handle* Dart_Handle; typedef Dart_Handle Dart_PersistentHandle; typedef struct _Dart_WeakPersistentHandle* Dart_WeakPersistentHandle; +// These three structs are versioned by DART_API_DL_MAJOR_VERSION, bump the +// version when changing this struct. typedef void (*Dart_WeakPersistentHandleFinalizer)( void* isolate_callback_data, diff --git a/runtime/include/dart_api_dl.cc b/runtime/include/dart_api_dl.cc new file mode 100644 index 00000000000..5ab685d921e --- /dev/null +++ b/runtime/include/dart_api_dl.cc @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, 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/dart_api_dl.h" +#include "include/dart_version.h" +#include "include/internal/dart_api_dl_impl.h" + +#include + +#define DART_API_DL_DEFINITIONS(name) \ + using name##Type = decltype(&name); \ + name##Type name##_DL = nullptr; +DART_API_ALL_DL_SYMBOLS(DART_API_DL_DEFINITIONS) +#undef DART_API_DL_DEFINITIONS + +typedef void (*DartApiEntry_function)(); + +DartApiEntry_function FindFunctionPointer(const DartApiEntry* entries, + const char* name) { + while (entries->name != nullptr) { + if (strcmp(entries->name, name) == 0) return entries->function; + entries++; + } + return nullptr; +} + +intptr_t Dart_InitializeApiDL(void* data) { + DartApi* dart_api_data = reinterpret_cast(data); + + if (dart_api_data->major != DART_API_DL_MAJOR_VERSION) { + // If the DartVM we're running on does not have the same version as this + // file was compiled against, refuse to initialize. The symbols are not + // compatible. + return -1; + } + // Minor versions are allowed to be different. + // If the DartVM has a higher minor version, it will provide more symbols + // than we initialize here. + // If the DartVM has a lower minor version, it will not provide all symbols. + // In that case, we leave the missing symbols un-initialized. Those symbols + // should not be used by the Dart and native code. The client is responsible + // for checking the minor version number himself based on which symbols it + // is using. + // (If we would error out on this case, recompiling native code against a + // newer SDK would break all uses on older SDKs, which is too strict.) + + const DartApiEntry* dart_api_function_pointers = dart_api_data->functions; + +#define DART_API_DL_INIT(name) \ + name##_DL = reinterpret_cast( \ + FindFunctionPointer(dart_api_function_pointers, #name)); + DART_API_ALL_DL_SYMBOLS(DART_API_DL_INIT) +#undef DART_API_DL_INIT + + return 0; +} diff --git a/runtime/include/dart_api_dl.h b/runtime/include/dart_api_dl.h new file mode 100644 index 00000000000..ba8424dd1ec --- /dev/null +++ b/runtime/include/dart_api_dl.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020, 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. + */ + +#ifndef RUNTIME_INCLUDE_DART_API_DL_H_ +#define RUNTIME_INCLUDE_DART_API_DL_H_ + +#include "include/dart_api.h" +#include "include/dart_native_api.h" + +/** \mainpage Dynamically Linked Dart API + * + * This exposes a subset of symbols from dart_api.h and dart_native_api.h + * available in every Dart embedder through dynamic linking. + * + * All symbols are postfixed with _DL to indicate that they are dynamically + * linked and to prevent conflicts with the original symbol. + * + * Link `dart_api_dl.cc` file into your library and invoke + * `Dart_InitializeApiDL` with `NativeApi.initializeApiDLData`. + */ + +intptr_t Dart_InitializeApiDL(void* data); + +// IMPORTANT! Never update these signatures without properly updating +// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION. +// +// Verbatim copy of `dart_native_api.h` and `dart_api.h` symbols to trigger +// compile-time errors if the sybols in those files are updated without +// updating these. +// +// Function signatures and typedefs are carbon copied. Structs are typechecked +// nominally in C/C++, so they are not copied, instead a comment is added to +// their definition. +typedef int64_t Dart_Port_DL; + +typedef void (*Dart_NativeMessageHandler_DL)(Dart_Port_DL dest_port_id, + Dart_CObject* message); + +DART_EXTERN_C bool (*Dart_PostCObject_DL)(Dart_Port_DL port_id, + Dart_CObject* message); + +DART_EXTERN_C bool (*Dart_PostInteger_DL)(Dart_Port_DL port_id, + int64_t message); + +DART_EXTERN_C Dart_Port_DL (*Dart_NewNativePort_DL)( + const char* name, + Dart_NativeMessageHandler_DL handler, + bool handle_concurrently); + +DART_EXTERN_C bool (*Dart_CloseNativePort_DL)(Dart_Port_DL native_port_id); + +DART_EXTERN_C bool (*Dart_IsError_DL)(Dart_Handle handle); + +DART_EXTERN_C bool (*Dart_IsApiError_DL)(Dart_Handle handle); + +DART_EXTERN_C bool (*Dart_IsUnhandledExceptionError_DL)(Dart_Handle handle); + +DART_EXTERN_C bool (*Dart_IsCompilationError_DL)(Dart_Handle handle); + +DART_EXTERN_C bool (*Dart_IsFatalError_DL)(Dart_Handle handle); + +DART_EXTERN_C const char* (*Dart_GetError_DL)(Dart_Handle handle); + +DART_EXTERN_C bool (*Dart_ErrorHasException_DL)(Dart_Handle handle); + +DART_EXTERN_C Dart_Handle (*Dart_ErrorGetException_DL)(Dart_Handle handle); + +DART_EXTERN_C Dart_Handle (*Dart_ErrorGetStackTrace_DL)(Dart_Handle handle); + +DART_EXTERN_C Dart_Handle (*Dart_NewApiError_DL)(const char* error); + +DART_EXTERN_C Dart_Handle (*Dart_NewCompilationError_DL)(const char* error); + +DART_EXTERN_C Dart_Handle (*Dart_NewUnhandledExceptionError_DL)( + Dart_Handle exception); + +DART_EXTERN_C void (*Dart_PropagateError_DL)(Dart_Handle handle); + +DART_EXTERN_C Dart_Handle (*Dart_ToString_DL)(Dart_Handle object); + +DART_EXTERN_C bool (*Dart_IdentityEquals_DL)(Dart_Handle obj1, + Dart_Handle obj2); + +DART_EXTERN_C Dart_Handle (*Dart_HandleFromPersistent_DL)( + Dart_PersistentHandle object); + +DART_EXTERN_C Dart_Handle (*Dart_HandleFromWeakPersistent_DL)( + Dart_WeakPersistentHandle object); + +DART_EXTERN_C Dart_PersistentHandle (*Dart_NewPersistentHandle_DL)( + Dart_Handle object); + +DART_EXTERN_C void (*Dart_SetPersistentHandle_DL)(Dart_PersistentHandle obj1, + Dart_Handle obj2); + +DART_EXTERN_C void (*Dart_DeletePersistentHandle_DL)( + Dart_PersistentHandle object); + +DART_EXTERN_C Dart_WeakPersistentHandle (*Dart_NewWeakPersistentHandle_DL)( + Dart_Handle object, + void* peer, + intptr_t external_allocation_size, + Dart_WeakPersistentHandleFinalizer callback); + +DART_EXTERN_C void (*Dart_DeleteWeakPersistentHandle_DL)( + Dart_WeakPersistentHandle object); + +DART_EXTERN_C bool (*Dart_Post_DL)(Dart_Port_DL port_id, Dart_Handle object); + +DART_EXTERN_C Dart_Handle (*Dart_NewSendPort_DL)(Dart_Port_DL port_id); + +DART_EXTERN_C Dart_Handle (*Dart_SendPortGetId_DL)(Dart_Handle port, + Dart_Port_DL* port_id); + +DART_EXTERN_C void (*Dart_EnterScope_DL)(); + +DART_EXTERN_C void (*Dart_ExitScope_DL)(); +// IMPORTANT! Never update these signatures without properly updating +// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION. +// +// End of verbatim copy. + +#endif /* RUNTIME_INCLUDE_DART_API_DL_H_ */ /* NOLINT */ \ No newline at end of file diff --git a/runtime/include/dart_native_api.h b/runtime/include/dart_native_api.h index 7e941182f8e..495d4548566 100644 --- a/runtime/include/dart_native_api.h +++ b/runtime/include/dart_native_api.h @@ -83,6 +83,8 @@ typedef struct _Dart_CObject { } as_external_typed_data; } value; } Dart_CObject; +// This struct is versioned by DART_API_DL_MAJOR_VERSION, bump the version when +// changing this struct. /** * Posts a message on some port. The message will contain the Dart_CObject diff --git a/runtime/include/dart_version.h b/runtime/include/dart_version.h new file mode 100644 index 00000000000..777620be946 --- /dev/null +++ b/runtime/include/dart_version.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020, 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. + */ + +#ifndef RUNTIME_INCLUDE_DART_VERSION_H_ +#define RUNTIME_INCLUDE_DART_VERSION_H_ + +// On breaking changes the major version is increased. +// On backwards compatible changes the minor version is increased. +// The versioning covers the symbols exposed in dart_api_dl.h +#define DART_API_DL_MAJOR_VERSION 1 +#define DART_API_DL_MINOR_VERSION 0 + +#endif /* RUNTIME_INCLUDE_DART_VERSION_H_ */ /* NOLINT */ diff --git a/runtime/include/internal/dart_api_dl_impl.h b/runtime/include/internal/dart_api_dl_impl.h new file mode 100644 index 00000000000..f77063d99bf --- /dev/null +++ b/runtime/include/internal/dart_api_dl_impl.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020, 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. + */ + +#ifndef RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ +#define RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ + +// dart_native_api.h symbols can be called on any thread. +#define DART_NATIVE_API_DL_SYMBOLS(F) \ + /***** dart_native_api.h *****/ \ + /* Dart_Port */ \ + F(Dart_PostCObject) \ + F(Dart_PostInteger) \ + F(Dart_NewNativePort) \ + F(Dart_CloseNativePort) + +// dart_api.h symbols can only be called on Dart threads. +#define DART_API_DL_SYMBOLS(F) \ + /***** dart_api.h *****/ \ + /* Errors */ \ + F(Dart_IsError) \ + F(Dart_IsApiError) \ + F(Dart_IsUnhandledExceptionError) \ + F(Dart_IsCompilationError) \ + F(Dart_IsFatalError) \ + F(Dart_GetError) \ + F(Dart_ErrorHasException) \ + F(Dart_ErrorGetException) \ + F(Dart_ErrorGetStackTrace) \ + F(Dart_NewApiError) \ + F(Dart_NewCompilationError) \ + F(Dart_NewUnhandledExceptionError) \ + F(Dart_PropagateError) \ + /* Dart_Handle, Dart_PersistentHandle, Dart_WeakPersistentHandle */ \ + F(Dart_NewPersistentHandle) \ + F(Dart_SetPersistentHandle) \ + F(Dart_HandleFromPersistent) \ + F(Dart_DeletePersistentHandle) \ + F(Dart_NewWeakPersistentHandle) \ + F(Dart_HandleFromWeakPersistent) \ + F(Dart_DeleteWeakPersistentHandle) \ + /* Dart_Port */ \ + F(Dart_Post) \ + F(Dart_NewSendPort) \ + F(Dart_SendPortGetId) \ + /* Scopes */ \ + F(Dart_EnterScope) \ + F(Dart_ExitScope) + +#define DART_API_ALL_DL_SYMBOLS(F) \ + DART_NATIVE_API_DL_SYMBOLS(F) \ + DART_API_DL_SYMBOLS(F) + +struct DartApiEntry { + const char* name; + void (*function)(); +}; + +struct DartApi { + const int major; + const int minor; + const DartApiEntry* const functions; +}; + +#endif /* RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ */ /* NOLINT */ diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc index d4c6c431f63..1a8bc20facf 100644 --- a/runtime/lib/ffi.cc +++ b/runtime/lib/ffi.cc @@ -4,6 +4,8 @@ #include "include/dart_api.h" #include "include/dart_native_api.h" +#include "include/dart_version.h" +#include "include/internal/dart_api_dl_impl.h" #include "platform/globals.h" #include "vm/bootstrap_natives.h" #include "vm/class_finalizer.h" @@ -495,21 +497,41 @@ DEFINE_NATIVE_ENTRY(Ffi_pointerFromFunction, 1, 1) { return Pointer::New(type_arg, entry_point); } -DEFINE_NATIVE_ENTRY(NativeApiFunctionPointer, 0, 1) { +DEFINE_NATIVE_ENTRY(DartNativeApiFunctionPointer, 0, 1) { GET_NON_NULL_NATIVE_ARGUMENT(String, name_dart, arguments->NativeArgAt(0)); const char* name = name_dart.ToCString(); - if (strcmp(name, "Dart_PostCObject") == 0) { - return Integer::New(reinterpret_cast(Dart_PostCObject)); - } else if (strcmp(name, "Dart_NewNativePort") == 0) { - return Integer::New(reinterpret_cast(Dart_NewNativePort)); - } else if (strcmp(name, "Dart_CloseNativePort") == 0) { - return Integer::New(reinterpret_cast(Dart_CloseNativePort)); +#define RETURN_FUNCTION_ADDRESS(function_name) \ + if (strcmp(name, #function_name) == 0) { \ + return Integer::New(reinterpret_cast(function_name)); \ } + DART_NATIVE_API_DL_SYMBOLS(RETURN_FUNCTION_ADDRESS) +#undef RETURN_FUNCTION_ADDRESS const String& error = String::Handle( String::NewFormatted("Unknown dart_native_api.h symbol: %s.", name)); Exceptions::ThrowArgumentError(error); } +DEFINE_NATIVE_ENTRY(DartApiDLMajorVersion, 0, 0) { + return Integer::New(DART_API_DL_MAJOR_VERSION); +} + +DEFINE_NATIVE_ENTRY(DartApiDLMinorVersion, 0, 0) { + return Integer::New(DART_API_DL_MINOR_VERSION); +} + +static const DartApiEntry dart_api_entries[] = { +#define ENTRY(name) DartApiEntry{#name, reinterpret_cast(name)}, + DART_API_ALL_DL_SYMBOLS(ENTRY) +#undef ENTRY + DartApiEntry{nullptr, nullptr}}; + +static const DartApi dart_api_data = { + DART_API_DL_MAJOR_VERSION, DART_API_DL_MINOR_VERSION, dart_api_entries}; + +DEFINE_NATIVE_ENTRY(DartApiDLInitializeData, 0, 0) { + return Integer::New(reinterpret_cast(&dart_api_data)); +} + } // namespace dart diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 6ce5668eb48..5ed3f684200 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -413,7 +413,10 @@ namespace dart { V(Ffi_asExternalTypedData, 2) \ V(Ffi_dl_processLibrary, 0) \ V(Ffi_dl_executableLibrary, 0) \ - V(NativeApiFunctionPointer, 1) \ + V(DartApiDLInitializeData, 0) \ + V(DartApiDLMajorVersion, 0) \ + V(DartApiDLMinorVersion, 0) \ + V(DartNativeApiFunctionPointer, 1) \ V(TransferableTypedData_factory, 2) \ V(TransferableTypedData_materialize, 1) \ V(Wasm_initModule, 2) \ diff --git a/samples/ffi/async/sample_async_callback.dart b/samples/ffi/async/sample_async_callback.dart index f9d8ea46539..432a2ebfdbe 100644 --- a/samples/ffi/async/sample_async_callback.dart +++ b/samples/ffi/async/sample_async_callback.dart @@ -26,7 +26,11 @@ main() async { print("C T2 = Some C thread executing C."); print("C = C T1 or C T2."); print("Dart: Setup."); - registerDart_PostCObject(NativeApi.postCObject); + Expect.isTrue(NativeApi.majorVersion == 1); + Expect.isTrue(NativeApi.minorVersion >= 0); + final initializeApi = dl.lookupFunction), + int Function(Pointer)>("InitDartApiDL"); + Expect.isTrue(initializeApi(NativeApi.initializeApiDLData) == 0); final interactiveCppRequests = ReceivePort()..listen(requestExecuteCallback); final int nativePort = interactiveCppRequests.sendPort.nativePort; @@ -101,14 +105,6 @@ final stopWorkSimulator = final executeCallback = dl.lookupFunction), void Function(Pointer)>('ExecuteCallback'); -final registerDart_PostCObject = dl.lookupFunction< - Void Function( - Pointer)>> - functionPointer), - void Function( - Pointer)>> - functionPointer)>('RegisterDart_PostCObject'); - class Work extends Struct {} Future asyncSleep(int ms) { diff --git a/samples/ffi/async/sample_native_port_call.dart b/samples/ffi/async/sample_native_port_call.dart index 1e654ef8498..aa4b48e96d5 100644 --- a/samples/ffi/async/sample_native_port_call.dart +++ b/samples/ffi/async/sample_native_port_call.dart @@ -35,9 +35,11 @@ main() async { print("C T2 = Some C thread executing C."); print("C = C T1 or C T2."); print("Dart: Setup."); - registerDart_PostCObject(NativeApi.postCObject); - registerDart_NewNativePort(NativeApi.newNativePort); - registerDart_CloseNativePort(NativeApi.closeNativePort); + Expect.isTrue(NativeApi.majorVersion == 1); + Expect.isTrue(NativeApi.minorVersion >= 0); + final initializeApi = dl.lookupFunction), + int Function(Pointer)>("InitDartApiDL"); + Expect.isTrue(initializeApi(NativeApi.initializeApiDLData) == 0); final interactiveCppRequests = ReceivePort()..listen(handleCppRequests); final int nativePort = interactiveCppRequests.sendPort.nativePort; @@ -130,39 +132,6 @@ final startWorkSimulator2 = final stopWorkSimulator2 = dl.lookupFunction('StopWorkSimulator2'); -final registerDart_PostCObject = dl.lookupFunction< - Void Function( - Pointer)>> - functionPointer), - void Function( - Pointer)>> - functionPointer)>('RegisterDart_PostCObject'); - -final registerDart_NewNativePort = dl.lookupFunction< - Void Function( - Pointer< - NativeFunction< - Int64 Function( - Pointer, - Pointer>, - Int8)>> - functionPointer), - void Function( - Pointer< - NativeFunction< - Int64 Function( - Pointer, - Pointer>, - Int8)>> - functionPointer)>('RegisterDart_NewNativePort'); - -final registerDart_CloseNativePort = dl.lookupFunction< - Void Function( - Pointer> functionPointer), - void Function( - Pointer> functionPointer)>( - 'RegisterDart_CloseNativePort'); - Future asyncSleep(int ms) { return new Future.delayed(Duration(milliseconds: ms), () => true); } diff --git a/samples/ffi/sample_ffi_functions_callbacks_closures.dart b/samples/ffi/sample_ffi_functions_callbacks_closures.dart new file mode 100644 index 00000000000..ba876da7d44 --- /dev/null +++ b/samples/ffi/sample_ffi_functions_callbacks_closures.dart @@ -0,0 +1,67 @@ +// Copyright (c) 2020, 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 'package:expect/expect.dart'; + +import 'dylib_utils.dart'; + +void main() { + print('start main'); + + doDynamicLinking(); + + int counter = 0; + void closure() { + counter++; + } + + // C holds on to this closure through a `Dart_PersistenHandle`. + registerClosureCallback(closure); + + // Some time later this closure can be invoked. + invokeClosureCallback(); + Expect.equals(1, counter); + + // When C is done it needs to stop holding on to the closure such that the + // Dart GC can collect the closure. + releaseClosureCallback(); + + print('end main'); +} + +final testLibrary = dlopenPlatformSpecific("ffi_test_functions"); + +final registerClosureCallback = + testLibrary.lookupFunction( + "RegisterClosureCallback"); + +final invokeClosureCallback = testLibrary + .lookupFunction("InvokeClosureCallback"); + +final releaseClosureCallback = testLibrary + .lookupFunction("ReleaseClosureCallback"); + +void doClosureCallback(Object callback) { + final callback_as_function = callback as void Function(); + callback_as_function(); +} + +final closureCallbackPointer = + Pointer.fromFunction(doClosureCallback); + +void doDynamicLinking() { + Expect.isTrue(NativeApi.majorVersion == 1); + Expect.isTrue(NativeApi.minorVersion >= 0); + final initializeApi = testLibrary.lookupFunction< + IntPtr Function(Pointer), + int Function(Pointer)>("InitDartApiDL"); + Expect.isTrue(initializeApi(NativeApi.initializeApiDLData) == 0); + + final registerClosureCallback = testLibrary.lookupFunction< + Void Function(Pointer), + void Function(Pointer)>("RegisterClosureCallbackFP"); + registerClosureCallback(closureCallbackPointer); +} diff --git a/samples/ffi/samples_test.dart b/samples/ffi/samples_test.dart index 98144638ed0..954aa9cd722 100644 --- a/samples/ffi/samples_test.dart +++ b/samples/ffi/samples_test.dart @@ -6,20 +6,23 @@ // // SharedObjects=ffi_test_dynamic_library ffi_test_functions -import 'sample_ffi_bitfield.dart' as sample0; -import 'sample_ffi_data.dart' as sample1; -import 'sample_ffi_dynamic_library.dart' as sample2; -import 'sample_ffi_functions_callbacks.dart' as sample3; -import 'sample_ffi_functions_structs.dart' as sample4; -import 'sample_ffi_functions.dart' as sample5; -import 'sample_ffi_structs.dart' as sample6; +import 'sample_ffi_bitfield.dart' as bitfield; +import 'sample_ffi_data.dart' as data; +import 'sample_ffi_dynamic_library.dart' as dynamic_library; +import 'sample_ffi_functions_callbacks_closures.dart' + as functions_callbacks_closures; +import 'sample_ffi_functions_callbacks.dart' as functions_callbacks; +import 'sample_ffi_functions_structs.dart' as functions_structs; +import 'sample_ffi_functions.dart' as functions; +import 'sample_ffi_structs.dart' as structs; main() { - sample0.main(); - sample1.main(); - sample2.main(); - sample3.main(); - sample4.main(); - sample5.main(); - sample6.main(); + bitfield.main(); + data.main(); + dynamic_library.main(); + functions_callbacks_closures.main(); + functions_callbacks.main(); + functions_structs.main(); + functions.main(); + structs.main(); } diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 313ba52226c..bd50f4232d4 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -742,8 +742,11 @@ copy("copy_headers") { visibility = [ ":create_common_sdk" ] sources = [ "../runtime/include/dart_api.h", + "../runtime/include/dart_api_dl.h", "../runtime/include/dart_native_api.h", "../runtime/include/dart_tools_api.h", + "../runtime/include/dart_version.h", + "../runtime/include/internal/dart_api_dl_impl.h", ] outputs = [ "$root_out_dir/dart-sdk/include/{{source_file_part}}" ] } diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart index d42689ce0d3..735c7e28ff8 100644 --- a/sdk/lib/_internal/vm/lib/ffi_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart @@ -454,7 +454,14 @@ extension NativePort on SendPort { int get nativePort native "SendPortImpl_get_id"; } -int _nativeApiFunctionPointer(String symbol) native "NativeApiFunctionPointer"; +int _nativeApiFunctionPointer(String symbol) + native "DartNativeApiFunctionPointer"; + +int _initializeApiDLData() native "DartApiDLInitializeData"; + +int _dartApiMajorVersion() native "DartApiDLMajorVersion"; + +int _dartApiMinorVersion() native "DartApiDLMinorVersion"; @patch abstract class NativeApi { @@ -475,4 +482,14 @@ abstract class NativeApi { @patch static Pointer> get closeNativePort => Pointer.fromAddress(_nativeApiFunctionPointer("Dart_CloseNativePort")); + + @patch + static int get majorVersion => _dartApiMajorVersion(); + + @patch + static int get minorVersion => _dartApiMinorVersion(); + + @patch + static Pointer get initializeApiDLData => + Pointer.fromAddress(_initializeApiDLData()); } diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index 3b6e236b74b..68424f86632 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -562,8 +562,19 @@ class Dart_CObject extends Struct {} typedef Dart_NativeMessageHandler = Void Function(Int64, Pointer); -/// Exposes function pointers to functions in `dart_native_api.h`. +/// Utilities for accessing the Dart VM API from Dart code or +/// from C code via `dart_api_dl.h`. abstract class NativeApi { + /// On breaking changes the major version is increased. + /// + /// The versioning covers the API surface in `dart_api_dl.h`. + external static int get majorVersion; + + /// On backwards compatible changes the minor version is increased. + /// + /// The versioning covers the API surface in `dart_api_dl.h`. + external static int get minorVersion; + /// A function pointer to /// `bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message)` /// in `dart_native_api.h`. @@ -590,4 +601,8 @@ abstract class NativeApi { /// in `dart_native_api.h`. external static Pointer> get closeNativePort; + + /// Pass this to `Dart_InitializeApiDL` in your native code to enable using the + /// symbols in `dart_api_dl.h`. + external static Pointer get initializeApiDLData; } diff --git a/tests/ffi/vmspecific_handle_dynamically_linked_test.dart b/tests/ffi/vmspecific_handle_dynamically_linked_test.dart new file mode 100644 index 00000000000..e2237e86399 --- /dev/null +++ b/tests/ffi/vmspecific_handle_dynamically_linked_test.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2020, 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. +// +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +import 'package:expect/expect.dart'; + +import 'dylib_utils.dart'; + +void main() { + doDynamicLinking(); + testHandle(); +} + +void doDynamicLinking() { + Expect.isTrue(NativeApi.majorVersion == 1); + Expect.isTrue(NativeApi.minorVersion >= 0); + final initializeApi = testLibrary.lookupFunction< + IntPtr Function(Pointer), + int Function(Pointer)>("InitDartApiDL"); + Expect.isTrue(initializeApi(NativeApi.initializeApiDLData) == 0); +} + +void testHandle() { + final s = SomeClass(123); + print("passObjectToC($s)"); + final result = passObjectToC(s); + print("result = $result"); + Expect.isTrue(identical(s, result)); +} + +class SomeClass { + // We use this getter in the native api, don't tree shake it. + @pragma("vm:entry-point") + final int a; + SomeClass(this.a); +} + +final testLibrary = dlopenPlatformSpecific("ffi_test_functions"); + +final passObjectToC = testLibrary.lookupFunction("PassObjectToCUseDynamicLinking"); diff --git a/tests/ffi_2/vmspecific_handle_dynamically_linked_test.dart b/tests/ffi_2/vmspecific_handle_dynamically_linked_test.dart new file mode 100644 index 00000000000..e2237e86399 --- /dev/null +++ b/tests/ffi_2/vmspecific_handle_dynamically_linked_test.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2020, 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. +// +// SharedObjects=ffi_test_functions + +import 'dart:ffi'; + +import 'package:expect/expect.dart'; + +import 'dylib_utils.dart'; + +void main() { + doDynamicLinking(); + testHandle(); +} + +void doDynamicLinking() { + Expect.isTrue(NativeApi.majorVersion == 1); + Expect.isTrue(NativeApi.minorVersion >= 0); + final initializeApi = testLibrary.lookupFunction< + IntPtr Function(Pointer), + int Function(Pointer)>("InitDartApiDL"); + Expect.isTrue(initializeApi(NativeApi.initializeApiDLData) == 0); +} + +void testHandle() { + final s = SomeClass(123); + print("passObjectToC($s)"); + final result = passObjectToC(s); + print("result = $result"); + Expect.isTrue(identical(s, result)); +} + +class SomeClass { + // We use this getter in the native api, don't tree shake it. + @pragma("vm:entry-point") + final int a; + SomeClass(this.a); +} + +final testLibrary = dlopenPlatformSpecific("ffi_test_functions"); + +final passObjectToC = testLibrary.lookupFunction("PassObjectToCUseDynamicLinking");