blob: e859a615b7f59c1bd77f66e98347298d3b8af08a [file] [log] [blame]
// Copyright (c) 2019, 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:front_end/src/api_unstable/vm.dart'
show
messageFfiAbiSpecificIntegerInvalid,
messageFfiAbiSpecificIntegerMappingInvalid,
messageFfiPackedAnnotationAlignment,
messageNonPositiveArrayDimensions,
templateFfiCompoundImplementsFinalizable,
templateFfiEmptyStruct,
templateFfiFieldAnnotation,
templateFfiFieldNull,
templateFfiFieldCyclic,
templateFfiFieldNoAnnotation,
templateFfiTypeMismatch,
templateFfiFieldInitializer,
templateFfiPackedAnnotation,
templateFfiSizeAnnotation,
templateFfiSizeAnnotationDimensions,
templateFfiStructGeneric;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/core_types.dart';
import 'package:kernel/library_index.dart' show LibraryIndex;
import 'package:kernel/reference_from_index.dart';
import 'package:kernel/target/changed_structure_notifier.dart';
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
import 'package:kernel/type_environment.dart' show SubtypeCheckMode;
import 'package:kernel/util/graph.dart';
import 'abi.dart';
import 'common.dart';
import 'native_type_cfe.dart';
/// Checks and elaborates the dart:ffi compounds and their fields.
///
/// Input:
/// class Coord extends Struct {
/// @Double()
/// double x;
///
/// @Double()
/// double y;
///
/// Pointer<Coord> next;
/// }
///
/// Output:
/// class Coord extends Struct {
/// Coord.#fromTypedDataBase(Pointer<Coord> coord) : super._(coord);
///
/// set x(double v) => ...;
/// double get x => ...;
///
/// set y(double v) => ...;
/// double get y => ...;
///
/// set next(Pointer<Coord> v) => ...;
/// Pointer<Coord> get next => ...;
///
/// static int get #sizeOf => (const [24, 20, 24])[_abi()];
/// }
void transformLibraries(
Component component,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
List<Library> libraries,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex,
ChangedStructureNotifier? changedStructureNotifier) {
final LibraryIndex index = LibraryIndex(component, const [
'dart:core',
'dart:ffi',
'dart:_internal',
'dart:typed_data',
'dart:nativewrappers'
]);
if (!index.containsLibrary('dart:ffi')) {
// TODO: This check doesn't make sense: "dart:ffi" is always loaded/created
// for the VM target.
// If dart:ffi is not loaded, do not do the transformation.
return;
}
if (index.tryGetClass('dart:ffi', 'NativeFunction') == null) {
// If dart:ffi is not loaded (for real): do not do the transformation.
return;
}
final transformer = new _FfiDefinitionTransformer(index, coreTypes, hierarchy,
diagnosticReporter, referenceFromIndex, changedStructureNotifier);
libraries.forEach(transformer.visitLibrary);
transformer.manualVisitInTopologicalOrder();
}
class CompoundDependencyGraph<T> implements Graph<T> {
final Map<T, Iterable<T>> map;
CompoundDependencyGraph(this.map);
Iterable<T> get vertices => map.keys;
Iterable<T> neighborsOf(T vertex) => map[vertex]!;
}
/// Checks and elaborates the dart:ffi compounds and their fields.
class _FfiDefinitionTransformer extends FfiTransformer {
final LibraryIndex index;
// Data structures for topological navigation.
Map<Class, IndexedClass> indexedCompoundClasses = {};
Set<Class> transformCompounds = {};
Set<Class> transformCompoundsInvalid = {};
Map<Class, NativeTypeCfe> compoundCache = {};
ChangedStructureNotifier? changedStructureNotifier;
_FfiDefinitionTransformer(
this.index,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex,
this.changedStructureNotifier)
: super(index, coreTypes, hierarchy, diagnosticReporter,
referenceFromIndex) {}
/// Finds all compound class dependencies.
///
/// Works both for transformed and non-transformed compound classes.
Set<Class> _compoundClassDependencies(Class node) {
final fieldTypes = _compoundAnnotatedFields(node);
if (fieldTypes != null) {
// Transformed classes.
return _compoundAnnotatedDependencies(fieldTypes);
}
// Non-tranformed classes.
final dependencies = <Class>{};
final membersWithAnnotations =
_compoundFieldMembers(node, includeSetters: false);
for (final Member f in membersWithAnnotations) {
final type = _compoundMemberType(f);
if (isCompoundSubtype(type)) {
final clazz = (type as InterfaceType).classNode;
dependencies.add(clazz);
} else if (isArrayType(type)) {
final sizeAnnotations = _getArraySizeAnnotations(f);
if (sizeAnnotations.length == 1) {
final singleElementType = arraySingleElementType(type);
if (singleElementType is InterfaceType &&
isCompoundSubtype(singleElementType)) {
final clazz = singleElementType.classNode;
dependencies.add(clazz);
}
}
}
}
return dependencies;
}
/// Creates a dependency graph containing all compounds being compiled
/// in this compilation, and their transitive dependencies.
CompoundDependencyGraph<Class> _compoundDependencyGraph() {
Map<Class, Set<Class>> compoundClassDependencies = {};
final toProcess = [...transformCompounds, ...transformCompoundsInvalid];
while (toProcess.isNotEmpty) {
final clazz = toProcess.removeLast();
if (!compoundClassDependencies.containsKey(clazz)) {
final dependencies = _compoundClassDependencies(clazz);
compoundClassDependencies[clazz] = dependencies;
toProcess.addAll(dependencies);
}
}
return CompoundDependencyGraph(compoundClassDependencies);
}
void manualVisitInTopologicalOrder() {
final dependencyGraph = _compoundDependencyGraph();
final connectedComponents = computeStrongComponents(dependencyGraph);
connectedComponents.forEach((List<Class> component) {
bool report = false;
if (component.length > 1) {
// Indirect cycle.
report = true;
}
if (component.length == 1) {
if (dependencyGraph.map[component.single]!.contains(component.single)) {
// Direct cycle.
report = true;
}
}
if (report) {
component.forEach((Class e) {
diagnosticReporter.report(
templateFfiFieldCyclic.withArguments(e.superclass!.name, e.name,
component.map((e) => e.name).toList()),
e.fileOffset,
e.name.length,
e.fileUri);
compoundCache[e] = InvalidNativeTypeCfe("Cyclic members.");
if (transformCompoundsInvalid.contains(e) ||
transformCompounds.contains(e)) {
final indexedClass = indexedCompoundClasses[e];
_addSizeOfField(e, indexedClass);
}
});
} else {
// Only visit the ones without cycles.
final clazz = component.single;
final mustBeTransformed = (transformCompoundsInvalid.contains(clazz) ||
transformCompounds.contains(clazz));
if (!mustBeTransformed) {
compoundCache[clazz] = _compoundAnnotatedNativeTypeCfe(clazz);
} else {
final compoundData = _findFields(clazz);
final compoundType = compoundData.compoundType;
compoundCache[clazz] = compoundType;
final indexedClass = indexedCompoundClasses[clazz];
if (transformCompounds.contains(clazz) &&
compoundType is! InvalidNativeTypeCfe) {
// Only replace fields if valid.
_replaceFields(clazz, indexedClass, compoundData);
_addSizeOfField(clazz, indexedClass, compoundType.size);
} else {
// Do add a sizeOf field even if invalid.
_addSizeOfField(clazz, indexedClass);
}
changedStructureNotifier?.registerClassMemberChange(clazz);
}
}
});
}
@override
visitExtension(Extension node) {
// The extension and it's members are only metadata.
return node;
}
bool _isUserCompound(Class node) {
if (!hierarchy.isSubclassOf(node, compoundClass) ||
node == compoundClass ||
node == structClass ||
node == unionClass) {
return false;
}
return true;
}
bool _isUserAbiSpecificInteger(Class node) =>
hierarchy.isSubclassOf(node, abiSpecificIntegerClass) &&
node != abiSpecificIntegerClass;
@override
visitClass(Class node) {
if (_isUserAbiSpecificInteger(node)) {
final nativeTypeCfe = NativeTypeCfe(
this, node.getThisType(coreTypes, Nullability.nonNullable))
as AbiSpecificNativeTypeCfe;
if (nativeTypeCfe.abiSpecificTypes.length == 0) {
// Annotation missing, multiple annotations, or invalid mapping.
diagnosticReporter.report(messageFfiAbiSpecificIntegerMappingInvalid,
node.fileOffset, node.name.length, node.location!.file);
}
if (node.typeParameters.length != 0 ||
node.procedures.where((Procedure e) => !e.isSynthetic).length != 0 ||
node.fields.length != 0 ||
node.redirectingFactories.length != 0 ||
node.constructors.length != 1 ||
!node.constructors.single.isConst) {
// We want exactly one constructor, no other members and no type arguments.
diagnosticReporter.report(messageFfiAbiSpecificIntegerInvalid,
node.fileOffset, node.name.length, node.location!.file);
}
final IndexedClass? indexedClass =
currentLibraryIndex?.lookupIndexedClass(node.name);
_addSizeOfField(node, indexedClass, nativeTypeCfe.size);
_annotateAbiSpecificTypeWithMapping(node, nativeTypeCfe);
}
if (!_isUserCompound(node)) {
return node;
}
final packing = _checkCompoundClass(node);
final IndexedClass? indexedClass =
currentLibraryIndex?.lookupIndexedClass(node.name);
_checkConstructors(node, indexedClass);
if (indexedClass != null) {
indexedCompoundClasses[node] = indexedClass;
}
final fieldsValid = _checkFieldAnnotations(node, packing);
if (fieldsValid) {
// Only do the transformation if the compound is valid.
transformCompounds.add(node);
} else {
transformCompoundsInvalid.add(node);
}
return node;
}
/// Returns packing if any.
int? _checkCompoundClass(Class node) {
if (node.typeParameters.length > 0) {
diagnosticReporter.report(
templateFfiStructGeneric.withArguments(
node.superclass!.name, node.name),
node.fileOffset,
1,
node.location!.file);
}
if (node.superclass != structClass && node.superclass != unionClass) {
// Not a struct or union, but extends a struct or union.
// The error will be emitted by _FfiUseSiteTransformer.
return null;
}
final finalizableType = FutureOrType(
InterfaceType(finalizableClass, Nullability.nullable),
Nullability.nullable);
if (env.isSubtypeOf(InterfaceType(node, Nullability.nonNullable),
finalizableType, SubtypeCheckMode.ignoringNullabilities)) {
diagnosticReporter.report(
templateFfiCompoundImplementsFinalizable.withArguments(
node.superclass!.name, node.name),
node.fileOffset,
1,
node.location!.file);
}
if (node.superclass == structClass) {
final packingAnnotations = _getPackedAnnotations(node);
if (packingAnnotations.length > 1) {
diagnosticReporter.report(
templateFfiPackedAnnotation.withArguments(node.name),
node.fileOffset,
node.name.length,
node.location!.file);
}
if (packingAnnotations.isNotEmpty) {
final packing = packingAnnotations.first;
if (!(packing == 1 ||
packing == 2 ||
packing == 4 ||
packing == 8 ||
packing == 16)) {
diagnosticReporter.report(messageFfiPackedAnnotationAlignment,
node.fileOffset, node.name.length, node.location!.file);
}
return packing;
}
}
return null;
}
/// Returns members of [node] that possibly correspond to compound fields.
///
/// Note that getters and setters that originate from an external field have
/// the same `fileOffset`, we always returns getters first.
///
/// This works only for non-transformed compounds.
List<Member> _compoundFieldMembers(Class node, {bool includeSetters = true}) {
assert(_compoundAnnotatedFields(node) == null);
final getterSetters = node.procedures.where((p) {
if (!p.isExternal) {
// Getters and setters corresponding to native fields are external.
return false;
}
if (p.isSetter && includeSetters) {
return true;
}
return p.isGetter;
});
final compoundMembers = [...node.fields, ...getterSetters]..sort((m1, m2) {
if (m1.fileOffset == m2.fileOffset) {
// Getter and setter have same offset, getter comes first.
if (m1 is Procedure) {
return m1.isGetter ? -1 : 1;
}
// Generated fields with fileOffset identical to class, fallthrough.
}
return m1.fileOffset - m2.fileOffset;
});
return compoundMembers;
}
DartType _compoundMemberType(Member member) {
if (member is Field) {
return member.type;
}
final p = member as Procedure;
if (p.isGetter) {
return p.function.returnType;
}
return p.function.positionalParameters.single.type;
}
bool _checkFieldAnnotations(Class node, int? packing) {
bool success = true;
final membersWithAnnotations =
_compoundFieldMembers(node, includeSetters: false);
for (final Member f in membersWithAnnotations) {
if (f is Field) {
if (f.initializer is! NullLiteral) {
diagnosticReporter.report(
templateFfiFieldInitializer.withArguments(f.name.text),
f.fileOffset,
f.name.text.length,
f.fileUri);
// This class is invalid, but continue reporting other errors on it.
success = false;
}
}
final nativeTypeAnnos = _getNativeTypeAnnotations(f).toList();
final type = _compoundMemberType(f);
if (type is NullType) {
diagnosticReporter.report(
templateFfiFieldNull.withArguments(f.name.text),
f.fileOffset,
f.name.text.length,
f.fileUri);
// This class is invalid, but continue reporting other errors on it.
success = false;
} else if (isPointerType(type) ||
isCompoundSubtype(type) ||
isArrayType(type)) {
if (nativeTypeAnnos.length != 0) {
diagnosticReporter.report(
templateFfiFieldNoAnnotation.withArguments(f.name.text),
f.fileOffset,
f.name.text.length,
f.fileUri);
// This class is invalid, but continue reporting other errors on it.
success = false;
}
if (isArrayType(type)) {
final sizeAnnotations = _getArraySizeAnnotations(f);
if (sizeAnnotations.length == 1) {
final singleElementType = arraySingleElementType(type);
if (singleElementType is! InterfaceType) {
assert(singleElementType is InvalidType);
// This class is invalid, but continue reporting other errors on it.
// An error on the type will already have been reported.
success = false;
} else {
final dimensions = sizeAnnotations.single;
if (arrayDimensions(type) != dimensions.length) {
diagnosticReporter.report(
templateFfiSizeAnnotationDimensions
.withArguments(f.name.text),
f.fileOffset,
f.name.text.length,
f.fileUri);
}
for (var dimension in dimensions) {
if (dimension < 0) {
diagnosticReporter.report(messageNonPositiveArrayDimensions,
f.fileOffset, f.name.text.length, f.fileUri);
success = false;
}
}
}
} else {
diagnosticReporter.report(
templateFfiSizeAnnotation.withArguments(f.name.text),
f.fileOffset,
f.name.text.length,
f.fileUri);
success = false;
}
}
} else if (nativeTypeAnnos.length != 1) {
diagnosticReporter.report(
templateFfiFieldAnnotation.withArguments(f.name.text),
f.fileOffset,
f.name.text.length,
f.fileUri);
// This class is invalid, but continue reporting other errors on it.
success = false;
} else {
final DartType nativeType =
InterfaceType(nativeTypeAnnos.first, Nullability.legacy);
final DartType? shouldBeDartType = convertNativeTypeToDartType(
nativeType,
allowCompounds: true,
allowHandle: false);
if (shouldBeDartType == null ||
!env.isSubtypeOf(type, shouldBeDartType,
SubtypeCheckMode.ignoringNullabilities)) {
diagnosticReporter.report(
templateFfiTypeMismatch.withArguments(type, shouldBeDartType!,
nativeType, node.enclosingLibrary.isNonNullableByDefault),
f.fileOffset,
1,
f.location!.file);
// This class is invalid, but continue reporting other errors on it.
success = false;
}
}
}
return success;
}
void _checkConstructors(Class node, IndexedClass? indexedClass) {
final toRemove = <Initializer>[];
// Constructors cannot have initializers because initializers refer to
// fields, and the fields were replaced with getter/setter pairs.
for (final Constructor c in node.constructors) {
for (final Initializer i in c.initializers) {
if (i is FieldInitializer) {
toRemove.add(i);
diagnosticReporter.report(
templateFfiFieldInitializer.withArguments(i.field.name.text),
i.fileOffset,
1,
i.location!.file);
}
}
}
// Remove initializers referring to fields to prevent cascading errors.
for (final Initializer i in toRemove) {
final c = i.parent as Constructor;
c.initializers.remove(i);
}
/// Add a constructor which 'load' can use.
///
/// ```dart
/// #fromTypedDataBase(Object #typedDataBase) :
/// super._fromTypedDataBase(#typedDataBase);
/// ```
final VariableDeclaration typedDataBase = new VariableDeclaration(
"#typedDataBase",
type: coreTypes.objectNonNullableRawType);
final name = Name("#fromTypedDataBase");
final reference = indexedClass?.lookupConstructorReference(name);
final Constructor ctor = Constructor(
FunctionNode(EmptyStatement(),
positionalParameters: [typedDataBase],
returnType: InterfaceType(node, Nullability.nonNullable)),
name: name,
initializers: [
SuperInitializer(
node.superclass == structClass
? structFromTypedDataBase
: unionFromTypedDataBase,
Arguments([VariableGet(typedDataBase)]))
],
fileUri: node.fileUri,
reference: reference)
..fileOffset = node.fileOffset
..isNonNullableByDefault = node.enclosingLibrary.isNonNullableByDefault;
// Struct objects are manufactured in the VM by being passed by value
// in return position in FFI calls, and by value in arguments in FFI
// callbacks.
node.addConstructor(ctor);
}
// Works only for non-transformed classes.
CompoundData _findFields(Class node) {
final types = <NativeTypeCfe>[];
final fields = <int, Field>{};
final getters = <int, Procedure>{};
final setters = <int, Procedure>{};
int i = 0;
for (final Member m in _compoundFieldMembers(node)) {
final dartType = _compoundMemberType(m);
// Nullable.
NativeTypeCfe? type;
if (isArrayType(dartType)) {
final sizeAnnotations = _getArraySizeAnnotations(m).toList();
if (sizeAnnotations.length == 1) {
final arrayDimensions = sizeAnnotations.single;
if (this.arrayDimensions(dartType) == arrayDimensions.length) {
final elementType = arraySingleElementType(dartType);
if (elementType is! InterfaceType) {
assert(elementType is InvalidType);
type = InvalidNativeTypeCfe("Invalid element type.");
} else {
type = NativeTypeCfe(this, dartType,
compoundCache: compoundCache,
arrayDimensions: arrayDimensions);
}
} else {
type = InvalidNativeTypeCfe("Invalid array dimensions.");
}
}
} else if (isPointerType(dartType) || isCompoundSubtype(dartType)) {
type = NativeTypeCfe(this, dartType, compoundCache: compoundCache);
} else {
// The C type is in the annotation, not the field type itself.
final nativeTypeAnnos = _getNativeTypeAnnotations(m).toList();
if (nativeTypeAnnos.length == 1) {
final clazz = nativeTypeAnnos.first;
if (_isUserAbiSpecificInteger(clazz)) {
type = NativeTypeCfe(
this, clazz.getThisType(coreTypes, Nullability.nonNullable));
} else {
final nativeType = _getFieldType(clazz)!;
type = PrimitiveNativeTypeCfe(nativeType, clazz);
}
}
}
if ((m is Field || (m is Procedure && m.isGetter)) && type != null) {
types.add(type);
if (m is Field) {
fields[i] = m;
}
if (m is Procedure) {
getters[i] = m;
}
i++;
}
if (m is Procedure && m.isSetter) {
final index = i - 1; // The corresponding getter's index.
final getter = getters[index];
if (getter != null && getter.name == m.name) {
setters[i - 1] = m;
}
}
}
final packingAnnotations = _getPackedAnnotations(node);
final packing =
(!packingAnnotations.isEmpty) ? packingAnnotations.first : null;
final compoundType = () {
if (types.whereType<InvalidNativeTypeCfe>().isNotEmpty) {
return InvalidNativeTypeCfe("Nested member invalid.");
}
if (node.superclass == structClass) {
return StructNativeTypeCfe(node, types, packing: packing);
}
return UnionNativeTypeCfe(node, types);
}();
List<CompoundField> fieldsFound = [];
for (int j = 0; j < i; j++) {
fieldsFound
.add(CompoundField(types[j], fields[j], getters[j], setters[j]));
}
return CompoundData(fieldsFound, packing, compoundType);
}
/// Computes the field offsets (for all ABIs) in the compound and replaces
/// the fields with getters and setters using these offsets.
///
/// Returns the total size of the compound (for all ABIs).
void _replaceFields(
Class node, IndexedClass? indexedClass, CompoundData compoundData) {
final compoundType = compoundData.compoundType as CompoundNativeTypeCfe;
final compoundLayout = compoundType.layout;
_annoteCompoundWithFields(node, compoundType.members, compoundData.packing);
if (compoundType.members.isEmpty) {
diagnosticReporter.report(
templateFfiEmptyStruct.withArguments(
node.superclass!.name, node.name),
node.fileOffset,
node.name.length,
node.location!.file);
}
final unalignedAccess = compoundData.packing != null;
int i = 0;
for (final compoundField in compoundData.compoundFields) {
NativeTypeCfe type = compoundField.type;
Field? field = compoundField.field;
Procedure? getter = compoundField.getter;
Procedure? setter = compoundField.setter;
final fieldOffsets = compoundLayout
.map((Abi abi, CompoundLayout v) => MapEntry(abi, v.offsets[i]));
if (field != null) {
_generateMethodsForField(
node, field, type, fieldOffsets, unalignedAccess, indexedClass);
}
if (getter != null) {
getter.function.body = type.generateGetterStatement(
getter.function.returnType,
getter.fileOffset,
fieldOffsets,
unalignedAccess,
this);
getter.isExternal = false;
}
if (setter != null) {
setter.function.body = type.generateSetterStatement(
setter.function.positionalParameters.single.type,
setter.fileOffset,
fieldOffsets,
unalignedAccess,
setter.function.positionalParameters.single,
this);
setter.isExternal = false;
}
i++;
}
}
static const vmFfiStructFields = "vm:ffi:struct-fields";
InstanceConstant? _compoundAnnotatedFields(Class node) {
for (final annotation in node.annotations) {
if (annotation is ConstantExpression) {
final constant = annotation.constant;
if (constant is InstanceConstant &&
constant.classNode == pragmaClass &&
constant.fieldValues[pragmaName.fieldReference] ==
StringConstant(vmFfiStructFields)) {
return constant.fieldValues[pragmaOptions.fieldReference]
as InstanceConstant?;
}
}
}
return null;
}
Set<Class> _compoundAnnotatedDependencies(InstanceConstant layoutConstant) {
final fieldTypes = layoutConstant
.fieldValues[ffiStructLayoutTypesField.fieldReference] as ListConstant;
final result = <Class>{};
for (final fieldType in fieldTypes.entries) {
if (fieldType is TypeLiteralConstant) {
final type = fieldType.type;
if (isCompoundSubtype(type)) {
final clazz = (type as InterfaceType).classNode;
result.add(clazz);
}
}
}
return result;
}
/// Must only be called if all the dependencies are already in the cache.
CompoundNativeTypeCfe _compoundAnnotatedNativeTypeCfe(Class compoundClass) {
final layoutConstant = _compoundAnnotatedFields(compoundClass)!;
final fieldTypes = layoutConstant
.fieldValues[ffiStructLayoutTypesField.fieldReference] as ListConstant;
final members = <NativeTypeCfe>[];
for (final fieldType in fieldTypes.entries) {
if (fieldType is TypeLiteralConstant) {
final dartType = fieldType.type;
members
.add(NativeTypeCfe(this, dartType, compoundCache: compoundCache));
} else if (fieldType is InstanceConstant) {
final singleElementConstant =
fieldType.fieldValues[ffiInlineArrayElementTypeField.fieldReference]
as TypeLiteralConstant;
final singleElementType = NativeTypeCfe(
this, singleElementConstant.type,
compoundCache: compoundCache);
final arrayLengthConstant =
fieldType.fieldValues[ffiInlineArrayLengthField.fieldReference]
as IntConstant;
final arrayLength = arrayLengthConstant.value;
members.add(ArrayNativeTypeCfe(singleElementType, arrayLength));
}
}
if (compoundClass.superclass == structClass) {
final packingConstant = layoutConstant
.fieldValues[ffiStructLayoutPackingField.fieldReference];
if (packingConstant is IntConstant) {
return StructNativeTypeCfe(compoundClass, members,
packing: packingConstant.value);
}
return StructNativeTypeCfe(compoundClass, members);
}
return UnionNativeTypeCfe(compoundClass, members);
}
void _annoteCompoundWithFields(
Class node, List<NativeTypeCfe> types, int? packing) {
List<Constant> constants =
types.map((t) => t.generateConstant(this)).toList();
node.addAnnotation(ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
pragmaName.fieldReference: StringConstant(vmFfiStructFields),
pragmaOptions.fieldReference:
InstanceConstant(ffiStructLayoutClass.reference, [], {
ffiStructLayoutTypesField.fieldReference: ListConstant(
InterfaceType(typeClass, Nullability.nonNullable), constants),
ffiStructLayoutPackingField.fieldReference:
packing == null ? NullConstant() : IntConstant(packing)
})
}),
InterfaceType(pragmaClass, Nullability.nonNullable, [])));
}
static const vmFfiAbiSpecificIntMapping = 'vm:ffi:abi-specific-mapping';
void _annotateAbiSpecificTypeWithMapping(
Class node, AbiSpecificNativeTypeCfe nativeTypeCfe) {
final constants = [
for (final abi in Abi.values)
nativeTypeCfe.abiSpecificTypes[abi]?.generateConstant(this) ??
NullConstant()
];
node.addAnnotation(ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
pragmaName.fieldReference: StringConstant(vmFfiAbiSpecificIntMapping),
pragmaOptions.fieldReference: InstanceConstant(
ffiAbiSpecificMappingClass.reference,
[],
{
ffiAbiSpecificMappingNativeTypesField.fieldReference:
ListConstant(
InterfaceType(typeClass, Nullability.nullable),
constants,
),
},
)
}),
InterfaceType(pragmaClass, Nullability.nonNullable, [])));
}
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].
final getterStatement = type.generateGetterStatement(
field.type, field.fileOffset, offsets, unalignedAccess, this);
Reference getterReference =
indexedClass?.lookupGetterReference(field.name) ??
field.getterReference;
assert(getterReference == field.getterReference,
"Unexpected getter reference for ${field}, found $getterReference.");
final Procedure getter = Procedure(field.name, ProcedureKind.Getter,
FunctionNode(getterStatement, returnType: field.type),
fileUri: field.fileUri, reference: getterReference)
..fileOffset = field.fileOffset
..isNonNullableByDefault = field.isNonNullableByDefault
..annotations = field.annotations;
node.addProcedure(getter);
if (!field.isFinal) {
Reference? setterReference =
indexedClass?.lookupSetterReference(field.name) ??
field.setterReference;
assert(setterReference == field.setterReference,
"Unexpected setter reference for ${field}, found $setterReference.");
final VariableDeclaration argument =
VariableDeclaration('#v', type: field.type)
..fileOffset = field.fileOffset;
final setterStatement = type.generateSetterStatement(field.type,
field.fileOffset, offsets, unalignedAccess, argument, this);
final setter = Procedure(
field.name,
ProcedureKind.Setter,
FunctionNode(setterStatement,
returnType: VoidType(), positionalParameters: [argument]),
fileUri: field.fileUri,
reference: setterReference)
..fileOffset = field.fileOffset
..isNonNullableByDefault = field.isNonNullableByDefault;
node.addProcedure(setter);
}
node.fields.remove(field);
}
/// Sample output:
/// int get #sizeOf => (const [24,24,16])[_abi()];
///
/// 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]) {
if (sizes == null) {
sizes = {for (var abi in Abi.values) abi: 0};
}
final name = Name("#sizeOf");
final getterReference = indexedClass?.lookupGetterReference(name);
final Procedure getter = Procedure(
name,
ProcedureKind.Getter,
FunctionNode(ReturnStatement(runtimeBranchOnLayout(sizes)),
returnType: InterfaceType(intClass, Nullability.legacy)),
fileUri: compound.fileUri,
reference: getterReference,
isStatic: true)
..fileOffset = compound.fileOffset
..isNonNullableByDefault = true
..addAnnotation(ConstantExpression(
InstanceConstant(pragmaClass.reference, /*type_arguments=*/ [], {
pragmaName.fieldReference: StringConstant("vm:prefer-inline"),
pragmaOptions.fieldReference: NullConstant(),
})));
compound.addProcedure(getter);
}
NativeType? _getFieldType(Class c) {
final fieldType = getType(c);
if (fieldType == NativeType.kVoid) {
// Fields cannot have Void types.
return null;
}
return fieldType;
}
Iterable<Class> _getNativeTypeAnnotations(Member node) {
return node.annotations
.whereType<ConstantExpression>()
.map((expr) => expr.constant)
.whereType<InstanceConstant>()
.map((constant) => constant.classNode)
.where((klass) =>
_getFieldType(klass) != null || _isUserAbiSpecificInteger(klass));
}
Iterable<List<int>> _getArraySizeAnnotations(Member node) {
return node.annotations
.whereType<ConstantExpression>()
.map((e) => e.constant)
.whereType<InstanceConstant>()
.where((e) => e.classNode == arraySizeClass)
.map(_arraySize);
}
List<int> _arraySize(InstanceConstant constant) {
final dimensions =
constant.fieldValues[arraySizeDimensionsField.fieldReference];
if (dimensions != null) {
if (dimensions is ListConstant) {
final result = dimensions.entries
.whereType<IntConstant>()
.map((e) => e.value)
.toList();
return result;
}
}
final dimensionFields = [
arraySizeDimension1Field,
arraySizeDimension2Field,
arraySizeDimension3Field,
arraySizeDimension4Field,
arraySizeDimension5Field
];
final result = dimensionFields
.map((f) => constant.fieldValues[f.fieldReference])
.whereType<IntConstant>()
.map((c) => c.value)
.toList();
return result;
}
Iterable<int> _getPackedAnnotations(Class node) {
return node.annotations
.whereType<ConstantExpression>()
.map((expr) => expr.constant)
.whereType<InstanceConstant>()
.where((e) => e.classNode == packedClass)
.map((e) => e.fieldValues.values.single)
.whereType<IntConstant>()
.map((e) => e.value);
}
}
class CompoundData {
final List<CompoundField> compoundFields;
final int? packing;
final NativeTypeCfe compoundType;
CompoundData(this.compoundFields, this.packing, this.compoundType);
}
class CompoundField {
final NativeTypeCfe type;
final Field? field;
final Procedure? getter;
final Procedure? setter;
CompoundField(this.type, this.field, this.getter, this.setter);
}