[vm/ffi] Closure callbacks for async callbacks

This change is almost trivial. The closure is stored on the callback's
RawReceivePort, not in the VM. So we can basically just remove the CFE
check and it pretty much works. The only problem is that we can't set
function.FfiCallbackTarget anymore, so most of the CL is dealing with
that.

A few places were deciding whether an FFI trampoline was a call or a
callback based on whether function.FfiCallbackTarget() was null. But
now the target will be null for async callbacks. So instead I've added
a new value to the FfiCallbackKind enum (and renamed it), and changed
those checks.

Sync callback closures will be a separate CL, because they're more
complicated.

Bug: https://github.com/dart-lang/sdk/issues/52689
Change-Id: I8e5dfb557362e679f66195b735c3c382e6792840
TEST=async_void_function_callbacks_test.dart
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/316160
Commit-Queue: Liam Appelbe <liama@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/vm/lib/transformations/ffi/use_sites.dart b/pkg/vm/lib/transformations/ffi/use_sites.dart
index 43f4fd7..6f77b0a 100644
--- a/pkg/vm/lib/transformations/ffi/use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi/use_sites.dart
@@ -141,9 +141,6 @@
         final Expression func = node.arguments.positional[0];
         final DartType dartType = func.getStaticType(staticTypeContext!);
 
-        _ensureIsStaticFunction(
-            func, nativeCallableListenerConstructor.name.text);
-
         ensureNativeTypeValid(nativeType, node);
         ensureNativeTypeToDartType(nativeType, dartType, node);
 
@@ -599,7 +596,7 @@
   // void _handler(List args) => target(args[0], args[1], ...)
   // final _callback = NativeCallable<T>._(_handler, debugName);
   // _callback._pointer = _pointerAsyncFromFunction<NativeFunction<T>>(
-  //       _nativeAsyncCallbackFunction<T>(target), _callback._rawPort);
+  //       _nativeAsyncCallbackFunction<T>(), _callback._rawPort);
   // expression result: _callback;
   Expression _replaceNativeCallableListenerConstructor(
       ConstructorInvocation node) {
@@ -622,9 +619,13 @@
           functionType: Substitution.fromInterfaceType(listType)
               .substituteType(listElementAt.getterType) as FunctionType));
     }
-    final target = _getStaticFunctionTarget(node.arguments.positional[0]);
-    final handlerBody =
-        ExpressionStatement(StaticInvocation(target, Arguments(targetArgs)));
+    final target = node.arguments.positional[0];
+    final handlerBody = ExpressionStatement(FunctionInvocation(
+      FunctionAccessKind.FunctionType,
+      target,
+      Arguments(targetArgs),
+      functionType: targetType,
+    ));
     final handler = FunctionNode(handlerBody,
         positionalParameters: [args], returnType: VoidType())
       ..fileOffset = node.fileOffset;
@@ -635,7 +636,7 @@
             nativeCallablePrivateConstructor,
             Arguments([
               FunctionExpression(handler),
-              StringLiteral('NativeCallable(${target.name})'),
+              StringLiteral('NativeCallable($target)'),
             ], types: [
               targetType,
             ])),
@@ -644,12 +645,12 @@
       ..fileOffset = node.fileOffset;
 
     // _callback._pointer = _pointerAsyncFromFunction<NativeFunction<T>>(
