Skip to content

Commit 4d50558

Browse files
Clement Skaucommit-bot@chromium.org
Clement Skau
authored andcommitted
[VM/FFI] Adds FFI leaf calls.
This CL adds FFI leaf calls by adding `lookupFunction(.., isLeaf)` and `_asFunctionInternal(.., isLeaf)`, which generate FFI leaf calls. These calls skip a lot of the usual frame building and generated <-> native transition overhead. `benchmark/FfiCall/` shows a 1.1x - 4.3x speed-up between the regular FFI calls and their leaf call counterparts (JIT, x64, release). TEST=Adds `tests/ffi{,_2}/vmspecific_leaf_call_test.dart`. Tested FFI tests. Closes: #36707 Cq-Include-Trybots: luci.dart.try:vm-precomp-ffi-qemu-linux-release-arm-try,vm-ffi-android-release-arm64-try,vm-ffi-android-release-arm-try,vm-ffi-android-product-arm64-try,vm-ffi-android-product-arm-try,vm-ffi-android-debug-arm64-try,vm-ffi-android-debug-arm-try,vm-kernel-linux-debug-ia32-try,vm-kernel-win-debug-ia32-try,vm-kernel-win-debug-x64-try,vm-kernel-win-release-x64-try,vm-kernel-mac-debug-x64-try,vm-kernel-precomp-nnbd-mac-release-simarm64-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-precomp-asan-linux-release-x64-try,vm-kernel-precomp-linux-release-simarm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-precomp-ubsan-linux-release-x64-try,vm-kernel-precomp-tsan-linux-release-x64-try,vm-kernel-precomp-win-release-x64-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-reload-linux-debug-x64-try Bug: #36707 Change-Id: Id8824f36b0006bf09951207bd004356fe6e9f46e Cq-Do-Not-Cancel-Tryjobs: true Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/179768 Commit-Queue: Clement Skau <[email protected]> Reviewed-by: Daco Harkes <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent f364c8b commit 4d50558

File tree

55 files changed

+17070
-609
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+17070
-609
lines changed

benchmarks/FfiCall/dart/FfiCall.dart

Lines changed: 249 additions & 139 deletions
Large diffs are not rendered by default.

benchmarks/FfiCall/dart2/FfiCall.dart

Lines changed: 249 additions & 139 deletions
Large diffs are not rendered by default.

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3778,6 +3778,27 @@ const MessageCode messageFfiExpectedConstant = const MessageCode(
37783778
"FfiExpectedConstant",
37793779
message: r"""Exceptional return value must be a constant.""");
37803780

3781+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3782+
const Template<Message Function(String name)> templateFfiExpectedConstantArg =
3783+
const Template<Message Function(String name)>(
3784+
messageTemplate: r"""Argument '#name' must be a constant.""",
3785+
withArguments: _withArgumentsFfiExpectedConstantArg);
3786+
3787+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3788+
const Code<Message Function(String name)> codeFfiExpectedConstantArg =
3789+
const Code<Message Function(String name)>(
3790+
"FfiExpectedConstantArg",
3791+
);
3792+
3793+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3794+
Message _withArgumentsFfiExpectedConstantArg(String name) {
3795+
if (name.isEmpty) throw 'No name provided';
3796+
name = demangleMixinApplicationName(name);
3797+
return new Message(codeFfiExpectedConstantArg,
3798+
message: """Argument '${name}' must be a constant.""",
3799+
arguments: {'name': name});
3800+
}
3801+
37813802
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
37823803
const Template<Message Function(String name)>
37833804
templateFfiExtendsOrImplementsSealedClass =
@@ -3931,6 +3952,24 @@ Message _withArgumentsFfiFieldNull(String name) {
39313952
arguments: {'name': name});
39323953
}
39333954

3955+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3956+
const Code<Null> codeFfiLeafCallMustNotReturnHandle =
3957+
messageFfiLeafCallMustNotReturnHandle;
3958+
3959+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3960+
const MessageCode messageFfiLeafCallMustNotReturnHandle = const MessageCode(
3961+
"FfiLeafCallMustNotReturnHandle",
3962+
message: r"""FFI leaf call must not have Handle return type.""");
3963+
3964+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3965+
const Code<Null> codeFfiLeafCallMustNotTakeHandle =
3966+
messageFfiLeafCallMustNotTakeHandle;
3967+
3968+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
3969+
const MessageCode messageFfiLeafCallMustNotTakeHandle = const MessageCode(
3970+
"FfiLeafCallMustNotTakeHandle",
3971+
message: r"""FFI leaf call must not have Handle argument types.""");
3972+
39343973
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
39353974
const Template<
39363975
Message Function(String name)> templateFfiNotStatic = const Template<

