[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>
This commit is contained in:
Liam Appelbe 2023-07-26 23:23:26 +00:00 committed by Commit Queue
parent f2611b7c8e
commit edeac698c2
28 changed files with 164 additions and 113 deletions

View file

@ -141,9 +141,6 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
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 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
// 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 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
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 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
nativeCallablePrivateConstructor,
Arguments([
FunctionExpression(handler),
StringLiteral('NativeCallable(${target.name})'),
StringLiteral('NativeCallable($target)'),
], types: [
targetType,
])),
@ -644,12 +645,12 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
..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 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
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`.

View file

@ -6,6 +6,7 @@ import 'dart:ffi';
void main() {
testNativeCallableListener();
testNativeCallableListenerClosure();
}
void printInt(int i) => print(i);
@ -15,3 +16,11 @@ void testNativeCallableListener() {
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();
}

View file

@ -8,19 +8,33 @@ import "dart:ffi";
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
}

View file

@ -8,19 +8,33 @@ import "dart:ffi";
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
}

View file

@ -1386,7 +1386,7 @@ class FfiTrampolineDataSerializationCluster : public SerializationCluster {
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 @@ class FfiTrampolineDataDeserializationCluster : public DeserializationCluster {
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>();
}
}
};

View file

@ -425,7 +425,7 @@ struct CanonicalFfiCallbackFunctionTraits {
f1.FfiCSignature() == f2.FfiCSignature() &&
f1.FfiCallbackExceptionalReturn() ==
f2.FfiCallbackExceptionalReturn() &&
f1.GetFfiCallbackKind() == f2.GetFfiCallbackKind());
f1.GetFfiTrampolineKind() == f2.GetFfiTrampolineKind());
}
static bool ReportStats() { return false; }
};

View file

@ -3451,7 +3451,7 @@ void PrecompileParsedFunctionHelper::FinalizeCompilation(
}
if (function.IsFfiTrampoline() &&
function.FfiCallbackTarget() != Function::null()) {
function.GetFfiTrampolineKind() != FfiTrampolineKind::kCall) {
compiler::ffi::SetFfiCallbackCode(thread(), function, code);
}
}

View file

@ -835,10 +835,10 @@ void FlowGraphSerializer::WriteTrait<const Function&>::Write(
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 Function& FlowGraphDeserializer::ReadTrait<const Function&>::Read(
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));

View file

@ -47,6 +47,7 @@ FunctionPtr TrampolineFunction(const String& name,
function.SetFfiCSignature(c_signature);
function.SetFfiIsLeaf(is_leaf);
function.SetFfiTrampolineKind(FfiTrampolineKind::kCall);
return function.ptr();
}

View file

@ -18,7 +18,7 @@ namespace ffi {
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 @@ FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
// 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 @@ FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
// 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

View file

@ -23,7 +23,7 @@ namespace ffi {
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 / ...
//

View file

@ -3386,9 +3386,9 @@ Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) {
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::BuildFfiAsFunctionInternal() {
}
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 @@ Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction(
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();

View file

@ -398,7 +398,7 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
// 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.

View file

@ -4740,15 +4740,16 @@ Fragment FlowGraphBuilder::FfiConvertPrimitiveToNative(
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) {

View file

@ -389,7 +389,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
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();

View file

@ -477,7 +477,7 @@ CodePtr CompileParsedFunctionHelper::FinalizeCompilation(
}
if (function.IsFfiTrampoline() &&
function.FfiCallbackTarget() != Function::null()) {
function.GetFfiTrampolineKind() != FfiTrampolineKind::kCall) {
compiler::ffi::SetFfiCallbackCode(thread(), function, code);
}

View file

@ -270,7 +270,7 @@ namespace dart {
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) \

View file

@ -236,7 +236,7 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateSyncFfiCallback(
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 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateAsyncFfiCallback(
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);

View file

@ -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 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) {
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 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateAsyncFfiCallback) {
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 @@ ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_TrampolineRecycling) {
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 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_DeleteTrampolines) {
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 @@ static void RunBigRandomMultithreadedTest(uint64_t seed) {
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());

View file

@ -3608,10 +3608,10 @@ FfiCallbackMetadata::Trampoline Isolate::CreateSyncFfiCallback(
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) {

View file

@ -1246,7 +1246,7 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
const Function& function);
FfiCallbackMetadata::Trampoline CreateAsyncFfiCallback(
Zone* zone,
const Function& function,
const Function& send_function,
Dart_Port send_port);
void DeleteFfiCallback(FfiCallbackMetadata::Trampoline callback);

View file

@ -8297,7 +8297,7 @@ bool Function::FfiCSignatureReturnsStruct() const {
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 @@ int32_t Function::FfiCallbackId() const {
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 @@ void Function::SetFfiCallbackExceptionalReturn(const Instance& value) const {
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 @@ void FfiTrampolineData::set_callback_exceptional_return(
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() {

View file

@ -2949,9 +2949,10 @@ struct NameFormattingParams {
}
};
enum class FfiCallbackKind : uint8_t {
kSync,
kAsync,
enum class FfiTrampolineKind : uint8_t {
kCall,
kSyncCallback,
kAsyncCallback,
};
class Function : public Object {
@ -3012,10 +3013,10 @@ class Function : public Object {
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 @@ class FfiTrampolineData : public Object {
}
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;

View file

@ -1516,8 +1516,8 @@ class UntaggedFfiTrampolineData : public UntaggedObject {
// 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 {

View file

@ -100,6 +100,7 @@ class ObjectPointerVisitor;
V(ExternalOneByteString, "_ExternalOneByteString") \
V(ExternalTwoByteString, "_ExternalTwoByteString") \
V(FfiAbiSpecificMapping, "_FfiAbiSpecificMapping") \
V(FfiAsyncCallback, "_FfiAsyncCallback") \
V(FfiBool, "Bool") \
V(FfiCallback, "_FfiCallback") \
V(FfiDouble, "Double") \

View file

@ -146,8 +146,7 @@ external Pointer<NS> _pointerFromFunction<NS extends NativeFunction>(
@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>(

View file

@ -40,6 +40,7 @@ main(args, message) async {
await testNativeCallableUseAfterFree();
await testNativeCallableNestedCloseCall();
await testNativeCallableThrowInsideCallback();
await testNativeCallableClosure();
// Message passing tests.
globalVar = 1000;
@ -153,6 +154,28 @@ testNativeCallableThrowInsideCallback() async {
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 @@ class IsolateA {
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 @@ class IsolateB {
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()]);

View file

@ -41,7 +41,6 @@ void main() {
testNativeCallableGeneric2();
testNativeCallableWrongNativeFunctionSignature();
testNativeCallableTypeMismatch();
testNativeCallableClosure();
testNativeCallableAbstract();
testNativeCallableMustReturnVoid();
testLookupFunctionGeneric();
@ -381,8 +380,9 @@ void testNativeCallableGeneric() {
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 @@ void testNativeCallableTypeMismatch() {
// [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);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^