Skip to content

Commit fd2e9b9

Browse files
alexmarkovCommit Queue
authored and
Commit Queue
committed
[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 #54172 Issue #39692 CoreLibraryReviewExempt: Implementation change only. Change-Id: I92b3ff7391470686151ad0807e2cdbbf1a69d256 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/339662 Commit-Queue: Alexander Markov <[email protected]> Reviewed-by: Daco Harkes <[email protected]>
1 parent aca875d commit fd2e9b9

25 files changed

+461
-240
lines changed

pkg/vm/lib/transformations/ffi/common.dart

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ class FfiTransformer extends Transformer {
230230
final Procedure abiSpecificIntegerArrayElemAt;
231231
final Procedure abiSpecificIntegerArraySetElemAt;
232232
final Procedure asFunctionMethod;
233-
final Procedure asFunctionInternal;
233+
final Procedure ffiCallMethod;
234234
final Procedure sizeOfMethod;
235235
final Procedure lookupFunctionMethod;
236236
final Procedure fromFunctionMethod;
@@ -283,6 +283,8 @@ class FfiTransformer extends Transformer {
283283
final Field nativeCallablePointerField;
284284
final Procedure nativeAddressOf;
285285
final Procedure nativePrivateAddressOf;
286+
final Class ffiCallClass;
287+
final Field ffiCallIsLeafField;
286288

287289
late final InterfaceType nativeFieldWrapperClass1Type;
288290
late final InterfaceType voidType;
@@ -463,8 +465,7 @@ class FfiTransformer extends Transformer {
463465
index.getProcedure('dart:ffi', 'AbiSpecificIntegerArray', '[]='),
464466
asFunctionMethod = index.getProcedure(
465467
'dart:ffi', 'NativeFunctionPointer', 'asFunction'),
466-
asFunctionInternal =
467-
index.getTopLevelProcedure('dart:ffi', '_asFunctionInternal'),
468+
ffiCallMethod = index.getTopLevelProcedure('dart:ffi', '_ffiCall'),
468469
sizeOfMethod = index.getTopLevelProcedure('dart:ffi', 'sizeOf'),
469470
lookupFunctionMethod = index.getProcedure(
470471
'dart:ffi', 'DynamicLibraryExtension', 'lookupFunction'),
@@ -571,7 +572,9 @@ class FfiTransformer extends Transformer {
571572
nativeAddressOf =
572573
index.getMember('dart:ffi', 'Native', 'addressOf') as Procedure,
573574
nativePrivateAddressOf =
574-
index.getMember('dart:ffi', 'Native', '_addressOf') as Procedure {
575+
index.getMember('dart:ffi', 'Native', '_addressOf') as Procedure,
576+
ffiCallClass = index.getClass('dart:ffi', '_FfiCall'),
577+
ffiCallIsLeafField = index.getField('dart:ffi', '_FfiCall', 'isLeaf') {
575578
nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
576579
coreTypes, Nullability.nonNullable);
577580
voidType = nativeTypesClasses[NativeType.kVoid]!
@@ -1199,37 +1202,6 @@ class FfiTransformer extends Transformer {
11991202
..fileOffset = nestedExpression.fileOffset;
12001203
}
12011204

1202-
/// Creates an invocation to asFunctionInternal.
1203-
///
1204-
/// Adds a native effect invoking a compound constructors if this is used
1205-
/// as return type.
1206-
Expression buildAsFunctionInternal({
1207-
required Expression functionPointer,
1208-
required DartType nativeSignature,
1209-
required DartType dartSignature,
1210-
required bool isLeaf,
1211-
required int fileOffset,
1212-
}) {
1213-
final asFunctionInternalInvocation = StaticInvocation(
1214-
asFunctionInternal,
1215-
Arguments([
1216-
functionPointer,
1217-
BoolLiteral(isLeaf),
1218-
], types: [
1219-
dartSignature,
1220-
nativeSignature,
1221-
]))
1222-
..fileOffset = fileOffset;
1223-
1224-
final possibleCompoundReturn = findCompoundReturnType(dartSignature);
1225-
if (possibleCompoundReturn != null) {
1226-
return invokeCompoundConstructor(
1227-
asFunctionInternalInvocation, possibleCompoundReturn);
1228-
}
1229-
1230-
return asFunctionInternalInvocation;
1231-
}
1232-
12331205
/// Returns the compound [Class] if a compound is returned, otherwise `null`.
12341206
Class? findCompoundReturnType(DartType dartSignature) {
12351207
if (dartSignature is! FunctionType) {

pkg/vm/lib/transformations/ffi/use_sites.dart

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,14 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
113113
// callback.
114114
int callbackCount = 0;
115115

116+
// Used to create private top-level trampoline methods with unique names
117+
// for each call.
118+
int callCount = 0;
119+
116120
@override
117121
TreeNode visitLibrary(Library node) {
118122
callbackCount = 0;
123+
callCount = 0;
119124
return super.visitLibrary(node);
120125
}
121126

@@ -349,10 +354,12 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
349354
);
350355
final DartType nativeSignature = nativeType.typeArguments[0];
351356

352-
return buildAsFunctionInternal(
357+
return _replaceAsFunction(
353358
functionPointer: node.arguments.positional[0],
359+
pointerType: InterfaceType(
360+
pointerClass, Nullability.nonNullable, [nativeType]),
354361
nativeSignature: nativeSignature,
355-
dartSignature: dartType,
362+
dartSignature: dartType as FunctionType,
356363
isLeaf: isLeaf,
357364
fileOffset: node.fileOffset,
358365
);
@@ -428,6 +435,84 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
428435
return node;
429436
}
430437

438+
/// Create Dart function which calls native code.
439+
///
440+
/// Adds a native effect invoking a compound constructors if this is used
441+
/// as return type.
442+
Expression _replaceAsFunction({
443+
required Expression functionPointer,
444+
required DartType pointerType,
445+
required DartType nativeSignature,
446+
required FunctionType dartSignature,
447+
required bool isLeaf,
448+
required int fileOffset,
449+
}) {
450+
assert(dartSignature.namedParameters.isEmpty);
451+
final functionPointerVarName = '#ffiTarget$callCount';
452+
final closureName = '#ffiClosure$callCount';
453+
++callCount;
454+
455+
final pointerVar = VariableDeclaration(functionPointerVarName,
456+
initializer: functionPointer, type: pointerType, isSynthesized: true);
457+
458+
final positionalParameters = [
459+
for (int i = 0; i < dartSignature.positionalParameters.length; ++i)
460+
VariableDeclaration(
461+
'arg${i + 1}',
462+
type: dartSignature.positionalParameters[i],
463+
)
464+
];
465+
466+
final closure = FunctionDeclaration(
467+
VariableDeclaration(closureName,
468+
type: dartSignature, isSynthesized: true)
469+
..addAnnotation(ConstantExpression(
470+
InstanceConstant(coreTypes.pragmaClass.reference, [], {
471+
coreTypes.pragmaName.fieldReference:
472+
StringConstant('vm:ffi:call-closure'),
473+
coreTypes.pragmaOptions.fieldReference: InstanceConstant(
474+
ffiCallClass.reference,
475+
[nativeSignature],
476+
{
477+
ffiCallIsLeafField.fieldReference: BoolConstant(isLeaf),
478+
},
479+
),
480+
}))),
481+
FunctionNode(
482+
Block([
483+
for (final param in positionalParameters)
484+
ExpressionStatement(StaticInvocation(
485+
nativeEffectMethod, Arguments([VariableGet(param)]))),
486+
ReturnStatement(StaticInvocation(
487+
ffiCallMethod,
488+
Arguments([
489+
VariableGet(pointerVar),
490+
], types: [
491+
dartSignature.returnType,
492+
]))
493+
..fileOffset = fileOffset),
494+
]),
495+
positionalParameters: positionalParameters,
496+
requiredParameterCount: dartSignature.requiredParameterCount,
497+
returnType: dartSignature.returnType)
498+
..fileOffset = fileOffset)
499+
..fileOffset = fileOffset;
500+
501+
final result = BlockExpression(
502+
Block([
503+
pointerVar,
504+
closure,
505+
]),
506+
VariableGet(closure.variable));
507+
508+
final possibleCompoundReturn = findCompoundReturnType(dartSignature);
509+
if (possibleCompoundReturn != null) {
510+
return invokeCompoundConstructor(result, possibleCompoundReturn);
511+
}
512+
513+
return result;
514+
}
515+
431516
Expression invokeCompoundConstructors(
432517
Expression nestedExpression, List<Class> compoundClasses) =>
433518
compoundClasses
@@ -462,10 +547,6 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
462547
// 'lookupFunction' are constants, so by inlining the call to 'asFunction' at
463548
// the call-site, we ensure that there are no generic calls to 'asFunction'.
464549
Expression _replaceLookupFunction(StaticInvocation node) {
465-
// The generated code looks like:
466-
//
467-
// _asFunctionInternal<DS, NS>(lookup<NativeFunction<NS>>(symbolName),
468-
// isLeaf)
469550
final DartType nativeSignature = node.arguments.types[0];
470551
final DartType dartSignature = node.arguments.types[1];
471552

@@ -478,21 +559,19 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
478559
final FunctionType lookupFunctionType =
479560
libraryLookupMethod.getterType as FunctionType;
480561

481-
final Expression lookupResult = InstanceInvocation(
482-
InstanceAccessKind.Instance,
483-
node.arguments.positional[0],
484-
libraryLookupMethod.name,
485-
lookupArgs,
562+
final lookupResult = InstanceInvocation(InstanceAccessKind.Instance,
563+
node.arguments.positional[0], libraryLookupMethod.name, lookupArgs,
486564
interfaceTarget: libraryLookupMethod,
487565
functionType: FunctionTypeInstantiator.instantiate(
488566
lookupFunctionType, lookupTypeArgs));
489567

490568
final isLeaf = getIsLeafBoolean(node) ?? false;
491569

492-
return buildAsFunctionInternal(
570+
return _replaceAsFunction(
493571
functionPointer: lookupResult,
572+
pointerType: lookupResult.functionType.returnType,
494573
nativeSignature: nativeSignature,
495-
dartSignature: dartSignature,
574+
dartSignature: dartSignature as FunctionType,
496575
isLeaf: isLeaf,
497576
fileOffset: node.fileOffset,
498577
);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Tests for NativeFunctionPointer.asFunction transformation.
6+
7+
import 'dart:ffi';
8+
9+
testVoidNoArg() {
10+
final pointer =
11+
Pointer<NativeFunction<Void Function()>>.fromAddress(0xdeadbeef);
12+
final function = pointer.asFunction<void Function()>();
13+
function();
14+
}
15+
16+
testIntInt() {
17+
final pointer =
18+
Pointer<NativeFunction<Int32 Function(Int64)>>.fromAddress(0xdeadbeef);
19+
final function = pointer.asFunction<int Function(int)>();
20+
return function(42);
21+
}
22+
23+
testLeaf5Args() {
24+
final pointer = Pointer<
25+
NativeFunction<
26+
Int32 Function(
27+
Int32, Int32, Int32, Int32, Int32)>>.fromAddress(0xdeadbeef);
28+
final function =
29+
pointer.asFunction<int Function(int, int, int, int, int)>(isLeaf: true);
30+
return function(1, 2, 3, 4, 5);
31+
}
32+
33+
void main() {
34+
testVoidNoArg();
35+
testIntInt();
36+
testLeaf5Args();
37+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
library #lib;
2+
import self as self;
3+
import "dart:ffi" as ffi;
4+
import "dart:core" as core;
5+
import "dart:_internal" as _in;
6+
7+
import "dart:ffi";
8+
9+
static method testVoidNoArg() → dynamic {
10+
final ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<() → ffi::Void>>(3735928559);
11+
final () → void function = block {
12+
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> #ffiTarget0 = pointer;
13+
@#C4
14+
function #ffiClosure0() → void {
15+
return ffi::_ffiCall<void>(#ffiTarget0);
16+
}
17+
} =>#ffiClosure0;
18+
function(){() → void};
19+
}
20+
[@vm.unboxing-info.metadata=()->i]static method testIntInt() → dynamic {
21+
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);
22+
final (core::int) → core::int function = block {
23+
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> #ffiTarget1 = pointer;
24+
@#C6
25+
function #ffiClosure1(core::int arg1) → core::int {
26+
_in::_nativeEffect(arg1);
27+
return ffi::_ffiCall<core::int>(#ffiTarget1);
28+
}
29+
} =>#ffiClosure1;
30+
return function(42){(core::int) → core::int};
31+
}
32+
[@vm.unboxing-info.metadata=()->i]static method testLeaf5Args() → dynamic {
33+
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);
34+
final (core::int, core::int, core::int, core::int, core::int) → core::int function = block {
35+
[@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;
36+
@#C9
37+
function #ffiClosure2(core::int arg1, core::int arg2, core::int arg3, core::int arg4, core::int arg5) → core::int {
38+
_in::_nativeEffect(arg1);
39+
_in::_nativeEffect(arg2);
40+
_in::_nativeEffect(arg3);
41+
_in::_nativeEffect(arg4);
42+
_in::_nativeEffect(arg5);
43+
return ffi::_ffiCall<core::int>(#ffiTarget2);
44+
}
45+
} =>#ffiClosure2;
46+
return function(1, 2, 3, 4, 5){(core::int, core::int, core::int, core::int, core::int) → core::int};
47+
}
48+
static method main() → void {
49+
self::testVoidNoArg();
50+
self::testIntInt();
51+
self::testLeaf5Args();
52+
}
53+
constants {
54+
#C1 = "vm:ffi:call-closure"
55+
#C2 = false
56+
#C3 = ffi::_FfiCall<() → ffi::Void> {isLeaf:#C2}
57+
#C4 = core::pragma {name:#C1, options:#C3}
58+
#C5 = ffi::_FfiCall<(ffi::Int64) → ffi::Int32> {isLeaf:#C2}
59+
#C6 = core::pragma {name:#C1, options:#C5}
60+
#C7 = true
61+
#C8 = ffi::_FfiCall<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32> {isLeaf:#C7}
62+
#C9 = core::pragma {name:#C1, options:#C8}
63+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
library #lib;
2+
import self as self;
3+
import "dart:ffi" as ffi;
4+
import "dart:core" as core;
5+
import "dart:_internal" as _in;
6+
7+
import "dart:ffi";
8+
9+
static method testVoidNoArg() → dynamic {
10+
final ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> pointer = ffi::Pointer::fromAddress<ffi::NativeFunction<() → ffi::Void>>(3735928559);
11+
final () → void function = block {
12+
synthesized ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> #ffiTarget0 = pointer;
13+
@#C4
14+
function #ffiClosure0() → void {
15+
return ffi::_ffiCall<void>(#ffiTarget0);
16+
}
17+
} =>#ffiClosure0;
18+
function(){() → void};
19+
}
20+
static method testIntInt() → dynamic {
21+
final ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> pointer = ffi::Pointer::fromAddress<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>>(3735928559);
22+
final (core::int) → core::int function = block {
23+
synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> #ffiTarget1 = pointer;
24+
@#C6
25+
function #ffiClosure1(core::int arg1) → core::int {
26+
_in::_nativeEffect(arg1);
27+
return ffi::_ffiCall<core::int>(#ffiTarget1);
28+
}
29+
} =>#ffiClosure1;
30+
return function(42){(core::int) → core::int};
31+
}
32+
static method testLeaf5Args() → dynamic {
33+
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);
34+
final (core::int, core::int, core::int, core::int, core::int) → core::int function = block {
35+
synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>> #ffiTarget2 = pointer;
36+
@#C9
37+
function #ffiClosure2(core::int arg1, core::int arg2, core::int arg3, core::int arg4, core::int arg5) → core::int {
38+
_in::_nativeEffect(arg1);
39+
_in::_nativeEffect(arg2);
40+
_in::_nativeEffect(arg3);
41+
_in::_nativeEffect(arg4);
42+
_in::_nativeEffect(arg5);
43+
return ffi::_ffiCall<core::int>(#ffiTarget2);
44+
}
45+
} =>#ffiClosure2;
46+
return function(1, 2, 3, 4, 5){(core::int, core::int, core::int, core::int, core::int) → core::int};
47+
}
48+
static method main() → void {
49+
self::testVoidNoArg();
50+
self::testIntInt();
51+
self::testLeaf5Args();
52+
}
53+
constants {
54+
#C1 = "vm:ffi:call-closure"
55+
#C2 = false
56+
#C3 = ffi::_FfiCall<() → ffi::Void> {isLeaf:#C2}
57+
#C4 = core::pragma {name:#C1, options:#C3}
58+
#C5 = ffi::_FfiCall<(ffi::Int64) → ffi::Int32> {isLeaf:#C2}
59+
#C6 = core::pragma {name:#C1, options:#C5}
60+
#C7 = true
61+
#C8 = ffi::_FfiCall<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32> {isLeaf:#C7}
62+
#C9 = core::pragma {name:#C1, options:#C8}
63+
}

0 commit comments

Comments
 (0)