-    //       _nativeAsyncCallbackFunction<T>(target), _callback._rawPort);
+    //       _nativeAsyncCallbackFunction<T>(), _callback._rawPort);
     final pointerValue = StaticInvocation(
         pointerAsyncFromFunctionProcedure,
         Arguments([
-          StaticInvocation(
-              nativeAsyncCallbackFunctionProcedure, node.arguments),
+          StaticInvocation(nativeAsyncCallbackFunctionProcedure,
+              Arguments([], types: [targetType])),
           InstanceGet(InstanceAccessKind.Instance, VariableGet(nativeCallable),
               nativeCallablePortField.name,
               interfaceTarget: nativeCallablePortField,
@@ -892,16 +893,6 @@
     throw FfiStaticTypeError();
   }
 
-  Procedure _getStaticFunctionTarget(Expression node) {
-    if (node is StaticGet) {
-      return node.target as Procedure;
-    }
-    if (node is ConstantExpression) {
-      return (node.constant as StaticTearOffConstant).target;
-    }
-    throw ArgumentError.value(node, "node", "Not a static function");
-  }
-
   /// Returns the class that should not be implemented or extended.
   ///
   /// If the superclass is not sealed, returns `null`.
diff --git a/pkg/vm/testcases/transformations/ffi/native_callable.dart b/pkg/vm/testcases/transformations/ffi/native_callable.dart
index 55b0951..428768e 100644
--- a/pkg/vm/testcases/transformations/ffi/native_callable.dart
+++ b/pkg/vm/testcases/transformations/ffi/native_callable.dart
@@ -6,6 +6,7 @@
 
 void main() {
   testNativeCallableListener();
+  testNativeCallableListenerClosure();
 }
 
 void printInt(int i) => print(i);
@@ -15,3 +16,11 @@
   print(callback.nativeFunction);
   callback.close();
 }
+
+void testNativeCallableListenerClosure() {
+  int j = 123;
+  void closure(int i) => print(i + j);
+  final callback = NativeCallable<Void Function(Int32)>.listener(closure);
+  print(callback.nativeFunction);
+  callback.close();
+}
diff --git a/pkg/vm/testcases/transformations/ffi/native_callable.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/native_callable.dart.aot.expect
index be7c6b8..eb069ce 100644
--- a/pkg/vm/testcases/transformations/ffi/native_callable.dart.aot.expect
+++ b/pkg/vm/testcases/transformations/ffi/native_callable.dart.aot.expect
@@ -8,19 +8,33 @@
 
 static method main() → void {
   self::testNativeCallableListener();
+  self::testNativeCallableListenerClosure();
 }
 static method printInt(core::int i) → void
   return [@vm.inferred-type.metadata=dart.core::Null? (value: null)] core::print(i);
 static method testNativeCallableListener() → void {
   final ffi::NativeCallable<(ffi::Int32) → ffi::Void> callback = block {
     final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t1 = new ffi::NativeCallable::_<(ffi::Int32) → ffi::Void>((final core::List<dynamic> args) → void
-      self::printInt(args.{core::List::[]}(0){(core::int) → dynamic});
-);
-    [@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] [@vm.direct-call.metadata=dart.ffi::NativeCallable._pointer] #t1.{ffi::NativeCallable::_pointer} = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_pointerAsyncFromFunction<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(#C1), [@vm.direct-call.metadata=dart.ffi::NativeCallable._port] [@vm.inferred-type.metadata=dart.isolate::_RawReceivePort] #t1.{ffi::NativeCallable::_port}{iso::RawReceivePort});
+      #C1(args.{core::List::[]}(0){(core::int) → dynamic}){(ffi::Int32) → ffi::Void};
+, "NativeCallable(ConstantExpression(printInt))");
+    [@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] [@vm.direct-call.metadata=dart.ffi::NativeCallable._pointer] #t1.{ffi::NativeCallable::_pointer} = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_pointerAsyncFromFunction<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), [@vm.direct-call.metadata=dart.ffi::NativeCallable._port] [@vm.inferred-type.metadata=dart.isolate::_RawReceivePort] #t1.{ffi::NativeCallable::_port}{iso::RawReceivePort});
   } =>#t1;
   core::print([@vm.direct-call.metadata=dart.ffi::NativeCallable.nativeFunction] [@vm.inferred-type.metadata=dart.ffi::Pointer] callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>});
   [@vm.direct-call.metadata=dart.ffi::NativeCallable.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
 }
+static method testNativeCallableListenerClosure() → void {
+  core::int j = 123;
+  function closure(core::int i) → void
+    return core::print([@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(j){(core::num) → core::int});
+  final ffi::NativeCallable<(ffi::Int32) → ffi::Void> callback = block {
+    final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t2 = new ffi::NativeCallable::_<(ffi::Int32) → ffi::Void>((final core::List<dynamic> args) → void
+      closure(args.{core::List::[]}(0){(core::int) → dynamic}){(ffi::Int32) → ffi::Void};
+, "NativeCallable(VariableGetImpl(closure))");
+    [@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] [@vm.direct-call.metadata=dart.ffi::NativeCallable._pointer] #t2.{ffi::NativeCallable::_pointer} = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_pointerAsyncFromFunction<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), [@vm.direct-call.metadata=dart.ffi::NativeCallable._port] [@vm.inferred-type.metadata=dart.isolate::_RawReceivePort] #t2.{ffi::NativeCallable::_port}{iso::RawReceivePort});
+  } =>#t2;
+  core::print([@vm.direct-call.metadata=dart.ffi::NativeCallable.nativeFunction] [@vm.inferred-type.metadata=dart.ffi::Pointer] callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>});
+  [@vm.direct-call.metadata=dart.ffi::NativeCallable.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
+}
 constants  {
   #C1 = static-tearoff self::printInt
 }
diff --git a/pkg/vm/testcases/transformations/ffi/native_callable.dart.expect b/pkg/vm/testcases/transformations/ffi/native_callable.dart.expect
index b5b2c8d..0aeb8c7 100644
--- a/pkg/vm/testcases/transformations/ffi/native_callable.dart.expect
+++ b/pkg/vm/testcases/transformations/ffi/native_callable.dart.expect
@@ -8,19 +8,33 @@
 
 static method main() → void {
   self::testNativeCallableListener();
+  self::testNativeCallableListenerClosure();
 }
 static method printInt(core::int i) → void
   return core::print(i);
 static method testNativeCallableListener() → void {
   final ffi::NativeCallable<(ffi::Int32) → ffi::Void> callback = block {
     final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t1 = new ffi::NativeCallable::_<(ffi::Int32) → ffi::Void>((final core::List<dynamic> args) → void
-      self::printInt(args.{core::List::[]}(0){(core::int) → dynamic});
-, "NativeCallable(printInt)");
-    [@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] #t1.{ffi::NativeCallable::_pointer} = ffi::_pointerAsyncFromFunction<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(#C1), #t1.{ffi::NativeCallable::_port}{iso::RawReceivePort});
+      #C1(args.{core::List::[]}(0){(core::int) → dynamic}){(ffi::Int32) → ffi::Void};
+, "NativeCallable(ConstantExpression(printInt))");
+    [@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] #t1.{ffi::NativeCallable::_pointer} = ffi::_pointerAsyncFromFunction<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), #t1.{ffi::NativeCallable::_port}{iso::RawReceivePort});
   } =>#t1;
   core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>});
   callback.{ffi::NativeCallable::close}(){() → void};
 }
