[vm/ffi] Closure callbacks for sync callbacks

Bug: https://github.com/dart-lang/sdk/issues/52689
Change-Id: I54be397cfbf8519fe5b5a51b793fe46d602124d9
Fixes: https://github.com/dart-lang/sdk/issues/52689
Bug: https://github.com/dart-lang/sdk/issues/53096
TEST=isolate_local_function_callbacks_test.dart, plus generated tests and additions to existing tests
CoreLibraryReviewExempt: The isolate and FFI packages are VM-only
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/317060
Commit-Queue: Liam Appelbe <liama@google.com>
Reviewed-by: Liam Appelbe <liama@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Liam Appelbe 2023-08-25 03:35:44 +00:00 committed by Commit Queue
parent 33fb1e3456
commit e8d7425c4e
50 changed files with 14876 additions and 414 deletions

View file

@ -204,12 +204,13 @@ class FfiCode extends AnalyzerErrorCode {
hasPublishedDocs: true,
);
/// No parameters.
/// Parameters:
/// 0: the name of the method
static const FfiCode INVALID_EXCEPTION_VALUE = FfiCode(
'INVALID_EXCEPTION_VALUE',
"The method 'Pointer.fromFunction' can't have an exceptional return value "
"(the second argument) when the return type of the function is either "
"'void', 'Handle' or 'Pointer'.",
"The method {0} can't have an exceptional return value (the second "
"argument) when the return type of the function is either 'void', "
"'Handle' or 'Pointer'.",
correctionMessage: "Try removing the exceptional return value.",
hasPublishedDocs: true,
);
@ -264,12 +265,13 @@ class FfiCode extends AnalyzerErrorCode {
hasPublishedDocs: true,
);
/// No parameters.
/// Parameters:
/// 0: the name of the method
static const FfiCode MISSING_EXCEPTION_VALUE = FfiCode(
'MISSING_EXCEPTION_VALUE',
"The method 'Pointer.fromFunction' must have an exceptional return value "
"(the second argument) when the return type of the function is neither "
"'void', 'Handle', nor 'Pointer'.",
"The method {0} must have an exceptional return value (the second "
"argument) when the return type of the function is neither 'void', "
"'Handle', nor 'Pointer'.",
correctionMessage: "Try adding an exceptional return value.",
hasPublishedDocs: true,
);

View file

@ -1074,12 +1074,12 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
R.isHandle ||
R.isCompoundSubtype) {
if (argCount != 1) {
_errorReporter.reportErrorForNode(
FfiCode.INVALID_EXCEPTION_VALUE, node.argumentList.arguments[1]);
_errorReporter.reportErrorForNode(FfiCode.INVALID_EXCEPTION_VALUE,
node.argumentList.arguments[1], ['fromFunction']);
}
} else if (argCount != 2) {
_errorReporter.reportErrorForNode(
FfiCode.MISSING_EXCEPTION_VALUE, node.methodName);
FfiCode.MISSING_EXCEPTION_VALUE, node.methodName, ['fromFunction']);
} else {
Expression e = node.argumentList.arguments[1];
var eType = e.typeOrThrow;
@ -1143,10 +1143,15 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
node.argumentList.arguments, S, typeArguments[0]);
}
/// Validate the invocation of the constructor `NativeCallable.listener(f)`.
/// Validate the invocation of the constructor `NativeCallable.listener(f)`
/// or `NativeCallable.isolateLocal(f)`.
void _validateNativeCallable(InstanceCreationExpression node) {
final name = node.constructorName.name?.toString() ?? '';
final isolateLocal = name == 'isolateLocal';
// listener takes 1 arg, isolateLocal takes 1 or 2.
var argCount = node.argumentList.arguments.length;
if (argCount != 1) {
if (!(argCount == 1 || (isolateLocal && argCount == 2))) {
// There are other diagnostics reported against the invocation and the
// diagnostics generated below might be inaccurate, so don't report them.
return;
@ -1167,10 +1172,38 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
return;
}
// TODO(brianwilkerson) Validate that `f` is a top-level function.
var retType = (funcType as FunctionType).returnType;
if (retType is! VoidType) {
_errorReporter.reportErrorForNode(FfiCode.MUST_RETURN_VOID, f, [retType]);
var natRetType = (typeArg as FunctionType).returnType;
if (isolateLocal) {
if (retType is VoidType ||
natRetType.isPointer ||
natRetType.isHandle ||
natRetType.isCompoundSubtype) {
if (argCount != 1) {
_errorReporter.reportErrorForNode(FfiCode.INVALID_EXCEPTION_VALUE,
node.argumentList.arguments[1], [name]);
}
} else if (argCount != 2) {
_errorReporter
.reportErrorForNode(FfiCode.MISSING_EXCEPTION_VALUE, node, [name]);
} else {
var e = (node.argumentList.arguments[1] as NamedExpression).expression;
var eType = e.typeOrThrow;
if (!_validateCompatibleNativeType(eType, natRetType,
checkCovariance: true)) {
_errorReporter.reportErrorForNode(
FfiCode.MUST_BE_A_SUBTYPE, e, [eType, natRetType, name]);
}
if (!_isConst(e)) {
_errorReporter.reportErrorForNode(
FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, e, ['exceptionalReturn']);
}
}
} else {
if (retType is! VoidType) {
_errorReporter
.reportErrorForNode(FfiCode.MUST_RETURN_VOID, f, [retType]);
}
}
}

View file

@ -779,7 +779,11 @@ final class Pointer<T extends NativeType> extends NativeType {
final Pointer<Never> nullptr = Pointer.fromAddress(0);
class NativeCallable<T extends Function> {
NativeCallable.listener(@DartRepresentationOf('T') Function callback) {}
NativeCallable.isolateLocal(
@DartRepresentationOf("T") Function callback,
{Object? exceptionalReturn}) {}
NativeCallable.listener(@DartRepresentationOf("T") Function callback) {}
Pointer<NativeFunction<T>> get nativeFunction;

View file

@ -18135,7 +18135,8 @@ FfiCode:
argument whose value isn't a constant expression.
The analyzer also produces this diagnostic when the value of the
`exceptionalReturn` argument of `Pointer.fromFunction`.
`exceptionalReturn` argument of `Pointer.fromFunction` or
`NativeCallable.isolateLocal`.
For more information about FFI, see [C interop using dart:ffi][ffi].
@ -18591,15 +18592,18 @@ FfiCode:
}
```
INVALID_EXCEPTION_VALUE:
problemMessage: "The method 'Pointer.fromFunction' can't have an exceptional return value (the second argument) when the return type of the function is either 'void', 'Handle' or 'Pointer'."
problemMessage: "The method {0} can't have an exceptional return value (the second argument) when the return type of the function is either 'void', 'Handle' or 'Pointer'."
correctionMessage: Try removing the exceptional return value.
comment: No parameters.
comment: |-
Parameters:
0: the name of the method
hasPublishedDocs: true
documentation: |-
#### Description
The analyzer produces this diagnostic when an invocation of the method
`Pointer.fromFunction` has a second argument (the exceptional return
`Pointer.fromFunction` or `NativeCallable.isolateLocal`
has a second argument (the exceptional return
value) and the type to be returned from the invocation is either `void`,
`Handle` or `Pointer`.
@ -18888,15 +18892,18 @@ FfiCode:
}
```
MISSING_EXCEPTION_VALUE:
problemMessage: "The method 'Pointer.fromFunction' must have an exceptional return value (the second argument) when the return type of the function is neither 'void', 'Handle', nor 'Pointer'."
problemMessage: "The method {0} must have an exceptional return value (the second argument) when the return type of the function is neither 'void', 'Handle', nor 'Pointer'."
correctionMessage: Try adding an exceptional return value.
comment: No parameters.
comment: |-
Parameters:
0: the name of the method
hasPublishedDocs: true
documentation: |-
#### Description
The analyzer produces this diagnostic when an invocation of the method
`Pointer.fromFunction` doesn't have a second argument (the exceptional
`Pointer.fromFunction` or `NativeCallable.isolateLocal`
doesn't have a second argument (the exceptional
return value) when the type to be returned from the invocation is neither
`void`, `Handle`, nor `Pointer`.
@ -19028,7 +19035,8 @@ FfiCode:
#### Description
The analyzer produces this diagnostic when an invocation of either
`Pointer.fromFunction` or `DynamicLibrary.lookupFunction` has a type
`Pointer.fromFunction`, `DynamicLibrary.lookupFunction`,
or a `NativeCallable` constructor, has a type
argument(whether explicit or inferred) that isn't a native function type.
For more information about FFI, see [C interop using dart:ffi][ffi].
@ -19079,7 +19087,8 @@ FfiCode:
#### Description
The analyzer produces this diagnostic in two cases:
- In an invocation of `Pointer.fromFunction` where the type argument
- In an invocation of `Pointer.fromFunction`, or a
`NativeCallable` constructor where the type argument
(whether explicit or inferred) isn't a supertype of the type of the
function passed as the first argument to the method.
- In an invocation of `DynamicLibrary.lookupFunction` where the first type

View file

@ -16,6 +16,123 @@ main() {
@reflectiveTest
class FfiNativeCallableListenersMustReturnVoid
extends PubPackageResolutionTest {
test_NativeCallable_isolateLocal_argumentMustBeAConstant() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
int e = 123;
NativeCallable<Int32 Function(Int32)>.isolateLocal(f, exceptionalReturn: e);
}
''', [
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 143, 1),
]);
}
test_NativeCallable_isolateLocal_exceptionMustBeASubtype() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable<Int32 Function(Int32)>.isolateLocal(f, exceptionalReturn: '?');
}
''', [
error(FfiCode.MUST_BE_A_SUBTYPE, 128, 3),
]);
}
test_NativeCallable_isolateLocal_inferred() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable<Int32 Function(Int32)>? callback;
callback = NativeCallable.isolateLocal(f, exceptionalReturn: 4);
callback.close();
}
''', []);
}
test_NativeCallable_isolateLocal_invalidExceptionValue() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
void f(int i) => i * 2;
void g() {
NativeCallable<Void Function(Int32)>.isolateLocal(f, exceptionalReturn: 4);
}
''', [
error(FfiCode.INVALID_EXCEPTION_VALUE, 109, 20),
]);
}
test_NativeCallable_isolateLocal_missingExceptionValue() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable<Int32 Function(Int32)>.isolateLocal(f);
}
''', [
error(FfiCode.MISSING_EXCEPTION_VALUE, 55, 53),
]);
}
test_NativeCallable_isolateLocal_mustBeANativeFunctionType() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable<int Function(int)>.isolateLocal(f, exceptionalReturn: 4);
}
''', [
error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 55, 46),
]);
}
test_NativeCallable_isolateLocal_mustBeASubtype() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable<Int32 Function(Double)>.isolateLocal(f, exceptionalReturn: 4);
}
''', [
error(FfiCode.MUST_BE_A_SUBTYPE, 107, 1),
]);
}
test_NativeCallable_isolateLocal_mustHaveTypeArgs() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable.isolateLocal(f, exceptionalReturn: 4);
}
''', [
error(FfiCode.MUST_BE_A_NATIVE_FUNCTION_TYPE, 55, 27),
]);
}
test_NativeCallable_isolateLocal_ok() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
int f(int i) => i * 2;
void g() {
NativeCallable<Int32 Function(Int32)>.isolateLocal(f, exceptionalReturn: 4);
}
''', []);
}
test_NativeCallable_isolateLocal_okVoid() async {
await assertErrorsInCode(r'''
import 'dart:ffi';
void f(int i) => i * 2;
void g() {
NativeCallable<Void Function(Int32)>.isolateLocal(f);
}
''', []);
}
test_NativeCallable_listener_inferred() async {
await assertErrorsInCode(r'''
import 'dart:ffi';

View file

