Skip to content

Commit e16bb21

Browse files
committed
[vm/ffi] Optimize @Native calls
This CL removes static fields for storing the `@Native`'s function addresses. Instead, the function addresses are stored in the object pool for all archs except for ia32. ia32 has no AOT and no AppJit snapshots, so the addresses are directly embedded in the code. This CL removes the closure wrapping for `@Native`s. Instead of `pointer.asFunctionInternal()()` where `asFunction` returns a closure which contains the trampoline, the function is compiled to a body which contains the trampoline `Native()`. This is possible for `@Native`s because the dylib and symbol names are known statically. Doing the compilation in kernel_to_il instead of a CFE transform enables supporting static linking later. (The alternative would have been to transform in the cfe to a `@pragma('vm:cachable-idempotent')` instead of constructing the IL in kernel_to_il. To enable running resolution in ia32 in kernel_to_il.cc, the resolution function has been made available via `runtime/lib/ffi_dynamic_library.h`. Because the new calls are simply static calls, the TFA can figure out const arguments flowing to these calls. This leads to constant locations in the parameters to FfiCalls. So, this CL also introduces logic to move constants into `NativeLocation`s. TEST=runtime/vm/compiler/backend/il_test.cc TEST=tests/ffi/function_*_native_(leaf_)test.dart TEST=pkg/vm/testcases/transformations/ffi/ffinative_compound_return.dart Closes: #47625 Closes: #51618 Change-Id: Ic5d017005dedcedea40c455c4d24dbe774f91603 CoreLibraryReviewExempt: Internal FFI implementation changes Cq-Include-Trybots: luci.dart.try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64c-try,vm-aot-win-release-x64-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-x64-try,vm-kernel-linux-debug-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-ubsan-linux-release-x64-try,vm-win-debug-arm64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/284300 Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Alexander Markov <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent 600acad commit e16bb21

34 files changed

+1336
-889
lines changed

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,19 +1160,31 @@ class FfiTransformer extends Transformer {
11601160
]))
11611161
..fileOffset = fileOffset;
11621162

1163-
if (dartSignature is FunctionType) {
1164-
final returnType = dartSignature.returnType;
1165-
if (returnType is InterfaceType) {
1166-
final clazz = returnType.classNode;
1167-
if (clazz.superclass == structClass || clazz.superclass == unionClass) {
1168-
return invokeCompoundConstructor(asFunctionInternalInvocation, clazz);
1169-
}
1170-
}
1163+
final possibleCompoundReturn = findCompoundReturnType(dartSignature);
1164+
if (possibleCompoundReturn != null) {
1165+
return invokeCompoundConstructor(
1166+
asFunctionInternalInvocation, possibleCompoundReturn);
11711167
}
11721168

11731169
return asFunctionInternalInvocation;
11741170
}
11751171

1172+
/// Returns the compound [Class] if a compound is returned, otherwise `null`.
1173+
Class? findCompoundReturnType(DartType dartSignature) {
1174+
if (dartSignature is! FunctionType) {
1175+
return null;
1176+
}
1177+
final returnType = dartSignature.returnType;
1178+
if (returnType is! InterfaceType) {
1179+
return null;
1180+
}
1181+
final clazz = returnType.classNode;
1182+
if (clazz.superclass == structClass || clazz.superclass == unionClass) {
1183+
return clazz;
1184+
}
1185+
return null;
1186+
}
1187+
11761188
/// Returns
11771189
/// - `true` if leaf
11781190
/// - `false` if not leaf

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

Lines changed: 119 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -183,81 +183,6 @@ class FfiNativeTransformer extends FfiTransformer {
183183
);
184184
}
185185