pkg/analyzer/lib/error/error.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ const List<ErrorCode> errorCodeValues = [
462462
CompileTimeErrorCode.YIELD_IN_NON_GENERATOR,
463463
CompileTimeErrorCode.YIELD_OF_INVALID_TYPE,
464464
FfiCode.ANNOTATION_ON_POINTER_FIELD,
465+
FfiCode.ARGUMENT_MUST_BE_A_CONSTANT,
465466
FfiCode.EMPTY_STRUCT,
466467
FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD,
467468
FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY,
@@ -470,6 +471,8 @@ const List<ErrorCode> errorCodeValues = [
470471
FfiCode.GENERIC_STRUCT_SUBCLASS,
471472
FfiCode.INVALID_EXCEPTION_VALUE,
472473
FfiCode.INVALID_FIELD_TYPE_IN_STRUCT,
474+
FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE,
475+
FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE,
473476
FfiCode.MISMATCHED_ANNOTATION_ON_STRUCT_FIELD,
474477
FfiCode.MISSING_ANNOTATION_ON_STRUCT_FIELD,
475478
FfiCode.MISSING_EXCEPTION_VALUE,

pkg/analyzer/lib/src/dart/error/ffi_code.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ class FfiCode extends AnalyzerErrorCode {
2121
"any annotations.",
2222
correction: "Try removing the annotation.");
2323

24+
/**
25+
* Parameters:
26+
* 0: the name of the argument
27+
*/
28+
static const FfiCode ARGUMENT_MUST_BE_A_CONSTANT = FfiCode(
29+
name: 'ARGUMENT_MUST_BE_A_CONSTANT',
30+
message: "Argument '{0}' must be a constant.",
31+
correction: "Try replacing the value with a literal or const.");
32+
2433
/**
2534
* Parameters:
2635
* 0: the name of the struct class
@@ -103,6 +112,22 @@ class FfiCode extends AnalyzerErrorCode {
103112
"Try using 'int', 'double', 'Array', 'Pointer', or subtype of "
104113
"'Struct' or 'Union'.");
105114

115+
/**
116+
* No parameters.
117+
*/
118+
static const FfiCode LEAF_CALL_MUST_NOT_RETURN_HANDLE = FfiCode(
119+
name: 'LEAF_CALL_MUST_NOT_RETURN_HANDLE',
120+
message: "FFI leaf call must not return a Handle.",
121+
correction: "Try changing the return type to primitive or struct.");
122+
123+
/**
124+
* No parameters.
125+
*/
126+
static const FfiCode LEAF_CALL_MUST_NOT_TAKE_HANDLE = FfiCode(
127+
name: 'LEAF_CALL_MUST_NOT_TAKE_HANDLE',
128+
message: "FFI leaf call must not take arguments of type Handle.",
129+
correction: "Try changing the argument type to primitive or struct.");
130+
106131
/**
107132
* No parameters.
108133
*/

pkg/analyzer/lib/src/generated/ffi_verifier.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:analyzer/dart/element/element.dart';
88
import 'package:analyzer/dart/element/type.dart';
99
import 'package:analyzer/error/listener.dart';
1010
import 'package:analyzer/src/dart/ast/extensions.dart';
11+
import 'package:analyzer/src/dart/element/element.dart';
1112
import 'package:analyzer/src/dart/element/type_system.dart';
1213
import 'package:analyzer/src/dart/error/ffi_code.dart';
1314

@@ -20,6 +21,7 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
2021
static const _allocatorExtensionName = 'AllocatorAlloc';
2122
static const _arrayClassName = 'Array';
2223
static const _dartFfiLibraryName = 'dart.ffi';
24+
static const _isLeafParamName = 'isLeaf';
2325
static const _opaqueClassName = 'Opaque';
2426

2527
static const List<String> _primitiveIntegerNativeTypes = [
@@ -341,6 +343,26 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
341343
return false;
342344
}
343345

346+
// Get the const bool value of `expr` if it exists.
347+
// Return null if it isn't a const bool.
348+
bool? _maybeGetBoolConstValue(Expression expr) {
349+
if (expr is BooleanLiteral) {
350+
return expr.value;
351+
} else if (expr is Identifier) {
352+
final staticElm = expr.staticElement;
353+
if (staticElm is ConstVariableElement) {
354+
return staticElm.computeConstantValue()?.toBoolValue();
355+
}
356+
if (staticElm is PropertyAccessorElementImpl) {
357+
final v = staticElm.variable;
358+
if (v is ConstVariableElement) {
359+
return v.computeConstantValue()?.toBoolValue();
360+
}
361+
}
362+
}
363+
return null;
364+
}
365+
344366
_PrimitiveDartType _primitiveNativeType(DartType nativeType) {
345367
if (nativeType is InterfaceType) {
346368
final element = nativeType.element;
@@ -461,7 +483,9 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
461483
_errorReporter.reportErrorForNode(
462484
FfiCode.MUST_BE_A_SUBTYPE, node, [TPrime, F, 'asFunction']);
463485
}
486+
_validateFfiLeafCallUsesNoHandles(node, TPrime, node);
464487
}
488+
_validateIsLeafIsConst(node);
465489
}
466490

467491
/// Validates that the given [nativeType] is, when native types are converted
@@ -551,6 +575,37 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
551575
}
552576
}
553577