+static method testNativeCallableListenerClosure() → void {
+  core::int j = 123;
+  function closure(core::int i) → void
+    return core::print(i.{core::num::+}(j){(core::num) → core::int});
+  final ffi::NativeCallable<(ffi::Int32) → ffi::Void> callback = block {
+    final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t2 = new ffi::NativeCallable::_<(ffi::Int32) → ffi::Void>((final core::List<dynamic> args) → void
+      closure(args.{core::List::[]}(0){(core::int) → dynamic}){(ffi::Int32) → ffi::Void};
+, "NativeCallable(VariableGetImpl(closure))");
+    [@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] #t2.{ffi::NativeCallable::_pointer} = ffi::_pointerAsyncFromFunction<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), #t2.{ffi::NativeCallable::_port}{iso::RawReceivePort});
+  } =>#t2;
+  core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>});
+  callback.{ffi::NativeCallable::close}(){() → void};
+}
 constants  {
   #C1 = static-tearoff self::printInt
 }
diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc
index 68bac7c..41264847 100644
--- a/runtime/vm/app_snapshot.cc
+++ b/runtime/vm/app_snapshot.cc
@@ -1386,7 +1386,7 @@
       AutoTraceObject(data);
       WriteFromTo(data);
       s->Write<int32_t>(data->untag()->callback_id_);
-      s->Write<uint8_t>(data->untag()->callback_kind_);
+      s->Write<uint8_t>(data->untag()->trampoline_kind_);
     }
   }
 
@@ -1415,7 +1415,7 @@
                                      FfiTrampolineData::InstanceSize());
       d.ReadFromTo(data);
       data->untag()->callback_id_ = d.Read<int32_t>();
-      data->untag()->callback_kind_ = d.Read<uint8_t>();
+      data->untag()->trampoline_kind_ = d.Read<uint8_t>();
     }
   }
 };
diff --git a/runtime/vm/canonical_tables.h b/runtime/vm/canonical_tables.h
index 0b0f5ef..8e723a7 100644
--- a/runtime/vm/canonical_tables.h
+++ b/runtime/vm/canonical_tables.h
@@ -425,7 +425,7 @@
             f1.FfiCSignature() == f2.FfiCSignature() &&
             f1.FfiCallbackExceptionalReturn() ==
                 f2.FfiCallbackExceptionalReturn() &&
-            f1.GetFfiCallbackKind() == f2.GetFfiCallbackKind());
+            f1.GetFfiTrampolineKind() == f2.GetFfiTrampolineKind());
   }
   static bool ReportStats() { return false; }
 };
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index 15b7936..ab07ee9 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -3451,7 +3451,7 @@
   }
 
   if (function.IsFfiTrampoline() &&
-      function.FfiCallbackTarget() != Function::null()) {
+      function.GetFfiTrampolineKind() != FfiTrampolineKind::kCall) {
     compiler::ffi::SetFfiCallbackCode(thread(), function, code);
   }
 }
diff --git a/runtime/vm/compiler/backend/il_serializer.cc b/runtime/vm/compiler/backend/il_serializer.cc
index b4c1cfc..1041327 100644
--- a/runtime/vm/compiler/backend/il_serializer.cc
+++ b/runtime/vm/compiler/backend/il_serializer.cc
@@ -835,10 +835,10 @@
       s->Write<const Function&>(Function::Handle(zone, x.FfiCallbackTarget()));
       s->Write<const FunctionType&>(
           FunctionType::Handle(zone, x.FfiCSignature()));