186-
// Create field holding the resolved native function pointer.
187-
//
188-
// For:
189-
// @FfiNative<IntPtr Function(Pointer<Void>)>('DoXYZ', isLeaf:true)
190-
// external int doXyz(NativeFieldWrapperClass1 obj);
191-
//
192-
// Create:
193-
// static final _doXyz$FfiNative$ptr =
194-
// Pointer<NativeFunction<IntPtr Function(Pointer<Void>)>>
195-
// .fromAddress(_ffi_resolver('..', 'DoXYZ', 1))
196-
// .asFunction<int Function(Pointer<Void>)>(isLeaf:true);
197-
Field _createResolvedFfiNativeField(
198-
String dartFunctionName,
199-
StringConstant nativeFunctionName,
200-
StringConstant? assetName,
201-
bool isLeaf,
202-
FunctionType dartFunctionType,
203-
FunctionType ffiFunctionType,
204-
int fileOffset,
205-
Uri fileUri,
206-
) {
207-
// Derive number of arguments from the native function signature.
208-
final numberNativeArgs = ffiFunctionType.positionalParameters.length;
209-
210-
final nativeFunctionType = InterfaceType(
211-
nativeFunctionClass,
212-
Nullability.legacy,
213-
<DartType>[ffiFunctionType],
214-
);
215-
216-
// _ffi_resolver('...', 'DoXYZ', 1)
217-
final resolverInvocation = FunctionInvocation(
218-
FunctionAccessKind.FunctionType,
219-
StaticGet(resolverField),
220-
Arguments(<Expression>[
221-
ConstantExpression(
222-
assetName ?? StringConstant(currentLibrary.importUri.toString())),
223-
ConstantExpression(nativeFunctionName),
224-
ConstantExpression(IntConstant(numberNativeArgs)),
225-
]),
226-
functionType: resolverField.type as FunctionType)
227-
..fileOffset = fileOffset;
228-
229-
// _fromAddress<NativeFunction<Double Function(Double)>>(...)
230-
final functionPointerExpression = StaticInvocation(
231-
fromAddressInternal,
232-
Arguments(
233-
<Expression>[resolverInvocation],
234-
types: [nativeFunctionType],
235-
))
236-
..fileOffset = fileOffset;
237-
238-
final asFunctionInvocation = buildAsFunctionInternal(
239-
functionPointer: functionPointerExpression,
240-
dartSignature: dartFunctionType,
241-
nativeSignature: ffiFunctionType,
242-
isLeaf: isLeaf,
243-
fileOffset: fileOffset,
244-
);
245-
246-
// static final _doXyz$FfiNative$Ptr = ...
247-
final fieldName =
248-
Name('_$dartFunctionName\$FfiNative\$Ptr', currentLibrary);
249-
final functionPointerField = Field.immutable(fieldName,
250-
type: dartFunctionType,
251-
initializer: asFunctionInvocation,
252-
isStatic: true,
253-
isFinal: true,
254-
fileUri: fileUri,
255-
getterReference: currentLibraryIndex?.lookupGetterReference(fieldName))
256-
..fileOffset = fileOffset;
257-
258-
return functionPointerField;
259-
}
260-
261186
// Whether a parameter of [dartParameterType], passed as [ffiParameterType],
262187
// needs to be converted to Pointer.
263188
bool _requiresPointerConversion(
@@ -350,7 +275,7 @@ class FfiNativeTransformer extends FfiTransformer {
350275
// reachabilityFence(#t0);
351276
// } => #t1
352277
Expression _wrapArgumentsAndReturn({
353-
required FunctionInvocation invocation,
278+
required StaticInvocation invocation,
354279
required FunctionType dartFunctionType,
355280
required FunctionType ffiFunctionType,
356281
bool checkReceiverForNullptr = false,
@@ -487,6 +412,8 @@ class FfiNativeTransformer extends FfiTransformer {
487412
return validSignature;
488413
}
489414

415+
static const vmFfiNative = 'vm:ffi:native';
416+
490417
Procedure _transformProcedure(
491418
Procedure node,
492419
StringConstant nativeFunctionName,
@@ -508,74 +435,135 @@ class FfiNativeTransformer extends FfiTransformer {
508435
return node;
509436
}
510437

438+
final pragmaConstant = ConstantExpression(
439+
InstanceConstant(pragmaClass.reference, [], {
440+
pragmaName.fieldReference: StringConstant(vmFfiNative),
441+
pragmaOptions.fieldReference: InstanceConstant(
442+
nativeClass.reference,
443+
[ffiFunctionType],
444+
{
445+
nativeSymbolField.fieldReference: nativeFunctionName,
446+
nativeAssetField.fieldReference: assetName ??
447+
StringConstant(currentLibrary.importUri.toString()),
448+
nativeIsLeafField.fieldReference: BoolConstant(isLeaf),
449+
},
450+
)
451+
}),
452+
InterfaceType(
453+
pragmaClass,
454+
Nullability.nonNullable,
455+
[],
456+
),
457+
);
458+
459+
final possibleCompoundReturn = findCompoundReturnType(dartFunctionType);
460+
if (dartFunctionType == wrappedDartFunctionType &&
461+
node.isStatic &&
462+
possibleCompoundReturn == null) {
463+
// We are not wrapping/unwrapping arguments or return value.
464+
node.addAnnotation(pragmaConstant);
465+
return node;
466+
}
467+
468+
// Introduce a new function as external with the annotation and give the
469+
// current function a body that does the wrapping/unwrapping.
470+
node.isExternal = false;
471+
472+
node.addAnnotation(ConstantExpression(
473+
InstanceConstant(pragmaClass.reference, [], {
474+
pragmaName.fieldReference: StringConstant("vm:prefer-inline"),
475+
pragmaOptions.fieldReference: NullConstant(),
476+
}),
477+
));
478+
511479
final parent = node.parent;
480+
var fileUri = currentLibrary.fileUri;
481+
if (parent is Class) {
482+
fileUri = parent.fileUri;
483+
} else if (parent is Library) {
484+
fileUri = parent.fileUri;
485+
}
512486

513-
// static final _myMethod$FfiNative$Ptr = ..
514-
final resolvedField = _createResolvedFfiNativeField(
515-
'${node.name.text}\$${node.kind.name}',
516-
nativeFunctionName,
517-
assetName,
518-
isLeaf,
519-
wrappedDartFunctionType,
520-
ffiFunctionType,
521-
node.fileOffset,
522-
node.fileUri,
523-
);
487+
int varCounter = 0;
488+
final nonWrappedFfiNative = Procedure(
489+
Name('_${node.name.text}\$${node.kind.name}\$FfiNative', currentLibrary),
490+
ProcedureKind.Method,
491+
FunctionNode(
492+
/*body=*/ null,
493+
requiredParameterCount: wrappedDartFunctionType.requiredParameterCount,
494+
positionalParameters: [
495+
for (final positionalParameter
496+
in wrappedDartFunctionType.positionalParameters)
497+
VariableDeclaration(
498+
/*name=*/ '#t${varCounter++}',
499+
type: positionalParameter,
500+
)..fileOffset = node.fileOffset,
501+
],
502+
returnType: wrappedDartFunctionType.returnType,
503+
)..fileOffset = node.fileOffset,
504+
fileUri: fileUri,
505+
isStatic: true,
506+
isExternal: true,
507+
)
508+
..isNonNullableByDefault = node.isNonNullableByDefault
509+
..fileOffset = node.fileOffset;
510+
nonWrappedFfiNative.addAnnotation(pragmaConstant);
524511

525-
// Add field to the parent the FfiNative function belongs to.
512+
// Add procedure to the parent the FfiNative function belongs to.
526513
if (parent is Class) {
527-
parent.addField(resolvedField);
514+
parent.addProcedure(nonWrappedFfiNative);
528515
} else if (parent is Library) {
529-
parent.addField(resolvedField);
516+
parent.addProcedure(nonWrappedFfiNative);
530517
} else {
531-
throw 'Unexpected parent of @FfiNative function. '
518+
throw 'Unexpected parent of @Native function. '
532519
'Expected Class or Library, but found ${parent}.';
533520
}
534521

535-
// _myFunction$FfiNative$Ptr(obj, x)
536-
final functionPointerInvocation = FunctionInvocation(
537-
FunctionAccessKind.FunctionType,
538-
StaticGet(resolvedField),
539-
Arguments(argumentList),
540-
functionType: wrappedDartFunctionType)
541-
..fileOffset = node.fileOffset;
522+
final nonWrappedInvocation = StaticInvocation(
523+
nonWrappedFfiNative,
524+
Arguments(argumentList),
525+
)..fileOffset = node.fileOffset;
542526

543527
Expression result = (wrappedDartFunctionType == dartFunctionType
544-
? functionPointerInvocation
528+
? nonWrappedInvocation
545529
: _wrapArgumentsAndReturn(
546-
invocation: functionPointerInvocation,
530+
invocation: nonWrappedInvocation,
547531
dartFunctionType: dartFunctionType,
548532
ffiFunctionType: ffiFunctionType,
549533
checkReceiverForNullptr: checkReceiverForNullptr,
550534
));
535+
if (possibleCompoundReturn != null) {
536+
result = invokeCompoundConstructor(result, possibleCompoundReturn);
537+
}
551538

552-
// => _myFunction$FfiNative$Ptr(
553-
// Pointer<Void>.fromAddress(_getNativeField(obj)), x)
554539
node.function.body = ReturnStatement(result)..parent = node.function;
555540

556541
return node;
557542
}
558543

559544
// Transform FfiNative instance methods.
545+
//
560546
// Example:
547+
//
561548
// class MyNativeClass extends NativeFieldWrapperClass1 {
562-
// @FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('MyClass_MyMethod')
549+
// @Native<IntPtr Function(Pointer<Void>, IntPtr)>(symbol: 'MyClass_MyMethod')
563550
// external int myMethod(int x);
564551
// }
552+
//
565553
// Becomes, roughly:
566-
// ... {
567-
// static final _myMethod$FfiNative$Ptr = ...
568-
// static _myMethod$FfiNative(MyNativeClass self, int x)
569-
// => _myMethod$FfiNative$Ptr(
570-
// Pointer<Void>.fromAddress(_getNativeField(self)), x);
571-
// int myMethod(int x) => _myMethod$FfiNative(this, x);
572-
// }
573554
//
574555
// ... {
575-
// static final _myMethod$FfiNative$Ptr = ...
576-
// int myMethod(int x)
577-
// => _myMethod$FfiNative$Ptr(
578-
// Pointer<Void>.fromAddress(_getNativeField(this)), x);
556+
// @pragma(
557+
// 'vm:ffi:native',
558+
// Native<IntPtr Function(Pointer<Void>, IntPtr)>(
559+
// symbol: 'MyClass_MyMethod',
560+
// assetId: '<library uri>',
561+
// ),
562+
// )
563+
// external int _myFunction$FfiNative(Pointer<Void> self, int x);
564+
//
565+
// @pragma('vm:prefer-inline')
566+
// int myMethod(int x) => _myMethod$FfiNative(_getNativeField(this), x);
579567
// }
580568
Procedure _transformInstanceMethod(
581569
Procedure node,
@@ -609,13 +597,26 @@ class FfiNativeTransformer extends FfiTransformer {
609597
}
610598

611599
// Transform FfiNative static functions.
600+
//
612601
// Example:
613-
// @FfiNative<IntPtr Function(Pointer<Void>, IntPtr)>('MyFunction')
602+
//
603+
// @Native<IntPtr Function(Pointer<Void>, IntPtr)>(symbol: 'MyFunction')
614604
// external int myFunction(MyNativeClass obj, int x);
605+
//
615606
// Becomes, roughly:
616-
// static final _myFunction$FfiNative$Ptr = ...
607+
//
608+
// @pragma(
609+
// 'vm:ffi:native',
610+
// Native<IntPtr Function(Pointer<Void>, IntPtr)>(
611+
// symbol: 'MyFunction',
612+
// assetId: '<library uri>',
613+
// ),
614+
// )
615+
// external int _myFunction$FfiNative(Pointer<Void> self, int x);
616+
//
617+
// @pragma('vm:prefer-inline')
617618
// int myFunction(MyNativeClass obj, int x)
618-
// => myFunction$FfiNative$Ptr(
619+
// => _myFunction$FfiNative(
619620
// Pointer<Void>.fromAddress(_getNativeField(obj)), x);
620621
Procedure _transformStaticFunction(
621622
Procedure node,
@@ -648,7 +649,7 @@ class FfiNativeTransformer extends FfiTransformer {
648649
@override
649650
visitProcedure(Procedure node) {
650651
// Only transform functions that are external and have FfiNative annotation:
651-
// @FfiNative<Double Function(Double)>('Math_sqrt')
652+
// @Native<Double Function(Double)>(symbol: 'Math_sqrt')
652653
// external double _square_root(double x);
653654
final ffiNativeAnnotation =
654655
tryGetNativeAnnotation(node) ?? tryGetFfiNativeAnnotation(node);
@@ -661,7 +662,6 @@ class FfiNativeTransformer extends FfiTransformer {
661662
1, node.location?.file);
662663
return node;
663664
}
664-
node.isExternal = false;
665665

666666
node.annotations.remove(ffiNativeAnnotation);
667667

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
352352
.where((c) =>
353353
c.superclass == structClass || c.superclass == unionClass)
354354
.toList();
355-
return _invokeCompoundConstructors(replacement, compoundClasses);
355+
return invokeCompoundConstructors(replacement, compoundClasses);
356356
} else if (target == allocateMethod) {
357357
final DartType nativeType = node.arguments.types[0];
358358

@@ -387,7 +387,7 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
387387
return node;
388388
}
389389

390-
Expression _invokeCompoundConstructors(
390+
Expression invokeCompoundConstructors(
391391
Expression nestedExpression, List<Class> compoundClasses) =>
392392
compoundClasses
393393
.distinct()
@@ -748,7 +748,7 @@ mixin _FfiUseSiteTransformer on FfiTransformer {
748748
.map((t) => t.classNode)
749749
.where((c) => c.superclass == structClass || c.superclass == unionClass)
750750
.toList();
751-
return _invokeCompoundConstructors(replacement, compoundClasses);
751+
return invokeCompoundConstructors(replacement, compoundClasses);
752752
}
753753

754754
Expression _replaceGetRef(StaticInvocation node) {

0 commit comments

Comments
 (0)