578+
void _validateFfiLeafCallUsesNoHandles(
579+
MethodInvocation node, DartType nativeType, AstNode errorNode) {
580+
final args = node.argumentList.arguments;
581+
if (args.isNotEmpty) {
582+
for (final arg in args) {
583+
if (arg is NamedExpression) {
584+
if (arg.element?.name == _isLeafParamName) {
585+
// Handles are ok for regular (non-leaf) calls. Check `isLeaf:true`.
586+
final bool? isLeaf = _maybeGetBoolConstValue(arg.expression);
587+
if (isLeaf != null && isLeaf) {
588+
if (nativeType is FunctionType) {
589+
if (_primitiveNativeType(nativeType.returnType) ==
590+
_PrimitiveDartType.handle) {
591+
_errorReporter.reportErrorForNode(
592+
FfiCode.LEAF_CALL_MUST_NOT_RETURN_HANDLE, errorNode);
593+
}
594+
for (final param in nativeType.normalParameterTypes) {
595+
if (_primitiveNativeType(param) ==
596+
_PrimitiveDartType.handle) {
597+
_errorReporter.reportErrorForNode(
598+
FfiCode.LEAF_CALL_MUST_NOT_TAKE_HANDLE, errorNode);
599+
}
600+
}
601+
}
602+
}
603+
}
604+
}
605+
}
606+
}
607+
}
608+
554609
/// Validate that the fields declared by the given [node] meet the
555610
/// requirements for fields within a struct or union class.
556611
void _validateFieldsInCompound(FieldDeclaration node) {
@@ -654,6 +709,27 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
654709
}
655710
}
656711

712+
void _validateIsLeafIsConst(MethodInvocation node) {
713+
// Ensure `isLeaf` is const as we need the value at compile time to know
714+
// which trampoline to generate.
715+
final args = node.argumentList.arguments;
716+
if (args.isNotEmpty) {
717+
for (final arg in args) {
718+
if (arg is NamedExpression) {
719+
if (arg.element?.name == _isLeafParamName) {
720+
if (_maybeGetBoolConstValue(arg.expression) == null) {
721+
final AstNode errorNode = node;
722+
_errorReporter.reportErrorForNode(
723+
FfiCode.ARGUMENT_MUST_BE_A_CONSTANT,
724+
errorNode,
725+
[_isLeafParamName]);
726+
}
727+
}
728+
}
729+
}
730+
}
731+
}
732+
657733
/// Validate the invocation of the instance method
658734
/// `DynamicLibrary.lookupFunction<S, F>()`.
659735
void _validateLookupFunction(MethodInvocation node) {
@@ -678,6 +754,8 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
678754
_errorReporter.reportErrorForNode(
679755
FfiCode.MUST_BE_A_SUBTYPE, errorNode, [S, F, 'lookupFunction']);
680756
}
757+
_validateIsLeafIsConst(node);
758+
_validateFfiLeafCallUsesNoHandles(node, S, typeArguments![0]);
681759
}
682760

683761
/// Validate that none of the [annotations] are from `dart:ffi`.

