From fb082e9e8f4a9bebdf7bb7dd9efd340f6d784da8 Mon Sep 17 00:00:00 2001
From: Donghyun Kim <temeddix@gmail.com>
Date: Fri, 13 Sep 2024 02:03:42 +0900
Subject: [PATCH] Use leaf calls on possible platforms

---
 flutter_package/lib/src/interface_os.dart |  67 +------
 flutter_package/lib/src/load_os.dart      | 208 ++++++++++++++++++++--
 2 files changed, 206 insertions(+), 69 deletions(-)

diff --git a/flutter_package/lib/src/interface_os.dart b/flutter_package/lib/src/interface_os.dart
index c4a62121..a58ced68 100644
--- a/flutter_package/lib/src/interface_os.dart
+++ b/flutter_package/lib/src/interface_os.dart
@@ -15,12 +15,9 @@ void setCompiledLibPathReal(String path) {
 Future<void> prepareInterfaceReal(
   AssignRustSignal assignRustSignal,
 ) async {
-  // Load the native library.
-  loadRustLibrary();
-
   /// This should be called once at startup
   /// to enable `allo_isolate` to send data from the Rust side.
-  storeDartPostCObjectReal(NativeApi.postCObject);
+  rustLibrary.storeDartPostCObject(NativeApi.postCObject);
 
   // Prepare ports for communication over isolates.
   final rustSignalPort = ReceivePort();
@@ -50,67 +47,21 @@ Future<void> prepareInterfaceReal(
   });
 
   // Make Rust prepare its isolate to send data to Dart.
-  prepareIsolateReal(rustSignalPort.sendPort.nativePort);
+  rustLibrary.prepareIsolate(rustSignalPort.sendPort.nativePort);
 }
 
-@Native<Void Function()>(
-  isLeaf: true,
-  symbol: 'start_rust_logic_extern',
-)
-external void startRustLogicReal();
-
-@Native<Void Function()>(
-  isLeaf: true,
-  symbol: 'stop_rust_logic_extern',
-)
-external void stopRustLogicReal();
+void startRustLogicReal() {
+  rustLibrary.startRustLogic();
+}
 
-typedef SendDartSignalReal = Void Function(
-  Int32,
-  Pointer<Uint8>,
-  UintPtr,
-  Pointer<Uint8>,
-  UintPtr,
-);
-@Native<SendDartSignalReal>(
-  isLeaf: true,
-  symbol: 'send_dart_signal_extern',
-)
-external void sendDartSignalExtern(
-  int messageId,
-  Pointer<Uint8> messageBytesAddress,
-  int messageBytesLength,
-  Pointer<Uint8> binaryAddress,
-  int binaryLength,
-);
+void stopRustLogicReal() {
+  rustLibrary.stopRustLogic();
+}
 
 void sendDartSignalReal(
   int messageId,
   Uint8List messageBytes,
   Uint8List binary,
 ) {
-  sendDartSignalExtern(
-    messageId,
-    messageBytes.address,
-    messageBytes.length,
-    binary.address,
-    binary.length,
-  );
+  rustLibrary.sendDartSignal(messageId, messageBytes, binary);
 }
-
-@Native<Void Function(Int64)>(
-  isLeaf: true,
-  symbol: 'prepare_isolate_extern',
-)
-external void prepareIsolateReal(
-  int port,
-);
-
-typedef InnerFunction = Int8 Function(Int64, Pointer<Dart_CObject>);
-@Native<Void Function(Pointer<NativeFunction<InnerFunction>>)>(
-  isLeaf: true,
-  symbol: 'store_dart_post_cobject',
-)
-external void storeDartPostCObjectReal(
-  Pointer<NativeFunction<InnerFunction>> postCObject,
-);
diff --git a/flutter_package/lib/src/load_os.dart b/flutter_package/lib/src/load_os.dart
index 2672ba5f..4b5bc157 100644
--- a/flutter_package/lib/src/load_os.dart
+++ b/flutter_package/lib/src/load_os.dart
@@ -1,5 +1,7 @@
 import 'dart:io' as io;
 import 'dart:ffi';