-      if (x.FfiCallbackTarget() != Object::null()) {
+      if (x.GetFfiTrampolineKind() != FfiTrampolineKind::kCall) {
         s->Write<const Instance&>(
             Instance::Handle(zone, x.FfiCallbackExceptionalReturn()));
-        s->Write<uint8_t>(static_cast<uint8_t>(x.GetFfiCallbackKind()));
+        s->Write<uint8_t>(static_cast<uint8_t>(x.GetFfiTrampolineKind()));
       } else {
         s->Write<const String&>(String::Handle(zone, x.name()));
         s->Write<const FunctionType&>(
@@ -925,7 +925,8 @@
       const FunctionType& c_signature = d->Read<const FunctionType&>();
       if (!callback_target.IsNull()) {
         const Instance& exceptional_return = d->Read<const Instance&>();
-        FfiCallbackKind kind = static_cast<FfiCallbackKind>(d->Read<uint8_t>());
+        FfiTrampolineKind kind =
+            static_cast<FfiTrampolineKind>(d->Read<uint8_t>());
         return Function::ZoneHandle(
             zone, compiler::ffi::NativeCallbackFunction(
                       c_signature, callback_target, exceptional_return, kind));
diff --git a/runtime/vm/compiler/ffi/call.cc b/runtime/vm/compiler/ffi/call.cc
index 0cee4de..d6e14df 100644
--- a/runtime/vm/compiler/ffi/call.cc
+++ b/runtime/vm/compiler/ffi/call.cc
@@ -47,6 +47,7 @@
 
   function.SetFfiCSignature(c_signature);
   function.SetFfiIsLeaf(is_leaf);
+  function.SetFfiTrampolineKind(FfiTrampolineKind::kCall);
 
   return function.ptr();
 }
diff --git a/runtime/vm/compiler/ffi/callback.cc b/runtime/vm/compiler/ffi/callback.cc
index 2d37300..8ba6e6c 100644
--- a/runtime/vm/compiler/ffi/callback.cc
+++ b/runtime/vm/compiler/ffi/callback.cc
@@ -18,7 +18,7 @@
 FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
                                    const Function& dart_target,
                                    const Instance& exceptional_return,
-                                   FfiCallbackKind kind) {
+                                   FfiTrampolineKind kind) {
   Thread* const thread = Thread::Current();
   Zone* const zone = thread->zone();
   Function& function = Function::Handle(zone);
@@ -28,9 +28,12 @@
   // Create a new Function named '<target>_FfiCallback' and stick it in the
   // 'dart:ffi' library. Note that these functions will never be invoked by
   // Dart, so they may have duplicate names.
-  const auto& name = String::Handle(
-      zone, Symbols::FromConcat(thread, Symbols::FfiCallback(),
-                                String::Handle(zone, dart_target.name())));
+  const auto& name =
+      kind == FfiTrampolineKind::kSyncCallback
+          ? String::Handle(zone, Symbols::FromConcat(
+                                     thread, Symbols::FfiCallback(),
+                                     String::Handle(zone, dart_target.name())))
+          : Symbols::FfiAsyncCallback();
   const Library& lib = Library::Handle(zone, Library::FfiLibrary());
   const Class& owner_class = Class::Handle(zone, lib.toplevel_class());
   auto& signature = FunctionType::Handle(zone, FunctionType::New());
@@ -47,7 +50,7 @@
   // the body.
   function.SetFfiCSignature(c_signature);
   function.SetFfiCallbackTarget(dart_target);
-  function.SetFfiCallbackKind(kind);
+  function.SetFfiTrampolineKind(kind);
 
   // We need to load the exceptional return value as a constant in the generated
   // function. Even though the FE ensures that it is a constant, it could still
diff --git a/runtime/vm/compiler/ffi/callback.h b/runtime/vm/compiler/ffi/callback.h
index 02b4294..0df4759 100644
--- a/runtime/vm/compiler/ffi/callback.h
+++ b/runtime/vm/compiler/ffi/callback.h
@@ -23,7 +23,7 @@
 FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
                                    const Function& dart_target,
                                    const Instance& exceptional_return,
-                                   FfiCallbackKind kind);
+                                   FfiTrampolineKind kind);
 
 // Builds a mapping from `callback-id` to code object / ...
 //
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 9190a70..37fdafb 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -3386,9 +3386,9 @@
     case MethodRecognizer::kFfiAsFunctionInternal:
       return BuildFfiAsFunctionInternal();
     case MethodRecognizer::kFfiNativeCallbackFunction:
-      return BuildFfiNativeCallbackFunction(FfiCallbackKind::kSync);
+      return BuildFfiNativeCallbackFunction(FfiTrampolineKind::kSyncCallback);
     case MethodRecognizer::kFfiNativeAsyncCallbackFunction:
-      return BuildFfiNativeCallbackFunction(FfiCallbackKind::kAsync);
+      return BuildFfiNativeCallbackFunction(FfiTrampolineKind::kAsyncCallback);
     case MethodRecognizer::kFfiLoadAbiSpecificInt:
       return BuildLoadAbiSpecificInt(/*at_index=*/false);
     case MethodRecognizer::kFfiLoadAbiSpecificIntAtIndex:
@@ -6321,19 +6321,20 @@
 }
 
 Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction(
-    FfiCallbackKind kind) {
+    FfiTrampolineKind kind) {
   // The call-site must look like this (guaranteed by the FE which inserts it):
   //
-  // FfiCallbackKind::kSync:
+  // FfiTrampolineKind::kSyncCallback:
   //   _nativeCallbackFunction<NativeSignatureType>(target, exceptionalReturn)
   //
-  // FfiCallbackKind::kAsync:
-  //   _nativeAsyncCallbackFunction<NativeSignatureType>(target)
+  // FfiTrampolineKind::kAsyncCallback:
+  //   _nativeAsyncCallbackFunction<NativeSignatureType>()
   //
   // The FE also guarantees that both arguments are constants.
 
   // Target, and for kSync callbacks, the exceptional return.
-  const intptr_t expected_argc = kind == FfiCallbackKind::kSync ? 2 : 1;
+  const intptr_t expected_argc =
+      kind == FfiTrampolineKind::kSyncCallback ? 2 : 0;
 
   const intptr_t argc = ReadUInt();  // Read argument count.
   ASSERT(argc == expected_argc);
@@ -6352,19 +6353,21 @@
   ASSERT(positional_count == expected_argc);
 
   // Read target expression and extract the target function.
-  code += BuildExpression();  // Build first positional argument (target).
-  Definition* target_def = B->Peek();
-  ASSERT(target_def->IsConstant());
-  const Closure& target_closure =
-      Closure::Cast(target_def->AsConstant()->value());
-  ASSERT(!target_closure.IsNull());
-  Function& target = Function::Handle(Z, target_closure.function());
-  ASSERT(!target.IsNull() && target.IsImplicitClosureFunction());
-  target = target.parent_function();
-  code += Drop();
-
+  Function& target = Function::Handle(Z, Function::null());
   Instance& exceptional_return = Instance::ZoneHandle(Z, Instance::null());
-  if (kind == FfiCallbackKind::kSync) {
+  if (kind == FfiTrampolineKind::kSyncCallback) {
+    // Build first positional argument (target).
+    code += BuildExpression();
+    Definition* target_def = B->Peek();
+    ASSERT(target_def->IsConstant());
+    const Closure& target_closure =
+        Closure::Cast(target_def->AsConstant()->value());
+    ASSERT(!target_closure.IsNull());
+    target = target_closure.function();
+    ASSERT(!target.IsNull() && target.IsImplicitClosureFunction());
+    target = target.parent_function();
+    code += Drop();
+
     // Build second positional argument (exceptionalReturn).
     code += BuildExpression();
     Definition* exceptional_return_def = B->Peek();
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index 14c4e3d..72b2aac 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -398,7 +398,7 @@
 
   // Build FG for '_nativeCallbackFunction'. Reads an Arguments from the
   // Kernel buffer and pushes the resulting Function object.
-  Fragment BuildFfiNativeCallbackFunction(FfiCallbackKind kind);
+  Fragment BuildFfiNativeCallbackFunction(FfiTrampolineKind kind);
 
   // Piece of a StringConcatenation.
   // Represents either a StringLiteral, or a Reader offset to the expression.
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 4845de3..1aee656 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -4740,15 +4740,16 @@
 
 FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline(
     const Function& function) {
-  if (function.FfiCallbackTarget() != Function::null()) {
-    if (function.GetFfiCallbackKind() == FfiCallbackKind::kSync) {
+  switch (function.GetFfiTrampolineKind()) {
+    case FfiTrampolineKind::kSyncCallback:
       return BuildGraphOfSyncFfiCallback(function);
-    } else {
+    case FfiTrampolineKind::kAsyncCallback:
       return BuildGraphOfAsyncFfiCallback(function);
-    }
-  } else {
-    return BuildGraphOfFfiNative(function);
+    case FfiTrampolineKind::kCall:
+      return BuildGraphOfFfiNative(function);
   }
+  UNREACHABLE();
+  return nullptr;
 }
 
 FlowGraph* FlowGraphBuilder::BuildGraphOfFfiNative(const Function& function) {
diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc
index 76a4c3d..fb8ad2c 100644
--- a/runtime/vm/compiler/frontend/scope_builder.cc
+++ b/runtime/vm/compiler/frontend/scope_builder.cc
@@ -389,7 +389,7 @@
     case UntaggedFunction::kFfiTrampoline: {
       needs_expr_temp_ = true;
       // Callbacks and calls with handles need try/catch variables.
-      if ((function.FfiCallbackTarget() != Function::null() ||
+      if ((function.GetFfiTrampolineKind() != FfiTrampolineKind::kCall ||
            function.FfiCSignatureContainsHandles())) {
         ++depth_.try_;
         AddTryVariables();
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index b63cf22..b1c1a1e 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -477,7 +477,7 @@
   }
 
   if (function.IsFfiTrampoline() &&
-      function.FfiCallbackTarget() != Function::null()) {
+      function.GetFfiTrampolineKind() != FfiTrampolineKind::kCall) {
     compiler::ffi::SetFfiCallbackCode(thread(), function, code);
   }
 
diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h
index 252a74f..f0bf74f 100644
--- a/runtime/vm/compiler/recognized_methods_list.h
+++ b/runtime/vm/compiler/recognized_methods_list.h
@@ -270,7 +270,7 @@
   V(::, _asFunctionInternal, FfiAsFunctionInternal, 0x630c8491)                \
   V(::, _nativeCallbackFunction, FfiNativeCallbackFunction, 0x3fe722bc)        \
   V(::, _nativeAsyncCallbackFunction, FfiNativeAsyncCallbackFunction,          \
-    0x2057e24d)                                                                \
+    0xbec4b7b9)                                                                \
   V(::, _nativeEffect, NativeEffect, 0x536f42b1)                               \
   V(::, _loadAbiSpecificInt, FfiLoadAbiSpecificInt, 0x77f95c92)                \
   V(::, _loadAbiSpecificIntAtIndex, FfiLoadAbiSpecificIntAtIndex, 0x6a963ed4)  \
diff --git a/runtime/vm/ffi_callback_metadata.cc b/runtime/vm/ffi_callback_metadata.cc
index 9b8943c..5340eb2 100644
--- a/runtime/vm/ffi_callback_metadata.cc
+++ b/runtime/vm/ffi_callback_metadata.cc
@@ -236,7 +236,7 @@
     Zone* zone,
     const Function& function,
     Metadata** list_head) {
-  ASSERT(function.GetFfiCallbackKind() == FfiCallbackKind::kSync);
+  ASSERT(function.GetFfiTrampolineKind() == FfiTrampolineKind::kSyncCallback);
   TrampolineType trampoline_type = TrampolineType::kSync;
 
 #if defined(TARGET_ARCH_IA32)
@@ -262,7 +262,7 @@
     const Function& function,
     Dart_Port send_port,
     Metadata** list_head) {
-  ASSERT(function.GetFfiCallbackKind() == FfiCallbackKind::kAsync);
+  ASSERT(function.GetFfiTrampolineKind() == FfiTrampolineKind::kAsyncCallback);
   return CreateMetadataEntry(isolate, TrampolineType::kAsync,
                              GetEntryPoint(zone, function), send_port,
                              list_head);
diff --git a/runtime/vm/ffi_callback_metadata_test.cc b/runtime/vm/ffi_callback_metadata_test.cc
index da489ca..91500ce 100644
--- a/runtime/vm/ffi_callback_metadata_test.cc
+++ b/runtime/vm/ffi_callback_metadata_test.cc
@@ -22,7 +22,7 @@
 
 namespace dart {
 
-FunctionPtr CreateTestFunction(FfiCallbackKind kind) {
+FunctionPtr CreateTestFunction(FfiTrampolineKind kind) {
   const auto& ffi_lib = Library::Handle(Library::FfiLibrary());
   const auto& ffi_void = Class::Handle(ffi_lib.LookupClass(Symbols::FfiVoid()));
   const auto& ffi_void_type =
@@ -92,7 +92,7 @@
     auto* zone = thread->zone();
 
     const auto& func =
-        Function::Handle(CreateTestFunction(FfiCallbackKind::kSync));
+        Function::Handle(CreateTestFunction(FfiTrampolineKind::kSyncCallback));
     const auto& code = Code::Handle(func.EnsureHasCode());
     EXPECT(!code.IsNull());
 
@@ -183,7 +183,7 @@
     auto* zone = thread->zone();
 
     const Function& func =
-        Function::Handle(CreateTestFunction(FfiCallbackKind::kAsync));
+        Function::Handle(CreateTestFunction(FfiTrampolineKind::kAsyncCallback));
     const Code& code = Code::Handle(func.EnsureHasCode());
     EXPECT(!code.IsNull());
 
@@ -267,7 +267,7 @@
   auto* fcm = FfiCallbackMetadata::Instance();
 
   const Function& func =
-      Function::Handle(CreateTestFunction(FfiCallbackKind::kAsync));
+      Function::Handle(CreateTestFunction(FfiTrampolineKind::kAsyncCallback));
   const Code& code = Code::Handle(func.EnsureHasCode());
   EXPECT(!code.IsNull());
 
@@ -334,7 +334,7 @@
   FfiCallbackMetadata::Metadata* list_head = nullptr;
 
   const auto& sync_func =
-      Function::Handle(CreateTestFunction(FfiCallbackKind::kSync));
+      Function::Handle(CreateTestFunction(FfiTrampolineKind::kSyncCallback));
   const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
   EXPECT(!sync_code.IsNull());
 
@@ -414,11 +414,11 @@
   FfiCallbackMetadata::Metadata* list_head = nullptr;
 
   const Function& async_func =
-      Function::Handle(CreateTestFunction(FfiCallbackKind::kAsync));
+      Function::Handle(CreateTestFunction(FfiTrampolineKind::kAsyncCallback));
   const Code& async_code = Code::Handle(async_func.EnsureHasCode());
   EXPECT(!async_code.IsNull());
   const Function& sync_func =
-      Function::Handle(CreateTestFunction(FfiCallbackKind::kSync));
+      Function::Handle(CreateTestFunction(FfiTrampolineKind::kSyncCallback));
   const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
   EXPECT(!sync_code.IsNull());
 
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 3bbd35e..bb4f00c 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -3608,10 +3608,10 @@
 
 FfiCallbackMetadata::Trampoline Isolate::CreateAsyncFfiCallback(
     Zone* zone,
-    const Function& function,
+    const Function& send_function,
     Dart_Port send_port) {
   return FfiCallbackMetadata::Instance()->CreateAsyncFfiCallback(
-      this, zone, function, send_port, &ffi_callback_list_head_);
+      this, zone, send_function, send_port, &ffi_callback_list_head_);
 }
 
 void Isolate::DeleteFfiCallback(FfiCallbackMetadata::Trampoline callback) {
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index d002459..8644800 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -1246,7 +1246,7 @@
       const Function& function);
   FfiCallbackMetadata::Trampoline CreateAsyncFfiCallback(
       Zone* zone,
-      const Function& function,
+      const Function& send_function,
       Dart_Port send_port);
   void DeleteFfiCallback(FfiCallbackMetadata::Trampoline callback);
 
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 12c6550..12a9ae0 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -8297,7 +8297,7 @@
 
 int32_t Function::FfiCallbackId() const {
   ASSERT(IsFfiTrampoline());
-  ASSERT(FfiCallbackTarget() != Object::null());
+  ASSERT(GetFfiTrampolineKind() != FfiTrampolineKind::kCall);
 
   const auto& obj = Object::Handle(data());
   ASSERT(!obj.IsNull());
@@ -8310,7 +8310,7 @@
 
 void Function::AssignFfiCallbackId(int32_t callback_id) const {
   ASSERT(IsFfiTrampoline());
-  ASSERT(FfiCallbackTarget() != Object::null());
+  ASSERT(GetFfiTrampolineKind() != FfiTrampolineKind::kCall);
 
   const auto& obj = Object::Handle(data());
   ASSERT(!obj.IsNull());
@@ -8362,18 +8362,18 @@
   FfiTrampolineData::Cast(obj).set_callback_exceptional_return(value);
 }
 
-FfiCallbackKind Function::GetFfiCallbackKind() const {
+FfiTrampolineKind Function::GetFfiTrampolineKind() const {
   ASSERT(IsFfiTrampoline());
   const Object& obj = Object::Handle(data());
   ASSERT(!obj.IsNull());
-  return FfiTrampolineData::Cast(obj).callback_kind();
+  return FfiTrampolineData::Cast(obj).trampoline_kind();
 }
 
-void Function::SetFfiCallbackKind(FfiCallbackKind value) const {
+void Function::SetFfiTrampolineKind(FfiTrampolineKind value) const {
   ASSERT(IsFfiTrampoline());
   const Object& obj = Object::Handle(data());
   ASSERT(!obj.IsNull());
-  FfiTrampolineData::Cast(obj).set_callback_kind(value);
+  FfiTrampolineData::Cast(obj).set_trampoline_kind(value);
 }
 
 const char* Function::KindToCString(UntaggedFunction::Kind kind) {
@@ -11473,8 +11473,8 @@
   untag()->set_callback_exceptional_return(value.ptr());
 }
 
-void FfiTrampolineData::set_callback_kind(FfiCallbackKind kind) const {
-  StoreNonPointer(&untag()->callback_kind_, static_cast<uint8_t>(kind));
+void FfiTrampolineData::set_trampoline_kind(FfiTrampolineKind kind) const {
+  StoreNonPointer(&untag()->trampoline_kind_, static_cast<uint8_t>(kind));
 }
 
 FfiTrampolineDataPtr FfiTrampolineData::New() {
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 89d1872..e51cfd6 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2949,9 +2949,10 @@
   }
 };
 
-enum class FfiCallbackKind : uint8_t {
-  kSync,
-  kAsync,
+enum class FfiTrampolineKind : uint8_t {
+  kCall,
+  kSyncCallback,
+  kAsyncCallback,
 };
 
 class Function : public Object {
@@ -3012,10 +3013,10 @@
   void SetFfiCallbackExceptionalReturn(const Instance& value) const;
 
   // Can only be called on FFI trampolines.
-  FfiCallbackKind GetFfiCallbackKind() const;
+  FfiTrampolineKind GetFfiTrampolineKind() const;
 
   // Can only be called on FFI trampolines.
-  void SetFfiCallbackKind(FfiCallbackKind value) const;
+  void SetFfiTrampolineKind(FfiTrampolineKind value) const;
 
   // Return the signature of this function.
   PRECOMPILER_WSR_FIELD_DECLARATION(FunctionType, signature);
@@ -4347,10 +4348,10 @@
   }
   void set_callback_exceptional_return(const Instance& value) const;
 
-  FfiCallbackKind callback_kind() const {
-    return static_cast<FfiCallbackKind>(untag()->callback_kind_);
+  FfiTrampolineKind trampoline_kind() const {
+    return static_cast<FfiTrampolineKind>(untag()->trampoline_kind_);
   }
-  void set_callback_kind(FfiCallbackKind kind) const;
+  void set_trampoline_kind(FfiTrampolineKind kind) const;
 
   int32_t callback_id() const { return untag()->callback_id_; }
   void set_callback_id(int32_t value) const;
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 0f2fff8..b2e498b 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -1516,8 +1516,8 @@
   // Whether this is a leaf call - i.e. one that doesn't call back into Dart.
   bool is_leaf_;
 
-  // The kind of callback this is. See FfiCallbackKind.
-  uint8_t callback_kind_;
+  // The kind of trampoline this is. See FfiTrampolineKind.
+  uint8_t trampoline_kind_;
 };
 
 class UntaggedField : public UntaggedObject {
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index daebdb1..3bad042 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -100,6 +100,7 @@
   V(ExternalOneByteString, "_ExternalOneByteString")                           \
   V(ExternalTwoByteString, "_ExternalTwoByteString")                           \
   V(FfiAbiSpecificMapping, "_FfiAbiSpecificMapping")                           \
+  V(FfiAsyncCallback, "_FfiAsyncCallback")                                     \
   V(FfiBool, "Bool")                                                           \
   V(FfiCallback, "_FfiCallback")                                               \
   V(FfiDouble, "Double")                                                       \
diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart
index 878c912..e131c4b3 100644
--- a/sdk/lib/_internal/vm/lib/ffi_patch.dart
+++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart
@@ -146,8 +146,7 @@
 
 @pragma("vm:recognized", "other")
 @pragma("vm:external-name", "Ffi_nativeAsyncCallbackFunction")
-external dynamic _nativeAsyncCallbackFunction<NS extends Function>(
-    Function target);
+external dynamic _nativeAsyncCallbackFunction<NS extends Function>();
 
 @pragma("vm:external-name", "Ffi_pointerAsyncFromFunction")
 external Pointer<NS> _pointerAsyncFromFunction<NS extends NativeFunction>(
diff --git a/tests/ffi/async_void_function_callbacks_test.dart b/tests/ffi/async_void_function_callbacks_test.dart
index 8fb1a28..6c036f4 100644
--- a/tests/ffi/async_void_function_callbacks_test.dart
+++ b/tests/ffi/async_void_function_callbacks_test.dart
@@ -40,6 +40,7 @@
   await testNativeCallableUseAfterFree();
   await testNativeCallableNestedCloseCall();
   await testNativeCallableThrowInsideCallback();
+  await testNativeCallableClosure();
 
   // Message passing tests.
   globalVar = 1000;
@@ -153,6 +154,28 @@
   callback.close();
 }
 
+testNativeCallableClosure() async {
+  final lib = NativeLibrary();
+  int c = 70000;
+  void foo(int a, int b) {
+    print("foo");
+    simpleFunctionResult.complete(a + b + c);
+  }
+
+  final callback = NativeCallable<CallbackNativeType>.listener(foo);
+
+  simpleFunctionResult = Completer<int>();
+  lib.callFunctionOnSameThread(1000, callback.nativeFunction);
+  Expect.equals(71123, await simpleFunctionResult.future);
+
+  c = 80000;
+  simpleFunctionResult = Completer<int>();
+  lib.callFunctionOnSameThread(4000, callback.nativeFunction);
+  Expect.equals(84123, await simpleFunctionResult.future);
+
+  callback.close();
+}
+
 final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
 
 // Global variable that is 1000 on isolate A, and 2000 on isolate B.
@@ -565,7 +588,6 @@
     instance = this;
     fnPtrsA = FnPtrs.fromCallbacks(callbacksA);
     atm.toIsoA = atm.toThis;
-    print("IsolateA fn ptr: ${fnPtrsA.addGlobalVarPtr.toRadixString(16)}");
   }
 
   Future<void> messageLoop() async {
@@ -634,7 +656,6 @@
     instance = this;
     fnPtrsB = FnPtrs.fromCallbacks(callbacksB);
     atm.toIsoB = atm.toThis;
-    print("IsolateB fn ptr: ${fnPtrsB.addGlobalVarPtr.toRadixString(16)}");
     sendPort.send(['sendPort', recvPort.sendPort]);
     sendPort.send(['testPort', atm.toThis]);
     sendPort.send(['fnPtrs', fnPtrsB.toList()]);
diff --git a/tests/ffi/vmspecific_static_checks_test.dart b/tests/ffi/vmspecific_static_checks_test.dart
index 2107dd4..b21f89c 100644
--- a/tests/ffi/vmspecific_static_checks_test.dart
+++ b/tests/ffi/vmspecific_static_checks_test.dart
@@ -41,7 +41,6 @@
   testNativeCallableGeneric2();
   testNativeCallableWrongNativeFunctionSignature();
   testNativeCallableTypeMismatch();
-  testNativeCallableClosure();
   testNativeCallableAbstract();
   testNativeCallableMustReturnVoid();
   testLookupFunctionGeneric();
@@ -381,8 +380,9 @@
     NativeCallable<NativeVoidFunc>? result;
     result = NativeCallable.listener(f);
     //                               ^
-    // [cfe] listener expects a static function as parameter. dart:ffi only supports calling static Dart functions from native code. Closures and tear-offs are not supported because they can capture context.
     // [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
+    //                      ^
+    // [cfe] Expected type 'T' to be 'void Function()', which is the Dart type corresponding to 'NativeFunction<Void Function()>'.
     return result;
   }
 
@@ -420,14 +420,6 @@
   // [cfe] Expected type 'void Function(int)' to be 'void Function()', which is the Dart type corresponding to 'NativeFunction<Void Function()>'.
 }
 
-void testNativeCallableClosure() {
-  VoidFunc someClosure = () => print("Closure");
-  NativeCallable<NativeVoidFunc> p;
-  p = NativeCallable.listener(someClosure);
-  //                          ^
-  // [cfe] listener expects a static function as parameter. dart:ffi only supports calling static Dart functions from native code. Closures and tear-offs are not supported because they can capture context.
-}
-
 void testNativeCallableAbstract() {
   final f = NativeCallable<Function>.listener(testNativeCallableAbstract);
   //        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^