pkg/analyzer/lib/src/test_utilities/mock_sdk.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,8 @@ class NativeType {
620620
const NativeType();
621621
}
622622
623+
class Handle extends NativeType {}
624+
623625
class Void extends NativeType {}
624626
625627
class Int8 extends NativeType {
@@ -674,7 +676,7 @@ final Pointer<Never> nullptr = Pointer.fromAddress(0);
674676
675677
extension NativeFunctionPointer<NF extends Function>
676678
on Pointer<NativeFunction<NF>> {
677-
external DF asFunction<DF extends Function>();
679+
external DF asFunction<DF extends Function>({bool isLeaf:false});
678680
}
679681
680682
class _Compound extends NativeType {}
@@ -689,11 +691,13 @@ class Packed {
689691
const Packed(this.memberAlignment);
690692
}
691693
692-
abstract class DynamicLibrary {}
694+
abstract class DynamicLibrary {
695+
external factory DynamicLibrary.open(String name);
696+
}
693697
694698
extension DynamicLibraryExtension on DynamicLibrary {
695699
external F lookupFunction<T extends Function, F extends Function>(
696-
String symbolName);
700+
String symbolName, {bool isLeaf:false});
697701
}
698702
699703
abstract class NativeFunction<T extends Function> extends NativeType {}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) 2021, 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+
import 'package:analyzer/src/dart/error/ffi_code.dart';
6+
import 'package:test_reflective_loader/test_reflective_loader.dart';
7+
8+
import '../dart/resolution/context_collection_resolution.dart';
9+
10+
main() {
11+
defineReflectiveSuite(() {
12+
defineReflectiveTests(ArgumentMustBeAConstantTest);
13+
});
14+
}
15+
16+
@reflectiveTest
17+
class ArgumentMustBeAConstantTest extends PubPackageResolutionTest {
18+
test_AsFunctionIsLeafGlobal() async {
19+
await assertErrorsInCode(r'''
20+
import 'dart:ffi';
21+
typedef Int8UnOp = Int8 Function(Int8);
22+
typedef IntUnOp = int Function(int);
23+
bool isLeaf = false;
24+
doThings() {
25+
Pointer<NativeFunction<Int8UnOp>> p = Pointer.fromAddress(1337);
26+
IntUnOp f = p.asFunction(isLeaf:isLeaf);
27+
f(8);
28+
}
29+
''', [
30+
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 211, 27),
31+
]);
32+
}
33+
34+
test_AsFunctionIsLeafLocal() async {
35+
await assertErrorsInCode(r'''
36+
import 'dart:ffi';
37+
typedef Int8UnOp = Int8 Function(Int8);
38+
typedef IntUnOp = int Function(int);
39+
doThings() {
40+
bool isLeaf = false;
41+
Pointer<NativeFunction<Int8UnOp>> p = Pointer.fromAddress(1337);
42+
IntUnOp f = p.asFunction(isLeaf:isLeaf);
43+
f(8);
44+
}
45+
''', [
46+
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 213, 27),
47+
]);
48+
}
49+
50+
test_AsFunctionIsLeafParam() async {
51+
await assertErrorsInCode(r'''
52+
import 'dart:ffi';
53+
typedef Int8UnOp = Int8 Function(Int8);
54+
typedef IntUnOp = int Function(int);
55+
doThings(bool isLeaf) {
56+
Pointer<NativeFunction<Int8UnOp>> p = Pointer.fromAddress(1337);
57+
IntUnOp f = p.asFunction(isLeaf:isLeaf);
58+
f(8);
59+
}
60+
''', [
61+
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 201, 27),
62+
]);
63+
}
64+
65+
test_LookupFunctionIsLeaf() async {
66+
await assertErrorsInCode(r'''
67+
import 'dart:ffi';
68+
typedef Int8UnOp = Int8 Function(Int8);
69+
typedef IntUnOp = int Function(int);
70+
doThings(bool isLeaf) {
71+
DynamicLibrary l = DynamicLibrary.open("my_lib");
72+
l.lookupFunction<Int8UnOp, IntUnOp>("timesFour", isLeaf:isLeaf);
73+
}
74+
''', [
75+
error(FfiCode.ARGUMENT_MUST_BE_A_CONSTANT, 174, 63),
76+
]);
77+
}
78+
}

0 commit comments

Comments
 (0)