@ -726,7 +726,8 @@ The analyzer produces this diagnostic when an invocation of either
argument whose value isn't a constant expression.
The analyzer also produces this diagnostic when the value of the
`exceptionalReturn` argument of `Pointer.fromFunction`.
`exceptionalReturn` argument of `Pointer.fromFunction` or
`NativeCallable.isolateLocal`.
For more information about FFI, see [C interop using dart:ffi][ffi].
@ -8907,13 +8908,14 @@ dependencies:
### invalid_exception_value
_The method 'Pointer.fromFunction' can't have an exceptional return value (the
second argument) when the return type of the function is either 'void', 'Handle' or 'Pointer'._
_The method {0} can't have an exceptional return value (the second argument)
when the return type of the function is either 'void', 'Handle' or 'Pointer'._
#### Description
The analyzer produces this diagnostic when an invocation of the method
`Pointer.fromFunction` has a second argument (the exceptional return
`Pointer.fromFunction` or `NativeCallable.isolateLocal`
has a second argument (the exceptional return
value) and the type to be returned from the invocation is either `void`,
`Handle` or `Pointer`.
@ -11898,13 +11900,14 @@ void f(E e) {
### missing_exception_value
_The method 'Pointer.fromFunction' must have an exceptional return value (the
second argument) when the return type of the function is neither 'void', 'Handle', nor 'Pointer'._
_The method {0} must have an exceptional return value (the second argument) when
the return type of the function is neither 'void', 'Handle', nor 'Pointer'._
#### Description
The analyzer produces this diagnostic when an invocation of the method
`Pointer.fromFunction` doesn't have a second argument (the exceptional
`Pointer.fromFunction` or `NativeCallable.isolateLocal`
doesn't have a second argument (the exceptional
return value) when the type to be returned from the invocation is neither
`void`, `Handle`, nor `Pointer`.
@ -13022,7 +13025,8 @@ _The type '{0}' given to '{1}' must be a valid 'dart:ffi' native function type._
#### Description
The analyzer produces this diagnostic when an invocation of either
`Pointer.fromFunction` or `DynamicLibrary.lookupFunction` has a type
`Pointer.fromFunction`, `DynamicLibrary.lookupFunction`,
or a `NativeCallable` constructor, has a type
argument(whether explicit or inferred) that isn't a native function type.
For more information about FFI, see [C interop using dart:ffi][ffi].
@ -13068,7 +13072,8 @@ _The type '{0}' must be a subtype of '{1}' for '{2}'._
#### Description
The analyzer produces this diagnostic in two cases:
- In an invocation of `Pointer.fromFunction` where the type argument
- In an invocation of `Pointer.fromFunction`, or a
`NativeCallable` constructor where the type argument
(whether explicit or inferred) isn't a supertype of the type of the
function passed as the first argument to the method.
- In an invocation of `DynamicLibrary.lookupFunction` where the first type

View file

@ -246,10 +246,11 @@ class FfiTransformer extends Transformer {
final Procedure fromAddressInternal;
final Procedure libraryLookupMethod;
final Procedure abiMethod;
final Procedure pointerFromFunctionProcedure;
final Procedure pointerAsyncFromFunctionProcedure;
final Procedure createNativeCallableListenerProcedure;
final Procedure nativeCallbackFunctionProcedure;
final Procedure nativeAsyncCallbackFunctionProcedure;
final Procedure createNativeCallableIsolateLocalProcedure;
final Procedure nativeIsolateLocalCallbackFunctionProcedure;
final Map<NativeType, Procedure> loadMethods;
final Map<NativeType, Procedure> loadUnalignedMethods;
final Map<NativeType, Procedure> storeMethods;
@ -272,8 +273,10 @@ class FfiTransformer extends Transformer {
final Procedure checkAbiSpecificIntegerMappingFunction;
final Class rawRecvPortClass;
final Class nativeCallableClass;
final Constructor nativeCallableListenerConstructor;
final Constructor nativeCallablePrivateConstructor;
final Procedure nativeCallableIsolateLocalConstructor;
final Constructor nativeCallablePrivateIsolateLocalConstructor;
final Procedure nativeCallableListenerConstructor;
final Constructor nativeCallablePrivateListenerConstructor;
final Field nativeCallablePortField;
final Field nativeCallablePointerField;
@ -464,14 +467,17 @@ class FfiTransformer extends Transformer {
libraryLookupMethod =
index.getProcedure('dart:ffi', 'DynamicLibrary', 'lookup'),
abiMethod = index.getTopLevelProcedure('dart:ffi', '_abi'),
pointerFromFunctionProcedure =
index.getTopLevelProcedure('dart:ffi', '_pointerFromFunction'),
pointerAsyncFromFunctionProcedure =
index.getTopLevelProcedure('dart:ffi', '_pointerAsyncFromFunction'),
createNativeCallableListenerProcedure = index.getTopLevelProcedure(
'dart:ffi', '_createNativeCallableListener'),
createNativeCallableIsolateLocalProcedure = index.getTopLevelProcedure(
'dart:ffi', '_createNativeCallableIsolateLocal'),
nativeCallbackFunctionProcedure =
index.getTopLevelProcedure('dart:ffi', '_nativeCallbackFunction'),
nativeAsyncCallbackFunctionProcedure = index.getTopLevelProcedure(
'dart:ffi', '_nativeAsyncCallbackFunction'),
nativeIsolateLocalCallbackFunctionProcedure =
index.getTopLevelProcedure(
'dart:ffi', '_nativeIsolateLocalCallbackFunction'),
nativeTypesClasses = nativeTypeClassNames.map((nativeType, name) =>
MapEntry(nativeType, index.getClass('dart:ffi', name))),
classNativeTypes = nativeTypeClassNames.map((nativeType, name) =>
@ -529,14 +535,18 @@ class FfiTransformer extends Transformer {
'dart:ffi', "_checkAbiSpecificIntegerMapping"),
rawRecvPortClass = index.getClass('dart:isolate', 'RawReceivePort'),
nativeCallableClass = index.getClass('dart:ffi', 'NativeCallable'),
nativeCallableIsolateLocalConstructor =
index.getProcedure('dart:ffi', 'NativeCallable', 'isolateLocal'),
nativeCallablePrivateIsolateLocalConstructor =
index.getConstructor('dart:ffi', '_NativeCallableIsolateLocal', ''),
nativeCallableListenerConstructor =
index.getConstructor('dart:ffi', 'NativeCallable', 'listener'),
nativeCallablePrivateConstructor =
index.getConstructor('dart:ffi', 'NativeCallable', '_'),
index.getProcedure('dart:ffi', 'NativeCallable', 'listener'),
nativeCallablePrivateListenerConstructor =
index.getConstructor('dart:ffi', '_NativeCallableListener', ''),
nativeCallablePortField =
index.getField('dart:ffi', 'NativeCallable', '_port'),
index.getField('dart:ffi', '_NativeCallableListener', '_port'),
nativeCallablePointerField =
index.getField('dart:ffi', 'NativeCallable', '_pointer') {
index.getField('dart:ffi', '_NativeCallableBase', '_pointer') {
nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
coreTypes, Nullability.nonNullable);
voidType = nativeTypesClasses[NativeType.kVoid]!

View file

@ -134,43 +134,6 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
target.name != Name("#fromTypedDataBase")) {
diagnosticReporter.report(messageFfiCreateOfStructOrUnion,
node.fileOffset, 1, node.location?.file);
} else if (target == nativeCallableListenerConstructor) {
try {
final DartType nativeType = InterfaceType(nativeFunctionClass,
currentLibrary.nonNullable, [node.arguments.types[0]]);
final Expression func = node.arguments.positional[0];
final DartType dartType = func.getStaticType(staticTypeContext!);
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(nativeType, dartType, node);
final funcType = dartType as FunctionType;
// Check return type.
if (funcType.returnType != VoidType()) {
diagnosticReporter.report(
templateFfiNativeCallableListenerReturnVoid.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
func.fileOffset,
1,
func.location?.file);
return node;
}
final replacement = _replaceNativeCallableListenerConstructor(node);
final compoundClasses = funcType.positionalParameters
.whereType<InterfaceType>()
.map((t) => t.classNode)
.where((c) =>
c.superclass == structClass || c.superclass == unionClass)
.toList();
return _invokeCompoundConstructors(replacement, compoundClasses);
} on FfiStaticTypeError {
// It's OK to swallow the exception because the diagnostics issued will
// cause compilation to fail. By continuing, we can report more
// diagnostics before compilation ends.
}
}
return super.visitConstructorInvocation(node);
}
@ -354,90 +317,33 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
fileOffset: node.fileOffset,
);
} else if (target == fromFunctionMethod) {
return _verifyAndReplaceNativeCallableIsolateLocal(node,
fromFunction: true);
} else if (target == nativeCallableIsolateLocalConstructor) {
return _verifyAndReplaceNativeCallableIsolateLocal(node);
} else if (target == nativeCallableListenerConstructor) {
final DartType nativeType = InterfaceType(nativeFunctionClass,
currentLibrary.nonNullable, [node.arguments.types[0]]);
final Expression func = node.arguments.positional[0];
final DartType dartType = func.getStaticType(staticTypeContext!);
_ensureIsStaticFunction(func, fromFunctionMethod.name.text);
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(nativeType, dartType, node);
final funcType = dartType as FunctionType;
// Check `exceptionalReturn`'s type.
final Class expectedReturnClass =
((node.arguments.types[0] as FunctionType).returnType
as InterfaceType)
.classNode;
final NativeType? expectedReturn = getType(expectedReturnClass);
if (expectedReturn == NativeType.kVoid ||
expectedReturn == NativeType.kPointer ||
expectedReturn == NativeType.kHandle ||
expectedReturnClass.superclass == structClass ||
expectedReturnClass.superclass == unionClass) {
if (node.arguments.positional.length > 1) {
diagnosticReporter.report(
templateFfiExpectedNoExceptionalReturn.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
node.fileOffset,
1,
node.location?.file);
return node;
}
node.arguments.positional.add(NullLiteral()..parent = node);
} else {
// The exceptional return value is not optional for other return
// types.
if (node.arguments.positional.length < 2) {
diagnosticReporter.report(
templateFfiExpectedExceptionalReturn.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
node.fileOffset,
1,
node.location?.file);
return node;
}
final Expression exceptionalReturn = node.arguments.positional[1];
// The exceptional return value must be a constant so that it be
// referenced by precompiled trampoline's object pool.
if (exceptionalReturn is! BasicLiteral &&
!(exceptionalReturn is ConstantExpression &&
exceptionalReturn.constant is PrimitiveConstant)) {
diagnosticReporter.report(messageFfiExpectedConstant,
node.fileOffset, 1, node.location?.file);
return node;
}
// Moreover it may not be null.
if (exceptionalReturn is NullLiteral ||
(exceptionalReturn is ConstantExpression &&
exceptionalReturn.constant is NullConstant)) {
diagnosticReporter.report(messageFfiExceptionalReturnNull,
node.fileOffset, 1, node.location?.file);
return node;
}
final DartType returnType =
exceptionalReturn.getStaticType(staticTypeContext!);
if (!env.isSubtypeOf(returnType, funcType.returnType,
SubtypeCheckMode.ignoringNullabilities)) {
diagnosticReporter.report(
templateFfiDartTypeMismatch.withArguments(returnType,
funcType.returnType, currentLibrary.isNonNullableByDefault),
exceptionalReturn.fileOffset,
1,
exceptionalReturn.location?.file);
return node;
}
// Check return type.
if (funcType.returnType != VoidType()) {
diagnosticReporter.report(
templateFfiNativeCallableListenerReturnVoid.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
func.fileOffset,
1,
func.location?.file);
return node;
}
final replacement = _replaceFromFunction(node);
final replacement = _replaceNativeCallableListenerConstructor(node);
final compoundClasses = funcType.positionalParameters
.whereType<InterfaceType>()
@ -558,8 +464,8 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
// compile-time and run-time aspects of creating the closure:
//
// final dynamic _#ffiCallback0 = Pointer.fromFunction<T>(f, e) =>
// _pointerFromFunction<NativeFunction<T>>(
// _nativeCallbackFunction<T>(f, e));
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeCallbackFunction<T>(f, e), null, false);
//
// ... _#ffiCallback0 ...
//
@ -568,7 +474,8 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
//
// Creating this closure requires a runtime call, so we save the result in a
// synthetic top-level field to avoid recomputing it.
Expression _replaceFromFunction(StaticInvocation node) {
Expression _replaceFromFunction(
StaticInvocation node, Expression exceptionalReturn) {
final nativeFunctionType = InterfaceType(
nativeFunctionClass, currentLibrary.nonNullable, node.arguments.types);
var name = Name("_#ffiCallback${callbackCount++}", currentLibrary);
@ -577,9 +484,16 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
type: InterfaceType(
pointerClass, currentLibrary.nonNullable, [nativeFunctionType]),
initializer: StaticInvocation(
pointerFromFunctionProcedure,
createNativeCallableIsolateLocalProcedure,
Arguments([
StaticInvocation(nativeCallbackFunctionProcedure, node.arguments)
StaticInvocation(
nativeCallbackFunctionProcedure,
Arguments([
node.arguments.positional[0],
exceptionalReturn,
], types: node.arguments.types)),
NullLiteral(),
BoolLiteral(false),
], types: [
nativeFunctionType
])),
@ -592,14 +506,65 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
return StaticGet(field);
}
// NativeCallable<T>.isolateLocal(target, exceptionalReturn) calls become:
// isStaticFunction is false:
// _NativeCallableIsolateLocal<T>(
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeIsolateLocalCallbackFunction<T>(exceptionalReturn),
// target,
// true));
// isStaticFunction is true:
// _NativeCallableIsolateLocal<T>(
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeCallbackFunction<T>(target, exceptionalReturn),
// null,
// true);
Expression _replaceNativeCallableIsolateLocalConstructor(
StaticInvocation node,
Expression exceptionalReturn,
bool isStaticFunction) {
final nativeFunctionType = InterfaceType(
nativeFunctionClass, currentLibrary.nonNullable, node.arguments.types);
final target = node.arguments.positional[0];
late StaticInvocation pointerValue;
if (isStaticFunction) {
pointerValue = StaticInvocation(
createNativeCallableIsolateLocalProcedure,
Arguments([
StaticInvocation(
nativeCallbackFunctionProcedure,
Arguments([
target,
exceptionalReturn,
], types: node.arguments.types)),
NullLiteral(),
BoolLiteral(true),
], types: [
nativeFunctionType,
]));
} else {
pointerValue = StaticInvocation(
createNativeCallableIsolateLocalProcedure,
Arguments([
StaticInvocation(nativeIsolateLocalCallbackFunctionProcedure,
Arguments([exceptionalReturn], types: node.arguments.types)),
target,
BoolLiteral(true),
], types: [
nativeFunctionType,
]));
}
return ConstructorInvocation(nativeCallablePrivateIsolateLocalConstructor,
Arguments([pointerValue], types: node.arguments.types));
}
// NativeCallable<T>.listener(target) calls become:
// void _handler(List args) => target(args[0], args[1], ...)
// final _callback = NativeCallable<T>._(_handler, debugName);
// _callback._pointer = _pointerAsyncFromFunction<NativeFunction<T>>(
// final _callback = _NativeCallableListener<T>(_handler, debugName);
// _callback._pointer = _createNativeCallableListener<NativeFunction<T>>(
// _nativeAsyncCallbackFunction<T>(), _callback._rawPort);
// expression result: _callback;
Expression _replaceNativeCallableListenerConstructor(
ConstructorInvocation node) {
Expression _replaceNativeCallableListenerConstructor(StaticInvocation node) {
final nativeFunctionType = InterfaceType(
nativeFunctionClass, currentLibrary.nonNullable, node.arguments.types);
final listType = InterfaceType(listClass, currentLibrary.nonNullable);
@ -612,7 +577,6 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
..fileOffset = node.fileOffset;
final targetArgs = <Expression>[];
for (int i = 0; i < targetType.positionalParameters.length; ++i) {
// Do we need an `as` expression?
targetArgs.add(InstanceInvocation(InstanceAccessKind.Instance,
VariableGet(args), listElementAt.name, Arguments([IntLiteral(i)]),
interfaceTarget: listElementAt,
@ -630,10 +594,10 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
positionalParameters: [args], returnType: VoidType())
..fileOffset = node.fileOffset;
// final _callback = NativeCallable<T>._(_handler, debugName);
// final _callback = NativeCallable<T>._listener(_handler, debugName);
final nativeCallable = VariableDeclaration.forValue(
ConstructorInvocation(
nativeCallablePrivateConstructor,
nativeCallablePrivateListenerConstructor,
Arguments([
FunctionExpression(handler),
StringLiteral('NativeCallable($target)'),
@ -644,10 +608,10 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
isFinal: true)
..fileOffset = node.fileOffset;
// _callback._pointer = _pointerAsyncFromFunction<NativeFunction<T>>(
// _callback._pointer = _createNativeCallableListener<NativeFunction<T>>(
// _nativeAsyncCallbackFunction<T>(), _callback._rawPort);
final pointerValue = StaticInvocation(
pointerAsyncFromFunctionProcedure,
createNativeCallableListenerProcedure,
Arguments([
StaticInvocation(nativeAsyncCallbackFunctionProcedure,
Arguments([], types: [targetType])),
@ -675,6 +639,121 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
VariableGet(nativeCallable));
}
Expression _verifyAndReplaceNativeCallableIsolateLocal(StaticInvocation node,
{bool fromFunction = false}) {
final DartType nativeType = InterfaceType(nativeFunctionClass,
currentLibrary.nonNullable, [node.arguments.types[0]]);
final Expression func = node.arguments.positional[0];
final DartType dartType = func.getStaticType(staticTypeContext!);
final isStaticFunction = _isStaticFunction(func);
if (fromFunction && !isStaticFunction) {
diagnosticReporter.report(
templateFfiNotStatic.withArguments(fromFunctionMethod.name.text),
func.fileOffset,
1,
func.location?.file);
return node;
}
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeToDartType(nativeType, dartType, node);
final funcType = dartType as FunctionType;
// Check `exceptionalReturn`'s type.
final Class expectedReturnClass =
((node.arguments.types[0] as FunctionType).returnType as InterfaceType)
.classNode;
final NativeType? expectedReturn = getType(expectedReturnClass);
Expression exceptionalReturn = NullLiteral();
bool hasExceptionalReturn = false;
if (fromFunction) {
if (node.arguments.positional.length > 1) {
exceptionalReturn = node.arguments.positional[1];
hasExceptionalReturn = true;
}
} else {
if (node.arguments.named.isNotEmpty) {
exceptionalReturn = node.arguments.named[0].value;
hasExceptionalReturn = true;
}
}
if (expectedReturn == NativeType.kVoid ||
expectedReturn == NativeType.kPointer ||
expectedReturn == NativeType.kHandle ||
expectedReturnClass.superclass == structClass ||
expectedReturnClass.superclass == unionClass) {
if (hasExceptionalReturn) {
diagnosticReporter.report(
templateFfiExpectedNoExceptionalReturn.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
node.fileOffset,
1,
node.location?.file);
return node;
}
} else {
// The exceptional return value is not optional for other return types.
if (!hasExceptionalReturn) {
diagnosticReporter.report(
templateFfiExpectedExceptionalReturn.withArguments(
funcType.returnType, currentLibrary.isNonNullableByDefault),
node.fileOffset,
1,
node.location?.file);
return node;
}
// The exceptional return value must be a constant so that it can be
// referenced by precompiled trampoline's object pool.
if (exceptionalReturn is! BasicLiteral &&
!(exceptionalReturn is ConstantExpression &&
exceptionalReturn.constant is PrimitiveConstant)) {
diagnosticReporter.report(messageFfiExpectedConstant, node.fileOffset,
1, node.location?.file);
return node;
}
// Moreover it may not be null.
if (exceptionalReturn is NullLiteral ||
(exceptionalReturn is ConstantExpression &&
exceptionalReturn.constant is NullConstant)) {
diagnosticReporter.report(messageFfiExceptionalReturnNull,
node.fileOffset, 1, node.location?.file);
return node;
}
final DartType returnType =
exceptionalReturn.getStaticType(staticTypeContext!);
if (!env.isSubtypeOf(returnType, funcType.returnType,
SubtypeCheckMode.ignoringNullabilities)) {
diagnosticReporter.report(
templateFfiDartTypeMismatch.withArguments(returnType,
funcType.returnType, currentLibrary.isNonNullableByDefault),
exceptionalReturn.fileOffset,
1,
exceptionalReturn.location?.file);
return node;
}
}
final replacement = fromFunction
? _replaceFromFunction(node, exceptionalReturn)
: _replaceNativeCallableIsolateLocalConstructor(
node, exceptionalReturn, isStaticFunction);
final compoundClasses = funcType.positionalParameters
.whereType<InterfaceType>()
.map((t) => t.classNode)
.where((c) => c.superclass == structClass || c.superclass == unionClass)
.toList();
return _invokeCompoundConstructors(replacement, compoundClasses);
}
Expression _replaceGetRef(StaticInvocation node) {
final dartType = node.arguments.types[0];
final clazz = (dartType as InterfaceType).classNode;
@ -882,16 +961,9 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
..fileOffset = node.fileOffset);
}
void _ensureIsStaticFunction(Expression node, String methodName) {
if ((node is StaticGet && node.target is Procedure) ||
(node is ConstantExpression &&
node.constant is StaticTearOffConstant)) {
return;
}
diagnosticReporter.report(templateFfiNotStatic.withArguments(methodName),
node.fileOffset, 1, node.location?.file);
throw FfiStaticTypeError();
}
bool _isStaticFunction(Expression node) =>
(node is StaticGet && node.target is Procedure) ||
(node is ConstantExpression && node.constant is StaticTearOffConstant);
/// Returns the class that should not be implemented or extended.
///

View file

@ -7,6 +7,12 @@ import 'dart:ffi';
void main() {
testNativeCallableListener();
testNativeCallableListenerClosure();
testNativeCallableIsolateLocalVoid();
testNativeCallableIsolateLocalVoidClosure();
testNativeCallableIsolateLocalPointer();
testNativeCallableIsolateLocalPointerClosure();
testNativeCallableIsolateLocalInt();
testNativeCallableIsolateLocalIntClosure();
}
void printInt(int i) => print(i);
@ -24,3 +30,53 @@ void testNativeCallableListenerClosure() {
print(callback.nativeFunction);
callback.close();
}
void testNativeCallableIsolateLocalVoid() {
final callback = NativeCallable<Void Function(Int32)>.isolateLocal(printInt);
print(callback.nativeFunction);
callback.close();
}
void testNativeCallableIsolateLocalVoidClosure() {
int j = 123;
void closure(int i) => print(i + j);
final callback = NativeCallable<Void Function(Int32)>.isolateLocal(closure);
print(callback.nativeFunction);
callback.close();
}
Pointer intToPointer(int i) => Pointer.fromAddress(i);
void testNativeCallableIsolateLocalPointer() {
final callback =
NativeCallable<Pointer Function(Int32)>.isolateLocal(intToPointer);
print(callback.nativeFunction);
callback.close();
}
void testNativeCallableIsolateLocalPointerClosure() {
int j = 123;
Pointer closure(int i) => Pointer.fromAddress(i + j);
final callback =
NativeCallable<Pointer Function(Int32)>.isolateLocal(closure);
print(callback.nativeFunction);
callback.close();
}
int negateInt(int i) => -i;
void testNativeCallableIsolateLocalInt() {
final callback = NativeCallable<Int Function(Int32)>.isolateLocal(negateInt,
exceptionalReturn: 123);
print(callback.nativeFunction);
callback.close();
}
void testNativeCallableIsolateLocalIntClosure() {
int j = 123;
int closure(int i) => i + j;
final callback = NativeCallable<Int Function(Int32)>.isolateLocal(closure,
exceptionalReturn: 123);
print(callback.nativeFunction);
callback.close();
}

View file

@ -9,32 +9,83 @@ import "dart:ffi";
static method main() → void {
self::testNativeCallableListener();
self::testNativeCallableListenerClosure();
self::testNativeCallableIsolateLocalVoid();
self::testNativeCallableIsolateLocalVoidClosure();
self::testNativeCallableIsolateLocalPointer();
self::testNativeCallableIsolateLocalPointerClosure();
self::testNativeCallableIsolateLocalInt();
self::testNativeCallableIsolateLocalIntClosure();
}
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
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t1 = new ffi::_NativeCallableListener::•<(ffi::Int32) → ffi::Void>((final core::List<dynamic> args) → void
#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});
[@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] [@vm.direct-call.metadata=dart.ffi::_NativeCallableBase._pointer] #t1.{ffi::_NativeCallableBase::_pointer} = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableListener<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), [@vm.direct-call.metadata=dart.ffi::_NativeCallableListener._port] [@vm.inferred-type.metadata=dart.isolate::_RawReceivePort] #t1.{ffi::_NativeCallableListener::_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};
core::print([@vm.direct-call.metadata=dart.ffi::_NativeCallableBase.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::_NativeCallableListener.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableListenerClosure() → void {
[@vm.inferred-type.metadata=dart.core::_Smi (value: 123)] 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
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t2 = new ffi::_NativeCallableListener::•<(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});
[@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] [@vm.direct-call.metadata=dart.ffi::_NativeCallableBase._pointer] #t2.{ffi::_NativeCallableBase::_pointer} = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableListener<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), [@vm.direct-call.metadata=dart.ffi::_NativeCallableListener._port] [@vm.inferred-type.metadata=dart.isolate::_RawReceivePort] #t2.{ffi::_NativeCallableListener::_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};
core::print([@vm.direct-call.metadata=dart.ffi::_NativeCallableBase.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::_NativeCallableListener.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableIsolateLocalVoid() → void {
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Void>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeCallbackFunction<(ffi::Int32) → ffi::Void>(#C1, null), null, true));
core::print([@vm.direct-call.metadata=dart.ffi::_NativeCallableBase.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::_NativeCallableIsolateLocal.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableIsolateLocalVoidClosure() → void {
[@vm.inferred-type.metadata=dart.core::_Smi (value: 123)] 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 = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Void>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeIsolateLocalCallbackFunction<(ffi::Int32) → ffi::Void>(null), closure, true));
core::print([@vm.direct-call.metadata=dart.ffi::_NativeCallableBase.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::_NativeCallableIsolateLocal.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
}
static method intToPointer(core::int i) → ffi::Pointer<ffi::NativeType>
return [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeType>(i);
static method testNativeCallableIsolateLocalPointer() → void {
final ffi::NativeCallable<(ffi::Int32) → ffi::Pointer<ffi::NativeType>> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>>(ffi::_nativeCallbackFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>(#C2, null), null, true));
core::print([@vm.direct-call.metadata=dart.ffi::_NativeCallableBase.nativeFunction] [@vm.inferred-type.metadata=dart.ffi::Pointer] callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>>});
[@vm.direct-call.metadata=dart.ffi::_NativeCallableIsolateLocal.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableIsolateLocalPointerClosure() → void {
[@vm.inferred-type.metadata=dart.core::_Smi (value: 123)] core::int j = 123;
function closure(core::int i) → ffi::Pointer<ffi::NativeType>
return ffi::Pointer::fromAddress<ffi::NativeType>([@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::Pointer<ffi::NativeType>> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>>(ffi::_nativeIsolateLocalCallbackFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>(null), closure, true));
core::print([@vm.direct-call.metadata=dart.ffi::_NativeCallableBase.nativeFunction] [@vm.inferred-type.metadata=dart.ffi::Pointer] callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>>});
[@vm.direct-call.metadata=dart.ffi::_NativeCallableIsolateLocal.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
}
[@vm.unboxing-info.metadata=(b)->i]static method negateInt(core::int i) → core::int
return [@vm.direct-call.metadata=dart.core::_IntegerImplementation.unary-] [@vm.inferred-type.metadata=int (skip check)] i.{core::int::unary-}(){() → core::int};
static method testNativeCallableIsolateLocalInt() → void {
final ffi::NativeCallable<(ffi::Int32) → ffi::Int> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Int>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Int>>(ffi::_nativeCallbackFunction<(ffi::Int32) → ffi::Int>(#C3, 123), null, true));
core::print([@vm.direct-call.metadata=dart.ffi::_NativeCallableBase.nativeFunction] [@vm.inferred-type.metadata=dart.ffi::Pointer] callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Int>>});
[@vm.direct-call.metadata=dart.ffi::_NativeCallableIsolateLocal.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableIsolateLocalIntClosure() → void {
[@vm.inferred-type.metadata=dart.core::_Smi (value: 123)] core::int j = 123;
function closure(core::int i) → core::int
return [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=!? (skip check)] i.{core::num::+}(j){(core::num) → core::int};
final ffi::NativeCallable<(ffi::Int32) → ffi::Int> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Int>([@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Int>>(ffi::_nativeIsolateLocalCallbackFunction<(ffi::Int32) → ffi::Int>(123), closure, true));
core::print([@vm.direct-call.metadata=dart.ffi::_NativeCallableBase.nativeFunction] [@vm.inferred-type.metadata=dart.ffi::Pointer] callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Int>>});
[@vm.direct-call.metadata=dart.ffi::_NativeCallableIsolateLocal.close] [@vm.inferred-type.metadata=!? (skip check)] callback.{ffi::NativeCallable::close}(){() → void};
}
constants {
#C1 = static-tearoff self::printInt
#C2 = static-tearoff self::intToPointer
#C3 = static-tearoff self::negateInt
}

View file

@ -9,15 +9,21 @@ import "dart:ffi";
static method main() → void {
self::testNativeCallableListener();
self::testNativeCallableListenerClosure();
self::testNativeCallableIsolateLocalVoid();
self::testNativeCallableIsolateLocalVoidClosure();
self::testNativeCallableIsolateLocalPointer();
self::testNativeCallableIsolateLocalPointerClosure();
self::testNativeCallableIsolateLocalInt();
self::testNativeCallableIsolateLocalIntClosure();
}
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
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t1 = new ffi::_NativeCallableListener::•<(ffi::Int32) → ffi::Void>((final core::List<dynamic> args) → void
#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});
[@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] #t1.{ffi::_NativeCallableBase::_pointer} = ffi::_createNativeCallableListener<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), #t1.{ffi::_NativeCallableListener::_port}{iso::RawReceivePort});
} =>#t1;
core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>});
callback.{ffi::NativeCallable::close}(){() → void};
@ -27,14 +33,59 @@ static method testNativeCallableListenerClosure() → void {
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
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t2 = new ffi::_NativeCallableListener::•<(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});
[@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] #t2.{ffi::_NativeCallableBase::_pointer} = ffi::_createNativeCallableListener<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), #t2.{ffi::_NativeCallableListener::_port}{iso::RawReceivePort});
} =>#t2;
core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>});
callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableIsolateLocalVoid() → void {
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Void>(ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeCallbackFunction<(ffi::Int32) → ffi::Void>(#C1, null), null, true));
core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>});
callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableIsolateLocalVoidClosure() → 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 = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Void>(ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeIsolateLocalCallbackFunction<(ffi::Int32) → ffi::Void>(null), closure, true));
core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>});
callback.{ffi::NativeCallable::close}(){() → void};
}
static method intToPointer(core::int i) → ffi::Pointer<ffi::NativeType>
return ffi::Pointer::fromAddress<ffi::NativeType>(i);
static method testNativeCallableIsolateLocalPointer() → void {
final ffi::NativeCallable<(ffi::Int32) → ffi::Pointer<ffi::NativeType>> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>(ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>>(ffi::_nativeCallbackFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>(#C2, null), null, true));
core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>>});
callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableIsolateLocalPointerClosure() → void {
core::int j = 123;
function closure(core::int i) → ffi::Pointer<ffi::NativeType>
return ffi::Pointer::fromAddress<ffi::NativeType>(i.{core::num::+}(j){(core::num) → core::int});
final ffi::NativeCallable<(ffi::Int32) → ffi::Pointer<ffi::NativeType>> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>(ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>>(ffi::_nativeIsolateLocalCallbackFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>(null), closure, true));
core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Pointer<ffi::NativeType>>>});
callback.{ffi::NativeCallable::close}(){() → void};
}
static method negateInt(core::int i) → core::int
return i.{core::int::unary-}(){() → core::int};
static method testNativeCallableIsolateLocalInt() → void {
final ffi::NativeCallable<(ffi::Int32) → ffi::Int> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Int>(ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Int>>(ffi::_nativeCallbackFunction<(ffi::Int32) → ffi::Int>(#C3, 123), null, true));
core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Int>>});
callback.{ffi::NativeCallable::close}(){() → void};
}
static method testNativeCallableIsolateLocalIntClosure() → void {
core::int j = 123;
function closure(core::int i) → core::int
return i.{core::num::+}(j){(core::num) → core::int};
final ffi::NativeCallable<(ffi::Int32) → ffi::Int> callback = new ffi::_NativeCallableIsolateLocal::•<(ffi::Int32) → ffi::Int>(ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(ffi::Int32) → ffi::Int>>(ffi::_nativeIsolateLocalCallbackFunction<(ffi::Int32) → ffi::Int>(123), closure, true));
core::print(callback.{ffi::NativeCallable::nativeFunction}{ffi::Pointer<ffi::NativeFunction<(ffi::Int32) → ffi::Int>>});
callback.{ffi::NativeCallable::close}(){() → void};
}
constants {
#C1 = static-tearoff self::printInt
#C2 = static-tearoff self::intToPointer
#C3 = static-tearoff self::negateInt
}

View file

@ -51,8 +51,8 @@ final class Struct12 extends ffi::Struct {
: super ffi::Struct::_fromTypedDataBase(#typedDataBase)
;
}
[@vm.inferred-type.metadata=dart.ffi::Pointer]static final field ffi::Pointer<ffi::NativeFunction<(self::Struct3) → ffi::Int32>> _#ffiCallback0 = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_pointerFromFunction<ffi::NativeFunction<(self::Struct3) → ffi::Int32>>(ffi::_nativeCallbackFunction<(self::Struct3) → ffi::Int32>(#C16, 0))/*isLegacy*/;
[@vm.inferred-type.metadata=dart.ffi::Pointer]static final field ffi::Pointer<ffi::NativeFunction<() → self::Struct7>> _#ffiCallback1 = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_pointerFromFunction<ffi::NativeFunction<() → self::Struct7>>(ffi::_nativeCallbackFunction<() → self::Struct7>(#C17, null))/*isLegacy*/;
[@vm.inferred-type.metadata=dart.ffi::Pointer]static final field ffi::Pointer<ffi::NativeFunction<(self::Struct3) → ffi::Int32>> _#ffiCallback0 = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<(self::Struct3) → ffi::Int32>>(ffi::_nativeCallbackFunction<(self::Struct3) → ffi::Int32>(#C16, 0), null, false)/*isLegacy*/;
[@vm.inferred-type.metadata=dart.ffi::Pointer]static final field ffi::Pointer<ffi::NativeFunction<() → self::Struct7>> _#ffiCallback1 = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableIsolateLocal<ffi::NativeFunction<() → self::Struct7>>(ffi::_nativeCallbackFunction<() → self::Struct7>(#C17, null), null, false)/*isLegacy*/;
static method main() → void {
self::testLookupFunctionReturn();
self::testLookupFunctionArgument();

View file

@ -1231,4 +1231,13 @@ DART_EXPORT void CallFunctionOnNewThreadNonBlocking(int64_t response_id,
thread.detach();
}
////////////////////////////////////////////////////////////////////////////////
// Tests for isolate local callbacks.
DART_EXPORT void CallTwoIntFunction(int32_t (*fn)(int32_t, int32_t),
int32_t a,
int32_t b) {
fn(a, b);
}
} // namespace dart

View file

@ -38,25 +38,38 @@ DEFINE_NATIVE_ENTRY(Ffi_asFunctionInternal, 2, 2) {
UNREACHABLE();
}
DEFINE_NATIVE_ENTRY(Ffi_pointerFromFunction, 1, 1) {
const auto& function = Function::CheckedHandle(zone, arguments->NativeArg0());
return Pointer::New(isolate->CreateSyncFfiCallback(zone, function));
}
DEFINE_NATIVE_ENTRY(Ffi_pointerAsyncFromFunction, 1, 2) {
const auto& function = Function::CheckedHandle(zone, arguments->NativeArg0());
DEFINE_NATIVE_ENTRY(Ffi_createNativeCallableListener, 1, 2) {
const auto& send_function =
Function::CheckedHandle(zone, arguments->NativeArg0());
const auto& port =
ReceivePort::CheckedHandle(zone, arguments->NativeArgAt(1));
return Pointer::New(
isolate->CreateAsyncFfiCallback(zone, function, port.Id()));
isolate->CreateAsyncFfiCallback(zone, send_function, port.Id()));
}
DEFINE_NATIVE_ENTRY(Ffi_deleteAsyncFunctionPointer, 1, 1) {
DEFINE_NATIVE_ENTRY(Ffi_createNativeCallableIsolateLocal, 1, 3) {
const auto& trampoline =
Function::CheckedHandle(zone, arguments->NativeArg0());
const auto& target = Closure::CheckedHandle(zone, arguments->NativeArgAt(1));
const bool keep_isolate_alive =
Bool::CheckedHandle(zone, arguments->NativeArgAt(2)).value();
return Pointer::New(isolate->CreateIsolateLocalFfiCallback(
zone, trampoline, target, keep_isolate_alive));
}
DEFINE_NATIVE_ENTRY(Ffi_deleteNativeCallable, 1, 1) {
const auto& pointer = Pointer::CheckedHandle(zone, arguments->NativeArg0());
isolate->DeleteFfiCallback(pointer.NativeAddress());
return Object::null();
}
DEFINE_NATIVE_ENTRY(Ffi_updateNativeCallableKeepIsolateAliveCounter, 1, 1) {
const int64_t delta =
Integer::CheckedHandle(zone, arguments->NativeArg0()).AsInt64Value();
isolate->UpdateNativeCallableKeepIsolateAliveCounter(delta);
return Object::null();
}
DEFINE_NATIVE_ENTRY(DartNativeApiFunctionPointer, 0, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(String, name_dart, arguments->NativeArgAt(0));
const char* name = name_dart.ToCString();

View file

@ -2094,7 +2094,7 @@ class FfiTrampolineDataSerializationCluster : public SerializationCluster {
AutoTraceObject(data);
WriteFromTo(data);
s->Write<int32_t>(data->untag()->callback_id_);
s->Write<uint8_t>(data->untag()->trampoline_kind_);
s->Write<uint8_t>(data->untag()->ffi_function_kind_);
}
}
@ -2123,7 +2123,7 @@ class FfiTrampolineDataDeserializationCluster : public DeserializationCluster {
FfiTrampolineData::InstanceSize());
d.ReadFromTo(data);
data->untag()->callback_id_ = d.Read<int32_t>();
data->untag()->trampoline_kind_ = d.Read<uint8_t>();
data->untag()->ffi_function_kind_ = d.Read<uint8_t>();
}
}
};

View file

@ -366,9 +366,10 @@ namespace dart {
V(VMService_AddUserTagsToStreamableSampleList, 1) \
V(VMService_RemoveUserTagsFromStreamableSampleList, 1) \
V(Ffi_asFunctionInternal, 2) \
V(Ffi_pointerFromFunction, 1) \
V(Ffi_pointerAsyncFromFunction, 2) \
V(Ffi_deleteAsyncFunctionPointer, 1) \
V(Ffi_createNativeCallableListener, 2) \
V(Ffi_createNativeCallableIsolateLocal, 3) \
V(Ffi_deleteNativeCallable, 1) \
V(Ffi_updateNativeCallableKeepIsolateAliveCounter, 1) \
V(Ffi_dl_open, 1) \
V(Ffi_dl_close, 1) \
V(Ffi_dl_lookup, 2) \

View file

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

View file

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

View file

@ -832,10 +832,10 @@ void FlowGraphSerializer::WriteTrait<const Function&>::Write(
return;
}
case UntaggedFunction::kFfiTrampoline: {
s->Write<uint8_t>(static_cast<uint8_t>(x.GetFfiTrampolineKind()));
s->Write<uint8_t>(static_cast<uint8_t>(x.GetFfiFunctionKind()));
s->Write<const FunctionType&>(
FunctionType::Handle(zone, x.FfiCSignature()));
if (x.GetFfiTrampolineKind() != FfiTrampolineKind::kCall) {
if (x.GetFfiFunctionKind() != FfiFunctionKind::kCall) {
s->Write<const Function&>(
Function::Handle(zone, x.FfiCallbackTarget()));
s->Write<const Instance&>(
@ -922,10 +922,10 @@ const Function& FlowGraphDeserializer::ReadTrait<const Function&>::Read(
target.GetDynamicInvocationForwarder(name));
}
case UntaggedFunction::kFfiTrampoline: {
const FfiTrampolineKind kind =
static_cast<FfiTrampolineKind>(d->Read<uint8_t>());
const FfiFunctionKind kind =
static_cast<FfiFunctionKind>(d->Read<uint8_t>());
const FunctionType& c_signature = d->Read<const FunctionType&>();
if (kind != FfiTrampolineKind::kCall) {
if (kind != FfiFunctionKind::kCall) {
const Function& callback_target = d->Read<const Function&>();
const Instance& exceptional_return = d->Read<const Instance&>();
return Function::ZoneHandle(

View file

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

View file

@ -15,10 +15,28 @@ namespace compiler {
namespace ffi {
const String& NativeCallbackFunctionName(Thread* thread,
Zone* zone,
const Function& dart_target,
FfiFunctionKind kind) {
switch (kind) {
case FfiFunctionKind::kAsyncCallback:
return Symbols::FfiAsyncCallback();
case FfiFunctionKind::kIsolateLocalClosureCallback:
return Symbols::FfiIsolateLocalCallback();
case FfiFunctionKind::kIsolateLocalStaticCallback:
return String::Handle(
zone, Symbols::FromConcat(thread, Symbols::FfiCallback(),
String::Handle(zone, dart_target.name())));
default:
UNREACHABLE();
}
}
FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
const Function& dart_target,
const Instance& exceptional_return,
FfiTrampolineKind kind) {
FfiFunctionKind kind) {
Thread* const thread = Thread::Current();
Zone* const zone = thread->zone();
Function& function = Function::Handle(zone);
@ -29,11 +47,7 @@ FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
// 'dart:ffi' library. Note that these functions will never be invoked by
// Dart, so they may have duplicate names.
const auto& name =
kind == FfiTrampolineKind::kSyncCallback
? String::Handle(zone, Symbols::FromConcat(
thread, Symbols::FfiCallback(),
String::Handle(zone, dart_target.name())))
: Symbols::FfiAsyncCallback();
NativeCallbackFunctionName(thread, zone, dart_target, kind);
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());
@ -50,7 +64,7 @@ FunctionPtr NativeCallbackFunction(const FunctionType& c_signature,
// the body.
function.SetFfiCSignature(c_signature);
function.SetFfiCallbackTarget(dart_target);
function.SetFfiTrampolineKind(kind);
function.SetFfiFunctionKind(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,
FfiTrampolineKind kind);
FfiFunctionKind kind);
// Builds a mapping from `callback-id` to code object / ...
//

View file

@ -3372,9 +3372,13 @@ Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) {
case MethodRecognizer::kFfiAsFunctionInternal:
return BuildFfiAsFunctionInternal();
case MethodRecognizer::kFfiNativeCallbackFunction:
return BuildFfiNativeCallbackFunction(FfiTrampolineKind::kSyncCallback);
return BuildFfiNativeCallbackFunction(
FfiFunctionKind::kIsolateLocalStaticCallback);
case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction:
return BuildFfiNativeCallbackFunction(
FfiFunctionKind::kIsolateLocalClosureCallback);
case MethodRecognizer::kFfiNativeAsyncCallbackFunction:
return BuildFfiNativeCallbackFunction(FfiTrampolineKind::kAsyncCallback);
return BuildFfiNativeCallbackFunction(FfiFunctionKind::kAsyncCallback);
case MethodRecognizer::kFfiLoadAbiSpecificInt:
return BuildLoadAbiSpecificInt(/*at_index=*/false);
case MethodRecognizer::kFfiLoadAbiSpecificIntAtIndex:
@ -6239,20 +6243,25 @@ Fragment StreamingFlowGraphBuilder::BuildFfiAsFunctionInternal() {
}
Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction(
FfiTrampolineKind kind) {
FfiFunctionKind kind) {
// The call-site must look like this (guaranteed by the FE which inserts it):
//
// FfiTrampolineKind::kSyncCallback:
// FfiFunctionKind::kIsolateLocalStaticCallback:
// _nativeCallbackFunction<NativeSignatureType>(target, exceptionalReturn)
//
// FfiTrampolineKind::kAsyncCallback:
// FfiFunctionKind::kAsyncCallback:
// _nativeAsyncCallbackFunction<NativeSignatureType>()
//
// The FE also guarantees that both arguments are constants.
// FfiFunctionKind::kIsolateLocalClosureCallback:
// _nativeIsolateLocalCallbackFunction<NativeSignatureType>(
// exceptionalReturn)
//
// The FE also guarantees that the arguments are constants.
// Target, and for kSync callbacks, the exceptional return.
const bool has_target = kind == FfiFunctionKind::kIsolateLocalStaticCallback;
const bool has_exceptional_return = kind != FfiFunctionKind::kAsyncCallback;
const intptr_t expected_argc =
kind == FfiTrampolineKind::kSyncCallback ? 2 : 0;
static_cast<int>(has_target) + static_cast<int>(has_exceptional_return);
const intptr_t argc = ReadUInt(); // Read argument count.
ASSERT(argc == expected_argc);
@ -6273,8 +6282,9 @@ Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction(
// Read target expression and extract the target function.
Function& target = Function::Handle(Z, Function::null());
Instance& exceptional_return = Instance::ZoneHandle(Z, Instance::null());
if (kind == FfiTrampolineKind::kSyncCallback) {
// Build first positional argument (target).
if (has_target) {
// Build target argument.
code += BuildExpression();
Definition* target_def = B->Peek();
ASSERT(target_def->IsConstant());
@ -6285,8 +6295,10 @@ Fragment StreamingFlowGraphBuilder::BuildFfiNativeCallbackFunction(
ASSERT(!target.IsNull() && target.IsImplicitClosureFunction());
target = target.parent_function();
code += Drop();
}
// Build second positional argument (exceptionalReturn).
if (has_exceptional_return) {
// Build exceptionalReturn argument.
code += BuildExpression();
Definition* exceptional_return_def = B->Peek();
ASSERT(exceptional_return_def->IsConstant());

View file

@ -394,7 +394,7 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
// Build FG for '_nativeCallbackFunction'. Reads an Arguments from the
// Kernel buffer and pushes the resulting Function object.
Fragment BuildFfiNativeCallbackFunction(FfiTrampolineKind kind);
Fragment BuildFfiNativeCallbackFunction(FfiFunctionKind kind);
// Piece of a StringConcatenation.
// Represents either a StringLiteral, or a Reader offset to the expression.

View file

@ -984,6 +984,7 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
case MethodRecognizer::kFfiLoadPointer:
case MethodRecognizer::kFfiNativeCallbackFunction:
case MethodRecognizer::kFfiNativeAsyncCallbackFunction:
case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction:
case MethodRecognizer::kFfiStoreInt8:
case MethodRecognizer::kFfiStoreInt16:
case MethodRecognizer::kFfiStoreInt32:
@ -1267,7 +1268,8 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
body += IntConstant(static_cast<int64_t>(compiler::ffi::TargetAbi()));
break;
case MethodRecognizer::kFfiNativeCallbackFunction:
case MethodRecognizer::kFfiNativeAsyncCallbackFunction: {
case MethodRecognizer::kFfiNativeAsyncCallbackFunction:
case MethodRecognizer::kFfiNativeIsolateLocalCallbackFunction: {
const auto& error = String::ZoneHandle(
Z, Symbols::New(thread_,
"This function should be handled on call site."));
@ -4739,12 +4741,13 @@ Fragment FlowGraphBuilder::FfiConvertPrimitiveToNative(
FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline(
const Function& function) {
switch (function.GetFfiTrampolineKind()) {
case FfiTrampolineKind::kSyncCallback:
switch (function.GetFfiFunctionKind()) {
case FfiFunctionKind::kIsolateLocalStaticCallback:
case FfiFunctionKind::kIsolateLocalClosureCallback:
return BuildGraphOfSyncFfiCallback(function);
case FfiTrampolineKind::kAsyncCallback:
case FfiFunctionKind::kAsyncCallback:
return BuildGraphOfAsyncFfiCallback(function);
case FfiTrampolineKind::kCall:
case FfiFunctionKind::kCall:
return BuildGraphOfFfiNative(function);
}
UNREACHABLE();
@ -4966,6 +4969,8 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfSyncFfiCallback(
RELEASE_ASSERT(error == nullptr);
RELEASE_ASSERT(marshaller_ptr != nullptr);
const auto& marshaller = *marshaller_ptr;
const bool is_closure = function.GetFfiFunctionKind() ==
FfiFunctionKind::kIsolateLocalClosureCallback;
graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
@ -4985,19 +4990,46 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfSyncFfiCallback(
Fragment body = TryCatch(try_handler_index);
++try_depth_;
LocalVariable* closure = nullptr;
if (is_closure) {
// Load and unwrap closure persistent handle.
body += LoadThread();
body +=
LoadUntagged(compiler::target::Thread::unboxed_runtime_arg_offset());
body += RawLoadField(compiler::target::PersistentHandle::ptr_offset());
closure = MakeTemporary();
}
// Box and push the arguments.
for (intptr_t i = 0; i < marshaller.num_args(); i++) {
body += LoadNativeArg(marshaller, i);
}
// Call the target.
//
// TODO(36748): Determine the hot-reload semantics of callbacks and update the
// rebind-rule accordingly.
body += StaticCall(TokenPosition::kNoSource,
Function::ZoneHandle(Z, function.FfiCallbackTarget()),
marshaller.num_args(), Array::empty_array(),
ICData::kNoRebind);
if (is_closure) {
// Call the target. The +1 in the argument count is because the closure
// itself is the first argument.
const intptr_t argument_count = marshaller.num_args() + 1;
body += LoadLocal(closure);
if (!FLAG_precompiled_mode) {
// The ClosureCallInstr() takes one explicit input (apart from arguments).
// It uses it to find the target address (in AOT from
// Closure::entry_point, in JIT from Closure::function_::entry_point).
body += LoadNativeField(Slot::Closure_function());
}
body +=
ClosureCall(Function::null_function(), TokenPosition::kNoSource,
/*type_args_len=*/0, argument_count, Array::null_array());
} else {
// Call the target.
//
// TODO(36748): Determine the hot-reload semantics of callbacks and update
// the rebind-rule accordingly.
body += StaticCall(TokenPosition::kNoSource,
Function::ZoneHandle(Z, function.FfiCallbackTarget()),
marshaller.num_args(), Array::empty_array(),
ICData::kNoRebind);
}
if (marshaller.IsVoid(compiler::ffi::kResultIndex)) {
body += Drop();
body += IntConstant(0);

View file

@ -395,7 +395,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
case UntaggedFunction::kFfiTrampoline: {
needs_expr_temp_ = true;
// Callbacks and calls with handles need try/catch variables.
if ((function.GetFfiTrampolineKind() != FfiTrampolineKind::kCall ||
if ((function.GetFfiFunctionKind() != FfiFunctionKind::kCall ||
function.FfiCSignatureContainsHandles())) {
++depth_.try_;
AddTryVariables();

View file

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

View file

@ -271,6 +271,8 @@ namespace dart {
V(::, _nativeCallbackFunction, FfiNativeCallbackFunction, 0x3fe722bc) \
V(::, _nativeAsyncCallbackFunction, FfiNativeAsyncCallbackFunction, \
0xbec4b7b9) \
V(::, _nativeIsolateLocalCallbackFunction, \
FfiNativeIsolateLocalCallbackFunction, 0x03e1a51f) \
V(::, _nativeEffect, NativeEffect, 0x536f42b1) \
V(::, _loadAbiSpecificInt, FfiLoadAbiSpecificInt, 0x77f96053) \
V(::, _loadAbiSpecificIntAtIndex, FfiLoadAbiSpecificIntAtIndex, 0x6a964295) \

View file

@ -710,6 +710,11 @@ class LocalHandle : public AllStatic {
static word InstanceSize();
};
class PersistentHandle : public AllStatic {
public:
static word ptr_offset();
};
class Pointer : public AllStatic {
public:
static word type_arguments_offset();

View file

@ -285,6 +285,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0xc;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x8;
@ -999,6 +1000,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -1713,6 +1715,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0xc;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x8;
@ -2426,6 +2429,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -3144,6 +3148,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -3859,6 +3864,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -4574,6 +4580,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0xc;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x8;
@ -5289,6 +5296,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -5996,6 +6004,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0xc;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x8;
@ -6702,6 +6711,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -7408,6 +7418,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0xc;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x8;
@ -8113,6 +8124,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -8823,6 +8835,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -9530,6 +9543,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -10237,6 +10251,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0xc;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x8;
@ -10944,6 +10959,7 @@ static constexpr dart::compiler::target::word
static constexpr dart::compiler::target::word
ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word OneByteString_data_offset = 0x10;
static constexpr dart::compiler::target::word PersistentHandle_ptr_offset = 0x0;
static constexpr dart::compiler::target::word PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word Pointer_type_arguments_offset =
0x10;
@ -11685,6 +11701,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0xc;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x8;
@ -12474,6 +12492,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -13270,6 +13290,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -14062,6 +14084,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -14854,6 +14878,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -15648,6 +15674,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0xc;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x8;
@ -16438,6 +16466,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -17220,6 +17250,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0xc;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x8;
@ -18000,6 +18032,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -18787,6 +18821,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -19570,6 +19606,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -20353,6 +20391,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;
@ -21138,6 +21178,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x238;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0xc;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x4;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x8;
@ -21919,6 +21961,8 @@ static constexpr dart::compiler::target::word
AOT_ObjectStore_suspend_state_yield_async_star_offset = 0x470;
static constexpr dart::compiler::target::word AOT_OneByteString_data_offset =
0x10;
static constexpr dart::compiler::target::word AOT_PersistentHandle_ptr_offset =
0x0;
static constexpr dart::compiler::target::word AOT_PointerBase_data_offset = 0x8;
static constexpr dart::compiler::target::word
AOT_Pointer_type_arguments_offset = 0x10;

View file

@ -211,6 +211,7 @@
FIELD(ObjectStore, suspend_state_suspend_sync_star_at_start_offset) \
FIELD(ObjectStore, suspend_state_yield_async_star_offset) \
FIELD(OneByteString, data_offset) \
FIELD(PersistentHandle, ptr_offset) \
FIELD(PointerBase, data_offset) \
FIELD(Pointer, type_arguments_offset) \
FIELD(ReceivePort, send_port_offset) \

View file

@ -5,6 +5,7 @@
#include "vm/ffi_callback_metadata.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/dart_api_state.h"
#include "vm/flag_list.h"
#include "vm/object.h"
#include "vm/runtime_entry.h"
@ -157,7 +158,7 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateMetadataEntry(
Isolate* target_isolate,
TrampolineType trampoline_type,
uword target_entry_point,
Dart_Port send_port,
uint64_t context,
Metadata** list_head) {
MutexLocker locker(&lock_);
EnsureFreeListNotEmptyLocked();
@ -174,7 +175,7 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateMetadataEntry(
next_entry->list_prev_ = entry;
}
*entry = Metadata(target_isolate, trampoline_type, target_entry_point,
send_port, nullptr, next_entry);
context, nullptr, next_entry);
*list_head = entry;
return TrampolineOfMetadata(entry);
}
@ -190,15 +191,28 @@ void FfiCallbackMetadata::AddToFreeListLocked(Metadata* entry) {
free_list_tail_->free_list_next_ = entry;
free_list_tail_ = entry;
}
entry->context_ = 0;
entry->target_isolate_ = nullptr;
entry->free_list_next_ = nullptr;
}
void FfiCallbackMetadata::DeleteCallbackLocked(Metadata* entry) {
ASSERT(lock_.IsOwnedByCurrentThread());
if (entry->trampoline_type_ != TrampolineType::kAsync &&
entry->context_ != 0) {
ASSERT(entry->target_isolate_ != nullptr);
auto* api_state = entry->target_isolate_->group()->api_state();
ASSERT(api_state != nullptr);
api_state->FreePersistentHandle(entry->closure_handle());
}
AddToFreeListLocked(entry);
}
void FfiCallbackMetadata::DeleteAllCallbacks(Metadata** list_head) {
MutexLocker locker(&lock_);
for (Metadata* entry = *list_head; entry != nullptr;) {
Metadata* next = entry->list_next();
AddToFreeListLocked(entry);
DeleteCallbackLocked(entry);
entry = next;
}
*list_head = nullptr;
@ -220,7 +234,7 @@ void FfiCallbackMetadata::DeleteCallback(Trampoline trampoline,
if (next != nullptr) {
next->list_prev_ = prev;
}
AddToFreeListLocked(entry);
DeleteCallbackLocked(entry);
}
uword FfiCallbackMetadata::GetEntryPoint(Zone* zone, const Function& function) {
@ -231,12 +245,44 @@ uword FfiCallbackMetadata::GetEntryPoint(Zone* zone, const Function& function) {
return code.EntryPoint();
}
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateSyncFfiCallback(
PersistentHandle* FfiCallbackMetadata::CreatePersistentHandle(
Isolate* isolate,
const Closure& closure) {
auto* api_state = isolate->group()->api_state();
ASSERT(api_state != nullptr);
auto* handle = api_state->AllocatePersistentHandle();
handle->set_ptr(closure);
return handle;
}
FfiCallbackMetadata::Trampoline
FfiCallbackMetadata::CreateIsolateLocalFfiCallback(Isolate* isolate,
Zone* zone,
const Function& function,
const Closure& closure,
Metadata** list_head) {
if (closure.IsNull()) {
// If the closure is null, it means the target is a static function, so is
// baked into the trampoline and is an ordinary sync callback.
ASSERT(function.GetFfiFunctionKind() ==
FfiFunctionKind::kIsolateLocalStaticCallback);
return CreateSyncFfiCallbackImpl(isolate, zone, function, nullptr,
list_head);
} else {
ASSERT(function.GetFfiFunctionKind() ==
FfiFunctionKind::kIsolateLocalClosureCallback);
return CreateSyncFfiCallbackImpl(isolate, zone, function,
CreatePersistentHandle(isolate, closure),
list_head);
}
}
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateSyncFfiCallbackImpl(
Isolate* isolate,
Zone* zone,
const Function& function,
PersistentHandle* closure,
Metadata** list_head) {
ASSERT(function.GetFfiTrampolineKind() == FfiTrampolineKind::kSyncCallback);
TrampolineType trampoline_type = TrampolineType::kSync;
#if defined(TARGET_ARCH_IA32)
@ -252,20 +298,20 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateSyncFfiCallback(
#endif
return CreateMetadataEntry(isolate, trampoline_type,
GetEntryPoint(zone, function), ILLEGAL_PORT,
list_head);
GetEntryPoint(zone, function),
reinterpret_cast<uint64_t>(closure), list_head);
}
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateAsyncFfiCallback(
Isolate* isolate,
Zone* zone,
const Function& function,
const Function& send_function,
Dart_Port send_port,
Metadata** list_head) {
ASSERT(function.GetFfiTrampolineKind() == FfiTrampolineKind::kAsyncCallback);
ASSERT(send_function.GetFfiFunctionKind() == FfiFunctionKind::kAsyncCallback);
return CreateMetadataEntry(isolate, TrampolineType::kAsync,
GetEntryPoint(zone, function), send_port,
list_head);
GetEntryPoint(zone, send_function),
static_cast<uint64_t>(send_port), list_head);
}
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::TrampolineOfMetadata(

View file

@ -12,6 +12,8 @@
namespace dart {
class PersistentHandle;
// Stores metadata related to FFI callbacks (Dart functions that are assigned a
// function pointer that can be invoked by native code). This is essentially a
// map from trampoline pointer to Metadata, with some logic to assign and memory
@ -37,10 +39,8 @@ class FfiCallbackMetadata {
enum class TrampolineType : uint8_t {
kSync = 0,
kAsync = 1,
#if defined(TARGET_ARCH_IA32)
kSyncStackDelta4 = 2,
#endif
kSyncStackDelta4 = 1, // Only used by TARGET_ARCH_IA32
kAsync = 2,
};
enum RuntimeFunctions {
@ -55,12 +55,6 @@ class FfiCallbackMetadata {
// Returns the FfiCallbackMetadata singleton.
static FfiCallbackMetadata* Instance();
// Creates a sync callback trampoline for the given function.
Trampoline CreateSyncFfiCallback(Isolate* isolate,
Zone* zone,
const Function& function,
Metadata** list_head);
// Creates an async callback trampoline for the given function and associates
// it with the send_port.
Trampoline CreateAsyncFfiCallback(Isolate* isolate,
@ -69,6 +63,13 @@ class FfiCallbackMetadata {
Dart_Port send_port,
Metadata** list_head);
// Creates an isolate local callback trampoline for the given function.
Trampoline CreateIsolateLocalFfiCallback(Isolate* isolate,
Zone* zone,
const Function& function,
const Closure& closure,
Metadata** list_head);
// Deletes a single trampoline.
void DeleteCallback(Trampoline trampoline, Metadata** list_head);
@ -87,7 +88,9 @@ class FfiCallbackMetadata {
// safe because Instructions objects are never moved by the GC.
uword target_entry_point_;
Dart_Port send_port_;
// For async callbacks, this is the send port. For sync callbacks this
// is a persistent handle to the callback's closure, or null.
uint64_t context_;
// Links in the Isolate's list of callbacks.
Metadata* list_prev_;
@ -101,13 +104,13 @@ class FfiCallbackMetadata {
Metadata(Isolate* target_isolate,
TrampolineType trampoline_type,
uword target_entry_point,
Dart_Port send_port,
uint64_t context,
Metadata* list_prev,
Metadata* list_next)
: target_isolate_(target_isolate),
trampoline_type_(trampoline_type),
target_entry_point_(target_entry_point),
send_port_(send_port),
context_(context),
list_prev_(list_prev),
list_next_(list_next) {}
@ -119,7 +122,7 @@ class FfiCallbackMetadata {
return target_isolate_ == other.target_isolate_ &&
trampoline_type_ == other.trampoline_type_ &&
target_entry_point_ == other.target_entry_point_ &&
send_port_ == other.send_port_;
context_ == other.context_;
}
// Whether the callback is still alive.
@ -139,10 +142,27 @@ class FfiCallbackMetadata {
return target_entry_point_;
}
// The persistent handle to the closure that the NativeCallable.isolateLocal
// is wrapping.
PersistentHandle* closure_handle() const {
ASSERT(IsLive());
ASSERT(trampoline_type_ == TrampolineType::kSync ||
trampoline_type_ == TrampolineType::kSyncStackDelta4);
return reinterpret_cast<PersistentHandle*>(context_);
}
// For async callbacks, this is the send port. For sync callbacks this is a
// persistent handle to the callback's closure, or null.
uint64_t context() const {
ASSERT(IsLive());
return context_;
}
// The send port that the async callback will send a message to.
Dart_Port send_port() const {
ASSERT(IsLive());
return send_port_;
ASSERT(trampoline_type_ == TrampolineType::kAsync);
return static_cast<Dart_Port>(context_);
}
// To efficiently delete all the callbacks for a isolate, they are stored in
@ -265,16 +285,24 @@ class FfiCallbackMetadata {
~FfiCallbackMetadata();
void EnsureStubPageLocked();
void AddToFreeListLocked(Metadata* entry);
void DeleteCallbackLocked(Metadata* entry);
void FillRuntimeFunction(VirtualMemory* page, uword index, void* function);
VirtualMemory* AllocateTrampolinePage();
void EnsureFreeListNotEmptyLocked();
Trampoline CreateMetadataEntry(Isolate* target_isolate,
TrampolineType trampoline_type,
uword target_entry_point,
Dart_Port send_port,
uint64_t context,
Metadata** list_head);
Trampoline CreateSyncFfiCallbackImpl(Isolate* isolate,
Zone* zone,
const Function& function,
PersistentHandle* closure,
Metadata** list_head);
Trampoline TryAllocateFromFreeListLocked();
static uword GetEntryPoint(Zone* zone, const Function& function);
static PersistentHandle* CreatePersistentHandle(Isolate* isolate,
const Closure& closure);
static FfiCallbackMetadata* singleton_;

View file

@ -22,7 +22,7 @@
namespace dart {
FunctionPtr CreateTestFunction(FfiTrampolineKind kind) {
FunctionPtr CreateTestFunction(FfiFunctionKind 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 =
@ -91,12 +91,13 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) {
auto* zone = thread->zone();
const auto& func =
Function::Handle(CreateTestFunction(FfiTrampolineKind::kSyncCallback));
const auto& func = Function::Handle(
CreateTestFunction(FfiFunctionKind::kIsolateLocalStaticCallback));
const auto& code = Code::Handle(func.EnsureHasCode());
EXPECT(!code.IsNull());
tramp1 = isolate->CreateSyncFfiCallback(zone, func);
tramp1 = isolate->CreateIsolateLocalFfiCallback(
zone, func, Closure::Handle(Closure::null()), false);
EXPECT_NE(tramp1, 0u);
{
@ -105,7 +106,7 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) {
EXPECT(m1.IsLive());
EXPECT_EQ(m1.target_isolate(), isolate);
EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m1.send_port(), ILLEGAL_PORT);
EXPECT_EQ(m1.closure_handle(), nullptr);
EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
@ -116,7 +117,8 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) {
EXPECT_EQ(e1->list_next(), nullptr);
}
tramp2 = isolate->CreateSyncFfiCallback(zone, func);
tramp2 = isolate->CreateIsolateLocalFfiCallback(
zone, func, Closure::Handle(Closure::null()), false);
EXPECT_NE(tramp2, 0u);
EXPECT_NE(tramp2, tramp1);
@ -126,7 +128,7 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) {
EXPECT(m2.IsLive());
EXPECT_EQ(m2.target_isolate(), isolate);
EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m2.send_port(), ILLEGAL_PORT);
EXPECT_EQ(m2.closure_handle(), nullptr);
EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
}
@ -183,7 +185,7 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateAsyncFfiCallback) {
auto* zone = thread->zone();
const Function& func =
Function::Handle(CreateTestFunction(FfiTrampolineKind::kAsyncCallback));
Function::Handle(CreateTestFunction(FfiFunctionKind::kAsyncCallback));
const Code& code = Code::Handle(func.EnsureHasCode());
EXPECT(!code.IsNull());
@ -261,13 +263,117 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateAsyncFfiCallback) {
}
}
VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateIsolateLocalFfiCallback) {
auto* fcm = FfiCallbackMetadata::Instance();
FfiCallbackMetadata::Trampoline tramp1 = 0;
FfiCallbackMetadata::Trampoline tramp2 = 0;
{
TestIsolateScope isolate_scope;
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ASSERT(thread->isolate() == isolate_scope.isolate());
TransitionNativeToVM transition(thread);
StackZone stack_zone(thread);
HandleScope handle_scope(thread);
auto* zone = thread->zone();
const Function& func = Function::Handle(
CreateTestFunction(FfiFunctionKind::kIsolateLocalClosureCallback));
const Code& code = Code::Handle(func.EnsureHasCode());
EXPECT(!code.IsNull());
// Using a FfiFunctionKind::kSync function as a dummy closure.
const Function& closure_func = Function::Handle(
CreateTestFunction(FfiFunctionKind::kIsolateLocalStaticCallback));
const Context& context = Context::Handle(Context::null());
const Closure& closure1 = Closure::Handle(
Closure::New(Object::null_type_arguments(),
Object::null_type_arguments(), closure_func, context));
EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr);
tramp1 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure1, true);
EXPECT_NE(tramp1, 0u);
{
FfiCallbackMetadata::Metadata m1 =
fcm->LookupMetadataForTrampoline(tramp1);
EXPECT(m1.IsLive());
EXPECT_EQ(m1.target_isolate(), isolate);
EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m1.closure_handle()->ptr(), closure1.ptr());
EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
// head -> tramp1
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
EXPECT_EQ(e1->list_prev(), nullptr);
EXPECT_EQ(e1->list_next(), nullptr);
}
const Closure& closure2 = Closure::Handle(
Closure::New(Object::null_type_arguments(),
Object::null_type_arguments(), closure_func, context));
tramp2 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure2, true);
EXPECT_NE(tramp2, 0u);
EXPECT_NE(tramp2, tramp1);
{
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampoline(tramp2);
EXPECT(m2.IsLive());
EXPECT_EQ(m2.target_isolate(), isolate);
EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m2.closure_handle()->ptr(), closure2.ptr());
EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
}
{
// head -> tramp2 -> tramp1
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
auto* e2 = fcm->MetadataOfTrampoline(tramp2);
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
EXPECT_EQ(e2->list_prev(), nullptr);
EXPECT_EQ(e2->list_next(), e1);
EXPECT_EQ(e1->list_prev(), e2);
EXPECT_EQ(e1->list_next(), nullptr);
}
{
isolate->DeleteFfiCallback(tramp2);
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampoline(tramp2);
EXPECT(!m2.IsLive());
// head -> tramp1
auto* e1 = fcm->MetadataOfTrampoline(tramp1);
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
EXPECT_EQ(e1->list_prev(), nullptr);
EXPECT_EQ(e1->list_next(), nullptr);
}
}
{
// Isolate has shut down, so all callbacks should be deleted.
FfiCallbackMetadata::Metadata m1 = fcm->LookupMetadataForTrampoline(tramp1);
EXPECT(!m1.IsLive());
FfiCallbackMetadata::Metadata m2 = fcm->LookupMetadataForTrampoline(tramp2);
EXPECT(!m2.IsLive());
}
}
ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_TrampolineRecycling) {
Isolate* isolate = thread->isolate();
auto* zone = thread->zone();
auto* fcm = FfiCallbackMetadata::Instance();
const Function& func =
Function::Handle(CreateTestFunction(FfiTrampolineKind::kAsyncCallback));
Function::Handle(CreateTestFunction(FfiFunctionKind::kAsyncCallback));
const Code& code = Code::Handle(func.EnsureHasCode());
EXPECT(!code.IsNull());
@ -333,15 +439,16 @@ VM_UNIT_TEST_CASE(FfiCallbackMetadata_DeleteTrampolines) {
std::unordered_set<FfiCallbackMetadata::Trampoline> tramps;
FfiCallbackMetadata::Metadata* list_head = nullptr;
const auto& sync_func =
Function::Handle(CreateTestFunction(FfiTrampolineKind::kSyncCallback));
const auto& sync_func = Function::Handle(
CreateTestFunction(FfiFunctionKind::kIsolateLocalStaticCallback));
const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
EXPECT(!sync_code.IsNull());
// Create some callbacks.
for (int itr = 0; itr < kCreations; ++itr) {
tramps.insert(fcm->CreateSyncFfiCallback(isolate, thread->zone(), sync_func,
&list_head));
tramps.insert(fcm->CreateIsolateLocalFfiCallback(
isolate, thread->zone(), sync_func, Closure::Handle(Closure::null()),
&list_head));
}
// Delete some of the callbacks.
@ -414,11 +521,11 @@ static void RunBigRandomMultithreadedTest(uint64_t seed) {
FfiCallbackMetadata::Metadata* list_head = nullptr;
const Function& async_func =
Function::Handle(CreateTestFunction(FfiTrampolineKind::kAsyncCallback));
Function::Handle(CreateTestFunction(FfiFunctionKind::kAsyncCallback));
const Code& async_code = Code::Handle(async_func.EnsureHasCode());
EXPECT(!async_code.IsNull());
const Function& sync_func =
Function::Handle(CreateTestFunction(FfiTrampolineKind::kSyncCallback));
const Function& sync_func = Function::Handle(
CreateTestFunction(FfiFunctionKind::kIsolateLocalStaticCallback));
const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
EXPECT(!sync_code.IsNull());
@ -452,8 +559,9 @@ static void RunBigRandomMultithreadedTest(uint64_t seed) {
if ((random.NextUInt32() % 2) == 0) {
// 50% chance of creating a sync callback.
tramp.port = ILLEGAL_PORT;
tramp.tramp = fcm->CreateSyncFfiCallback(isolate, thread->zone(),
sync_func, &list_head);
tramp.tramp = fcm->CreateIsolateLocalFfiCallback(
isolate, thread->zone(), sync_func,
Closure::Handle(Closure::null()), &list_head);
} else {
// 50% chance of creating an async callback.
tramp.port = PortMap::CreatePort(new FakeMessageHandler());
@ -469,11 +577,12 @@ static void RunBigRandomMultithreadedTest(uint64_t seed) {
auto metadata = fcm->LookupMetadataForTrampoline(tramp.tramp);
EXPECT(metadata.IsLive());
EXPECT_EQ(metadata.target_isolate(), isolate);
EXPECT_EQ(metadata.send_port(), tramp.port);
if (metadata.trampoline_type() ==
FfiCallbackMetadata::TrampolineType::kSync) {
EXPECT_EQ(metadata.closure_handle(), nullptr);
EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint());
} else {
EXPECT_EQ(metadata.send_port(), tramp.port);
EXPECT_EQ(metadata.target_entry_point(), async_code.EntryPoint());
}
}

View file

@ -1029,8 +1029,9 @@ class IsolateMessageHandler : public MessageHandler {
virtual bool KeepAliveLocked() {
// If the message handler was asked to shutdown we shut down.
if (!MessageHandler::KeepAliveLocked()) return false;
// Otherwise we only stay alive as long as there's active receive ports.
return isolate_->HasLivePorts();
// Otherwise we only stay alive as long as there's active receive ports, or
// there are FFI callbacks keeping the isolate alive.
return isolate_->HasLivePorts() || isolate_->HasOpenNativeCallables();
}
private:
@ -3617,13 +3618,6 @@ void Isolate::WaitForOutstandingSpawns() {
}
}
FfiCallbackMetadata::Trampoline Isolate::CreateSyncFfiCallback(
Zone* zone,
const Function& function) {
return FfiCallbackMetadata::Instance()->CreateSyncFfiCallback(
this, zone, function, &ffi_callback_list_head_);
}
FfiCallbackMetadata::Trampoline Isolate::CreateAsyncFfiCallback(
Zone* zone,
const Function& send_function,
@ -3632,6 +3626,18 @@ FfiCallbackMetadata::Trampoline Isolate::CreateAsyncFfiCallback(
this, zone, send_function, send_port, &ffi_callback_list_head_);
}
FfiCallbackMetadata::Trampoline Isolate::CreateIsolateLocalFfiCallback(
Zone* zone,
const Function& trampoline,
const Closure& target,
bool keep_isolate_alive) {
if (keep_isolate_alive) {
UpdateNativeCallableKeepIsolateAliveCounter(1);
}
return FfiCallbackMetadata::Instance()->CreateIsolateLocalFfiCallback(
this, zone, trampoline, target, &ffi_callback_list_head_);
}
bool Isolate::HasLivePorts() {
ASSERT(0 <= open_ports_ && 0 <= open_ports_keepalive_ &&
open_ports_keepalive_ <= open_ports_);
@ -3686,6 +3692,16 @@ void Isolate::DeleteFfiCallback(FfiCallbackMetadata::Trampoline callback) {
&ffi_callback_list_head_);
}
void Isolate::UpdateNativeCallableKeepIsolateAliveCounter(intptr_t delta) {
ffi_callback_keep_alive_counter_ += delta;
ASSERT(ffi_callback_keep_alive_counter_ >= 0);
}
bool Isolate::HasOpenNativeCallables() {
ASSERT(ffi_callback_keep_alive_counter_ >= 0);
return ffi_callback_keep_alive_counter_ > 0;
}
#if !defined(PRODUCT)
void IsolateGroup::CloneClassTableForReload() {
RELEASE_ASSERT(class_table_ == heap_walk_class_table_);

View file

@ -1241,14 +1241,18 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
deopt_context_ = value;
}
FfiCallbackMetadata::Trampoline CreateSyncFfiCallback(
Zone* zone,
const Function& function);
FfiCallbackMetadata::Trampoline CreateAsyncFfiCallback(
Zone* zone,
const Function& send_function,
Dart_Port send_port);
FfiCallbackMetadata::Trampoline CreateIsolateLocalFfiCallback(
Zone* zone,
const Function& trampoline,
const Closure& target,
bool keep_isolate_alive);
void DeleteFfiCallback(FfiCallbackMetadata::Trampoline callback);
void UpdateNativeCallableKeepIsolateAliveCounter(intptr_t delta);
bool HasOpenNativeCallables();
bool HasLivePorts();
ReceivePortPtr CreateReceivePort(const String& debug_name);
@ -1649,6 +1653,7 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
intptr_t defer_finalization_count_ = 0;
DeoptContext* deopt_context_ = nullptr;
FfiCallbackMetadata::Metadata* ffi_callback_list_head_ = nullptr;
intptr_t ffi_callback_keep_alive_counter_ = 0;
GrowableObjectArrayPtr tag_table_;

View file

@ -8429,7 +8429,7 @@ bool Function::FfiCSignatureReturnsStruct() const {
int32_t Function::FfiCallbackId() const {
ASSERT(IsFfiTrampoline());
ASSERT(GetFfiTrampolineKind() != FfiTrampolineKind::kCall);
ASSERT(GetFfiFunctionKind() != FfiFunctionKind::kCall);
const auto& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
@ -8442,7 +8442,7 @@ int32_t Function::FfiCallbackId() const {
void Function::AssignFfiCallbackId(int32_t callback_id) const {
ASSERT(IsFfiTrampoline());
ASSERT(GetFfiTrampolineKind() != FfiTrampolineKind::kCall);
ASSERT(GetFfiFunctionKind() != FfiFunctionKind::kCall);
const auto& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
@ -8494,18 +8494,18 @@ void Function::SetFfiCallbackExceptionalReturn(const Instance& value) const {
FfiTrampolineData::Cast(obj).set_callback_exceptional_return(value);
}
FfiTrampolineKind Function::GetFfiTrampolineKind() const {
FfiFunctionKind Function::GetFfiFunctionKind() const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
return FfiTrampolineData::Cast(obj).trampoline_kind();
return FfiTrampolineData::Cast(obj).ffi_function_kind();
}
void Function::SetFfiTrampolineKind(FfiTrampolineKind value) const {
void Function::SetFfiFunctionKind(FfiFunctionKind value) const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
FfiTrampolineData::Cast(obj).set_trampoline_kind(value);
FfiTrampolineData::Cast(obj).set_ffi_function_kind(value);
}
const char* Function::KindToCString(UntaggedFunction::Kind kind) {
@ -11632,8 +11632,8 @@ void FfiTrampolineData::set_callback_exceptional_return(
untag()->set_callback_exceptional_return(value.ptr());
}
void FfiTrampolineData::set_trampoline_kind(FfiTrampolineKind kind) const {
StoreNonPointer(&untag()->trampoline_kind_, static_cast<uint8_t>(kind));
void FfiTrampolineData::set_ffi_function_kind(FfiFunctionKind kind) const {
StoreNonPointer(&untag()->ffi_function_kind_, static_cast<uint8_t>(kind));
}
FfiTrampolineDataPtr FfiTrampolineData::New() {

View file

@ -2949,9 +2949,10 @@ struct NameFormattingParams {
}
};
enum class FfiTrampolineKind : uint8_t {
enum class FfiFunctionKind : uint8_t {
kCall,
kSyncCallback,
kIsolateLocalStaticCallback,
kIsolateLocalClosureCallback,
kAsyncCallback,
};
@ -3013,10 +3014,10 @@ class Function : public Object {
void SetFfiCallbackExceptionalReturn(const Instance& value) const;
// Can only be called on FFI trampolines.
FfiTrampolineKind GetFfiTrampolineKind() const;
FfiFunctionKind GetFfiFunctionKind() const;
// Can only be called on FFI trampolines.
void SetFfiTrampolineKind(FfiTrampolineKind value) const;
void SetFfiFunctionKind(FfiFunctionKind value) const;
// Return the signature of this function.
PRECOMPILER_WSR_FIELD_DECLARATION(FunctionType, signature);
@ -4355,10 +4356,10 @@ class FfiTrampolineData : public Object {
}
void set_callback_exceptional_return(const Instance& value) const;
FfiTrampolineKind trampoline_kind() const {
return static_cast<FfiTrampolineKind>(untag()->trampoline_kind_);
FfiFunctionKind ffi_function_kind() const {
return static_cast<FfiFunctionKind>(untag()->ffi_function_kind_);
}
void set_trampoline_kind(FfiTrampolineKind kind) const;
void set_ffi_function_kind(FfiFunctionKind kind) const;
int32_t callback_id() const { return untag()->callback_id_; }
void set_callback_id(int32_t value) const;

View file

@ -1506,8 +1506,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 trampoline this is. See FfiTrampolineKind.
uint8_t trampoline_kind_;
// The kind of trampoline this is. See FfiFunctionKind.
uint8_t ffi_function_kind_;
};
class UntaggedField : public UntaggedObject {

View file

@ -4094,6 +4094,8 @@ extern "C" Thread* DLRT_GetFfiCallbackMetadata(
current_thread->ExitSafepoint();
current_thread->set_unboxed_int64_runtime_arg(metadata.context());
TRACE_RUNTIME_CALL("GetFfiCallbackMetadata thread %p", current_thread);
TRACE_RUNTIME_CALL("GetFfiCallbackMetadata entry_point %p",
(void*)*out_entry_point);

View file

@ -115,11 +115,11 @@ class ObjectPointerVisitor;
V(FfiInt64, "Int64") \
V(FfiInt8, "Int8") \
V(FfiIntPtr, "IntPtr") \
V(FfiIsolateLocalCallback, "_FfiIsolateLocalCallback") \
V(FfiNativeFunction, "NativeFunction") \
V(FfiNativeType, "NativeType") \
V(FfiNativeTypes, "nativeTypes") \
V(FfiPointer, "Pointer") \
V(FfiPointerAsyncFromFunctionSendArgs, "_pointerAsyncFromFunctionSendArgs") \
V(FfiStructLayout, "_FfiStructLayout") \
V(FfiStructLayoutArray, "_FfiInlineArray") \
V(FfiTrampolineData, "FfiTrampolineData") \

View file

@ -152,22 +152,33 @@ external Float64List _asExternalTypedDataDouble(
external dynamic _nativeCallbackFunction<NS extends Function>(
Function target, Object? exceptionalReturn);
@pragma("vm:external-name", "Ffi_pointerFromFunction")
external Pointer<NS> _pointerFromFunction<NS extends NativeFunction>(
dynamic function);
@pragma("vm:recognized", "other")
@pragma("vm:external-name", "Ffi_nativeAsyncCallbackFunction")
external dynamic _nativeAsyncCallbackFunction<NS extends Function>();
@pragma("vm:external-name", "Ffi_pointerAsyncFromFunction")
external Pointer<NS> _pointerAsyncFromFunction<NS extends NativeFunction>(
@pragma("vm:external-name", "Ffi_createNativeCallableListener")
external Pointer<NS> _createNativeCallableListener<NS extends NativeFunction>(
dynamic function, RawReceivePort port);
@pragma("vm:external-name", "Ffi_deleteAsyncFunctionPointer")
external void _deleteAsyncFunctionPointer<NS extends NativeFunction>(
@pragma("vm:external-name", "Ffi_createNativeCallableIsolateLocal")
external Pointer<NS>
_createNativeCallableIsolateLocal<NS extends NativeFunction>(
dynamic trampoline, dynamic target, bool keepIsolateAlive);
@pragma("vm:external-name", "Ffi_deleteNativeCallable")
external void _deleteNativeCallable<NS extends NativeFunction>(
Pointer<NS> pointer);
@pragma("vm:external-name", "Ffi_updateNativeCallableKeepIsolateAliveCounter")
external void
_updateNativeCallableKeepIsolateAliveCounter<NS extends NativeFunction>(
int delta);
@pragma("vm:recognized", "other")
@pragma("vm:external-name", "Ffi_nativeIsolateLocalCallbackFunction")
external dynamic _nativeIsolateLocalCallbackFunction<NS extends Function>(
dynamic exceptionalReturn);
@patch
@pragma("vm:entry-point")
final class Pointer<T extends NativeType> {
@ -199,40 +210,77 @@ final class Pointer<T extends NativeType> {
Pointer<U> cast<U extends NativeType>() => Pointer.fromAddress(address);
}
@patch
final class NativeCallable<T extends Function> {
Pointer<NativeFunction<T>> _pointer = nullptr;
final RawReceivePort _port;
abstract final class _NativeCallableBase<T extends Function>
implements NativeCallable<T> {
Pointer<NativeFunction<T>> _pointer;
@patch
NativeCallable.listener(@DartRepresentationOf("T") Function callback)
: _port = RawReceivePort()..close() {
throw UnsupportedError("NativeCallable cannot be constructed dynamically.");
}
_NativeCallableBase(this._pointer);
NativeCallable._(void Function(List) handler, String portDebugName)
: _port = RawReceivePort(
Zone.current.bindUnaryCallbackGuarded(handler), portDebugName);
@patch
@override
Pointer<NativeFunction<T>> get nativeFunction => _pointer;
@patch
@override
void close() {
if (_pointer == nullptr) {
throw StateError("NativeCallable is already closed.");
}
_port.close();
_deleteAsyncFunctionPointer(_pointer);
_deleteNativeCallable(_pointer);
_pointer = nullptr;
}
}
@patch
final class _NativeCallableIsolateLocal<T extends Function>
extends _NativeCallableBase<T> {
bool _keepIsolateAlive = true;
_NativeCallableIsolateLocal(super._pointer);
@override
void close() {
super.close();
_setKeepIsolateAlive(false);
}
@override
void set keepIsolateAlive(bool value) {
if (_pointer == nullptr) {
throw StateError("NativeCallable is already closed.");
}
_setKeepIsolateAlive(value);
}
void _setKeepIsolateAlive(bool value) {
if (_keepIsolateAlive != value) {
_keepIsolateAlive = value;
_updateNativeCallableKeepIsolateAliveCounter(value ? 1 : -1);
}
}
@override
bool get keepIsolateAlive => _keepIsolateAlive;
}
final class _NativeCallableListener<T extends Function>
extends _NativeCallableBase<T> {
final RawReceivePort _port;
_NativeCallableListener(void Function(List) handler, String portDebugName)
: _port = RawReceivePort(
Zone.current.bindUnaryCallbackGuarded(handler), portDebugName),
super(nullptr);
@override
void close() {
super.close();
_port.close();
}
@override
void set keepIsolateAlive(bool value) {
_port.keepIsolateAlive = value;
}
@patch
@override
bool get keepIsolateAlive => _port.keepIsolateAlive;
}

View file

@ -58,8 +58,9 @@ final class Pointer<T extends NativeType> extends NativeType {
/// isolate's lifetime. After the isolate it was created in is terminated,
/// invoking it from native code will cause undefined behavior.
///
/// Does not accept dynamic invocations -- where the type of the receiver is
/// [dynamic].
/// [Pointer.fromFunction] only accepts static or top level functions. Use
/// [NativeCallable.isolateLocal] to create callbacks from any Dart function
/// or closure.
external static Pointer<NativeFunction<T>> fromFunction<T extends Function>(
@DartRepresentationOf('T') Function f,
[Object? exceptionalReturn]);
@ -171,7 +172,32 @@ extension NativeFunctionPointer<NF extends Function>
/// native function will call the Dart function in some way, with the arguments
/// converted to Dart values.
@Since('3.1')
final class NativeCallable<T extends Function> {
abstract final class NativeCallable<T extends Function> {
/// Constructs a [NativeCallable] that must be invoked from the same thread
/// that created it.
///
/// If an exception is thrown by the [callback], the native function will
/// return the `exceptionalReturn`, which must be assignable to the return
/// type of the [callback].
///
/// The returned function address can only be invoked on the mutator (main)
/// thread of the current isolate. It will abort the process if invoked on any
/// other thread. Use [NativeCallable.listener] to create callbacks that can
/// be invoked from any thread.
///
/// Unlike [Pointer.fromFunction], [NativeCallable]s can be constructed from
/// any Dart function or closure, not just static or top level functions.
///
/// This callback must be [close]d when it is no longer needed, but it will
/// *not* keep its [Isolate] alive. After the isolate is terminated, or
/// [NativeCallable.close] is called, invoking the [nativeFunction] from
/// native code will cause undefined behavior.
factory NativeCallable.isolateLocal(
@DartRepresentationOf("T") Function callback,
{Object? exceptionalReturn}) {
throw UnsupportedError("NativeCallable cannot be constructed dynamically.");
}
/// Constructs a [NativeCallable] that can be invoked from any thread.
///
/// When the native code invokes the function [nativeFunction], the arguments
@ -188,14 +214,16 @@ final class NativeCallable<T extends Function> {
///
/// This callback must be [close]d when it is no longer needed. The [Isolate]
/// that created the callback will be kept alive until [close] is called.
external NativeCallable.listener(
@DartRepresentationOf("T") Function callback);
factory NativeCallable.listener(
@DartRepresentationOf("T") Function callback) {
throw UnsupportedError("NativeCallable cannot be constructed dynamically.");
}
/// The native function pointer which can be used to invoke the `callback`
/// passed to the constructor.
///
/// If this receiver has been [close]d, the pointer is a [nullptr].
external Pointer<NativeFunction<T>> get nativeFunction;
Pointer<NativeFunction<T>> get nativeFunction;
/// Closes this callback and releases its resources.
///
@ -205,15 +233,14 @@ final class NativeCallable<T extends Function> {
/// This method must not be called more than once on each native callback.
///
/// It is safe to call [close] inside the [callback].
external void close();
void close();
/// Whether this [NativeCallable] keeps its [Isolate] alive.
///
/// By default, [NativeCallable]s keep the [Isolate] that created them alive
/// until [close] is called. If [keepIsolateAlive] is set to `false`, the
/// isolate may close while the port is still open.
external bool get keepIsolateAlive;
external void set keepIsolateAlive(bool value);
/// isolate may exit even if the [NativeCallable] isn't closed.
external bool keepIsolateAlive;
}
//

View file

@ -18,7 +18,7 @@ class AsyncCallbackTest {
final Future<void> Function() afterCallbackChecks;
// Either a NativeCallable or a Pointer.fromFunction.
final dynamic callback;
final Object callback;
AsyncCallbackTest(this.name, this.callback, this.afterCallbackChecks) {}
@ -26,11 +26,12 @@ class AsyncCallbackTest {
final NativeAsyncCallbackTestFn tester = ffiTestFunctions.lookupFunction<
NativeAsyncCallbackTest, NativeAsyncCallbackTestFn>("TestAsync$name");
tester(callback is NativeCallable ? callback.nativeFunction : callback);
final cb = callback;
tester(cb is NativeCallable ? cb.nativeFunction : cb as Pointer);
await afterCallbackChecks();
if (callback is NativeCallable) {
callback.close();
if (cb is NativeCallable) {
cb.close();
}
}
}

View file

@ -15,10 +15,12 @@ typedef NativeCallbackTestFn = int Function(Pointer);
class CallbackTest {
final String name;
final Pointer callback;
final void Function() afterCallbackChecks;
final bool isLeaf;
// Either a NativeCallable or a Pointer.fromFunction.
final Object callback;
CallbackTest(this.name, this.callback, {this.isLeaf = false})
: afterCallbackChecks = noChecks {}
CallbackTest.withCheck(this.name, this.callback, this.afterCallbackChecks,
@ -31,13 +33,18 @@ class CallbackTest {
: ffiTestFunctions.lookupFunction<NativeCallbackTest,
NativeCallbackTestFn>("Test$name", isLeaf: false);
final int testCode = tester(callback);
final cb = callback;
final int testCode =
tester(cb is NativeCallable ? cb.nativeFunction : cb as Pointer);
if (testCode != 0) {
Expect.fail("Test $name failed.");
}
afterCallbackChecks();
if (cb is NativeCallable) {
cb.close();
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -755,23 +755,28 @@ void ${dartName}AfterCallback() {
""";
}
String get dartCallbackTestConstructor {
String dartCallbackTestConstructor({required bool isNativeCallable}) {
String exceptionalPrefix = isNativeCallable ? "exceptionalReturn: " : "";
String exceptionalReturn = "";
if (returnValue is FundamentalType) {
final returnValue_ = returnValue as FundamentalType;
if (returnValue_.isFloatingPoint) {
exceptionalReturn = ", 0.0";
exceptionalReturn = ", ${exceptionalPrefix}0.0";
} else if (returnValue_.isInteger) {
exceptionalReturn = ", 0";
exceptionalReturn = ", ${exceptionalPrefix}0";
} else if (returnValue_.isBool) {
exceptionalReturn = ", false";
exceptionalReturn = ", ${exceptionalPrefix}false";
} else {
throw 'Unexpected type $returnValue_';
}
}
final T = '${cName}Type';
final constructor = isNativeCallable
? 'NativeCallable<$T>.isolateLocal'
: 'Pointer.fromFunction<$T>';
return """
CallbackTest.withCheck("$cName",
Pointer.fromFunction<${cName}Type>($dartName$exceptionalReturn),
$constructor($dartName$exceptionalReturn),
${dartName}AfterCallback),
""";
}
@ -1219,36 +1224,43 @@ Future<void> writeDartCallbackTest(
List<FunctionType> functions, {
required bool isVarArgs,
}) async {
await Future.wait([
true,
if (!isVarArgs) false,
].map((isNnbd) async {
final StringBuffer buffer = StringBuffer();
buffer.write(headerDartCallbackTest(
isNnbd: isNnbd,
copyrightYear: isVarArgs ? 2023 : 2020,
));
for (bool isNnbd in [true, if (!isVarArgs) false]) {
for (bool isNativeCallable in [false, if (isNnbd) true]) {
final StringBuffer buffer = StringBuffer();
buffer.write(headerDartCallbackTest(
isNnbd: isNnbd,
copyrightYear: isVarArgs || isNativeCallable ? 2023 : 2020,
));
buffer.write("""
final testCases = [
${functions.map((e) => e.dartCallbackTestConstructor).join("\n")}
];
""");
buffer.write("""
final testCases = [
${functions.map((e) => e.dartCallbackTestConstructor(isNativeCallable: isNativeCallable)).join("\n")}
];
""");
buffer.writeAll(functions.map((e) => e.dartCallbackCode(isNnbd: isNnbd)));
buffer.writeAll(functions.map((e) => e.dartCallbackCode(isNnbd: isNnbd)));
final path = callbackTestPath(isNnbd: isNnbd, isVarArgs: isVarArgs);
await File(path).writeAsString(buffer.toString());
await runProcess(Platform.resolvedExecutable, ["format", path]);
}));
final path = callbackTestPath(
isNnbd: isNnbd,
isVarArgs: isVarArgs,
isNativeCallable: isNativeCallable);
await File(path).writeAsString(buffer.toString());
await runProcess(Platform.resolvedExecutable, ["format", path]);
}
}
}
String callbackTestPath({required bool isNnbd, required bool isVarArgs}) {
String callbackTestPath({
required bool isNnbd,
required bool isVarArgs,
required bool isNativeCallable,
}) {
final folder = isNnbd ? "ffi" : "ffi_2";
final baseName = isVarArgs ? "varargs" : "structs_by_value";
final natCall = isNativeCallable ? "_native_callable" : "";
return Platform.script
.resolve(
"../../$folder/function_callbacks_${baseName}_generated_test.dart")
"../../$folder/function_callbacks_${baseName}${natCall}_generated_test.dart")
.toFilePath();
}

View file

@ -0,0 +1,211 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Dart test program for testing dart:ffi async callbacks.
//
// VMOptions=--stacktrace-every=100
// VMOptions=--write-protect-code --no-dual-map-code
// VMOptions=--write-protect-code --no-dual-map-code --stacktrace-every=100
// VMOptions=--use-slow-path
// VMOptions=--use-slow-path --stacktrace-every=100
// VMOptions=--use-slow-path --write-protect-code --no-dual-map-code
// VMOptions=--use-slow-path --write-protect-code --no-dual-map-code --stacktrace-every=100
// VMOptions=--dwarf_stack_traces --no-retain_function_objects --no-retain_code_objects
// VMOptions=--test_il_serialization
// VMOptions=--profiler
// SharedObjects=ffi_test_functions
import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';
import 'dart:math';
import 'dart:io';
import "package:expect/expect.dart";
import 'dylib_utils.dart';
main(args, message) async {
testNativeCallableStatic();
testNativeCallableClosure();
testNativeCallableDoubleCloseError();
testNativeCallableNestedCloseCallStatic();
testNativeCallableNestedCloseCallClosure();
testNativeCallableExceptionalReturnStatic();
testNativeCallableExceptionalReturnClosure();
await testNativeCallableDontKeepAliveStatic();
await testNativeCallableDontKeepAliveClosure();
testNativeCallableKeepAliveGetter();
print("All tests completed :)");
}
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
typedef TwoIntFnNativeType = Int32 Function(Pointer, Int32, Int32);
typedef TwoIntFnType = int Function(Pointer, int, int);
final callTwoIntFunction = ffiTestFunctions
.lookupFunction<TwoIntFnNativeType, TwoIntFnType>("CallTwoIntFunction");
typedef CallbackNativeType = Int32 Function(Int32, Int32);
int add(int a, int b) {
return a + b;
}
testNativeCallableStatic() {
final callback = NativeCallable<CallbackNativeType>.isolateLocal(add,
exceptionalReturn: 0);
Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
callback.close();
}
testNativeCallableClosure() {
int c = 70000;
int closure(int a, int b) {
return a + b + c;
}
final callback = NativeCallable<CallbackNativeType>.isolateLocal(closure,
exceptionalReturn: 0);
Expect.equals(71234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
c = 80000;
Expect.equals(81234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
callback.close();
}
testNativeCallableDoubleCloseError() {
final callback = NativeCallable<CallbackNativeType>.isolateLocal(add,
exceptionalReturn: 0);
Expect.notEquals(nullptr, callback.nativeFunction);
callback.close();
Expect.equals(nullptr, callback.nativeFunction);
Expect.throwsStateError(() {
callback.close();
});
}
late NativeCallable selfClosingStaticCallback;
int selfClosingStatic(int a, int b) {
selfClosingStaticCallback.close();
return a + b;
}
testNativeCallableNestedCloseCallStatic() {
selfClosingStaticCallback = NativeCallable<CallbackNativeType>.isolateLocal(
selfClosingStatic,
exceptionalReturn: 0);
Expect.equals(1234,
callTwoIntFunction(selfClosingStaticCallback.nativeFunction, 1000, 234));
// The callback is already closed.
Expect.equals(nullptr, selfClosingStaticCallback.nativeFunction);
}
testNativeCallableNestedCloseCallClosure() {
late NativeCallable callback;
int selfClosing(int a, int b) {
callback.close();
return a + b;
}
callback = NativeCallable<CallbackNativeType>.isolateLocal(selfClosing,
exceptionalReturn: 0);
Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
// The callback is already closed.
Expect.equals(nullptr, callback.nativeFunction);
}
int throwerCallback(int a, int b) {
if (a != 1000) {
throw "Oh no!";
}
return a + b;
}
testNativeCallableExceptionalReturnStatic() {
final callback = NativeCallable<CallbackNativeType>.isolateLocal(
throwerCallback,
exceptionalReturn: 5678);
Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
Expect.equals(5678, callTwoIntFunction(callback.nativeFunction, 0, 0));
callback.close();
}
testNativeCallableExceptionalReturnClosure() {
int thrower(int a, int b) {
if (a != 1000) {
throw "Oh no!";
}
return a + b;
}
final callback = NativeCallable<CallbackNativeType>.isolateLocal(thrower,
exceptionalReturn: 5678);
Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
Expect.equals(5678, callTwoIntFunction(callback.nativeFunction, 0, 0));
callback.close();
}
Future<void> testNativeCallableDontKeepAliveStatic() async {
final exitPort = ReceivePort();
await Isolate.spawn((_) async {
final callback = NativeCallable<CallbackNativeType>.isolateLocal(add,
exceptionalReturn: 0);
Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
callback.keepIsolateAlive = false;
}, null, onExit: exitPort.sendPort);
await exitPort.first;
exitPort.close();
}
Future<void> testNativeCallableDontKeepAliveClosure() async {
int c = 70000;
int closure(int a, int b) {
return a + b + c;
}
final exitPort = ReceivePort();
await Isolate.spawn((_) async {
final callback = NativeCallable<CallbackNativeType>.isolateLocal(closure,
exceptionalReturn: 0);
Expect.equals(
71234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
callback.keepIsolateAlive = false;
}, null, onExit: exitPort.sendPort);
await exitPort.first;
exitPort.close();
}
testNativeCallableKeepAliveGetter() {
final callback = NativeCallable<CallbackNativeType>.isolateLocal(add,
exceptionalReturn: 0);
// Check that only the flag changes are counted by decrementing and
// incrementing a lot, and by different amounts.
for (int i = 0; i < 100; ++i) {
callback.keepIsolateAlive = false;
Expect.isFalse(callback.keepIsolateAlive);
}
for (int i = 0; i < 200; ++i) {
callback.keepIsolateAlive = true;
Expect.isTrue(callback.keepIsolateAlive);
}
callback.close();
}

View file

@ -37,12 +37,19 @@ void main() {
testFromFunctionTearOff();
testFromFunctionAbstract();
testFromFunctionFunctionExceptionValueMustBeConst();
testNativeCallableGeneric();
testNativeCallableGeneric2();
testNativeCallableWrongNativeFunctionSignature();
testNativeCallableTypeMismatch();
testNativeCallableAbstract();
testNativeCallableMustReturnVoid();
testNativeCallableListenerGeneric();
testNativeCallableListenerGeneric2();
testNativeCallableListenerWrongNativeFunctionSignature();
testNativeCallableListenerTypeMismatch();
testNativeCallableListenerAbstract();
testNativeCallableListenerMustReturnVoid();
testNativeCallableIsolateLocalGeneric();
testNativeCallableIsolateLocalGeneric2();
testNativeCallableIsolateLocalWrongNativeFunctionSignature();
testNativeCallableIsolateLocalTypeMismatch();
testNativeCallableIsolateLocalTearOff();
testNativeCallableIsolateLocalAbstract();
testNativeCallableIsolateLocalFunctionExceptionValueMustBeConst();
testLookupFunctionGeneric();
testLookupFunctionGeneric2();
testLookupFunctionWrongNativeFunctionSignature();
@ -375,7 +382,7 @@ void myVoidFunc2(int x) {
print("x = $x");
}
void testNativeCallableGeneric() {
void testNativeCallableListenerGeneric() {
NativeCallable? generic<T extends Function>(T f) {
NativeCallable<NativeVoidFunc>? result;
result = NativeCallable.listener(f);
@ -389,7 +396,7 @@ void testNativeCallableGeneric() {
generic(myVoidFunc);
}
void testNativeCallableGeneric2() {
void testNativeCallableListenerGeneric2() {
NativeCallable<T>? generic<T extends Function>() {
NativeCallable<T>? result;
result = NativeCallable.listener(myVoidFunc);
@ -403,7 +410,7 @@ void testNativeCallableGeneric2() {
generic<NativeVoidFunc>();
}
void testNativeCallableWrongNativeFunctionSignature() {
void testNativeCallableListenerWrongNativeFunctionSignature() {
/**/ NativeCallable<NativeVoidFunc>.listener(myVoidFunc2);
// ^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
@ -411,7 +418,7 @@ void testNativeCallableWrongNativeFunctionSignature() {
// [cfe] Expected type 'void Function(int)' to be 'void Function()', which is the Dart type corresponding to 'NativeFunction<Void Function()>'.
}
void testNativeCallableTypeMismatch() {
void testNativeCallableListenerTypeMismatch() {
NativeCallable<NativeVoidFunc> p;
p = NativeCallable.listener(myVoidFunc2);
// ^^^^^^^^^^^
@ -420,20 +427,93 @@ void testNativeCallableTypeMismatch() {
// [cfe] Expected type 'void Function(int)' to be 'void Function()', which is the Dart type corresponding to 'NativeFunction<Void Function()>'.
}
void testNativeCallableAbstract() {
final f = NativeCallable<Function>.listener(testNativeCallableAbstract);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [cfe] Expected type 'NativeFunction<Function>' to be a valid and instantiated subtype of 'NativeType'.
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_NATIVE_FUNCTION_TYPE
void testNativeCallableListenerAbstract() {
final f = NativeCallable<Function>.listener(
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [cfe] Expected type 'NativeFunction<Function>' to be a valid and instantiated subtype of 'NativeType'.
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_NATIVE_FUNCTION_TYPE
testNativeCallableListenerAbstract);
}
void testNativeCallableMustReturnVoid() {
void testNativeCallableListenerMustReturnVoid() {
final f = NativeCallable<NativeDoubleUnOp>.listener(myTimesThree);
// ^^^^^^^^^^^^
// [cfe] The return type of the function passed to NativeCallable.listener must be void rather than 'double'.
// [analyzer] COMPILE_TIME_ERROR.MUST_RETURN_VOID
}
void testNativeCallableIsolateLocalGeneric() {
NativeCallable<Function> generic<T extends Function>(T f) {
late NativeCallable<NativeDoubleUnOp> result;
result = NativeCallable.isolateLocal(f);
// ^
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
// ^
// [cfe] Expected type 'T' to be 'double Function(double)', which is the Dart type corresponding to 'NativeFunction<Double Function(Double)>'.
return result;
}
generic(myTimesThree);
}
void testNativeCallableIsolateLocalGeneric2() {
NativeCallable<T> generic<T extends Function>() {
late NativeCallable<T> result;
result = NativeCallable.isolateLocal(myTimesThree);
// ^
// [cfe] Expected type 'NativeFunction<T>' to be a valid and instantiated subtype of 'NativeType'.
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_NATIVE_FUNCTION_TYPE
return result;
}
generic<NativeDoubleUnOp>();
}
void testNativeCallableIsolateLocalWrongNativeFunctionSignature() {
/**/ NativeCallable<IntUnOp>.isolateLocal(myTimesFour);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_NATIVE_FUNCTION_TYPE
// ^
// [cfe] Expected type 'NativeFunction<int Function(int)>' to be a valid and instantiated subtype of 'NativeType'.
}
void testNativeCallableIsolateLocalTypeMismatch() {
NativeCallable<NativeDoubleUnOp> p;
p = NativeCallable.isolateLocal(myTimesFour);
// ^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_SUBTYPE
// ^
// [cfe] Expected type 'int Function(int)' to be 'double Function(double)', which is the Dart type corresponding to 'NativeFunction<Double Function(Double)>'.
}
void testNativeCallableIsolateLocalTearOff() {
DoubleUnOp fld = X().tearoff;
NativeCallable<NativeDoubleUnOp> p;
p = NativeCallable.isolateLocal(fld);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.MISSING_EXCEPTION_VALUE
// ^
// [cfe] Expected an exceptional return value for a native callback returning 'double'.
}
void testNativeCallableIsolateLocalAbstract() {
NativeCallable<Function>.isolateLocal(testNativeCallableIsolateLocalAbstract);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.MUST_BE_A_NATIVE_FUNCTION_TYPE
// [cfe] Expected type 'NativeFunction<Function>' to be a valid and instantiated subtype of 'NativeType'.
}
void testNativeCallableIsolateLocalFunctionExceptionValueMustBeConst() {
final notAConst = 1.1;
NativeCallable<NativeDoubleUnOp> p;
p = NativeCallable.isolateLocal(myTimesThree, exceptionalReturn: notAConst);
// ^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.ARGUMENT_MUST_BE_A_CONSTANT
// ^
// [cfe] Exceptional return value must be a constant.
}
void testLookupFunctionGeneric() {
Function generic<T extends Function>() {
DynamicLibrary l = dlopenPlatformSpecific("ffi_test_dynamic_library");