Version 2.16.0-72.0.dev
Merge commit 'aa27868c84977eea064b02723978ac92c9df9e52' into 'dev'
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index cc621a7..b4924ce 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -477,6 +477,9 @@
CompileTimeErrorCode.YIELD_IN_NON_GENERATOR,
CompileTimeErrorCode.YIELD_EACH_OF_INVALID_TYPE,
CompileTimeErrorCode.YIELD_OF_INVALID_TYPE,
+ FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_EXTRA,
+ FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_MISSING,
+ FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED,
FfiCode.ANNOTATION_ON_POINTER_FIELD,
FfiCode.ARGUMENT_MUST_BE_A_CONSTANT,
FfiCode.CREATION_OF_STRUCT_OR_UNION,
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
index 4264f45..e794a9b 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.g.dart
@@ -18,6 +18,34 @@
/**
* No parameters.
*/
+ static const FfiCode ABI_SPECIFIC_INTEGER_MAPPING_EXTRA = FfiCode(
+ 'ABI_SPECIFIC_INTEGER_MAPPING_EXTRA',
+ "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size.",
+ correctionMessage: "Try removing the extra annotation.",
+ );
+
+ /**
+ * No parameters.
+ */
+ static const FfiCode ABI_SPECIFIC_INTEGER_MAPPING_MISSING = FfiCode(
+ 'ABI_SPECIFIC_INTEGER_MAPPING_MISSING',
+ "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size.",
+ correctionMessage: "Try adding an annotation.",
+ );
+
+ /**
+ * No parameters.
+ */
+ static const FfiCode ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED = FfiCode(
+ 'ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED',
+ "Only mappings to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', 'UInt32', and 'Uint64' are supported.",
+ correctionMessage:
+ "Try changing the value to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', 'UInt32', or 'Uint64'.",
+ );
+
+ /**
+ * No parameters.
+ */
static const FfiCode ANNOTATION_ON_POINTER_FIELD = FfiCode(
'ANNOTATION_ON_POINTER_FIELD',
"Fields in a struct class whose type is 'Pointer' should not have any annotations.",
@@ -302,9 +330,9 @@
*/
static const FfiCode NON_SIZED_TYPE_ARGUMENT = FfiCode(
'NON_SIZED_TYPE_ARGUMENT',
- "Type arguments to '{0}' can't have the type '{1}'. They can only be declared as native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct' or 'Union'.",
+ "Type arguments to '{0}' can't have the type '{1}'. They can only be declared as native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'.",
correctionMessage:
- "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct' or 'Union'.",
+ "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'.",
);
/**
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 767d937..69b37a5 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -16,6 +16,9 @@
/// used. See 'pkg/vm/lib/transformations/ffi_checks.md' for the specification
/// of the desired hints.
class FfiVerifier extends RecursiveAstVisitor<void> {
+ static const _abiSpecificIntegerClassName = 'AbiSpecificInteger';
+ static const _abiSpecificIntegerMappingClassName =
+ 'AbiSpecificIntegerMapping';
static const _allocatorClassName = 'Allocator';
static const _allocateExtensionMethodName = 'call';
static const _allocatorExtensionName = 'AllocatorAlloc';
@@ -25,7 +28,7 @@
static const _opaqueClassName = 'Opaque';
static const _ffiNativeName = 'FfiNative';
- static const Set<String> _primitiveIntegerNativeTypes = {
+ static const Set<String> _primitiveIntegerNativeTypesFixedSize = {
'Int8',
'Int16',
'Int32',
@@ -34,6 +37,9 @@
'Uint16',
'Uint32',
'Uint64',
+ };
+ static const Set<String> _primitiveIntegerNativeTypes = {
+ ..._primitiveIntegerNativeTypesFixedSize,
'IntPtr'
};
@@ -85,8 +91,12 @@
if (className == _structClassName) {
_validatePackedAnnotation(node.metadata);
}
+ } else if (className == _abiSpecificIntegerClassName) {
+ _validateAbiSpecificIntegerMappingAnnotation(
+ node.name, node.metadata);
} else if (className != _allocatorClassName &&
- className != _opaqueClassName) {
+ className != _opaqueClassName &&
+ className != _abiSpecificIntegerClassName) {
_errorReporter.reportErrorForNode(
FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS,
superclass.name,
@@ -452,6 +462,9 @@
if (nativeType.isArray) {
return true;
}
+ if (nativeType.isAbiSpecificIntegerSubtype) {
+ return true;
+ }
return false;
}
@@ -523,6 +536,9 @@
if (nativeType.isOpaqueSubtype) {
return true;
}
+ if (nativeType.isAbiSpecificIntegerSubtype) {
+ return true;
+ }
if (allowArray && nativeType.isArray) {
return _isValidFfiNativeType(nativeType.typeArguments.single,
allowVoid: false, allowEmptyStruct: false);
@@ -591,10 +607,51 @@
} else if (_primitiveBoolNativeType == name) {
return _PrimitiveDartType.bool;
}
+ if (element.type.returnType.isAbiSpecificIntegerSubtype) {
+ return _PrimitiveDartType.int;
+ }
}
return _PrimitiveDartType.none;
}
+ /// Validate that the [annotations] include at most one mapping annotation.
+ void _validateAbiSpecificIntegerMappingAnnotation(
+ AstNode errorNode, NodeList<Annotation> annotations) {
+ final ffiPackedAnnotations = annotations
+ .where((annotation) => annotation.isAbiSpecificIntegerMapping)
+ .toList();
+
+ if (ffiPackedAnnotations.isEmpty) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_MISSING, errorNode);
+ return;
+ }
+
+ if (ffiPackedAnnotations.length > 1) {
+ final extraAnnotations = ffiPackedAnnotations.skip(1);
+ for (final annotation in extraAnnotations) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_EXTRA, annotation.name);
+ }
+ }
+
+ final annotationConstant =
+ ffiPackedAnnotations.first.elementAnnotation?.computeConstantValue();
+ final mappingValues = annotationConstant?.getField('mapping')?.toMapValue();
+ if (mappingValues == null) {
+ return;
+ }
+ for (final nativeType in mappingValues.values) {
+ final nativeTypeName = nativeType?.type?.element?.name;
+ if (nativeTypeName != null &&
+ !_primitiveIntegerNativeTypesFixedSize.contains(nativeTypeName)) {
+ _errorReporter.reportErrorForNode(
+ FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED,
+ ffiPackedAnnotations.first.name);
+ }
+ }
+ }
+
void _validateAllocate(FunctionExpressionInvocation node) {
final typeArgumentTypes = node.typeArgumentTypes;
if (typeArgumentTypes == null || typeArgumentTypes.length != 1) {
@@ -619,7 +676,9 @@
bool requiredFound = false;
List<Annotation> extraAnnotations = [];
for (Annotation annotation in annotations) {
- if (annotation.element.ffiClass != null) {
+ if (annotation.element.ffiClass != null ||
+ annotation.element?.enclosingElement.isAbiSpecificIntegerSubclass ==
+ true) {
if (requiredFound) {
extraAnnotations.add(annotation);
} else {
@@ -744,7 +803,10 @@
bool _validateCompatibleNativeType(
DartType dartType, DartType nativeType, bool checkCovariance) {
final nativeReturnType = _primitiveNativeType(nativeType);
- if (nativeReturnType == _PrimitiveDartType.int) {
+ if (nativeReturnType == _PrimitiveDartType.int ||
+ (nativeType is InterfaceType &&
+ nativeType.superclass?.element.name ==
+ _abiSpecificIntegerClassName)) {
return dartType.isDartCoreInt;
} else if (nativeReturnType == _PrimitiveDartType.double) {
return dartType.isDartCoreDouble;
@@ -1197,6 +1259,14 @@
element.ffiClass != null &&
element.enclosingElement.name == 'Packed';
}
+
+ bool get isAbiSpecificIntegerMapping {
+ final element = this.element;
+ return element is ConstructorElement &&
+ element.ffiClass != null &&
+ element.enclosingElement.name ==
+ FfiVerifier._abiSpecificIntegerMappingClassName;
+ }
}
extension on ElementAnnotation {
@@ -1332,6 +1402,21 @@
return element is ClassElement && element.supertype.isUnion;
}
+ /// Return `true` if this represents the class `AbiSpecificInteger`.
+ bool get isAbiSpecificInteger {
+ final element = this;
+ return element is ClassElement &&
+ element.name == FfiVerifier._abiSpecificIntegerClassName &&
+ element.isFfiClass;
+ }
+
+ /// Return `true` if this represents a subclass of the class
+ /// `AbiSpecificInteger`.
+ bool get isAbiSpecificIntegerSubclass {
+ final element = this;
+ return element is ClassElement && element.supertype.isAbiSpecificInteger;
+ }
+
/// If this is a class element from `dart:ffi`, return it.
ClassElement? get ffiClass {
var element = this;
@@ -1398,6 +1483,11 @@
final self = this;
return self is InterfaceType && self.element.isUnion;
}
+
+ bool get isAbiSpecificInteger {
+ final self = this;
+ return self is InterfaceType && self.element.isAbiSpecificInteger;
+ }
}
extension on DartType {
@@ -1467,6 +1557,22 @@
return false;
}
+ /// Returns `true` iff this is an Abi-specific integer type,
+ /// i.e. a subtype of `AbiSpecificInteger`.
+ bool get isAbiSpecificIntegerSubtype {
+ final self = this;
+ if (self is InterfaceType) {
+ final superType = self.element.supertype;
+ if (superType != null) {
+ final superClassElement = superType.element;
+ return superClassElement.name ==
+ FfiVerifier._abiSpecificIntegerClassName &&
+ superClassElement.isFfiClass;
+ }
+ }
+ return false;
+ }
+
/// Returns `true` iff this is a opaque type, i.e. a subtype of `Opaque`.
bool get isOpaqueSubtype {
final self = this;
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index a7ecd5a..7693ddd 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -47,7 +47,7 @@
Future<T> whenComplete(action());
- static Future<List<T>> wait<T>(Iterable<Future<T>> futures,
+ static Future<List<T>> wait<T>(Iterable<Future<T>> futures,
{void cleanUp(T successValue)?}) => throw 0;
}
@@ -705,6 +705,10 @@
const Double();
}
+class IntPtr extends NativeType {
+ const IntPtr();
+}
+
class Pointer<T extends NativeType> extends NativeType {
external factory Pointer.fromAddress(int ptr);
@@ -789,6 +793,50 @@
final bool isLeaf;
const FfiNative(this.nativeName, {this.isLeaf: false});
}
+
+class Abi {
+ static const androidArm = _androidArm;
+ static const androidArm64 = _androidArm64;
+ static const androidIA32 = _androidIA32;
+
+ static const _androidArm = Abi._(_Architecture.arm, _OS.android);
+ static const _androidArm64 = Abi._(_Architecture.arm64, _OS.android);
+ static const _androidIA32 = Abi._(_Architecture.ia32, _OS.android);
+
+ final _OS _os;
+
+ final _Architecture _architecture;
+
+ const Abi._(this._architecture, this._os);
+}
+
+enum _Architecture {
+ arm,
+ arm64,
+ ia32,
+ x64,
+}
+
+enum _OS {
+ android,
+ fuchsia,
+ ios,
+ linux,
+ macos,
+ windows,
+}
+
+
+class AbiSpecificInteger extends NativeType {
+ const AbiSpecificInteger();
+}
+
+class AbiSpecificIntegerMapping {
+ final Map<Abi, NativeType> mapping;
+
+ const AbiSpecificIntegerMapping(this.mapping);
+}
+
''',
)
]);
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 8c2b4be..7ad997e 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -13775,6 +13775,18 @@
}
```
FfiCode:
+ ABI_SPECIFIC_INTEGER_MAPPING_EXTRA:
+ problemMessage: "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size."
+ correctionMessage: Try removing the extra annotation.
+ comment: No parameters.
+ ABI_SPECIFIC_INTEGER_MAPPING_MISSING:
+ problemMessage: "Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a 'NativeType' integer with a fixed size."
+ correctionMessage: Try adding an annotation.
+ comment: No parameters.
+ ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED:
+ problemMessage: "Only mappings to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', 'UInt32', and 'Uint64' are supported."
+ correctionMessage: Try changing the value to 'Int8', 'Int16', 'Int32', 'Int64', 'Uint8', 'Uint16', 'UInt32', or 'Uint64'.
+ comment: No parameters.
ANNOTATION_ON_POINTER_FIELD:
problemMessage: "Fields in a struct class whose type is 'Pointer' should not have any annotations."
correctionMessage: Try removing the annotation.
@@ -13916,8 +13928,8 @@
correctionMessage: Try changing the input to a positive number.
comment: No parameters.
NON_SIZED_TYPE_ARGUMENT:
- problemMessage: "Type arguments to '{0}' can't have the type '{1}'. They can only be declared as native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct' or 'Union'."
- correctionMessage: "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct' or 'Union'."
+ problemMessage: "Type arguments to '{0}' can't have the type '{1}'. They can only be declared as native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'."
+ correctionMessage: "Try using a native integer, 'Float', 'Double', 'Pointer', or subtype of 'Struct', 'Union', or 'AbiSpecificInteger'."
comment: |-
Parameters:
0: the type of the field
diff --git a/pkg/analyzer/test/src/diagnostics/abi_specific_integer_mapping_test.dart b/pkg/analyzer/test/src/diagnostics/abi_specific_integer_mapping_test.dart
new file mode 100644
index 0000000..746ce71
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/abi_specific_integer_mapping_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, 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.
+
+import 'package:analyzer/src/dart/error/ffi_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(AbiSpecificIntegerMappingTest);
+ });
+}
+
+@reflectiveTest
+class AbiSpecificIntegerMappingTest extends PubPackageResolutionTest {
+ test_doubleMapping() async {
+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+@AbiSpecificIntegerMapping({})
+@AbiSpecificIntegerMapping({})
+class UintPtr extends AbiSpecificInteger {
+ const UintPtr();
+}
+''', [
+ error(FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_EXTRA, 51, 25),
+ ]);
+ }
+
+ test_invalidMapping() async {
+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+@AbiSpecificIntegerMapping({
+ Abi.androidArm: Uint32(),
+ Abi.androidArm64: IntPtr(),
+ Abi.androidIA32: UintPtr(),
+})
+class UintPtr extends AbiSpecificInteger {
+ const UintPtr();
+}
+''', [
+ error(FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED, 20, 25),
+ ]);
+ }
+
+ test_noMapping() async {
+ await assertErrorsInCode(r'''
+import 'dart:ffi';
+class UintPtr extends AbiSpecificInteger {
+ const UintPtr();
+}
+''', [
+ error(FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_MISSING, 25, 7),
+ ]);
+ }
+
+ test_singleMapping() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+@AbiSpecificIntegerMapping({})
+class UintPtr extends AbiSpecificInteger {
+ const UintPtr();
+}
+''');
+ }
+
+ test_validMapping() async {
+ await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+@AbiSpecificIntegerMapping({
+ Abi.androidArm: Uint32(),
+ Abi.androidArm64: Uint64(),
+ Abi.androidIA32: Uint32(),
+})
+class UintPtr extends AbiSpecificInteger {
+ const UintPtr();
+}
+''');
+ }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 779326c..4456cef 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -4,6 +4,7 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import 'abi_specific_integer_mapping_test.dart' as abi_specific_integer_mapping;
import 'abstract_class_member_test.dart' as abstract_class_member;
import 'abstract_field_constructor_initializer_test.dart'
as abstract_field_constructor_initializer;
@@ -721,6 +722,7 @@
main() {
defineReflectiveSuite(() {
+ abi_specific_integer_mapping.main();
abstract_class_member.main();
abstract_field_constructor_initializer.main();
abstract_field_initializer.main();
diff --git a/pkg/meta/lib/meta.dart b/pkg/meta/lib/meta.dart
index 584fe42..ba14be9 100644
--- a/pkg/meta/lib/meta.dart
+++ b/pkg/meta/lib/meta.dart
@@ -274,7 +274,7 @@
/// Used to annotate a method, field, or getter within a class, mixin, or
/// extension, or a or top-level getter, variable or function to indicate that
-/// the value obtained by invoking it should be use. A value is considered used
+/// the value obtained by invoking it should be used. A value is considered used
/// if it is assigned to a variable, passed to a function, or used as the target
/// of an invocation, or invoked (if the result is itself a function).
///
diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart
index 54ffd5c..42235ff 100644
--- a/pkg/vm/lib/transformations/ffi/common.dart
+++ b/pkg/vm/lib/transformations/ffi/common.dart
@@ -234,6 +234,7 @@
final Procedure lookupFunctionTearoff;
final Procedure getNativeFieldFunction;
final Procedure reachabilityFenceFunction;
+ final Procedure checkAbiSpecificIntegerMappingFunction;
late final InterfaceType nativeFieldWrapperClass1Type;
late final InterfaceType voidType;
@@ -417,7 +418,9 @@
getNativeFieldFunction = index.getTopLevelProcedure(
'dart:nativewrappers', '_getNativeField'),
reachabilityFenceFunction =
- index.getTopLevelProcedure('dart:_internal', 'reachabilityFence') {
+ index.getTopLevelProcedure('dart:_internal', 'reachabilityFence'),
+ checkAbiSpecificIntegerMappingFunction = index.getTopLevelProcedure(
+ 'dart:ffi', "_checkAbiSpecificIntegerMapping") {
nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
coreTypes, Nullability.nonNullable);
voidType = nativeTypesClasses[NativeType.kVoid]!
@@ -453,7 +456,7 @@
/// [Bool] -> [bool]
/// [Void] -> [void]
/// [Pointer]<T> -> [Pointer]<T>
- /// T extends [Pointer] -> T
+ /// T extends [Compound] -> T
/// [Handle] -> [Object]
/// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
/// where DartRepresentationOf(Tn) -> Sn
@@ -535,27 +538,42 @@
InterfaceType _listOfIntType() => InterfaceType(
listClass, Nullability.legacy, [coreTypes.intLegacyRawType]);
- ConstantExpression intListConstantExpression(List<int> values) =>
+ ConstantExpression intListConstantExpression(List<int?> values) =>
ConstantExpression(
- ListConstant(coreTypes.intLegacyRawType,
- [for (var v in values) IntConstant(v)]),
+ ListConstant(coreTypes.intLegacyRawType, [
+ for (var v in values)
+ if (v != null) IntConstant(v) else NullConstant()
+ ]),
_listOfIntType());
/// Expression that queries VM internals at runtime to figure out on which ABI
/// we are.
- Expression runtimeBranchOnLayout(Map<Abi, int> values) {
- return InstanceInvocation(
+ Expression runtimeBranchOnLayout(Map<Abi, int?> values) {
+ final result = InstanceInvocation(
InstanceAccessKind.Instance,
intListConstantExpression([
- for (final abi in Abi.values) values[abi]!,
+ for (final abi in Abi.values) values[abi],
]),
listElementAt.name,
Arguments([StaticInvocation(abiMethod, Arguments([]))]),
interfaceTarget: listElementAt,
functionType: Substitution.fromInterfaceType(_listOfIntType())
.substituteType(listElementAt.getterType) as FunctionType);
+ if (values.isPartial) {
+ return checkAbiSpecificIntegerMapping(result);
+ }
+ return result;
}
+ Expression checkAbiSpecificIntegerMapping(Expression nullableExpression) =>
+ StaticInvocation(
+ checkAbiSpecificIntegerMappingFunction,
+ Arguments(
+ [nullableExpression],
+ types: [InterfaceType(intClass, Nullability.nonNullable)],
+ ),
+ );
+
/// Generates an expression that returns a new `Pointer<dartType>` offset
/// by [offset] from [pointer].
///
@@ -819,3 +837,8 @@
}
return false;
}
+
+extension on Map<Abi, Object?> {
+ bool get isPartial =>
+ [for (final abi in Abi.values) this[abi]].contains(null);
+}
diff --git a/pkg/vm/lib/transformations/ffi/definitions.dart b/pkg/vm/lib/transformations/ffi/definitions.dart
index 29ae1d9..fbf2052 100644
--- a/pkg/vm/lib/transformations/ffi/definitions.dart
+++ b/pkg/vm/lib/transformations/ffi/definitions.dart
@@ -2,8 +2,6 @@
// 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.
-import 'dart:math' as math;
-
import 'package:front_end/src/api_unstable/vm.dart'
show
messageFfiPackedAnnotationAlignment,
@@ -33,6 +31,7 @@
import 'abi.dart';
import 'common.dart';
+import 'native_type_cfe.dart';
/// Checks and elaborates the dart:ffi compounds and their fields.
///
@@ -449,9 +448,8 @@
// This class is invalid, but continue reporting other errors on it.
success = false;
} else {
- final DartType nativeType = InterfaceType(
- nativeTypesClasses[_getFieldType(nativeTypeAnnos.first)!]!,
- Nullability.legacy);
+ final DartType nativeType =
+ InterfaceType(nativeTypeAnnos.first, Nullability.legacy);
final DartType? shouldBeDartType = convertNativeTypeToDartType(
nativeType,
allowCompounds: true,
@@ -705,7 +703,6 @@
static const vmFfiStructFields = "vm:ffi:struct-fields";
- // return value is nullable.
InstanceConstant? _compoundAnnotatedFields(Class node) {
for (final annotation in node.annotations) {
if (annotation is ConstantExpression) {
@@ -775,7 +772,6 @@
return UnionNativeTypeCfe(compoundClass, members);
}
- // packing is `int?`.
void _annoteCompoundWithFields(
Class node, List<NativeTypeCfe> types, int? packing) {
List<Constant> constants =
@@ -795,8 +791,13 @@
InterfaceType(pragmaClass, Nullability.nonNullable, [])));
}
- void _generateMethodsForField(Class node, Field field, NativeTypeCfe type,
- Map<Abi, int> offsets, bool unalignedAccess, IndexedClass? indexedClass) {
+ void _generateMethodsForField(
+ Class node,
+ Field field,
+ NativeTypeCfe type,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ IndexedClass? indexedClass) {
// TODO(johnniwinther): Avoid passing [indexedClass]. When compiling
// incrementally, [field] should already carry the references from
// [indexedClass].
@@ -847,7 +848,7 @@
/// If sizes are not supplied still emits a field so that the use site
/// transformer can still rewrite to it.
void _addSizeOfField(Class compound, IndexedClass? indexedClass,
- [Map<Abi, int>? sizes = null]) {
+ [Map<Abi, int?>? sizes = null]) {
if (sizes == null) {
sizes = {for (var abi in Abi.values) abi: 0};
}
@@ -956,546 +957,3 @@
CompoundField(this.type, this.field, this.getter, this.setter);
}
-
-/// The layout of a `Struct` or `Union` in one [Abi].
-class CompoundLayout {
- /// Size of the entire struct or union.
- final int size;
-
- /// Alignment of struct or union when nested in a struct.
- final int alignment;
-
- /// Offset in bytes for each field, indexed by field number.
- ///
- /// Always 0 for unions.
- final List<int> offsets;
-
- CompoundLayout(this.size, this.alignment, this.offsets);
-}
-
-/// AST node wrapper for native types.
-///
-/// This algebraic data structure does not stand on its own but refers
-/// intimately to AST nodes such as [Class].
-abstract class NativeTypeCfe {
- factory NativeTypeCfe(FfiTransformer transformer, DartType dartType,
- {List<int>? arrayDimensions,
- Map<Class, NativeTypeCfe> compoundCache = const {}}) {
- if (transformer.isPrimitiveType(dartType)) {
- final clazz = (dartType as InterfaceType).classNode;
- final nativeType = transformer.getType(clazz)!;
- return PrimitiveNativeTypeCfe(nativeType, clazz);
- }
- if (transformer.isPointerType(dartType)) {
- return PointerNativeTypeCfe();
- }
- if (transformer.isCompoundSubtype(dartType)) {
- final clazz = (dartType as InterfaceType).classNode;
- if (compoundCache.containsKey(clazz)) {
- return compoundCache[clazz]!;
- } else {
- throw "Class '$clazz' not found in compoundCache.";
- }
- }
- if (transformer.isArrayType(dartType)) {
- if (arrayDimensions == null) {
- throw "Must have array dimensions for ArrayType.";
- }
- if (arrayDimensions.length == 0) {
- throw "Must have a size for this array dimension.";
- }
- final elementType = transformer.arraySingleElementType(dartType);
- final elementCfeType =
- NativeTypeCfe(transformer, elementType, compoundCache: compoundCache);
- if (elementCfeType is InvalidNativeTypeCfe) {
- return elementCfeType;
- }
- return ArrayNativeTypeCfe.multi(elementCfeType, arrayDimensions);
- }
- throw "Invalid type $dartType";
- }
-
- /// The size in bytes per [Abi].
- Map<Abi, int> get size;
-
- /// The alignment inside structs in bytes per [Abi].
- ///
- /// This is not the alignment on stack, this is only calculated in the VM.
- Map<Abi, int> get alignment;
-
- /// Generates a Constant representing the type which is consumed by the VM.
- ///
- /// Takes [transformer] to be able to lookup classes and methods.
- ///
- /// See runtime/vm/compiler/ffi/native_type.cc:NativeType::FromAbstractType.
- Constant generateConstant(FfiTransformer transformer);
-
- /// Generates the return statement for a compound field getter with this type.
- ///
- /// Takes [transformer] to be able to lookup classes and methods.
- ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
- Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer);
-
- /// Generates the return statement for a compound field setter with this type.
- ///
- /// Takes [transformer] to be able to lookup classes and methods.
- ReturnStatement generateSetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- VariableDeclaration argument,
- FfiTransformer transformer);
-}
-
-class InvalidNativeTypeCfe implements NativeTypeCfe {
- final String reason;
-
- InvalidNativeTypeCfe(this.reason);
-
- @override
- Map<Abi, int> get alignment => throw reason;
-
- @override
- Constant generateConstant(FfiTransformer transformer) => throw reason;
-
- @override
- ReturnStatement generateGetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- FfiTransformer transformer) =>
- throw reason;
-
- @override
- ReturnStatement generateSetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- VariableDeclaration argument,
- FfiTransformer transformer) =>
- throw reason;
-
- @override
- Map<Abi, int> get size => throw reason;
-}
-
-class PrimitiveNativeTypeCfe implements NativeTypeCfe {
- final NativeType nativeType;
-
- final Class clazz;
-
- PrimitiveNativeTypeCfe(this.nativeType, this.clazz);
-
- @override
- Map<Abi, int> get size {
- final int size = nativeTypeSizes[nativeType]!;
- if (size == WORD_SIZE) {
- return wordSize;
- }
- return {for (var abi in Abi.values) abi: size};
- }
-
- @override
- Map<Abi, int> get alignment => {
- for (var abi in Abi.values)
- abi: nonSizeAlignment[abi]![nativeType] ?? size[abi]!
- };
-
- @override
- Constant generateConstant(FfiTransformer transformer) =>
- TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
-
- bool get isFloat =>
- nativeType == NativeType.kFloat || nativeType == NativeType.kDouble;
-
- bool isUnaligned(Map<Abi, int> offsets) {
- final alignments = alignment;
- for (final abi in offsets.keys) {
- final offset = offsets[abi]!;
- final alignment = alignments[abi]!;
- if (offset % alignment != 0) {
- return true;
- }
- }
- return false;
- }
-
- /// Sample output for `int get x =>`:
- ///
- /// ```
- /// _loadInt8(_typedDataBase, offset);
- /// ```
- @override
- ReturnStatement generateGetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- FfiTransformer transformer) =>
- ReturnStatement(StaticInvocation(
- (unalignedAccess && isFloat
- ? transformer.loadUnalignedMethods
- : transformer.loadMethods)[nativeType]!,
- Arguments([
- transformer.getCompoundTypedDataBaseField(
- ThisExpression(), fileOffset),
- transformer.runtimeBranchOnLayout(offsets)
- ]))
- ..fileOffset = fileOffset);
-
- /// Sample output for `set x(int #v) =>`:
- ///
- /// ```
- /// _storeInt8(_typedDataBase, offset, #v);
- /// ```
- @override
- ReturnStatement generateSetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- VariableDeclaration argument,
- FfiTransformer transformer) =>
- ReturnStatement(StaticInvocation(
- (unalignedAccess && isFloat
- ? transformer.storeUnalignedMethods
- : transformer.storeMethods)[nativeType]!,
- Arguments([
- transformer.getCompoundTypedDataBaseField(
- ThisExpression(), fileOffset),
- transformer.runtimeBranchOnLayout(offsets),
- VariableGet(argument)
- ]))
- ..fileOffset = fileOffset);
-}
-
-class PointerNativeTypeCfe implements NativeTypeCfe {
- @override
- Map<Abi, int> get size => wordSize;
-
- @override
- Map<Abi, int> get alignment => wordSize;
-
- @override
- Constant generateConstant(FfiTransformer transformer) => TypeLiteralConstant(
- InterfaceType(transformer.pointerClass, Nullability.nonNullable, [
- InterfaceType(
- transformer.pointerClass.superclass!, Nullability.nonNullable)
- ]));
-
- /// Sample output for `Pointer<Int8> get x =>`:
- ///
- /// ```
- /// _fromAddress<Int8>(_loadIntPtr(_typedDataBase, offset));
- /// ```
- @override
- ReturnStatement generateGetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- FfiTransformer transformer) =>
- ReturnStatement(StaticInvocation(
- transformer.fromAddressInternal,
- Arguments([
- StaticInvocation(
- transformer.loadMethods[NativeType.kIntptr]!,
- Arguments([
- transformer.getCompoundTypedDataBaseField(
- ThisExpression(), fileOffset),
- transformer.runtimeBranchOnLayout(offsets)
- ]))
- ..fileOffset = fileOffset
- ], types: [
- (dartType as InterfaceType).typeArguments.single
- ]))
- ..fileOffset = fileOffset);
-
- /// Sample output for `set x(Pointer<Int8> #v) =>`:
- ///
- /// ```
- /// _storeIntPtr(_typedDataBase, offset, (#v as Pointer<Int8>).address);
- /// ```
- @override
- ReturnStatement generateSetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- VariableDeclaration argument,
- FfiTransformer transformer) =>
- ReturnStatement(StaticInvocation(
- transformer.storeMethods[NativeType.kIntptr]!,
- Arguments([
- transformer.getCompoundTypedDataBaseField(
- ThisExpression(), fileOffset),
- transformer.runtimeBranchOnLayout(offsets),
- InstanceGet(InstanceAccessKind.Instance, VariableGet(argument),
- transformer.addressGetter.name,
- interfaceTarget: transformer.addressGetter,
- resultType: transformer.addressGetter.getterType)
- ..fileOffset = fileOffset
- ]))
- ..fileOffset = fileOffset);
-}
-
-abstract class CompoundNativeTypeCfe implements NativeTypeCfe {
- final Class clazz;
-
- final List<NativeTypeCfe> members;
-
- final Map<Abi, CompoundLayout> layout;
-
- CompoundNativeTypeCfe._(this.clazz, this.members, this.layout);
-
- @override
- Map<Abi, int> get size =>
- layout.map((abi, layout) => MapEntry(abi, layout.size));
-
- @override
- Map<Abi, int> get alignment =>
- layout.map((abi, layout) => MapEntry(abi, layout.alignment));
-
- @override
- Constant generateConstant(FfiTransformer transformer) =>
- TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
-
- /// Sample output for `MyStruct get x =>`:
- ///
- /// ```
- /// MyStruct.#fromTypedDataBase(
- /// typedDataBaseOffset(_typedDataBase, offset, size, dartType)
- /// );
- /// ```
- @override
- ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
- Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer) {
- final constructor = clazz.constructors
- .firstWhere((c) => c.name == Name("#fromTypedDataBase"));
-
- return ReturnStatement(ConstructorInvocation(
- constructor,
- Arguments([
- transformer.typedDataBaseOffset(
- transformer.getCompoundTypedDataBaseField(
- ThisExpression(), fileOffset),
- transformer.runtimeBranchOnLayout(offsets),
- transformer.runtimeBranchOnLayout(size),
- dartType,
- fileOffset)
- ]))
- ..fileOffset = fileOffset);
- }
-
- /// Sample output for `set x(MyStruct #v) =>`:
- ///
- /// ```
- /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
- /// ```
- @override
- ReturnStatement generateSetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- VariableDeclaration argument,
- FfiTransformer transformer) =>
- ReturnStatement(StaticInvocation(
- transformer.memCopy,
- Arguments([
- transformer.getCompoundTypedDataBaseField(
- ThisExpression(), fileOffset),
- transformer.runtimeBranchOnLayout(offsets),
- transformer.getCompoundTypedDataBaseField(
- VariableGet(argument), fileOffset),
- ConstantExpression(IntConstant(0)),
- transformer.runtimeBranchOnLayout(size),
- ]))
- ..fileOffset = fileOffset);
-}
-
-class StructNativeTypeCfe extends CompoundNativeTypeCfe {
- // Nullable int.
- final int? packing;
-
- factory StructNativeTypeCfe(Class clazz, List<NativeTypeCfe> members,
- {int? packing}) {
- final layout = {
- for (var abi in Abi.values) abi: _calculateLayout(members, packing, abi)
- };
- return StructNativeTypeCfe._(clazz, members, packing, layout);
- }
-
- StructNativeTypeCfe._(Class clazz, List<NativeTypeCfe> members, this.packing,
- Map<Abi, CompoundLayout> layout)
- : super._(clazz, members, layout);
-
- // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
- // NativeStructType::FromNativeTypes.
- static CompoundLayout _calculateLayout(
- List<NativeTypeCfe> types, int? packing, Abi abi) {
- int offset = 0;
- final offsets = <int>[];
- int structAlignment = 1;
- for (int i = 0; i < types.length; i++) {
- final int size = types[i].size[abi]!;
- int alignment = types[i].alignment[abi]!;
- if (packing != null && packing < alignment) {
- alignment = packing;
- }
- if (alignment > 0) {
- offset = _alignOffset(offset, alignment);
- }
- offsets.add(offset);
- offset += size;
- structAlignment = math.max(structAlignment, alignment);
- }
- final int size = _alignOffset(offset, structAlignment);
- return CompoundLayout(size, structAlignment, offsets);
- }
-}
-
-class UnionNativeTypeCfe extends CompoundNativeTypeCfe {
- factory UnionNativeTypeCfe(Class clazz, List<NativeTypeCfe> members) {
- final layout = {
- for (var abi in Abi.values) abi: _calculateLayout(members, abi)
- };
- return UnionNativeTypeCfe._(clazz, members, layout);
- }
-
- UnionNativeTypeCfe._(
- Class clazz, List<NativeTypeCfe> members, Map<Abi, CompoundLayout> layout)
- : super._(clazz, members, layout);
-
- // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
- // NativeUnionType::FromNativeTypes.
- static CompoundLayout _calculateLayout(List<NativeTypeCfe> types, Abi abi) {
- int unionSize = 1;
- int unionAlignment = 1;
- for (int i = 0; i < types.length; i++) {
- final int size = types[i].size[abi]!;
- int alignment = types[i].alignment[abi]!;
- unionSize = math.max(unionSize, size);
- unionAlignment = math.max(unionAlignment, alignment);
- }
- final int size = _alignOffset(unionSize, unionAlignment);
- return CompoundLayout(size, unionAlignment, List.filled(types.length, 0));
- }
-}
-
-class ArrayNativeTypeCfe implements NativeTypeCfe {
- final NativeTypeCfe elementType;
- final int length;
-
- ArrayNativeTypeCfe(this.elementType, this.length);
-
- factory ArrayNativeTypeCfe.multi(
- NativeTypeCfe elementType, List<int> dimensions) {
- if (dimensions.length == 1) {
- return ArrayNativeTypeCfe(elementType, dimensions.single);
- }
- return ArrayNativeTypeCfe(
- ArrayNativeTypeCfe.multi(elementType, dimensions.sublist(1)),
- dimensions.first);
- }
-
- List<int> get dimensions {
- final elementType = this.elementType;
- if (elementType is ArrayNativeTypeCfe) {
- return [length, ...elementType.dimensions];
- }
- return [length];
- }
-
- List<int> get nestedDimensions => dimensions.sublist(1);
-
- int get dimensionsFlattened =>
- dimensions.fold(1, (accumulator, element) => accumulator * element);
-
- NativeTypeCfe get singleElementType {
- final elementType = this.elementType;
- if (elementType is ArrayNativeTypeCfe) {
- return elementType.singleElementType;
- }
- return elementType;
- }
-
- @override
- Map<Abi, int> get size =>
- elementType.size.map((abi, size) => MapEntry(abi, size * length));
-
- @override
- Map<Abi, int> get alignment => elementType.alignment;
-
- // Note that we flatten multi dimensional arrays.
- @override
- Constant generateConstant(FfiTransformer transformer) =>
- InstanceConstant(transformer.ffiInlineArrayClass.reference, [], {
- transformer.ffiInlineArrayElementTypeField.fieldReference:
- singleElementType.generateConstant(transformer),
- transformer.ffiInlineArrayLengthField.fieldReference:
- IntConstant(dimensionsFlattened)
- });
-
- /// Sample output for `Array<Int8> get x =>`:
- ///
- /// ```
- /// Array<Int8>._(
- /// typedDataBaseOffset(_typedDataBase, offset, size, typeArgument)
- /// );
- /// ```
- @override
- ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
- Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer) {
- InterfaceType typeArgument =
- (dartType as InterfaceType).typeArguments.single as InterfaceType;
- return ReturnStatement(ConstructorInvocation(
- transformer.arrayConstructor,
- Arguments([
- transformer.typedDataBaseOffset(
- transformer.getCompoundTypedDataBaseField(
- ThisExpression(), fileOffset),
- transformer.runtimeBranchOnLayout(offsets),
- transformer.runtimeBranchOnLayout(size),
- typeArgument,
- fileOffset),
- ConstantExpression(IntConstant(length)),
- transformer.intListConstantExpression(nestedDimensions)
- ], types: [
- typeArgument
- ]))
- ..fileOffset = fileOffset);
- }
-
- /// Sample output for `set x(Array #v) =>`:
- ///
- /// ```
- /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
- /// ```
- @override
- ReturnStatement generateSetterStatement(
- DartType dartType,
- int fileOffset,
- Map<Abi, int> offsets,
- bool unalignedAccess,
- VariableDeclaration argument,
- FfiTransformer transformer) =>
- ReturnStatement(StaticInvocation(
- transformer.memCopy,
- Arguments([
- transformer.getCompoundTypedDataBaseField(
- ThisExpression(), fileOffset),
- transformer.runtimeBranchOnLayout(offsets),
- transformer.getArrayTypedDataBaseField(
- VariableGet(argument), fileOffset),
- ConstantExpression(IntConstant(0)),
- transformer.runtimeBranchOnLayout(size),
- ]))
- ..fileOffset = fileOffset);
-}
-
-int _alignOffset(int offset, int alignment) =>
- ((offset + alignment - 1) ~/ alignment) * alignment;
diff --git a/pkg/vm/lib/transformations/ffi/native_type_cfe.dart b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
new file mode 100644
index 0000000..862d1bd
--- /dev/null
+++ b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
@@ -0,0 +1,627 @@
+// Copyright (c) 2021, 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.
+
+import 'dart:math' as math;
+
+import 'package:kernel/ast.dart';
+
+import 'abi.dart';
+import 'common.dart';
+
+/// AST node wrapper for native types.
+///
+/// This algebraic data structure does not stand on its own but refers
+/// intimately to AST nodes such as [Class].
+abstract class NativeTypeCfe {
+ factory NativeTypeCfe(FfiTransformer transformer, DartType dartType,
+ {List<int>? arrayDimensions,
+ Map<Class, NativeTypeCfe> compoundCache = const {}}) {
+ if (transformer.isPrimitiveType(dartType)) {
+ final clazz = (dartType as InterfaceType).classNode;
+ final nativeType = transformer.getType(clazz)!;
+ return PrimitiveNativeTypeCfe(nativeType, clazz);
+ }
+ if (transformer.isPointerType(dartType)) {
+ return PointerNativeTypeCfe();
+ }
+ if (transformer.isCompoundSubtype(dartType)) {
+ final clazz = (dartType as InterfaceType).classNode;
+ if (compoundCache.containsKey(clazz)) {
+ return compoundCache[clazz]!;
+ } else {
+ throw "Class '$clazz' not found in compoundCache.";
+ }
+ }
+ if (transformer.isArrayType(dartType)) {
+ if (arrayDimensions == null) {
+ throw "Must have array dimensions for ArrayType.";
+ }
+ if (arrayDimensions.length == 0) {
+ throw "Must have a size for this array dimension.";
+ }
+ final elementType = transformer.arraySingleElementType(dartType);
+ final elementCfeType =
+ NativeTypeCfe(transformer, elementType, compoundCache: compoundCache);
+ if (elementCfeType is InvalidNativeTypeCfe) {
+ return elementCfeType;
+ }
+ return ArrayNativeTypeCfe.multi(elementCfeType, arrayDimensions);
+ }
+ throw "Invalid type $dartType";
+ }
+
+ /// The size in bytes per [Abi].
+ Map<Abi, int?> get size;
+
+ /// The alignment inside structs in bytes per [Abi].
+ ///
+ /// This is not the alignment on stack, this is only calculated in the VM.
+ Map<Abi, int?> get alignment;
+
+ /// Generates a Constant representing the type which is consumed by the VM.
+ ///
+ /// Takes [transformer] to be able to lookup classes and methods.
+ ///
+ /// See runtime/vm/compiler/ffi/native_type.cc:NativeType::FromAbstractType.
+ Constant generateConstant(FfiTransformer transformer);
+
+ /// Generates the return statement for a compound field getter with this type.
+ ///
+ /// Takes [transformer] to be able to lookup classes and methods.
+ ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
+ Map<Abi, int?> offsets, bool unalignedAccess, FfiTransformer transformer);
+
+ /// Generates the return statement for a compound field setter with this type.
+ ///
+ /// Takes [transformer] to be able to lookup classes and methods.
+ ReturnStatement generateSetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ VariableDeclaration argument,
+ FfiTransformer transformer);
+}
+
+class InvalidNativeTypeCfe implements NativeTypeCfe {
+ final String reason;
+
+ InvalidNativeTypeCfe(this.reason);
+
+ @override
+ Map<Abi, int?> get alignment => throw reason;
+
+ @override
+ Constant generateConstant(FfiTransformer transformer) => throw reason;
+
+ @override
+ ReturnStatement generateGetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ FfiTransformer transformer) =>
+ throw reason;
+
+ @override
+ ReturnStatement generateSetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ VariableDeclaration argument,
+ FfiTransformer transformer) =>
+ throw reason;
+
+ @override
+ Map<Abi, int?> get size => throw reason;
+}
+
+class PrimitiveNativeTypeCfe implements NativeTypeCfe {
+ final NativeType nativeType;
+
+ final Class clazz;
+
+ PrimitiveNativeTypeCfe(this.nativeType, this.clazz);
+
+ @override
+ Map<Abi, int?> get size {
+ final int size = nativeTypeSizes[nativeType]!;
+ if (size == WORD_SIZE) {
+ return wordSize;
+ }
+ return {for (var abi in Abi.values) abi: size};
+ }
+
+ @override
+ Map<Abi, int> get alignment => {
+ for (var abi in Abi.values)
+ abi: nonSizeAlignment[abi]![nativeType] ?? size[abi]!
+ };
+
+ @override
+ Constant generateConstant(FfiTransformer transformer) =>
+ TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
+
+ bool get isFloat =>
+ nativeType == NativeType.kFloat || nativeType == NativeType.kDouble;
+
+ bool isUnaligned(Map<Abi, int?> offsets) {
+ final alignments = alignment;
+ for (final abi in offsets.keys) {
+ final offset = offsets[abi]!;
+ final alignment = alignments[abi]!;
+ if (offset % alignment != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// Sample output for `int get x =>`:
+ ///
+ /// ```
+ /// _loadInt8(_typedDataBase, offset);
+ /// ```
+ @override
+ ReturnStatement generateGetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ FfiTransformer transformer) =>
+ ReturnStatement(StaticInvocation(
+ (unalignedAccess && isFloat
+ ? transformer.loadUnalignedMethods
+ : transformer.loadMethods)[nativeType]!,
+ Arguments([
+ transformer.getCompoundTypedDataBaseField(
+ ThisExpression(), fileOffset),
+ transformer.runtimeBranchOnLayout(offsets)
+ ]))
+ ..fileOffset = fileOffset);
+
+ /// Sample output for `set x(int #v) =>`:
+ ///
+ /// ```
+ /// _storeInt8(_typedDataBase, offset, #v);
+ /// ```
+ @override
+ ReturnStatement generateSetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ VariableDeclaration argument,
+ FfiTransformer transformer) =>
+ ReturnStatement(StaticInvocation(
+ (unalignedAccess && isFloat
+ ? transformer.storeUnalignedMethods
+ : transformer.storeMethods)[nativeType]!,
+ Arguments([
+ transformer.getCompoundTypedDataBaseField(
+ ThisExpression(), fileOffset),
+ transformer.runtimeBranchOnLayout(offsets),
+ VariableGet(argument)
+ ]))
+ ..fileOffset = fileOffset);
+}
+
+class PointerNativeTypeCfe implements NativeTypeCfe {
+ @override
+ Map<Abi, int?> get size => wordSize;
+
+ @override
+ Map<Abi, int?> get alignment => wordSize;
+
+ @override
+ Constant generateConstant(FfiTransformer transformer) => TypeLiteralConstant(
+ InterfaceType(transformer.pointerClass, Nullability.nonNullable, [
+ InterfaceType(
+ transformer.pointerClass.superclass!, Nullability.nonNullable)
+ ]));
+
+ /// Sample output for `Pointer<Int8> get x =>`:
+ ///
+ /// ```
+ /// _fromAddress<Int8>(_loadIntPtr(_typedDataBase, offset));
+ /// ```
+ @override
+ ReturnStatement generateGetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ FfiTransformer transformer) =>
+ ReturnStatement(StaticInvocation(
+ transformer.fromAddressInternal,
+ Arguments([
+ StaticInvocation(
+ transformer.loadMethods[NativeType.kIntptr]!,
+ Arguments([
+ transformer.getCompoundTypedDataBaseField(
+ ThisExpression(), fileOffset),
+ transformer.runtimeBranchOnLayout(offsets)
+ ]))
+ ..fileOffset = fileOffset
+ ], types: [
+ (dartType as InterfaceType).typeArguments.single
+ ]))
+ ..fileOffset = fileOffset);
+
+ /// Sample output for `set x(Pointer<Int8> #v) =>`:
+ ///
+ /// ```
+ /// _storeIntPtr(_typedDataBase, offset, (#v as Pointer<Int8>).address);
+ /// ```
+ @override
+ ReturnStatement generateSetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ VariableDeclaration argument,
+ FfiTransformer transformer) =>
+ ReturnStatement(StaticInvocation(
+ transformer.storeMethods[NativeType.kIntptr]!,
+ Arguments([
+ transformer.getCompoundTypedDataBaseField(
+ ThisExpression(), fileOffset),
+ transformer.runtimeBranchOnLayout(offsets),
+ InstanceGet(InstanceAccessKind.Instance, VariableGet(argument),
+ transformer.addressGetter.name,
+ interfaceTarget: transformer.addressGetter,
+ resultType: transformer.addressGetter.getterType)
+ ..fileOffset = fileOffset
+ ]))
+ ..fileOffset = fileOffset);
+}
+
+/// The layout of a `Struct` or `Union` in one [Abi].
+class CompoundLayout {
+ /// Size of the entire struct or union.
+ final int? size;
+
+ /// Alignment of struct or union when nested in a struct.
+ final int? alignment;
+
+ /// Offset in bytes for each field, indexed by field number.
+ ///
+ /// Always 0 for unions.
+ final List<int?> offsets;
+
+ CompoundLayout(this.size, this.alignment, this.offsets);
+}
+
+abstract class CompoundNativeTypeCfe implements NativeTypeCfe {
+ final Class clazz;
+
+ final List<NativeTypeCfe> members;
+
+ final Map<Abi, CompoundLayout> layout;
+
+ CompoundNativeTypeCfe._(this.clazz, this.members, this.layout);
+
+ @override
+ Map<Abi, int?> get size =>
+ layout.map((abi, layout) => MapEntry(abi, layout.size));
+
+ @override
+ Map<Abi, int?> get alignment =>
+ layout.map((abi, layout) => MapEntry(abi, layout.alignment));
+
+ @override
+ Constant generateConstant(FfiTransformer transformer) =>
+ TypeLiteralConstant(InterfaceType(clazz, Nullability.nonNullable));
+
+ /// Sample output for `MyStruct get x =>`:
+ ///
+ /// ```
+ /// MyStruct.#fromTypedDataBase(
+ /// typedDataBaseOffset(_typedDataBase, offset, size, dartType)
+ /// );
+ /// ```
+ @override
+ ReturnStatement generateGetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ FfiTransformer transformer) {
+ final constructor = clazz.constructors
+ .firstWhere((c) => c.name == Name("#fromTypedDataBase"));
+
+ return ReturnStatement(ConstructorInvocation(
+ constructor,
+ Arguments([
+ transformer.typedDataBaseOffset(
+ transformer.getCompoundTypedDataBaseField(
+ ThisExpression(), fileOffset),
+ transformer.runtimeBranchOnLayout(offsets),
+ transformer.runtimeBranchOnLayout(size),
+ dartType,
+ fileOffset)
+ ]))
+ ..fileOffset = fileOffset);
+ }
+
+ /// Sample output for `set x(MyStruct #v) =>`:
+ ///
+ /// ```
+ /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
+ /// ```
+ @override
+ ReturnStatement generateSetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ VariableDeclaration argument,
+ FfiTransformer transformer) =>
+ ReturnStatement(StaticInvocation(
+ transformer.memCopy,
+ Arguments([
+ transformer.getCompoundTypedDataBaseField(
+ ThisExpression(), fileOffset),
+ transformer.runtimeBranchOnLayout(offsets),
+ transformer.getCompoundTypedDataBaseField(
+ VariableGet(argument), fileOffset),
+ ConstantExpression(IntConstant(0)),
+ transformer.runtimeBranchOnLayout(size),
+ ]))
+ ..fileOffset = fileOffset);
+}
+
+class StructNativeTypeCfe extends CompoundNativeTypeCfe {
+ // Nullable int.
+ final int? packing;
+
+ factory StructNativeTypeCfe(Class clazz, List<NativeTypeCfe> members,
+ {int? packing}) {
+ final layout = {
+ for (var abi in Abi.values) abi: _calculateLayout(members, packing, abi)
+ };
+ return StructNativeTypeCfe._(clazz, members, packing, layout);
+ }
+
+ StructNativeTypeCfe._(Class clazz, List<NativeTypeCfe> members, this.packing,
+ Map<Abi, CompoundLayout> layout)
+ : super._(clazz, members, layout);
+
+ // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
+ // NativeStructType::FromNativeTypes.
+ static CompoundLayout _calculateLayout(
+ List<NativeTypeCfe> types, int? packing, Abi abi) {
+ int? offset = 0;
+ final offsets = <int?>[];
+ int? structAlignment = 1;
+ for (int i = 0; i < types.length; i++) {
+ final int? size = types[i].size[abi];
+ int? alignment = types[i].alignment[abi];
+ if (packing != null) {
+ alignment = min(packing, alignment);
+ }
+ if (alignment != null && alignment > 0) {
+ offset = offset.align(alignment);
+ }
+ offsets.add(offset);
+ offset += size;
+ structAlignment = max(structAlignment, alignment);
+ }
+ final int? size = offset.align(structAlignment);
+ return CompoundLayout(size, structAlignment, offsets);
+ }
+}
+
+class UnionNativeTypeCfe extends CompoundNativeTypeCfe {
+ factory UnionNativeTypeCfe(Class clazz, List<NativeTypeCfe> members) {
+ final layout = {
+ for (var abi in Abi.values) abi: _calculateLayout(members, abi)
+ };
+ return UnionNativeTypeCfe._(clazz, members, layout);
+ }
+
+ UnionNativeTypeCfe._(
+ Class clazz, List<NativeTypeCfe> members, Map<Abi, CompoundLayout> layout)
+ : super._(clazz, members, layout);
+
+ // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
+ // NativeUnionType::FromNativeTypes.
+ static CompoundLayout _calculateLayout(List<NativeTypeCfe> types, Abi abi) {
+ int? unionSize = 1;
+ int? unionAlignment = 1;
+ for (int i = 0; i < types.length; i++) {
+ final int? size = types[i].size[abi];
+ int? alignment = types[i].alignment[abi];
+ unionSize = max(unionSize, size);
+ unionAlignment = max(unionAlignment, alignment);
+ }
+ final int? size = unionSize.align(unionAlignment);
+ return CompoundLayout(size, unionAlignment, List.filled(types.length, 0));
+ }
+}
+
+class ArrayNativeTypeCfe implements NativeTypeCfe {
+ final NativeTypeCfe elementType;
+ final int length;
+
+ ArrayNativeTypeCfe(this.elementType, this.length);
+
+ factory ArrayNativeTypeCfe.multi(
+ NativeTypeCfe elementType, List<int> dimensions) {
+ if (dimensions.length == 1) {
+ return ArrayNativeTypeCfe(elementType, dimensions.single);
+ }
+ return ArrayNativeTypeCfe(
+ ArrayNativeTypeCfe.multi(elementType, dimensions.sublist(1)),
+ dimensions.first);
+ }
+
+ List<int> get dimensions {
+ final elementType = this.elementType;
+ if (elementType is ArrayNativeTypeCfe) {
+ return [length, ...elementType.dimensions];
+ }
+ return [length];
+ }
+
+ List<int> get nestedDimensions => dimensions.sublist(1);
+
+ int get dimensionsFlattened =>
+ dimensions.fold(1, (accumulator, element) => accumulator * element);
+
+ NativeTypeCfe get singleElementType {
+ final elementType = this.elementType;
+ if (elementType is ArrayNativeTypeCfe) {
+ return elementType.singleElementType;
+ }
+ return elementType;
+ }
+
+ @override
+ Map<Abi, int?> get size =>
+ elementType.size.map((abi, size) => MapEntry(abi, size * length));
+
+ @override
+ Map<Abi, int?> get alignment => elementType.alignment;
+
+ // Note that we flatten multi dimensional arrays.
+ @override
+ Constant generateConstant(FfiTransformer transformer) =>
+ InstanceConstant(transformer.ffiInlineArrayClass.reference, [], {
+ transformer.ffiInlineArrayElementTypeField.fieldReference:
+ singleElementType.generateConstant(transformer),
+ transformer.ffiInlineArrayLengthField.fieldReference:
+ IntConstant(dimensionsFlattened)
+ });
+
+ /// Sample output for `Array<Int8> get x =>`:
+ ///
+ /// ```
+ /// Array<Int8>._(
+ /// typedDataBaseOffset(_typedDataBase, offset, size, typeArgument)
+ /// );
+ /// ```
+ @override
+ ReturnStatement generateGetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ FfiTransformer transformer) {
+ InterfaceType typeArgument =
+ (dartType as InterfaceType).typeArguments.single as InterfaceType;
+ return ReturnStatement(ConstructorInvocation(
+ transformer.arrayConstructor,
+ Arguments([
+ transformer.typedDataBaseOffset(
+ transformer.getCompoundTypedDataBaseField(
+ ThisExpression(), fileOffset),
+ transformer.runtimeBranchOnLayout(offsets),
+ transformer.runtimeBranchOnLayout(size),
+ typeArgument,
+ fileOffset),
+ ConstantExpression(IntConstant(length)),
+ transformer.intListConstantExpression(nestedDimensions)
+ ], types: [
+ typeArgument
+ ]))
+ ..fileOffset = fileOffset);
+ }
+
+ /// Sample output for `set x(Array #v) =>`:
+ ///
+ /// ```
+ /// _memCopy(_typedDataBase, offset, #v._typedDataBase, 0, size);
+ /// ```
+ @override
+ ReturnStatement generateSetterStatement(
+ DartType dartType,
+ int fileOffset,
+ Map<Abi, int?> offsets,
+ bool unalignedAccess,
+ VariableDeclaration argument,
+ FfiTransformer transformer) =>
+ ReturnStatement(StaticInvocation(
+ transformer.memCopy,
+ Arguments([
+ transformer.getCompoundTypedDataBaseField(
+ ThisExpression(), fileOffset),
+ transformer.runtimeBranchOnLayout(offsets),
+ transformer.getArrayTypedDataBaseField(
+ VariableGet(argument), fileOffset),
+ ConstantExpression(IntConstant(0)),
+ transformer.runtimeBranchOnLayout(size),
+ ]))
+ ..fileOffset = fileOffset);
+}
+
+extension on int? {
+ int? align(int? alignment) =>
+ ((this + alignment - 1) ~/ alignment) * alignment;
+
+ int? operator *(int? other) {
+ final this_ = this;
+ if (this_ == null) {
+ return null;
+ }
+ if (other == null) {
+ return null;
+ }
+ return this_ * other;
+ }
+
+ int? operator +(int? other) {
+ final this_ = this;
+ if (this_ == null) {
+ return null;
+ }
+ if (other == null) {
+ return null;
+ }
+ return this_ + other;
+ }
+
+ int? operator -(int? other) {
+ final this_ = this;
+ if (this_ == null) {
+ return null;
+ }
+ if (other == null) {
+ return null;
+ }
+ return this_ - other;
+ }
+
+ int? operator ~/(int? other) {
+ final this_ = this;
+ if (this_ == null) {
+ return null;
+ }
+ if (other == null) {
+ return null;
+ }
+ return this_ ~/ other;
+ }
+}
+
+int? max(int? a, int? b) {
+ if (a == null) {
+ return null;
+ }
+ if (b == null) {
+ return null;
+ }
+ return math.max(a, b);
+}
+
+int? min(int? a, int? b) {
+ if (a == null) {
+ return null;
+ }
+ if (b == null) {
+ return null;
+ }
+ return math.min(a, b);
+}
diff --git a/runtime/vm/compiler/ffi/marshaller.cc b/runtime/vm/compiler/ffi/marshaller.cc
index dbaff91..6cf5ece 100644
--- a/runtime/vm/compiler/ffi/marshaller.cc
+++ b/runtime/vm/compiler/ffi/marshaller.cc
@@ -25,9 +25,10 @@
const intptr_t kNativeParamsStartAt = 1;
// Representations of the arguments and return value of a C signature function.
-static const NativeFunctionType& NativeFunctionSignature(
+const NativeFunctionType* NativeFunctionTypeFromFunctionType(
Zone* zone,
- const FunctionType& c_signature) {
+ const FunctionType& c_signature,
+ const char** error) {
ASSERT(c_signature.NumOptionalParameters() == 0);
ASSERT(c_signature.NumOptionalPositionalParameters() == 0);
@@ -38,29 +39,41 @@
for (intptr_t i = 0; i < num_arguments; i++) {
AbstractType& arg_type = AbstractType::Handle(
zone, c_signature.ParameterTypeAt(i + kNativeParamsStartAt));
- const auto& rep = NativeType::FromAbstractType(zone, arg_type);
- argument_representations.Add(&rep);
+ const auto rep = NativeType::FromAbstractType(zone, arg_type, error);
+ if (*error != nullptr) {
+ return nullptr;
+ }
+ argument_representations.Add(rep);
}
const auto& result_type =
AbstractType::Handle(zone, c_signature.result_type());
- const auto& result_representation =
- NativeType::FromAbstractType(zone, result_type);
+ const auto result_representation =
+ NativeType::FromAbstractType(zone, result_type, error);
+ if (*error != nullptr) {
+ return nullptr;
+ }
- const auto& result = *new (zone) NativeFunctionType(argument_representations,
- result_representation);
+ const auto result = new (zone)
+ NativeFunctionType(argument_representations, *result_representation);
return result;
}
-BaseMarshaller::BaseMarshaller(Zone* zone, const Function& dart_signature)
- : zone_(zone),
- dart_signature_(dart_signature),
- c_signature_(
- FunctionType::ZoneHandle(zone, dart_signature.FfiCSignature())),
- native_calling_convention_(NativeCallingConvention::FromSignature(
- zone,
- NativeFunctionSignature(zone_, c_signature_))) {
- ASSERT(dart_signature_.IsZoneHandle());
+CallMarshaller* CallMarshaller::FromFunction(Zone* zone,
+ const Function& function,
+ const char** error) {
+ ASSERT(function.IsZoneHandle());
+ const auto& c_signature =
+ FunctionType::ZoneHandle(zone, function.FfiCSignature());
+ const auto native_function_signature =
+ NativeFunctionTypeFromFunctionType(zone, c_signature, error);
+ if (*error != nullptr) {
+ return nullptr;
+ }
+ const auto& native_calling_convention =
+ NativeCallingConvention::FromSignature(zone, *native_function_signature);
+ return new (zone)
+ CallMarshaller(zone, function, c_signature, native_calling_convention);
}
AbstractTypePtr BaseMarshaller::CType(intptr_t arg_index) const {
@@ -72,6 +85,27 @@
return c_signature_.ParameterTypeAt(arg_index + kNativeParamsStartAt);
}
+// Keep consistent with Function::FfiCSignatureReturnsStruct.
+bool BaseMarshaller::IsCompound(intptr_t arg_index) const {
+ const auto& type = AbstractType::Handle(zone_, CType(arg_index));
+ if (IsFfiTypeClassId(type.type_class_id())) {
+ return false;
+ }
+#ifdef DEBUG
+ const auto& cls = Class::Handle(this->zone_, type.type_class());
+ const auto& superClass = Class::Handle(this->zone_, cls.SuperClass());
+ // TODO(http://dartbug.com/42563): Implement AbiSpecificInt.
+ const bool is_struct =
+ String::Handle(this->zone_, superClass.UserVisibleName())
+ .Equals(Symbols::Struct());
+ const bool is_union =
+ String::Handle(this->zone_, superClass.UserVisibleName())
+ .Equals(Symbols::Union());
+ RELEASE_ASSERT(is_struct || is_union);
+#endif
+ return true;
+}
+
bool BaseMarshaller::ContainsHandles() const {
return dart_signature_.FfiCSignatureContainsHandles();
}
@@ -574,13 +608,26 @@
intptr_t argument_slots_required_ = 0;
};
-CallbackMarshaller::CallbackMarshaller(Zone* zone,
- const Function& dart_signature)
- : BaseMarshaller(zone, dart_signature),
- callback_locs_(CallbackArgumentTranslator::TranslateArgumentLocations(
- zone_,
- native_calling_convention_.argument_locations(),
- native_calling_convention_.return_location())) {}
+CallbackMarshaller* CallbackMarshaller::FromFunction(Zone* zone,
+ const Function& function,
+ const char** error) {
+ ASSERT(function.IsZoneHandle());
+ const auto& c_signature =
+ FunctionType::ZoneHandle(zone, function.FfiCSignature());
+ const auto native_function_signature =
+ NativeFunctionTypeFromFunctionType(zone, c_signature, error);
+ if (*error != nullptr) {
+ return nullptr;
+ }
+ const auto& native_calling_convention =
+ NativeCallingConvention::FromSignature(zone, *native_function_signature);
+ const auto& callback_locs =
+ CallbackArgumentTranslator::TranslateArgumentLocations(
+ zone, native_calling_convention.argument_locations(),
+ native_calling_convention.return_location());
+ return new (zone) CallbackMarshaller(
+ zone, function, c_signature, native_calling_convention, callback_locs);
+}
const NativeLocation& CallbackMarshaller::NativeLocationOfNativeParameter(
intptr_t def_index) const {
diff --git a/runtime/vm/compiler/ffi/marshaller.h b/runtime/vm/compiler/ffi/marshaller.h
index fc29f67..b7607e9 100644
--- a/runtime/vm/compiler/ffi/marshaller.h
+++ b/runtime/vm/compiler/ffi/marshaller.h
@@ -28,6 +28,13 @@
// Values below 0 index result (result might be multiple if composite).
const intptr_t kResultIndex = -1;
+// Inspects the function signature and transitively any class and field
+// definitions and annotations.
+const NativeFunctionType* NativeFunctionTypeFromFunctionType(
+ Zone* zone,
+ const FunctionType& c_signature,
+ const char** error);
+
// Provides the mapping from the native calling convention to the Dart calling
// convention.
//
@@ -114,11 +121,7 @@
kFfiBoolCid;
}
- bool IsCompound(intptr_t arg_index) const {
- const auto& type = AbstractType::Handle(zone_, CType(arg_index));
- const bool predefined = IsFfiTypeClassId(type.type_class_id());
- return !predefined;
- }
+ bool IsCompound(intptr_t arg_index) const;
// Treated as a null constant in Dart.
bool IsVoid(intptr_t arg_index) const {
@@ -131,7 +134,14 @@
StringPtr function_name() const { return dart_signature_.name(); }
protected:
- BaseMarshaller(Zone* zone, const Function& dart_signature);
+ BaseMarshaller(Zone* zone,
+ const Function& dart_signature,
+ const FunctionType& c_signature,
+ const NativeCallingConvention& native_calling_convention)
+ : zone_(zone),
+ dart_signature_(dart_signature),
+ c_signature_(c_signature),
+ native_calling_convention_(native_calling_convention) {}
~BaseMarshaller() {}
@@ -145,8 +155,18 @@
class CallMarshaller : public BaseMarshaller {
public:
- CallMarshaller(Zone* zone, const Function& dart_signature)
- : BaseMarshaller(zone, dart_signature) {}
+ static CallMarshaller* FromFunction(Zone* zone,
+ const Function& function,
+ const char** error);
+
+ CallMarshaller(Zone* zone,
+ const Function& dart_signature,
+ const FunctionType& c_signature,
+ const NativeCallingConvention& native_calling_convention)
+ : BaseMarshaller(zone,
+ dart_signature,
+ c_signature,
+ native_calling_convention) {}
virtual Representation RepInFfiCall(intptr_t def_index_global) const;
@@ -173,7 +193,20 @@
class CallbackMarshaller : public BaseMarshaller {
public:
- CallbackMarshaller(Zone* zone, const Function& dart_signature);
+ static CallbackMarshaller* FromFunction(Zone* zone,
+ const Function& function,
+ const char** error);
+
+ CallbackMarshaller(Zone* zone,
+ const Function& dart_signature,
+ const FunctionType& c_signature,
+ const NativeCallingConvention& native_calling_convention,
+ const NativeLocations& callback_locs)
+ : BaseMarshaller(zone,
+ dart_signature,
+ c_signature,
+ native_calling_convention),
+ callback_locs_(callback_locs) {}
virtual Representation RepInFfiCall(intptr_t def_index_global) const;
diff --git a/runtime/vm/compiler/ffi/native_type.cc b/runtime/vm/compiler/ffi/native_type.cc
index 6dfaeb7..bf4d452a 100644
--- a/runtime/vm/compiler/ffi/native_type.cc
+++ b/runtime/vm/compiler/ffi/native_type.cc
@@ -7,6 +7,7 @@
#include "platform/assert.h"
#include "platform/globals.h"
#include "vm/class_id.h"
+#include "vm/compiler/ffi/abi.h"
#include "vm/constants.h"
#include "vm/zone_text_buffer.h"
@@ -386,53 +387,22 @@
}
}
-NativeType& NativeType::FromTypedDataClassId(Zone* zone, classid_t class_id) {
+const NativeType& NativeType::FromTypedDataClassId(Zone* zone,
+ classid_t class_id) {
ASSERT(IsFfiPredefinedClassId(class_id));
const auto fundamental_rep = TypeRepresentation(class_id);
return *new (zone) NativePrimitiveType(fundamental_rep);
}
#if !defined(FFI_UNIT_TESTS)
-NativeType& NativeType::FromAbstractType(Zone* zone, const AbstractType& type) {
- const classid_t class_id = type.type_class_id();
- if (IsFfiPredefinedClassId(class_id)) {
- return NativeType::FromTypedDataClassId(zone, class_id);
- }
-
- // User-defined structs.
- const auto& cls = Class::Handle(zone, type.type_class());
- const auto& superClass = Class::Handle(zone, cls.SuperClass());
- const bool is_struct = String::Handle(zone, superClass.UserVisibleName())
- .Equals(Symbols::Struct());
- ASSERT(is_struct || String::Handle(zone, superClass.UserVisibleName())
- .Equals(Symbols::Union()));
-
- auto& pragmas = Object::Handle(zone);
- Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, cls,
- Symbols::vm_ffi_struct_fields(), /*multiple=*/true,
- &pragmas);
- ASSERT(!pragmas.IsNull());
- ASSERT(pragmas.IsGrowableObjectArray());
- const auto& pragmas_array = GrowableObjectArray::Cast(pragmas);
- auto& pragma = Instance::Handle(zone);
- auto& clazz = Class::Handle(zone);
- auto& library = Library::Handle(zone);
- for (intptr_t i = 0; i < pragmas_array.Length(); i++) {
- pragma ^= pragmas_array.At(i);
- clazz ^= pragma.clazz();
- library ^= clazz.library();
- if (String::Handle(zone, clazz.UserVisibleName())
- .Equals(Symbols::FfiStructLayout()) &&
- String::Handle(zone, library.url()).Equals(Symbols::DartFfi())) {
- break;
- }
- }
-
+static const NativeType* CompoundFromPragma(Zone* zone,
+ const Instance& pragma,
+ bool is_struct,
+ const char** error) {
const auto& struct_layout = pragma;
- const auto& struct_layout_class = clazz;
- ASSERT(String::Handle(zone, struct_layout_class.UserVisibleName())
+ const auto& clazz = Class::Handle(zone, struct_layout.clazz());
+ ASSERT(String::Handle(zone, clazz.UserVisibleName())
.Equals(Symbols::FfiStructLayout()));
- ASSERT(String::Handle(zone, library.url()).Equals(Symbols::DartFfi()));
const auto& struct_layout_fields = Array::Handle(zone, clazz.fields());
ASSERT(struct_layout_fields.Length() == 2);
const auto& types_field =
@@ -460,8 +430,11 @@
// Subtype of NativeType: Struct, native integer or native float.
field_type ^= field_types.At(i);
const auto& field_native_type =
- NativeType::FromAbstractType(zone, field_type);
- field_native_types.Add(&field_native_type);
+ NativeType::FromAbstractType(zone, field_type, error);
+ if (*error != nullptr) {
+ return nullptr;
+ }
+ field_native_types.Add(field_native_type);
} else {
// Inline array.
const auto& struct_layout_array_class =
@@ -482,20 +455,67 @@
.Equals(Symbols::Length()));
const auto& length = Smi::Handle(
zone, Smi::RawCast(field_instance.GetField(length_field)));
- const auto& element_type = NativeType::FromAbstractType(zone, field_type);
- const auto& field_native_type =
- *new (zone) NativeArrayType(element_type, length.AsInt64Value());
- field_native_types.Add(&field_native_type);
+ const auto element_type =
+ NativeType::FromAbstractType(zone, field_type, error);
+ if (*error != nullptr) {
+ return nullptr;
+ }
+ const auto field_native_type =
+ new (zone) NativeArrayType(*element_type, length.AsInt64Value());
+ field_native_types.Add(field_native_type);
}
}
if (is_struct) {
- return NativeStructType::FromNativeTypes(zone, field_native_types,
- member_packing);
+ return &NativeStructType::FromNativeTypes(zone, field_native_types,
+ member_packing);
} else {
- return NativeUnionType::FromNativeTypes(zone, field_native_types);
+ return &NativeUnionType::FromNativeTypes(zone, field_native_types);
}
}
+
+// TODO(http://dartbug.com/42563): Implement AbiSpecificInt.
+const NativeType* NativeType::FromAbstractType(Zone* zone,
+ const AbstractType& type,
+ const char** error) {
+ const classid_t class_id = type.type_class_id();
+ if (IsFfiPredefinedClassId(class_id)) {
+ return &NativeType::FromTypedDataClassId(zone, class_id);
+ }
+
+ // User-defined structs or unions.
+ const auto& cls = Class::Handle(zone, type.type_class());
+ const auto& superClass = Class::Handle(zone, cls.SuperClass());
+ const bool is_struct = String::Handle(zone, superClass.UserVisibleName())
+ .Equals(Symbols::Struct());
+ const bool is_union = String::Handle(zone, superClass.UserVisibleName())
+ .Equals(Symbols::Union());
+ RELEASE_ASSERT(is_struct || is_union);
+
+ auto& pragmas = Object::Handle(zone);
+ String& pragma_name = String::Handle(zone);
+ pragma_name = Symbols::vm_ffi_struct_fields().ptr();
+ Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, cls,
+ pragma_name, /*multiple=*/true, &pragmas);
+ ASSERT(!pragmas.IsNull());
+ ASSERT(pragmas.IsGrowableObjectArray());
+ const auto& pragmas_array = GrowableObjectArray::Cast(pragmas);
+ auto& pragma = Instance::Handle(zone);
+ auto& clazz = Class::Handle(zone);
+ auto& library = Library::Handle(zone);
+ const String& class_symbol = Symbols::FfiStructLayout();
+ for (intptr_t i = 0; i < pragmas_array.Length(); i++) {
+ pragma ^= pragmas_array.At(i);
+ clazz ^= pragma.clazz();
+ library ^= clazz.library();
+ if (String::Handle(zone, clazz.UserVisibleName()).Equals(class_symbol) &&
+ String::Handle(zone, library.url()).Equals(Symbols::DartFfi())) {
+ break;
+ }
+ }
+
+ return CompoundFromPragma(zone, pragma, is_struct, error);
+}
#endif
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
diff --git a/runtime/vm/compiler/ffi/native_type.h b/runtime/vm/compiler/ffi/native_type.h
index 2ee284a..f60fea6 100644
--- a/runtime/vm/compiler/ffi/native_type.h
+++ b/runtime/vm/compiler/ffi/native_type.h
@@ -57,9 +57,11 @@
class NativeType : public ZoneAllocated {
public:
#if !defined(FFI_UNIT_TESTS)
- static NativeType& FromAbstractType(Zone* zone, const AbstractType& type);
+ static const NativeType* FromAbstractType(Zone* zone,
+ const AbstractType& type,
+ const char** error);
#endif
- static NativeType& FromTypedDataClassId(Zone* zone, classid_t class_id);
+ static const NativeType& FromTypedDataClassId(Zone* zone, classid_t class_id);
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
static NativePrimitiveType& FromUnboxedRepresentation(Zone* zone,
diff --git a/runtime/vm/compiler/ffi/native_type_vm_test.cc b/runtime/vm/compiler/ffi/native_type_vm_test.cc
index 196ae4c..50a1e07 100644
--- a/runtime/vm/compiler/ffi/native_type_vm_test.cc
+++ b/runtime/vm/compiler/ffi/native_type_vm_test.cc
@@ -17,7 +17,9 @@
const auto& ffi_library = Library::Handle(Library::FfiLibrary());
const auto& int8_class = Class::Handle(GetClass(ffi_library, "Int8"));
const auto& int8_type = Type::Handle(int8_class.DeclarationType());
- const auto& native_type = NativeType::FromAbstractType(Z, int8_type);
+ const char* error = nullptr;
+ const auto& native_type = *NativeType::FromAbstractType(Z, int8_type, &error);
+ EXPECT_NULLPTR(error);
EXPECT_EQ(1, native_type.SizeInBytes());
EXPECT_STREQ("int8", native_type.ToCString());
@@ -36,7 +38,10 @@
const auto& ffi_library = Library::Handle(Library::FfiLibrary());
const auto& bool_class = Class::Handle(GetClass(ffi_library, "Bool"));
const auto& bool_type = Type::Handle(bool_class.DeclarationType());
- const auto& bool_native_type = NativeType::FromAbstractType(Z, bool_type);
+ const char* error = nullptr;
+ const auto& bool_native_type =
+ *NativeType::FromAbstractType(Z, bool_type, &error);
+ EXPECT_NULLPTR(error);
const auto& uint8_native_type = *new (Z) NativePrimitiveType(kUint8);
@@ -67,8 +72,10 @@
const auto& struct_class = Class::Handle(GetClass(root_library, "MyStruct"));
const auto& struct_type = Type::Handle(struct_class.DeclarationType());
+ const char* error = nullptr;
const auto& native_type =
- NativeType::FromAbstractType(Z, struct_type).AsCompound();
+ NativeType::FromAbstractType(Z, struct_type, &error)->AsCompound();
+ EXPECT_NULLPTR(error);
EXPECT_EQ(2, native_type.members().length());
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 9b0e7cf..2b7346a 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -838,20 +838,46 @@
case MethodRecognizer::kTypedData_Float32x4Array_factory:
case MethodRecognizer::kTypedData_Int32x4Array_factory:
case MethodRecognizer::kTypedData_Float64x2Array_factory:
-#define FFI_LOAD_STORE(type) \
- case MethodRecognizer::kFfiLoad##type: \
- case MethodRecognizer::kFfiStore##type:
- CLASS_LIST_FFI_NUMERIC(FFI_LOAD_STORE)
- FFI_LOAD_STORE(FloatUnaligned)
- FFI_LOAD_STORE(DoubleUnaligned)
- FFI_LOAD_STORE(Pointer)
-#undef FFI_LOAD_STORE
+ case MethodRecognizer::kFfiLoadInt8:
+ case MethodRecognizer::kFfiLoadInt16:
+ case MethodRecognizer::kFfiLoadInt32:
+ case MethodRecognizer::kFfiLoadInt64:
+ case MethodRecognizer::kFfiLoadUint8:
+ case MethodRecognizer::kFfiLoadUint16:
+ case MethodRecognizer::kFfiLoadUint32:
+ case MethodRecognizer::kFfiLoadUint64:
+ case MethodRecognizer::kFfiLoadIntPtr:
+ case MethodRecognizer::kFfiLoadFloat:
+ case MethodRecognizer::kFfiLoadFloatUnaligned:
+ case MethodRecognizer::kFfiLoadDouble:
+ case MethodRecognizer::kFfiLoadDoubleUnaligned:
+ case MethodRecognizer::kFfiLoadPointer:
+ case MethodRecognizer::kFfiStoreInt8:
+ case MethodRecognizer::kFfiStoreInt16:
+ case MethodRecognizer::kFfiStoreInt32:
+ case MethodRecognizer::kFfiStoreInt64:
+ case MethodRecognizer::kFfiStoreUint8:
+ case MethodRecognizer::kFfiStoreUint16:
+ case MethodRecognizer::kFfiStoreUint32:
+ case MethodRecognizer::kFfiStoreUint64:
+ case MethodRecognizer::kFfiStoreIntPtr:
+ case MethodRecognizer::kFfiStoreFloat:
+ case MethodRecognizer::kFfiStoreFloatUnaligned:
+ case MethodRecognizer::kFfiStoreDouble:
+ case MethodRecognizer::kFfiStoreDoubleUnaligned:
+ case MethodRecognizer::kFfiStorePointer:
case MethodRecognizer::kFfiFromAddress:
case MethodRecognizer::kFfiGetAddress:
-#define FFI_AS_EXTERNAL_TYPED_DATA(type) \
- case MethodRecognizer::kFfiAsExternalTypedData##type:
- CLASS_LIST_FFI_NUMERIC_FIXED_SIZE(FFI_AS_EXTERNAL_TYPED_DATA)
-#undef FFI_AS_EXTERNAL_TYPED_DATA
+ case MethodRecognizer::kFfiAsExternalTypedDataInt8:
+ case MethodRecognizer::kFfiAsExternalTypedDataInt16:
+ case MethodRecognizer::kFfiAsExternalTypedDataInt32:
+ case MethodRecognizer::kFfiAsExternalTypedDataInt64:
+ case MethodRecognizer::kFfiAsExternalTypedDataUint8:
+ case MethodRecognizer::kFfiAsExternalTypedDataUint16:
+ case MethodRecognizer::kFfiAsExternalTypedDataUint32:
+ case MethodRecognizer::kFfiAsExternalTypedDataUint64:
+ case MethodRecognizer::kFfiAsExternalTypedDataFloat:
+ case MethodRecognizer::kFfiAsExternalTypedDataDouble:
case MethodRecognizer::kGetNativeField:
case MethodRecognizer::kObjectEquals:
case MethodRecognizer::kStringBaseLength:
@@ -1331,13 +1357,20 @@
ASSERT_EQUAL(function.NumParameters(), 0);
body += IntConstant(static_cast<int64_t>(compiler::ffi::TargetAbi()));
break;
-#define FFI_LOAD(type) case MethodRecognizer::kFfiLoad##type:
- CLASS_LIST_FFI_NUMERIC(FFI_LOAD)
- FFI_LOAD(FloatUnaligned)
- FFI_LOAD(DoubleUnaligned)
- FFI_LOAD(Pointer)
-#undef FFI_LOAD
- {
+ case MethodRecognizer::kFfiLoadInt8:
+ case MethodRecognizer::kFfiLoadInt16:
+ case MethodRecognizer::kFfiLoadInt32:
+ case MethodRecognizer::kFfiLoadInt64:
+ case MethodRecognizer::kFfiLoadUint8:
+ case MethodRecognizer::kFfiLoadUint16:
+ case MethodRecognizer::kFfiLoadUint32:
+ case MethodRecognizer::kFfiLoadUint64:
+ case MethodRecognizer::kFfiLoadIntPtr:
+ case MethodRecognizer::kFfiLoadFloat:
+ case MethodRecognizer::kFfiLoadFloatUnaligned:
+ case MethodRecognizer::kFfiLoadDouble:
+ case MethodRecognizer::kFfiLoadDoubleUnaligned:
+ case MethodRecognizer::kFfiLoadPointer: {
const classid_t ffi_type_arg_cid =
compiler::ffi::RecognizedMethodTypeArgCid(kind);
const AlignmentType alignment =
@@ -1403,13 +1436,20 @@
}
body += DropTempsPreserveTop(1); // Drop [arg_offset].
} break;
-#define FFI_STORE(type) case MethodRecognizer::kFfiStore##type:
- CLASS_LIST_FFI_NUMERIC(FFI_STORE)
- FFI_STORE(FloatUnaligned)
- FFI_STORE(DoubleUnaligned)
- FFI_STORE(Pointer)
-#undef FFI_STORE
- {
+ case MethodRecognizer::kFfiStoreInt8:
+ case MethodRecognizer::kFfiStoreInt16:
+ case MethodRecognizer::kFfiStoreInt32:
+ case MethodRecognizer::kFfiStoreInt64:
+ case MethodRecognizer::kFfiStoreUint8:
+ case MethodRecognizer::kFfiStoreUint16:
+ case MethodRecognizer::kFfiStoreUint32:
+ case MethodRecognizer::kFfiStoreUint64:
+ case MethodRecognizer::kFfiStoreIntPtr:
+ case MethodRecognizer::kFfiStoreFloat:
+ case MethodRecognizer::kFfiStoreFloatUnaligned:
+ case MethodRecognizer::kFfiStoreDouble:
+ case MethodRecognizer::kFfiStoreDoubleUnaligned:
+ case MethodRecognizer::kFfiStorePointer: {
const classid_t ffi_type_arg_cid =
compiler::ffi::RecognizedMethodTypeArgCid(kind);
const AlignmentType alignment =
@@ -1519,11 +1559,16 @@
body += Constant(Bool::False());
#endif // defined(ARCH_IS_64_BIT)
} break;
-#define FFI_AS_EXTERNAL_TYPED_DATA(type) \
- case MethodRecognizer::kFfiAsExternalTypedData##type:
- CLASS_LIST_FFI_NUMERIC_FIXED_SIZE(FFI_AS_EXTERNAL_TYPED_DATA)
-#undef FFI_AS_EXTERNAL_TYPED_DATA
- {
+ case MethodRecognizer::kFfiAsExternalTypedDataInt8:
+ case MethodRecognizer::kFfiAsExternalTypedDataInt16:
+ case MethodRecognizer::kFfiAsExternalTypedDataInt32:
+ case MethodRecognizer::kFfiAsExternalTypedDataInt64:
+ case MethodRecognizer::kFfiAsExternalTypedDataUint8:
+ case MethodRecognizer::kFfiAsExternalTypedDataUint16:
+ case MethodRecognizer::kFfiAsExternalTypedDataUint32:
+ case MethodRecognizer::kFfiAsExternalTypedDataUint64:
+ case MethodRecognizer::kFfiAsExternalTypedDataFloat:
+ case MethodRecognizer::kFfiAsExternalTypedDataDouble: {
const classid_t ffi_type_arg_cid =
compiler::ffi::RecognizedMethodTypeArgCid(kind);
const classid_t external_typed_data_cid =
@@ -1531,8 +1576,8 @@
auto class_table = thread_->isolate_group()->class_table();
ASSERT(class_table->HasValidClassAt(external_typed_data_cid));
- const auto& typed_data_class = Class::ZoneHandle(
- H.zone(), class_table->At(external_typed_data_cid));
+ const auto& typed_data_class =
+ Class::ZoneHandle(H.zone(), class_table->At(external_typed_data_cid));
// We assume that the caller has checked that the arguments are non-null
// and length is in the range [0, kSmiMax/elementSize].
@@ -4443,7 +4488,12 @@
Fragment function_body(instruction_cursor);
function_body += CheckStackOverflowInPrologue(function.token_pos());
- const auto& marshaller = *new (Z) compiler::ffi::CallMarshaller(Z, function);
+ const char* error = nullptr;
+ const auto marshaller_ptr =
+ compiler::ffi::CallMarshaller::FromFunction(Z, function, &error);
+ RELEASE_ASSERT(error == nullptr);
+ RELEASE_ASSERT(marshaller_ptr != nullptr);
+ const auto& marshaller = *marshaller_ptr;
const bool signature_contains_handles = marshaller.ContainsHandles();
@@ -4589,8 +4639,12 @@
}
FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCallback(const Function& function) {
- const auto& marshaller =
- *new (Z) compiler::ffi::CallbackMarshaller(Z, function);
+ const char* error = nullptr;
+ const auto marshaller_ptr =
+ compiler::ffi::CallbackMarshaller::FromFunction(Z, function, &error);
+ RELEASE_ASSERT(error == nullptr);
+ RELEASE_ASSERT(marshaller_ptr != nullptr);
+ const auto& marshaller = *marshaller_ptr;
graph_entry_ =
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 2513eb4..df10c58 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -7553,12 +7553,26 @@
kFfiHandleCid;
}
+// Keep consistent with BaseMarshaller::IsCompound.
bool Function::FfiCSignatureReturnsStruct() const {
ASSERT(IsFfiTrampoline());
- const FunctionType& c_signature = FunctionType::Handle(FfiCSignature());
- const auto& return_type = AbstractType::Handle(c_signature.result_type());
- const bool predefined = IsFfiTypeClassId(return_type.type_class_id());
- return !predefined;
+ Zone* zone = Thread::Current()->zone();
+ const auto& c_signature = FunctionType::Handle(zone, FfiCSignature());
+ const auto& type = AbstractType::Handle(zone, c_signature.result_type());
+ if (IsFfiTypeClassId(type.type_class_id())) {
+ return false;
+ }
+ // TODO(http://dartbug.com/42563): Implement AbiSpecificInt.
+#ifdef DEBUG
+ const auto& cls = Class::Handle(zone, type.type_class());
+ const auto& superClass = Class::Handle(zone, cls.SuperClass());
+ const bool is_struct = String::Handle(zone, superClass.UserVisibleName())
+ .Equals(Symbols::Struct());
+ const bool is_union = String::Handle(zone, superClass.UserVisibleName())
+ .Equals(Symbols::Union());
+ RELEASE_ASSERT(is_struct || is_union);
+#endif
+ return true;
}
int32_t Function::FfiCallbackId() const {
diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart
index 14180b6..3af29c3 100644
--- a/sdk/lib/_internal/vm/lib/ffi_patch.dart
+++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart
@@ -448,6 +448,16 @@
Pointer<Pointer<S>> pointer, int index) =>
Pointer.fromAddress(pointer.address + _intPtrSize * index);
+@pragma("vm:prefer-inline")
+@pragma("vm:entry-point")
+T _checkAbiSpecificIntegerMapping<T>(T? object) {
+ if (object == null) {
+ throw ArgumentError(
+ 'AbiSpecificInteger is missing mapping for "${Abi.current()}".');
+ }
+ return object;
+}
+
extension NativeFunctionPointer<NF extends Function>
on Pointer<NativeFunction<NF>> {
@patch
diff --git a/tools/VERSION b/tools/VERSION
index 9dfdd80..e7063a5 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 16
PATCH 0
-PRERELEASE 71
+PRERELEASE 72
PRERELEASE_PATCH 0
\ No newline at end of file