+import 'dart:typed_data';
+import 'package:ffi/ffi.dart';
 
 String? dynamicLibPath;
 
@@ -7,25 +9,209 @@ void setDynamicLibPath(String path) {
   dynamicLibPath = path;
 }
 
-void loadRustLibrary() {
+RustLibrary loadRustLibrary() {
   // Use provided dynamic library path if possible.
+  // Otherewise, use the default path.
   final path = dynamicLibPath;
+  DynamicLibrary lib;
   if (path != null) {
-    DynamicLibrary.open(path);
-  }
-
-  // Otherewise, use the default path.
-  if (io.Platform.isLinux) {
-    DynamicLibrary.open('libhub.so');
+    lib = DynamicLibrary.open(path);
+  } else if (io.Platform.isLinux) {
+    lib = DynamicLibrary.open('libhub.so');
   } else if (io.Platform.isAndroid) {
-    DynamicLibrary.open('libhub.so');
+    lib = DynamicLibrary.open('libhub.so');
   } else if (io.Platform.isWindows) {
-    DynamicLibrary.open('hub.dll');
+    lib = DynamicLibrary.open('hub.dll');
   } else if (io.Platform.isIOS) {
-    DynamicLibrary.open('rinf.framework/rinf');
+    lib = DynamicLibrary.open('rinf.framework/rinf');
   } else if (io.Platform.isMacOS) {
-    DynamicLibrary.open('rinf.framework/rinf');
+    lib = DynamicLibrary.open('rinf.framework/rinf');
   } else {
     throw UnsupportedError('This operating system is not supported.');
   }
+
+  if (io.Platform.isAndroid) {
+    // On Android, native library symbols are loaded in local space
+    // because of Flutter's `RTLD_LOCAL` behavior.
+    // Therefore we cannot use the efficient `RustLibraryNew`.
+    // - https://github.com/dart-lang/native/issues/923
+    return RustLibraryOld(lib);
+  } else {
+    // Native library symbols are loaded in global space
+    // thanks to Flutter's `RTLD_GLOBAL` behavior.
+    return RustLibraryNew();
+  }
+}
+
+// The central interface for calling native function.
+
+final rustLibrary = loadRustLibrary();
+
+// Common type aliases.
+// This is for better readability of the code.
+
+typedef PostCObjectInner = Int8 Function(Int64, Pointer<Dart_CObject>);
+typedef PostCObjectFn = NativeFunction<PostCObjectInner>;
+
+// Direct access to global function symbols loaded in the process.
+// These are available only if the native library is
+// loaded into global process with `RTLD_GLOBAL` configuration.
+
+@Native<Void Function()>(
+  isLeaf: true,
+  symbol: 'start_rust_logic_extern',
+)
+external void startRustLogicExtern();
+
+@Native<Void Function()>(
+  isLeaf: true,
+  symbol: 'stop_rust_logic_extern',
+)
+external void stopRustLogicExtern();
+
+typedef SendDartSignalExtern = Void Function(
+  Int32,
+  Pointer<Uint8>,
+  UintPtr,
+  Pointer<Uint8>,
+  UintPtr,
+);
+@Native<SendDartSignalExtern>(
+  isLeaf: true,
+  symbol: 'send_dart_signal_extern',
+)
+external void sendDartSignalExtern(
+  int messageId,
+  Pointer<Uint8> messageBytesAddress,
+  int messageBytesLength,
+  Pointer<Uint8> binaryAddress,
+  int binaryLength,
+);
+
+@Native<Void Function(Int64)>(
+  isLeaf: true,
+  symbol: 'prepare_isolate_extern',
+)
+external void prepareIsolateExtern(
+  int port,
+);
+
+@Native<Void Function(Pointer<PostCObjectFn>)>(
+  isLeaf: true,
+  symbol: 'store_dart_post_cobject',
+)
+external void storeDartPostCObjectExtern(
+  Pointer<PostCObjectFn> postCObject,
+);
+
+/// Abstract class for unifying the interface
+/// for calling native functions.
+abstract class RustLibrary {
+  void startRustLogic();
+  void stopRustLogic();
+  void sendDartSignal(
+    int messageId,
+    Uint8List messageBytes,
+    Uint8List binary,
+  );
+  void prepareIsolate(int port);
+  void storeDartPostCObject(Pointer<PostCObjectFn> postCObject);
+}
+
+/// Class for global native library symbols loaded with `RTLD_GLOBAL`.
+/// This is the efficient and ideal way to call native code.
+class RustLibraryNew extends RustLibrary {
+  void startRustLogic() {
+    startRustLogicExtern();
+  }
+
+  void stopRustLogic() {
+    stopRustLogicExtern();
+  }
+
+  void sendDartSignal(
+    int messageId,
+    Uint8List messageBytes,
+    Uint8List binary,
+  ) {
+    sendDartSignalExtern(
+      messageId,
+      messageBytes.address,
+      messageBytes.length,
+      binary.address,
+      binary.length,
+    );
+  }
+
+  void prepareIsolate(int port) {
+    prepareIsolateExtern(port);
+  }
+
+  void storeDartPostCObject(Pointer<PostCObjectFn> postCObject) {
+    storeDartPostCObjectExtern(postCObject);
+  }
+}
+
+/// Class for local native library symbols loaded with `RTLD_LOCAL`.
+/// This is relatively inefficient because `malloc.allocate` is required.
+/// `@Native` attributes can only used on global native symbols.
+class RustLibraryOld extends RustLibrary {
+  final DynamicLibrary lib;
+  RustLibraryOld(this.lib);
+
+  void storeDartPostCObject(Pointer<PostCObjectFn> postCObject) {
+    final rustFunction = lib.lookupFunction<
+        Pointer Function(Pointer<PostCObjectFn>),
+        Pointer Function(Pointer<PostCObjectFn>)>(
+      'store_dart_post_cobject',
+    );
+    rustFunction(postCObject);
+  }
+
+  void startRustLogic() {
+    final rustFunction = lib.lookupFunction<Void Function(), void Function()>(
+      'start_rust_logic_extern',
+    );
+    rustFunction();
+  }
+
+  void stopRustLogic() {
+    final rustFunction = lib.lookupFunction<Void Function(), void Function()>(
+      'stop_rust_logic_extern',
+    );
+    rustFunction();
+  }
+
+  void sendDartSignal(int messageId, Uint8List messageBytes, Uint8List binary) {
+    final Pointer<Uint8> messageMemory = malloc.allocate(messageBytes.length);
+    messageMemory.asTypedList(messageBytes.length).setAll(0, messageBytes);
+
+    final Pointer<Uint8> binaryMemory = malloc.allocate(binary.length);
+    binaryMemory.asTypedList(binary.length).setAll(0, binary);
+
+    final rustFunction = lib.lookupFunction<
+        Void Function(Int32, Pointer<Uint8>, UintPtr, Pointer<Uint8>, UintPtr),
+        void Function(int, Pointer<Uint8>, int, Pointer<Uint8>, int)>(
+      'send_dart_signal_extern',
+    );
+
+    rustFunction(
+      messageId,
+      messageMemory,
+      messageBytes.length,
+      binaryMemory,
+      binary.length,
+    );
+
+    malloc.free(messageMemory);
+    malloc.free(binaryMemory);
+  }
+
+  void prepareIsolate(int port) {
+    final rustFunction =
+        lib.lookupFunction<Void Function(Int64), void Function(int)>(
+      'prepare_isolate_extern',
+    );
+    rustFunction(port);
+  }
 }