[vm/ffi] Express FFI call closures explicitly in AST

Instead of implicitly creating closures for FFI
asFunction/lookupFunction APIs in the VM, now they are explicitly expressed in the kernel AST. That makes it possible to analyze
them in TFA.

FFI calls from Dart code into native are now performed in the following way:

```
  block {
    Pointer #ffiTarget0 = target;
    @pragma('vm:ffi:call-closure', _FfiCall<Int32 Function(Int32)>(isLeaf: false))
    #ffiClosure0(int arg1) {
      _nativeEffect(arg1);
      return _ffiCall<int>(#ffiTarget0);
    }
  } =>#ffiClosure0;
```

_ffiCall method is recognized by the VM and its call is replaced
directly with FFI calling sequence. _ffiCall uses closure
parameters implicitly. No extra trampolines are generated for FFI calls.

TEST=existing
Fixes https://github.com/dart-lang/sdk/issues/54172
Issue https://github.com/dart-lang/sdk/issues/39692

CoreLibraryReviewExempt: Implementation change only.
Change-Id: I92b3ff7391470686151ad0807e2cdbbf1a69d256
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/339662
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Alexander Markov
2023-12-12 18:34:39 +00:00
committed by Commit Queue
parent aca875dfea
commit fd2e9b9f1a
25 changed files with 461 additions and 240 deletions
+7 -35
View File
@@ -230,7 +230,7 @@ class FfiTransformer extends Transformer {
final Procedure abiSpecificIntegerArrayElemAt;
final Procedure abiSpecificIntegerArraySetElemAt;
final Procedure asFunctionMethod;
final Procedure asFunctionInternal;
final Procedure ffiCallMethod;
final Procedure sizeOfMethod;
final Procedure lookupFunctionMethod;
final Procedure fromFunctionMethod;
@@ -283,6 +283,8 @@ class FfiTransformer extends Transformer {
final Field nativeCallablePointerField;
final Procedure nativeAddressOf;
final Procedure nativePrivateAddressOf;
final Class ffiCallClass;
final Field ffiCallIsLeafField;
late final InterfaceType nativeFieldWrapperClass1Type;
late final InterfaceType voidType;
@@ -463,8 +465,7 @@ class FfiTransformer extends Transformer {
index.getProcedure('dart:ffi', 'AbiSpecificIntegerArray', '[]='),
asFunctionMethod = index.getProcedure(
'dart:ffi', 'NativeFunctionPointer', 'asFunction'),
asFunctionInternal =
index.getTopLevelProcedure('dart:ffi', '_asFunctionInternal'),
ffiCallMethod = index.getTopLevelProcedure('dart:ffi', '_ffiCall'),
sizeOfMethod = index.getTopLevelProcedure('dart:ffi', 'sizeOf'),
lookupFunctionMethod = index.getProcedure(
'dart:ffi', 'DynamicLibraryExtension', 'lookupFunction'),
@@ -571,7 +572,9 @@ class FfiTransformer extends Transformer {
nativeAddressOf =
index.getMember('dart:ffi', 'Native', 'addressOf') as Procedure,
nativePrivateAddressOf =
index.getMember('dart:ffi', 'Native', '_addressOf') as Procedure {
index.getMember('dart:ffi', 'Native', '_addressOf') as Procedure,
ffiCallClass = index.getClass('dart:ffi', '_FfiCall'),
ffiCallIsLeafField = index.getField('dart:ffi', '_FfiCall', 'isLeaf') {
nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
coreTypes, Nullability.nonNullable);
voidType = nativeTypesClasses[NativeType.kVoid]!
@@ -1199,37 +1202,6 @@ class FfiTransformer extends Transformer {
..fileOffset = nestedExpression.fileOffset;
}
/// Creates an invocation to asFunctionInternal.
///
/// Adds a native effect invoking a compound constructors if this is used
/// as return type.
Expression buildAsFunctionInternal({
required Expression functionPointer,
required DartType nativeSignature,
required DartType dartSignature,
required bool isLeaf,
required int fileOffset,
}) {
final asFunctionInternalInvocation = StaticInvocation(
asFunctionInternal,
Arguments([
functionPointer,
BoolLiteral(isLeaf),
], types: [
dartSignature,
nativeSignature,
]))
..fileOffset = fileOffset;
final possibleCompoundReturn = findCompoundReturnType(dartSignature);
if (possibleCompoundReturn != null) {
return invokeCompoundConstructor(
asFunctionInternalInvocation, possibleCompoundReturn);
}
return asFunctionInternalInvocation;
}
/// Returns the compound [Class] if a compound is returned, otherwise `null`.
Class? findCompoundReturnType(DartType dartSignature) {
if (dartSignature is! FunctionType) {
+92 -13
View File
@@ -113,9 +113,14 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
// callback.
int callbackCount = 0;
// Used to create private top-level trampoline methods with unique names
// for each call.
int callCount = 0;
@override
TreeNode visitLibrary(Library node) {
callbackCount = 0;
callCount = 0;
return super.visitLibrary(node);
}
@@ -349,10 +354,12 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
);
final DartType nativeSignature = nativeType.typeArguments[0];
return buildAsFunctionInternal(
return _replaceAsFunction(
functionPointer: node.arguments.positional[0],
pointerType: InterfaceType(
pointerClass, Nullability.nonNullable, [nativeType]),
nativeSignature: nativeSignature,
dartSignature: dartType,
dartSignature: dartType as FunctionType,
isLeaf: isLeaf,
fileOffset: node.fileOffset,
);
@@ -428,6 +435,84 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
return node;
}
/// Create Dart function which calls native code.
///
/// Adds a native effect invoking a compound constructors if this is used
/// as return type.
Expression _replaceAsFunction({
required Expression functionPointer,
required DartType pointerType,
required DartType nativeSignature,
required FunctionType dartSignature,
required bool isLeaf,
required int fileOffset,
}) {
assert(dartSignature.namedParameters.isEmpty);
final functionPointerVarName = '#ffiTarget$callCount';
final closureName = '#ffiClosure$callCount';
++callCount;
final pointerVar = VariableDeclaration(functionPointerVarName,
initializer: functionPointer, type: pointerType, isSynthesized: true);
final positionalParameters = [
for (int i = 0; i < dartSignature.positionalParameters.length; ++i)
VariableDeclaration(
'arg${i + 1}',
type: dartSignature.positionalParameters[i],
)
];
final closure = FunctionDeclaration(
VariableDeclaration(closureName,
type: dartSignature, isSynthesized: true)
..addAnnotation(ConstantExpression(
InstanceConstant(coreTypes.pragmaClass.reference, [], {
coreTypes.pragmaName.fieldReference:
StringConstant('vm:ffi:call-closure'),
coreTypes.pragmaOptions.fieldReference: InstanceConstant(
ffiCallClass.reference,
[nativeSignature],
{
ffiCallIsLeafField.fieldReference: BoolConstant(isLeaf),
},
),
}))),
FunctionNode(
Block([
for (final param in positionalParameters)
ExpressionStatement(StaticInvocation(
nativeEffectMethod, Arguments([VariableGet(param)]))),
ReturnStatement(StaticInvocation(
ffiCallMethod,
Arguments([
VariableGet(pointerVar),
], types: [
dartSignature.returnType,
]))
..fileOffset = fileOffset),
]),
positionalParameters: positionalParameters,
requiredParameterCount: dartSignature.requiredParameterCount,
returnType: dartSignature.returnType)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
final result = BlockExpression(
Block([
pointerVar,
closure,
]),
VariableGet(closure.variable));
final possibleCompoundReturn = findCompoundReturnType(dartSignature);
if (possibleCompoundReturn != null) {
return invokeCompoundConstructor(result, possibleCompoundReturn);
}
return result;
}
Expression invokeCompoundConstructors(
Expression nestedExpression, List<Class> compoundClasses) =>
compoundClasses
@@ -462,10 +547,6 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
// 'lookupFunction' are constants, so by inlining the call to 'asFunction' at
// the call-site, we ensure that there are no generic calls to 'asFunction'.
Expression _replaceLookupFunction(StaticInvocation node) {
// The generated code looks like:
//
// _asFunctionInternal<DS, NS>(lookup<NativeFunction<NS>>(symbolName),
// isLeaf)
final DartType nativeSignature = node.arguments.types[0];
final DartType dartSignature = node.arguments.types[1];
@@ -478,21 +559,19 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
final FunctionType lookupFunctionType =
libraryLookupMethod.getterType as FunctionType;
final Expression lookupResult = InstanceInvocation(
InstanceAccessKind.Instance,
node.arguments.positional[0],
libraryLookupMethod.name,
lookupArgs,
final lookupResult = InstanceInvocation(InstanceAccessKind.Instance,
node.arguments.positional[0], libraryLookupMethod.name, lookupArgs,
interfaceTarget: libraryLookupMethod,
functionType: FunctionTypeInstantiator.instantiate(
lookupFunctionType, lookupTypeArgs));
final isLeaf = getIsLeafBoolean(node) ?? false;
return buildAsFunctionInternal(
return _replaceAsFunction(
functionPointer: lookupResult,
pointerType: lookupResult.functionType.returnType,
nativeSignature: nativeSignature,
dartSignature: dartSignature,
dartSignature: dartSignature as FunctionType,
isLeaf: isLeaf,
fileOffset: node.fileOffset,
);
@@ -0,0 +1,37 @@
// 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.
// Tests for NativeFunctionPointer.asFunction transformation.
import 'dart:ffi';
testVoidNoArg() {
final pointer =
Pointer<NativeFunction<Void Function()>>.fromAddress(0xdeadbeef);
final function = pointer.asFunction<void Function()>();
function();
}
testIntInt() {
final pointer =
Pointer<NativeFunction<Int32 Function(Int64)>>.fromAddress(0xdeadbeef);
final function = pointer.asFunction<int Function(int)>();
return function(42);
}
testLeaf5Args() {
final pointer = Pointer<
NativeFunction<
Int32 Function(
Int32, Int32, Int32, Int32, Int32)>>.fromAddress(0xdeadbeef);
final function =
pointer.asFunction<int Function(int, int, int, int, int)>(isLeaf: true);
return function(1, 2, 3, 4, 5);
}
void main() {
testVoidNoArg();
testIntInt();
testLeaf5Args();
}
@@ -0,0 +1,63 @@
library #lib;
import self as self;
import "dart:ffi" as ffi;
import "dart:core" as core;
import "dart:_internal" as _in;
import "dart:ffi";
static method testVoidNoArg() → dynamic {
final ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<() → ffi::Void>>(3735928559);
final () → void function = block {
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> #ffiTarget0 = pointer;
@#C4
function #ffiClosure0() → void {
return ffi::_ffiCall<void>(#ffiTarget0);
}
} =>#ffiClosure0;
function(){() → void};
}
[@vm.unboxing-info.metadata=()->i]static method testIntInt() → dynamic {
final ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>>(3735928559);
final (core::int) → core::int function = block {
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> #ffiTarget1 = pointer;
@#C6
function #ffiClosure1(core::int arg1) → core::int {
_in::_nativeEffect(arg1);
return ffi::_ffiCall<core::int>(#ffiTarget1);
}
} =>#ffiClosure1;
return function(42){(core::int) → core::int};
}
[@vm.unboxing-info.metadata=()->i]static method testLeaf5Args() → dynamic {
final ffi::Pointer<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>>(3735928559);
final (core::int, core::int, core::int, core::int, core::int) → core::int function = block {
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>> #ffiTarget2 = pointer;
@#C9
function #ffiClosure2(core::int arg1, core::int arg2, core::int arg3, core::int arg4, core::int arg5) → core::int {
_in::_nativeEffect(arg1);
_in::_nativeEffect(arg2);
_in::_nativeEffect(arg3);
_in::_nativeEffect(arg4);
_in::_nativeEffect(arg5);
return ffi::_ffiCall<core::int>(#ffiTarget2);
}
} =>#ffiClosure2;
return function(1, 2, 3, 4, 5){(core::int, core::int, core::int, core::int, core::int) → core::int};
}
static method main() → void {
self::testVoidNoArg();
self::testIntInt();
self::testLeaf5Args();
}
constants {
#C1 = "vm:ffi:call-closure"
#C2 = false
#C3 = ffi::_FfiCall<() → ffi::Void> {isLeaf:#C2}
#C4 = core::pragma {name:#C1, options:#C3}
#C5 = ffi::_FfiCall<(ffi::Int64) → ffi::Int32> {isLeaf:#C2}
#C6 = core::pragma {name:#C1, options:#C5}
#C7 = true
#C8 = ffi::_FfiCall<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32> {isLeaf:#C7}
#C9 = core::pragma {name:#C1, options:#C8}
}
@@ -0,0 +1,63 @@
library #lib;
import self as self;
import "dart:ffi" as ffi;
import "dart:core" as core;
import "dart:_internal" as _in;
import "dart:ffi";
static method testVoidNoArg() → dynamic {
final ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> pointer = ffi::Pointer::fromAddress<ffi::NativeFunction<() → ffi::Void>>(3735928559);
final () → void function = block {
synthesized ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> #ffiTarget0 = pointer;
@#C4
function #ffiClosure0() → void {
return ffi::_ffiCall<void>(#ffiTarget0);
}
} =>#ffiClosure0;
function(){() → void};
}
static method testIntInt() → dynamic {
final ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> pointer = ffi::Pointer::fromAddress<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>>(3735928559);
final (core::int) → core::int function = block {
synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> #ffiTarget1 = pointer;
@#C6
function #ffiClosure1(core::int arg1) → core::int {
_in::_nativeEffect(arg1);
return ffi::_ffiCall<core::int>(#ffiTarget1);
}
} =>#ffiClosure1;
return function(42){(core::int) → core::int};
}
static method testLeaf5Args() → dynamic {
final ffi::Pointer<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>> pointer = ffi::Pointer::fromAddress<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>>(3735928559);
final (core::int, core::int, core::int, core::int, core::int) → core::int function = block {
synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>> #ffiTarget2 = pointer;
@#C9
function #ffiClosure2(core::int arg1, core::int arg2, core::int arg3, core::int arg4, core::int arg5) → core::int {
_in::_nativeEffect(arg1);
_in::_nativeEffect(arg2);
_in::_nativeEffect(arg3);
_in::_nativeEffect(arg4);
_in::_nativeEffect(arg5);
return ffi::_ffiCall<core::int>(#ffiTarget2);
}
} =>#ffiClosure2;
return function(1, 2, 3, 4, 5){(core::int, core::int, core::int, core::int, core::int) → core::int};
}
static method main() → void {
self::testVoidNoArg();
self::testIntInt();
self::testLeaf5Args();
}
constants {
#C1 = "vm:ffi:call-closure"
#C2 = false
#C3 = ffi::_FfiCall<() → ffi::Void> {isLeaf:#C2}
#C4 = core::pragma {name:#C1, options:#C3}
#C5 = ffi::_FfiCall<(ffi::Int64) → ffi::Int32> {isLeaf:#C2}
#C6 = core::pragma {name:#C1, options:#C5}
#C7 = true
#C8 = ffi::_FfiCall<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32> {isLeaf:#C7}
#C9 = core::pragma {name:#C1, options:#C8}
}
@@ -67,7 +67,13 @@ static method testLookupFunctionReturn() → void {
final ffi::DynamicLibrary dylib = [@vm.inferred-type.metadata=dart.ffi::DynamicLibrary] ffi::DynamicLibrary::executable();
final () → self::Struct1 function1 = block {
_in::_nativeEffect(new self::Struct1::#fromTypedDataBase([@vm.inferred-type.metadata=dart.typed_data::_Uint8List] typ::Uint8List::•(#C18)));
} =>ffi::_asFunctionInternal<() → self::Struct1, () → self::Struct1>([@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup] [@vm.inferred-type.metadata=dart.ffi::Pointer (skip check)] dylib.{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<() → self::Struct1>>("function1"){(core::String) → ffi::Pointer<ffi::NativeFunction<() → self::Struct1>>}, false);
} => block {
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<() → self::Struct1>> #ffiTarget0 = [@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup] [@vm.inferred-type.metadata=dart.ffi::Pointer (skip check)] dylib.{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<() → self::Struct1>>("function1"){(core::String) → ffi::Pointer<ffi::NativeFunction<() → self::Struct1>>};
@#C22
function #ffiClosure0() → self::Struct1 {
return ffi::_ffiCall<self::Struct1>(#ffiTarget0);
}
} =>#ffiClosure0;
final self::Struct1 struct1 = function1(){() → self::Struct1};
core::print(struct1);
}
@@ -75,7 +81,13 @@ static method testAsFunctionReturn() → void {
final ffi::Pointer<ffi::NativeFunction<() → self::Struct2>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<() → self::Struct2>>(3735928559);
final () → self::Struct2 function2 = block {
_in::_nativeEffect(new self::Struct2::#fromTypedDataBase([@vm.inferred-type.metadata=dart.typed_data::_Uint8List] typ::Uint8List::•(#C18)));
} =>ffi::_asFunctionInternal<() → self::Struct2, () → self::Struct2>(pointer, false);
} => block {
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<() → self::Struct2>> #ffiTarget1 = pointer;
@#C24
function #ffiClosure1() → self::Struct2 {
return ffi::_ffiCall<self::Struct2>(#ffiTarget1);
}
} =>#ffiClosure1;
final self::Struct2 struct2 = function2(){() → self::Struct2};
core::print(struct2);
}
@@ -90,12 +102,26 @@ static method testFromFunctionArgument() → void {
}
static method testLookupFunctionArgument() → void {
final ffi::DynamicLibrary dylib = [@vm.inferred-type.metadata=dart.ffi::DynamicLibrary] ffi::DynamicLibrary::executable();
final (self::Struct5) → void function5 = [@vm.inferred-type.metadata=dart.core::_Closure] ffi::_asFunctionInternal<(self::Struct5) → void, (self::Struct5) → ffi::Void>([@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup] [@vm.inferred-type.metadata=dart.ffi::Pointer (skip check)] dylib.{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<(self::Struct5) → ffi::Void>>("function5"){(core::String) → ffi::Pointer<ffi::NativeFunction<(self::Struct5) → ffi::Void>>}, false);
final (self::Struct5) → void function5 = block {
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<(self::Struct5) → ffi::Void>> #ffiTarget2 = [@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup] [@vm.inferred-type.metadata=dart.ffi::Pointer (skip check)] dylib.{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<(self::Struct5) → ffi::Void>>("function5"){(core::String) → ffi::Pointer<ffi::NativeFunction<(self::Struct5) → ffi::Void>>};
@#C26
function #ffiClosure2(self::Struct5 arg1) → void {
_in::_nativeEffect(arg1);
return ffi::_ffiCall<void>(#ffiTarget2);
}
} =>#ffiClosure2;
core::print(function5);
}
static method testAsFunctionArgument() → void {
final ffi::Pointer<ffi::NativeFunction<(self::Struct6) → ffi::Void>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<(self::Struct6) → ffi::Void>>(3735928559);
final (self::Struct6) → void function6 = [@vm.inferred-type.metadata=dart.core::_Closure] ffi::_asFunctionInternal<(self::Struct6) → void, (self::Struct6) → ffi::Void>(pointer, false);
final (self::Struct6) → void function6 = block {
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<(self::Struct6) → ffi::Void>> #ffiTarget3 = pointer;
@#C28
function #ffiClosure3(self::Struct6 arg1) → void {
_in::_nativeEffect(arg1);
return ffi::_ffiCall<void>(#ffiTarget3);
}
} =>#ffiClosure3;
core::print(function6);
}
static method returnStruct7() → self::Struct7 {
@@ -135,4 +161,14 @@ constants {
#C16 = static-tearoff self::useStruct3
#C17 = static-tearoff self::returnStruct7
#C18 = 1
#C19 = "vm:ffi:call-closure"
#C20 = false
#C21 = ffi::_FfiCall<() → self::Struct1> {isLeaf:#C20}
#C22 = core::pragma {name:#C19, options:#C21}
#C23 = ffi::_FfiCall<() → self::Struct2> {isLeaf:#C20}
#C24 = core::pragma {name:#C19, options:#C23}
#C25 = ffi::_FfiCall<(self::Struct5) → ffi::Void> {isLeaf:#C20}
#C26 = core::pragma {name:#C19, options:#C25}
#C27 = ffi::_FfiCall<(self::Struct6) → ffi::Void> {isLeaf:#C20}
#C28 = core::pragma {name:#C19, options:#C27}
}
+8
View File
@@ -45,3 +45,11 @@ Related files:
* [runtime/vm/kernel_loader.cc](../../../runtime/vm/kernel_loader.cc)
* [runtime/vm/object.cc](../../../runtime/vm/object.cc)
## FFI Calls
This pragma is used to mark Dart closures which perform FFI calls:
```
@pragma('vm:ffi:call-closure', _FfiCall<Int32 Function(Int32)>(isLeaf: false))
int #ffiCall0(int arg1) => _ffiCall<int>(target);
```
+1
View File
@@ -47,6 +47,7 @@ These pragma's are only used on AST nodes synthesized by us, so users defining t
| Pragma | Meaning |
| --- | --- |
| `vm:ffi:call-closure`| [Closure performing FFI calls](compiler/ffi_pragmas.md) |
| `vm:ffi:native-assets` | [Passing a native assets mapping to the VM](compiler/ffi_pragmas.md) |
| `vm:ffi:native`| [Passing a native arguments to the VM](compiler/ffi_pragmas.md) |
-5
View File
@@ -28,11 +28,6 @@
namespace dart {
// Static invocations to this method are translated directly in streaming FGB.
DEFINE_NATIVE_ENTRY(Ffi_asFunctionInternal, 2, 2) {
UNREACHABLE();
}
DEFINE_NATIVE_ENTRY(Ffi_createNativeCallableListener, 1, 2) {
const auto& send_function =
Function::CheckedHandle(zone, arguments->NativeArg0());
-1
View File
@@ -321,7 +321,6 @@ namespace dart {
V(VMService_DecodeAssets, 1) \
V(VMService_AddUserTagsToStreamableSampleList, 1) \
V(VMService_RemoveUserTagsFromStreamableSampleList, 1) \
V(Ffi_asFunctionInternal, 2) \
V(Ffi_createNativeCallableListener, 2) \
V(Ffi_createNativeCallableIsolateLocal, 3) \
V(Ffi_deleteNativeCallable, 1) \
@@ -1025,65 +1025,6 @@ Fragment BaseFlowGraphBuilder::Box(Representation from) {
return Fragment(box);
}
Fragment BaseFlowGraphBuilder::BuildFfiAsFunctionInternalCall(
const TypeArguments& signatures,
bool is_leaf) {
ASSERT(signatures.Length() == 2);
const auto& sig0 = AbstractType::Handle(signatures.TypeAt(0));
const auto& sig1 = AbstractType::Handle(signatures.TypeAt(1));
if (!signatures.IsInstantiated() || !sig0.IsFunctionType() ||
!sig1.IsFunctionType()) {
const auto& msg = String::Handle(String::NewFormatted(
"Invalid type arguments passed to dart:ffi _asFunctionInternal: %s",
String::Handle(signatures.UserVisibleName()).ToCString()));
const auto& language_error =
Error::Handle(LanguageError::New(msg, Report::kError, Heap::kOld));
Report::LongJump(language_error);
}
const auto& dart_type = FunctionType::Cast(sig0);
const auto& native_type = FunctionType::Cast(sig1);
// AbiSpecificTypes can have an incomplete mapping.
const char* error = nullptr;
compiler::ffi::NativeFunctionTypeFromFunctionType(zone_, native_type, &error);
if (error != nullptr) {
const auto& language_error = Error::Handle(
LanguageError::New(String::Handle(String::New(error, Heap::kOld)),
Report::kError, Heap::kOld));
Report::LongJump(language_error);
}
const auto& name =
String::Handle(parsed_function_->function().UserVisibleName());
const Function& target = Function::ZoneHandle(
compiler::ffi::TrampolineFunction(dart_type, native_type, is_leaf, name));
Fragment code;
// Store the pointer in the context, we cannot load the untagged address
// here as these can be unoptimized call sites.
LocalVariable* pointer = MakeTemporary();
code += Constant(target);
auto& context_slots = CompilerState::Current().GetDummyContextSlots(
/*context_id=*/0, /*num_variables=*/1);
code += AllocateContext(context_slots);
LocalVariable* context = MakeTemporary();
code += LoadLocal(context);
code += LoadLocal(pointer);
code += StoreNativeField(*context_slots[0]);
code += AllocateClosure();
// Drop address.
code += DropTempsPreserveTop(1);
return code;
}
Fragment BaseFlowGraphBuilder::DebugStepCheck(TokenPosition position) {
#ifdef PRODUCT
return Fragment();
@@ -405,12 +405,6 @@ class BaseFlowGraphBuilder {
return stack_ == nullptr ? 0 : stack_->definition()->temp_index() + 1;
}
// Builds the graph for an invocation of '_asFunctionInternal'.
//
// 'signatures' contains the pair [<dart signature>, <native signature>].
Fragment BuildFfiAsFunctionInternalCall(const TypeArguments& signatures,
bool is_leaf);
Fragment AllocateObject(TokenPosition position,
const Class& klass,
intptr_t argument_count);
@@ -3348,8 +3348,8 @@ Fragment StreamingFlowGraphBuilder::BuildStaticInvocation(TokenPosition* p) {
return BuildNativeEffect();
case MethodRecognizer::kReachabilityFence:
return BuildReachabilityFence();
case MethodRecognizer::kFfiAsFunctionInternal:
return BuildFfiAsFunctionInternal();
case MethodRecognizer::kFfiCall:
return BuildFfiCall();
case MethodRecognizer::kFfiNativeCallbackFunction:
return BuildFfiNativeCallbackFunction(
FfiFunctionKind::kIsolateLocalStaticCallback);
@@ -6221,34 +6221,46 @@ Fragment StreamingFlowGraphBuilder::BuildStoreAbiSpecificInt(bool at_index) {
return code;
}
Fragment StreamingFlowGraphBuilder::BuildFfiAsFunctionInternal() {
Fragment StreamingFlowGraphBuilder::BuildFfiCall() {
const intptr_t argc = ReadUInt(); // Read argument count.
ASSERT(argc == 2); // Pointer, isLeaf.
ASSERT(argc == 1); // Target pointer.
const intptr_t list_length = ReadListLength(); // Read types list length.
ASSERT(list_length == 2); // Dart signature, then native signature
// Read types.
const TypeArguments& type_arguments = T.BuildTypeArguments(list_length);
Fragment code;
T.BuildTypeArguments(list_length); // Read types.
// Read positional argument count.
const intptr_t positional_count = ReadListLength();
ASSERT(positional_count == 2);
code += BuildExpression(); // Build first positional argument (pointer).
ASSERT(positional_count == argc);
// The second argument, `isLeaf`, is only used internally and dictates whether
// we can do a lightweight leaf function call.
bool is_leaf = false;
Fragment frag = BuildExpression();
ASSERT(frag.entry->IsConstant());
if (frag.entry->AsConstant()->value().ptr() == Object::bool_true().ptr()) {
is_leaf = true;
}
Pop();
Fragment code;
// Push the target function pointer passed as Pointer object.
code += BuildExpression();
// This can only be Pointer, so it is always safe to LoadUntagged.
code += B->LoadUntagged(compiler::target::PointerBase::data_offset());
code += B->ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
// Skip (empty) named arguments list.
const intptr_t named_args_len = ReadListLength();
ASSERT(named_args_len == 0);
code += B->BuildFfiAsFunctionInternalCall(type_arguments, is_leaf);
const auto& native_type = FunctionType::ZoneHandle(
Z, parsed_function()->function().FfiCSignature());
// AbiSpecificTypes can have an incomplete mapping.
const char* error = nullptr;
compiler::ffi::NativeFunctionTypeFromFunctionType(Z, native_type, &error);
if (error != nullptr) {
const auto& language_error = Error::Handle(
LanguageError::New(String::Handle(String::New(error, Heap::kOld)),
Report::kError, Heap::kOld));
Report::LongJump(language_error);
}
code += B->FfiCallFunctionBody(parsed_function()->function(), native_type,
/*first_argument_parameter_offset=*/1);
ASSERT(code.is_closed());
NullConstant(); // Maintain stack balance.
return code;
}
@@ -387,9 +387,8 @@ class StreamingFlowGraphBuilder : public KernelReaderHelper {
Fragment BuildLoadAbiSpecificInt(bool at_index);
Fragment BuildStoreAbiSpecificInt(bool at_index);
// Build FG for '_asFunctionInternal'. Reads an Arguments from the
// Kernel buffer and pushes the resulting closure.
Fragment BuildFfiAsFunctionInternal();
// Build FG for FFI call.
Fragment BuildFfiCall();
// Build FG for '_nativeCallbackFunction'. Reads an Arguments from the
// Kernel buffer and pushes the resulting Function object.
+12 -57
View File
@@ -400,11 +400,12 @@ Fragment FlowGraphBuilder::InstanceCall(
}
Fragment FlowGraphBuilder::FfiCall(
const compiler::ffi::CallMarshaller& marshaller) {
const compiler::ffi::CallMarshaller& marshaller,
bool is_leaf) {
Fragment body;
FfiCallInstr* const call = new (Z) FfiCallInstr(
GetNextDeoptId(), marshaller, parsed_function_->function().FfiIsLeaf());
FfiCallInstr* const call =
new (Z) FfiCallInstr(GetNextDeoptId(), marshaller, is_leaf);
for (intptr_t i = call->InputCount() - 1; i >= 0; --i) {
call->SetInputAt(i, Pop());
@@ -5037,7 +5038,8 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline(
case FfiFunctionKind::kAsyncCallback:
return BuildGraphOfAsyncFfiCallback(function);
case FfiFunctionKind::kCall:
return BuildGraphOfFfiCall(function);
UNREACHABLE();
return nullptr;
}
UNREACHABLE();
return nullptr;
@@ -5122,26 +5124,6 @@ Fragment FlowGraphBuilder::FfiNativeLookupAddress(const Function& function) {
return FfiNativeLookupAddress(native_instance);
}
Fragment FlowGraphBuilder::FfiCallLookupAddress(const Function& function) {
ASSERT(function.IsFfiTrampoline());
const intptr_t kClosureParameterOffset = 0;
Fragment body;
// Push the function pointer, which is stored (as Pointer object) in the
// first slot of the context.
body +=
LoadLocal(parsed_function_->ParameterVariable(kClosureParameterOffset));
body += LoadNativeField(Slot::Closure_context());
body += LoadNativeField(Slot::GetContextVariableSlotFor(
thread_, *MakeImplicitClosureScope(
Z, Class::Handle(IG->object_store()->ffi_pointer_class()))
->context_variables()[0]));
// This can only be Pointer, so it is always safe to LoadUntagged.
body += LoadUntagged(compiler::target::PointerBase::data_offset());
body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
return body;
}
Fragment FlowGraphBuilder::FfiNativeFunctionBody(const Function& function) {
ASSERT(function.is_ffi_native());
ASSERT(!IsRecognizedMethodForFlowGraph(function));
@@ -5151,18 +5133,16 @@ Fragment FlowGraphBuilder::FfiNativeFunctionBody(const Function& function) {
Fragment body;
body += FfiNativeLookupAddress(function);
body += FfiCallFunctionBody(function, c_signature);
body += FfiCallFunctionBody(function, c_signature,
/*first_argument_parameter_offset=*/0);
return body;
}
Fragment FlowGraphBuilder::FfiCallFunctionBody(
const Function& function,
const FunctionType& c_signature) {
ASSERT(function.is_ffi_native() || function.IsFfiTrampoline());
const bool is_ffi_native = function.is_ffi_native();
const intptr_t kClosureParameterOffset = 0;
const intptr_t first_argument_parameter_offset =
is_ffi_native ? 0 : kClosureParameterOffset + 1;
const FunctionType& c_signature,
intptr_t first_argument_parameter_offset) {
ASSERT(function.is_ffi_native() || function.IsFfiCallClosure());
LocalVariable* address = MakeTemporary("address");
@@ -5252,7 +5232,7 @@ Fragment FlowGraphBuilder::FfiCallFunctionBody(
body += LoadLocal(return_compound_typed_data);
}
body += FfiCall(marshaller);
body += FfiCall(marshaller, function.FfiIsLeaf());
for (intptr_t i = 0; i < marshaller.num_args(); i++) {
if (marshaller.IsPointer(i)) {
@@ -5315,31 +5295,6 @@ Fragment FlowGraphBuilder::FfiCallFunctionBody(
return body;
}
FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCall(const Function& function) {
graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
auto normal_entry = BuildFunctionEntry(graph_entry_);
graph_entry_->set_normal_entry(normal_entry);
PrologueInfo prologue_info(-1, -1);
BlockEntryInstr* instruction_cursor =
BuildPrologue(normal_entry, &prologue_info);
Fragment function_body(instruction_cursor);
function_body += CheckStackOverflowInPrologue(function.token_pos());
const auto& c_signature =
FunctionType::ZoneHandle(Z, function.FfiCSignature());
function_body += FfiCallLookupAddress(function);
function_body += FfiCallFunctionBody(function, c_signature);
return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_,
prologue_info);
}
Fragment FlowGraphBuilder::LoadNativeArg(
const compiler::ffi::CallbackMarshaller& marshaller,
intptr_t arg_index) {
+4 -4
View File
@@ -137,8 +137,6 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
FlowGraph* BuildGraphOfFfiTrampoline(const Function& function);
FlowGraph* BuildGraphOfSyncFfiCallback(const Function& function);
FlowGraph* BuildGraphOfAsyncFfiCallback(const Function& function);
FlowGraph* BuildGraphOfFfiCall(const Function& function);
Fragment FfiCallLookupAddress(const Function& function);
// Resolves the address of a native symbol from the constant data of a
// vm:ffi:native pragma.
@@ -150,7 +148,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
Fragment FfiNativeLookupAddress(const Function& function);
// Expects target address on stack.
Fragment FfiCallFunctionBody(const Function& function,
const FunctionType& c_signature);
const FunctionType& c_signature,
intptr_t first_argument_parameter_offset);
Fragment FfiNativeFunctionBody(const Function& function);
Fragment NativeFunctionBody(const Function& function,
LocalVariable* first_parameter);
@@ -204,7 +203,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
bool receiver_is_not_smi = false,
bool is_call_on_this = false);
Fragment FfiCall(const compiler::ffi::CallMarshaller& marshaller);
Fragment FfiCall(const compiler::ffi::CallMarshaller& marshaller,
bool is_leaf);
Fragment CCall(
const compiler::ffi::NativeCallingConvention& native_calling_convention);
@@ -154,8 +154,7 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
FunctionNodeHelper::kPositionalParameters);
// NOTE: FunctionNode is read further below the if.
intptr_t pos = 0;
if (function.is_ffi_native()) {
if (function.is_ffi_native() || function.IsFfiCallClosure()) {
needs_expr_temp_ = true;
// Calls with handles need try/catch variables.
if (function.FfiCSignatureContainsHandles()) {
@@ -167,7 +166,9 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
FinalizeCatchVariables();
--depth_.catch_;
}
} else if (function.IsClosureFunction()) {
}
intptr_t pos = 0;
if (function.IsClosureFunction()) {
LocalVariable* closure_parameter = MakeVariable(
TokenPosition::kNoSource, TokenPosition::kNoSource,
Symbols::ClosureParameter(), AbstractType::dynamic_type());
@@ -272,7 +272,7 @@ namespace dart {
V(_WeakReference, get:target, WeakReference_getTarget, 0xc98185aa) \
V(_WeakReference, set:_target, WeakReference_setTarget, 0xc71add9a) \
V(::, _abi, FfiAbi, 0x7c3c2b95) \
V(::, _asFunctionInternal, FfiAsFunctionInternal, 0x630c8491) \
V(::, _ffiCall, FfiCall, 0x6118e962) \
V(::, _nativeCallbackFunction, FfiNativeCallbackFunction, 0x3fe722bc) \
V(::, _nativeAsyncCallbackFunction, FfiNativeAsyncCallbackFunction, \
0xbec4b7b9) \
+50 -17
View File
@@ -8384,10 +8384,16 @@ FunctionTypePtr Function::FfiCSignature() const {
ASSERT(!obj.IsNull());
return FfiTrampolineData::Cast(obj).c_signature();
}
ASSERT(is_ffi_native());
auto const& native_instance = Instance::Handle(GetNativeAnnotation());
auto& pragma_value = Instance::Handle(zone);
if (is_ffi_native()) {
pragma_value = GetNativeAnnotation();
} else if (IsFfiCallClosure()) {
pragma_value = GetFfiCallClosurePragmaValue();
} else {
UNREACHABLE();
}
const auto& type_args =
TypeArguments::Handle(zone, native_instance.GetTypeArguments());
TypeArguments::Handle(zone, pragma_value.GetTypeArguments());
ASSERT(type_args.Length() == 1);
const auto& native_type =
FunctionType::Cast(AbstractType::ZoneHandle(zone, type_args.TypeAt(0)));
@@ -8465,22 +8471,29 @@ void Function::AssignFfiCallbackId(int32_t callback_id) const {
}
bool Function::FfiIsLeaf() const {
Zone* zone = Thread::Current()->zone();
if (IsFfiTrampoline()) {
const Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
return FfiTrampolineData::Cast(obj).is_leaf();
}
ASSERT(is_ffi_native());
Zone* zone = Thread::Current()->zone();
auto const& native_instance = Instance::Handle(GetNativeAnnotation());
const auto& native_class = Class::Handle(zone, native_instance.clazz());
const auto& native_class_fields = Array::Handle(zone, native_class.fields());
ASSERT(native_class_fields.Length() == 4);
const auto& is_leaf_field =
Field::Handle(zone, Field::RawCast(native_class_fields.At(3)));
ASSERT(!is_leaf_field.is_static());
return Bool::Handle(zone,
Bool::RawCast(native_instance.GetField(is_leaf_field)))
auto& pragma_value = Instance::Handle(zone);
if (is_ffi_native()) {
pragma_value = GetNativeAnnotation();
} else if (IsFfiCallClosure()) {
pragma_value = GetFfiCallClosurePragmaValue();
} else {
UNREACHABLE();
}
const auto& pragma_value_class = Class::Handle(zone, pragma_value.clazz());
const auto& pragma_value_fields =
Array::Handle(zone, pragma_value_class.fields());
ASSERT(pragma_value_fields.Length() >= 1);
const auto& is_leaf_field = Field::Handle(
zone,
Field::RawCast(pragma_value_fields.At(pragma_value_fields.Length() - 1)));
ASSERT(is_leaf_field.name() == Symbols::isLeaf().ptr());
return Bool::Handle(zone, Bool::RawCast(pragma_value.GetField(is_leaf_field)))
.value();
}
@@ -9132,8 +9145,9 @@ static bool InVmTests(const Function& function) {
}
bool Function::ForceOptimize() const {
if (RecognizedKindForceOptimize() || IsFfiTrampoline() || is_ffi_native() ||
IsTypedDataViewFactory() || IsUnmodifiableTypedDataViewFactory()) {
if (RecognizedKindForceOptimize() || IsFfiCallClosure() ||
IsFfiTrampoline() || is_ffi_native() || IsTypedDataViewFactory() ||
IsUnmodifiableTypedDataViewFactory()) {
return true;
}
@@ -9173,6 +9187,25 @@ bool Function::IsCachableIdempotent() const {
return InVmTests(*this);
}
bool Function::IsFfiCallClosure() const {
if (!IsNonImplicitClosureFunction()) return false;
if (!has_pragma()) return false;
return Library::FindPragma(Thread::Current(), /*only_core=*/false, *this,
Symbols::vm_ffi_call_closure());
}
InstancePtr Function::GetFfiCallClosurePragmaValue() const {
ASSERT(IsFfiCallClosure());
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto& pragma_value = Object::Handle(zone);
Library::FindPragma(thread, /*only_core=*/false, *this,
Symbols::vm_ffi_call_closure(),
/*multiple=*/false, &pragma_value);
ASSERT(!pragma_value.IsNull());
return Instance::Cast(pragma_value).ptr();
}
bool Function::RecognizedKindForceOptimize() const {
switch (recognized_kind()) {
// Uses unboxed/untagged data not supported in unoptimized.
@@ -9247,7 +9280,7 @@ bool Function::RecognizedKindForceOptimize() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
bool Function::CanBeInlined() const {
if (ForceOptimize()) {
if (IsFfiTrampoline() || is_ffi_native()) {
if (IsFfiCallClosure() || IsFfiTrampoline() || is_ffi_native()) {
// We currently don't support inlining FFI trampolines. Some of them
// are naturally non-inlinable because they contain a try/catch block,
// but this condition is broader than strictly necessary.
+7
View File
@@ -3911,6 +3911,13 @@ class Function : public Object {
UntaggedFunction::kFfiTrampoline;
}
// Returns true if this function is a closure function
// used to represent ffi call.
bool IsFfiCallClosure() const;
// Returns value of vm:ffi:call-closure pragma.
InstancePtr GetFfiCallClosurePragmaValue() const;
// Returns true for functions which execution can be suspended
// using Suspend/Resume stubs. Such functions have an artificial
// :suspend_state local variable at the fixed location of the frame.
+2
View File
@@ -501,6 +501,7 @@ class ObjectPointerVisitor;
V(from, "from") \
V(get, "get") \
V(index_temp, ":index_temp") \
V(isLeaf, "isLeaf") \
V(isPaused, "isPaused") \
V(match_end_index, ":match_end_index") \
V(match_start_index, ":match_start_index") \
@@ -529,6 +530,7 @@ class ObjectPointerVisitor;
V(vm_exact_result_type, "vm:exact-result-type") \
V(vm_external_name, "vm:external-name") \
V(vm_ffi_abi_specific_mapping, "vm:ffi:abi-specific-mapping") \
V(vm_ffi_call_closure, "vm:ffi:call-closure") \
V(vm_ffi_native, "vm:ffi:native") \
V(vm_ffi_native_assets, "vm:ffi:native-assets") \
V(vm_ffi_struct_fields, "vm:ffi:struct-fields") \
+14 -6
View File
@@ -82,13 +82,21 @@ int sizeOf<T extends NativeType>() {
@pragma("vm:idempotent")
external Pointer<T> _fromAddress<T extends NativeType>(int ptr);
// The real implementation of this function (for interface calls) lives in
// BuildFfiAsFunctionInternal in the Kernel frontend. No calls can actually
// reach this function.
/// Argument for vm:ffi:call-closure pragma describing FFI call.
final class _FfiCall<NativeSignature> {
// Implementation note: VM hardcodes the layout of this class (number and
// order of its fields), so adding/removing/changing fields requires
// updating the VM code (see Function::GetFfiCallClosurePragmaValue()).
final bool isLeaf;
const _FfiCall({this.isLeaf = false});
}
// Helper function to perform FFI call.
// Inserted by FFI kernel transformation into the FFI call closures.
// Implemented in BuildFfiCall
// in runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc.
@pragma("vm:recognized", "other")
@pragma("vm:external-name", "Ffi_asFunctionInternal")
external DS _asFunctionInternal<DS extends Function, NS extends Function>(
Pointer<NativeFunction<NS>> ptr, bool isLeaf);
external ReturnType _ffiCall<ReturnType>(Pointer<NativeFunction> target);
@pragma("vm:recognized", "other")
@pragma("vm:idempotent")
+5 -1
View File
@@ -69,7 +69,7 @@ final class Pointer<T extends NativeType> extends NativeType {
/// On 32-bit systems, the upper 32-bits of the result are 0.
external int get address;
/// Cast Pointer<T> to a Pointer<V>.
/// Cast Pointer<T> to a Pointer<U>.
external Pointer<U> cast<U extends NativeType>();
/// Equality for Pointers only depends on their address.
@@ -1157,6 +1157,10 @@ abstract final class NativeApi {
/// NOTE: This is an experimental feature and may change in the future.
@Since('2.19')
final class Native<T> {
// Implementation note: VM hardcodes the layout of this class (number and
// order of its fields), so adding/removing/changing fields requires
// updating the VM code (see Function::GetNativeAnnotation()).
/// The native symbol to be resolved, if not using the default.
///
/// If not specified, the default symbol used for native function lookup
@@ -116,21 +116,27 @@ void testAsFunction() {
Expect.throws(() {
nullptr
.cast<NativeFunction<Int32 Function(Incomplete)>>()
.asFunction<int Function(int)>();
.asFunction<int Function(int)>()
.call(42);
});
Expect.throws(() {
nullptr
.cast<NativeFunction<Incomplete Function(Int32)>>()
.asFunction<int Function(int)>();
.asFunction<int Function(int)>()
.call(42);
});
final p = calloc<Int64>(100).cast<IncompleteArrayStruct>();
Expect.throws(() {
nullptr
.cast<NativeFunction<Int32 Function(IncompleteArrayStruct)>>()
.asFunction<int Function(IncompleteArrayStruct)>();
.asFunction<int Function(IncompleteArrayStruct)>()
.call(p.ref);
});
calloc.free(p);
Expect.throws(() {
nullptr
.cast<NativeFunction<IncompleteArrayStruct Function()>>()
.asFunction<IncompleteArrayStruct Function()>();
.asFunction<IncompleteArrayStruct Function()>()
.call();
});
}
+7 -1
View File
@@ -459,6 +459,12 @@ void testNoArgs() {
Expect.approxEquals(1337.0, result);
}
// Returns a possibly ofuscated 'arg2' identifier.
String get arg2ObfuscatedName {
final str = (arg2: 0).toString();
return str.substring('('.length, str.length - ': 0)'.length);
}
void testNativeFunctionNullableInt() {
final sumPlus42 = ffiTestFunctions.lookupFunction<
Int32 Function(Int32, Int32), int Function(int, int?)>("SumPlus42");
@@ -467,7 +473,7 @@ void testNativeFunctionNullableInt() {
sumPlus42(3, null);
} catch (e) {
// TODO(http://dartbug.com/47098): Save param names to dwarf.
Expect.isTrue(e.toString().contains('ffi_param2') ||
Expect.isTrue(e.toString().contains(arg2ObfuscatedName) ||
e.toString().contains('<optimized out>'));
}
}