blob: b06c63fb26af7f3dc323b52d11f2301322acc676 [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.
library vm.transformations.ffi_definitions;
import 'dart:math' as math;
import 'package:front_end/src/api_unstable/vm.dart'
show
templateFfiEmptyStruct,
templateFfiEmptyStructWarning,
templateFfiFieldAnnotation,
templateFfiFieldCyclic,
templateFfiFieldNoAnnotation,
templateFfiTypeMismatch,
templateFfiFieldInitializer,
templateFfiStructGeneric;
import 'package:kernel/ast.dart' hide MapEntry;
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 'ffi.dart';
/// Checks and elaborates the dart:ffi structs and fields.
///
/// Input:
/// class Coord extends Struct {
/// @Double()
/// double x;
///
/// @Double()
/// double y;
///
/// Coord next;
/// }
///
/// Output:
/// class Coord extends Struct {
/// Coord.#fromTypedDataBase(Pointer<Coord> coord) : super._(coord);
///
/// Pointer<Double> get _xPtr => addressOf.cast();
/// set x(double v) => _xPtr.store(v);
/// double get x => _xPtr.load();
///
/// Pointer<Double> get _yPtr => addressOf.offsetBy(...).cast();
/// set y(double v) => _yPtr.store(v);
/// double get y => _yPtr.load();
///
/// ffi.Pointer<Coordinate> get _nextPtr => addressof.offsetBy(...).cast();
/// set next(Coordinate v) => _nextPtr.store(v);
/// Coordinate get next => _nextPtr.load();
///
/// static final int #sizeOf = 24;
/// }
FfiTransformerData 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"]);
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 FfiTransformerData({}, {}, {});
}
if (index.tryGetClass('dart:ffi', 'NativeFunction') == null) {
// If dart:ffi is not loaded (for real): do not do the transformation.
return FfiTransformerData({}, {}, {});
}
final transformer = new _FfiDefinitionTransformer(index, coreTypes, hierarchy,
diagnosticReporter, referenceFromIndex, changedStructureNotifier);
libraries.forEach(transformer.visitLibrary);
transformer.manualVisitInTopologicalOrder();
return FfiTransformerData(transformer.replacedGetters,
transformer.replacedSetters, transformer.emptyStructs);
}
class StructDependencyGraph<T> implements Graph<T> {
final Map<T, Iterable<T>> map;
StructDependencyGraph(this.map);
Iterable<T> get vertices => map.keys;
Iterable<T> neighborsOf(T vertex) => map[vertex];
}
/// Checks and elaborates the dart:ffi structs and fields.
class _FfiDefinitionTransformer extends FfiTransformer {
final LibraryIndex index;
// Data structures for topological navigation.
Map<Class, IndexedClass> indexedStructClasses = {};
Map<Class, Set<Class>> structClassDependencies = {};
Map<Class, bool> fieldsValid = {};
Map<Class, Map<Abi, StructLayout>> structLayouts = {};
Map<Field, Procedure> replacedGetters = {};
Map<Field, Procedure> replacedSetters = {};
Set<Class> emptyStructs = {};
ChangedStructureNotifier changedStructureNotifier;
IndexedLibrary currentLibraryIndex;
_FfiDefinitionTransformer(
this.index,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex referenceFromIndex,
this.changedStructureNotifier)
: super(index, coreTypes, hierarchy, diagnosticReporter,
referenceFromIndex) {}
void manualVisitInTopologicalOrder() {
final connectedComponents =
computeStrongComponents(StructDependencyGraph(structClassDependencies));
connectedComponents.forEach((List<Class> component) {
bool report = false;
if (component.length > 1) {
// Indirect cycle.
report = true;
}
if (component.length == 1) {
if (structClassDependencies[component.single]
.contains(component.single)) {
// Direct cycle.
report = true;
}
}
if (report) {
component.forEach((Class e) {
diagnosticReporter.report(
templateFfiFieldCyclic.withArguments(
e.name, component.map((e) => e.name).toList()),
e.fileOffset,
e.name.length,
e.fileUri);
});
}
});
final structClassesSorted = connectedComponents.expand((i) => i).toList();
structClassesSorted.forEach(visitClassInTopologicalOrder);
}
@override
visitLibrary(Library node) {
currentLibraryIndex = referenceFromIndex?.lookupLibrary(node);
return super.visitLibrary(node);
}
@override
visitExtension(Extension node) {
// The extension and it's members are only metadata.
return node;
}
@override
visitClass(Class node) {
if (!hierarchy.isSubclassOf(node, structClass) || node == structClass) {
return node;
}
_checkStructClass(node);
// Struct objects are manufactured in the VM by 'allocate' and 'load'.
_makeEntryPoint(node);
final indexedClass = currentLibraryIndex?.lookupIndexedClass(node.name);
_checkConstructors(node, indexedClass);
indexedStructClasses[node] = indexedClass;
fieldsValid[node] = _checkFieldAnnotations(node);
return node;
}
void visitClassInTopologicalOrder(Class node) {
final indexedClass = indexedStructClasses[node];
if (fieldsValid[node]) {
final structSize = _replaceFields(node, indexedClass);
_replaceSizeOfMethod(node, structSize, indexedClass);
changedStructureNotifier?.registerClassMemberChange(node);
}
}
void _checkStructClass(Class node) {
if (node.typeParameters.length > 0) {
diagnosticReporter.report(
templateFfiStructGeneric.withArguments(node.name),
node.fileOffset,
1,
node.location.file);
}
if (node.supertype?.classNode != structClass) {
// Not a struct, but extends a struct. The error will be emitted by
// _FfiUseSiteTransformer.
return;
}
}
bool _isPointerType(DartType type) {
return env.isSubtypeOf(
type,
InterfaceType(pointerClass, Nullability.legacy, [
InterfaceType(nativeTypesClasses[NativeType.kNativeType.index],
Nullability.legacy)
]),
SubtypeCheckMode.ignoringNullabilities);
}
bool _isStructSubtype(DartType type) {
return env.isSubtypeOf(type, InterfaceType(structClass, Nullability.legacy),
SubtypeCheckMode.ignoringNullabilities);
}
/// Returns members of [node] that correspond to struct fields.
///
/// Note that getters and setters that originate from an external field have
/// the same `fileOffset`, we always returns getters first.
List<Member> _structFieldMembers(Class node) {
final externalGetterSetters = [...node.procedures]
..retainWhere((p) => p.isExternal && (p.isGetter || p.isSetter));
final structMembers = [...node.fields, ...externalGetterSetters]
..sort((m1, m2) {
if (m1.fileOffset == m2.fileOffset) {
// Getter and setter have same offset, getter comes first.
return (m1 as Procedure).isGetter ? -1 : 1;
}
return m1.fileOffset - m2.fileOffset;
});
return structMembers;
}
DartType _structFieldMemberType(Member member) {
if (member is Field) {
return member.type;
}
final Procedure p = member;
if (p.isGetter) {
return p.function.returnType;
}
return p.function.positionalParameters.single.type;
}
bool _checkFieldAnnotations(Class node) {
bool success = true;
structClassDependencies[node] = {};
final membersWithAnnotations = _structFieldMembers(node)
..retainWhere((m) => (m is Field) || (m is Procedure && m.isGetter));
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);
}
}
final nativeTypeAnnos = _getNativeTypeAnnotations(f).toList();
final type = _structFieldMemberType(f);
if (_isPointerType(type) || _isStructSubtype(type)) {
if (nativeTypeAnnos.length != 0) {
diagnosticReporter.report(
templateFfiFieldNoAnnotation.withArguments(f.name.text),
f.fileOffset,
f.name.text.length,
f.fileUri);
}
if (_isStructSubtype(type)) {
final clazz = (type as InterfaceType).classNode;
structClassDependencies[node].add(clazz);
}
} else if (nativeTypeAnnos.length != 1) {
diagnosticReporter.report(
templateFfiFieldAnnotation.withArguments(f.name.text),
f.fileOffset,
f.name.text.length,
f.fileUri);
} else {
final DartType nativeType = InterfaceType(
nativeTypesClasses[_getFieldType(nativeTypeAnnos.first).index],
Nullability.legacy);
final DartType shouldBeDartType = convertNativeTypeToDartType(
nativeType,
allowStructs: 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);
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) {
i.remove();
}
// Add a constructor which 'load' can use.
// C.#fromTypedDataBase(Object address) : super.fromPointer(address);
final VariableDeclaration pointer = new VariableDeclaration("#pointer");
final name = Name("#fromTypedDataBase");
final referenceFrom = indexedClass?.lookupConstructor(name);
final Constructor ctor = Constructor(
FunctionNode(EmptyStatement(), positionalParameters: [pointer]),
name: name,
initializers: [
SuperInitializer(structFromPointer, Arguments([VariableGet(pointer)]))
],
fileUri: node.fileUri,
reference: referenceFrom?.reference)
..fileOffset = node.fileOffset
..isNonNullableByDefault = node.enclosingLibrary.isNonNullableByDefault;
_makeEntryPoint(ctor);
node.addConstructor(ctor);
}
/// Computes the field offsets (for all ABIs) in the struct and replaces the
/// fields with getters and setters using these offsets.
///
/// Returns the total size of the struct (for all ABIs).
Map<Abi, int> _replaceFields(Class node, IndexedClass indexedClass) {
final classes = <Class>[];
final types = <NativeType>[];
final fields = <int, Field>{};
final getters = <int, Procedure>{};
final setters = <int, Procedure>{};
int i = 0;
for (final Member m in _structFieldMembers(node)) {
final dartType = _structFieldMemberType(m);
NativeType nativeType;
Class clazz;
if (_isPointerType(dartType)) {
nativeType = NativeType.kPointer;
clazz = pointerClass;
} else if (_isStructSubtype(dartType)) {
nativeType = NativeType.kStruct;
clazz = (dartType as InterfaceType).classNode;
if (emptyStructs.contains(clazz)) {
diagnosticReporter.report(
templateFfiEmptyStruct.withArguments(clazz.name),
m.fileOffset,
1,
m.location.file);
}
} else {
final nativeTypeAnnos = _getNativeTypeAnnotations(m).toList();
if (nativeTypeAnnos.length == 1) {
clazz = nativeTypeAnnos.first;
nativeType = _getFieldType(clazz);
}
}
if ((m is Field || (m is Procedure && m.isGetter)) &&
nativeType != null) {
types.add(nativeType);
classes.add(clazz);
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.
if (getters.containsKey(index)) {
setters[i - 1] = m;
}
}
}
_annoteStructWithFields(node, classes);
if (classes.isEmpty) {
diagnosticReporter.report(
templateFfiEmptyStructWarning.withArguments(node.name),
node.fileOffset,
node.name.length,
node.location.file);
emptyStructs.add(node);
}
final structLayout = <Abi, StructLayout>{};
for (final Abi abi in Abi.values) {
structLayout[abi] = _calculateStructLayout(types, classes, abi);
}
structLayouts[node] = structLayout;
for (final i in fields.keys) {
final fieldOffsets = structLayout
.map((Abi abi, StructLayout v) => MapEntry(abi, v.offsets[i]));
final methods = _generateMethodsForField(
fields[i], types[i], fieldOffsets, indexedClass);
methods.forEach((p) => node.addProcedure(p));
}
for (final Field f in fields.values) {
f.remove();
}
for (final i in getters.keys) {
final fieldOffsets = structLayout
.map((Abi abi, StructLayout v) => MapEntry(abi, v.offsets[i]));
Procedure getter = getters[i];
getter.function.body = _generateGetterStatement(
getter.function.returnType,
types[i],
getter.fileOffset,
fieldOffsets);
getter.isExternal = false;
}
for (final i in setters.keys) {
final fieldOffsets = structLayout
.map((Abi abi, StructLayout v) => MapEntry(abi, v.offsets[i]));
Procedure setter = setters[i];
setter.function.body = _generateSetterStatement(
setter.function.positionalParameters.single.type,
types[i],
setter.fileOffset,
fieldOffsets,
setter.function.positionalParameters.single);
setter.isExternal = false;
}
return structLayout.map((k, v) => MapEntry(k, v.size));
}
void _annoteStructWithFields(Class node, List<Class> fieldTypes) {
final types = fieldTypes.map((Class c) {
List<DartType> typeArg = const [];
if (c == pointerClass) {
typeArg = [
InterfaceType(pointerClass.superclass, Nullability.nonNullable)
];
}
return TypeLiteralConstant(
InterfaceType(c, Nullability.nonNullable, typeArg));
}).toList();
node.addAnnotation(ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
pragmaName.getterReference: StringConstant("vm:ffi:struct-fields"),
// TODO(dartbug.com/38158): Wrap list in class to be able to encode
// more information when needed.
pragmaOptions.getterReference: ListConstant(
InterfaceType(typeClass, Nullability.nonNullable), types)
}),
InterfaceType(pragmaClass, Nullability.nonNullable, [])));
}
Statement _generateGetterStatement(DartType dartType, NativeType type,
int fileOffset, Map<Abi, int> offsets) {
final bool isPointer = type == NativeType.kPointer;
final bool isStruct = type == NativeType.kStruct;
// Sample output:
// int get x => _loadInt8(pointer, offset);
//
// Treat Pointer fields different to get correct behavior without casts:
// Pointer<Int8> get x =>
// _fromAddress<Int8>(_loadIntPtr(pointer, offset));
//
// Nested structs:
// MyStruct get x =>
// MyStruct.#fromTypedDataBase(
// _addressOf is Pointer ?
// _fromAddress<MyStruct>((_addressOf as Pointer).address + offset) :
// (_addressOf as TypedData).buffer.asInt8List(
// (_addressOf as TypedData).offsetInBytes + offset,
// size
// )
// );
if (isStruct) {
final clazz = (dartType as InterfaceType).classNode;
final constructor = clazz.constructors
.firstWhere((c) => c.name == Name("#fromTypedDataBase"));
final lengths =
structLayouts[clazz].map((key, value) => MapEntry(key, value.size));
Expression thisDotAddressOf() =>
PropertyGet(ThisExpression(), addressOfField.name, addressOfField)
..fileOffset = fileOffset;
return ReturnStatement(ConstructorInvocation(
constructor,
Arguments([
ConditionalExpression(
IsExpression(thisDotAddressOf(),
InterfaceType(pointerClass, Nullability.legacy)),
StaticInvocation(
fromAddressInternal,
Arguments([
MethodInvocation(
PropertyGet(thisDotAddressOf(), addressGetter.name,
addressGetter)
..fileOffset = fileOffset,
numAddition.name,
Arguments([runtimeBranchOnLayout(offsets)]),
numAddition)
], types: [
dartType
]))
..fileOffset = fileOffset,
MethodInvocation(
PropertyGet(
StaticInvocation(
unsafeCastMethod,
Arguments([
thisDotAddressOf()
], types: [
InterfaceType(typedDataClass, Nullability.legacy)
]))
..fileOffset = fileOffset,
typedDataBufferGetter.name,
typedDataBufferGetter)
..fileOffset = fileOffset,
byteBufferAsUint8List.name,
Arguments([
MethodInvocation(
PropertyGet(
StaticInvocation(
unsafeCastMethod,
Arguments([
thisDotAddressOf()
], types: [
InterfaceType(
typedDataClass, Nullability.legacy)
]))
..fileOffset = fileOffset,
typedDataOffsetInBytesGetter.name,
typedDataOffsetInBytesGetter)
..fileOffset = fileOffset,
numAddition.name,
Arguments([runtimeBranchOnLayout(offsets)]),
numAddition),
runtimeBranchOnLayout(lengths)
]),
byteBufferAsUint8List),
InterfaceType(objectClass, Nullability.nonNullable))
]))
..fileOffset = fileOffset);
}
final loadMethod =
isPointer ? loadMethods[NativeType.kIntptr] : loadMethods[type];
Expression getterReturnValue = StaticInvocation(
loadMethod,
Arguments([
PropertyGet(ThisExpression(), addressOfField.name, addressOfField)
..fileOffset = fileOffset,
runtimeBranchOnLayout(offsets)
]))
..fileOffset = fileOffset;
if (isPointer) {
final typeArg = (dartType as InterfaceType).typeArguments.single;
getterReturnValue = StaticInvocation(
fromAddressInternal, Arguments([getterReturnValue], types: [typeArg]))
..fileOffset = fileOffset;
}
return ReturnStatement(getterReturnValue);
}
Statement _generateSetterStatement(DartType dartType, NativeType type,
int fileOffset, Map<Abi, int> offsets, VariableDeclaration argument) {
final bool isPointer = type == NativeType.kPointer;
final bool isStruct = type == NativeType.kStruct;
// Sample output:
// set x(int v) => _storeInt8(pointer, offset, v);
//
// Treat Pointer fields different to get correct behavior without casts:
// set x(Pointer<Int8> v) =>
// _storeIntPtr(pointer, offset, (v as Pointer<Int8>).address);
//
// Nested structs:
// set x(MyStruct v) =>
// _memCopy(this._address, offset, v._address, 0, size);
if (isStruct) {
final clazz = (dartType as InterfaceType).classNode;
final lengths =
structLayouts[clazz].map((key, value) => MapEntry(key, value.size));
return ReturnStatement(StaticInvocation(
memCopy,
Arguments([
PropertyGet(ThisExpression(), addressOfField.name, addressOfField)
..fileOffset = fileOffset,
runtimeBranchOnLayout(offsets),
PropertyGet(
VariableGet(argument), addressOfField.name, addressOfField)
..fileOffset = fileOffset,
ConstantExpression(IntConstant(0)),
runtimeBranchOnLayout(lengths),
]))
..fileOffset = fileOffset);
}
final storeMethod =
isPointer ? storeMethods[NativeType.kIntptr] : storeMethods[type];
Expression argumentExpression = VariableGet(argument)
..fileOffset = fileOffset;
if (isPointer) {
argumentExpression =
PropertyGet(argumentExpression, addressGetter.name, addressGetter)
..fileOffset = fileOffset;
}
return ReturnStatement(StaticInvocation(
storeMethod,
Arguments([
PropertyGet(ThisExpression(), addressOfField.name, addressOfField)
..fileOffset = fileOffset,
runtimeBranchOnLayout(offsets),
argumentExpression
]))
..fileOffset = fileOffset);
}
List<Procedure> _generateMethodsForField(Field field, NativeType type,
Map<Abi, int> offsets, IndexedClass indexedClass) {
final getterStatement =
_generateGetterStatement(field.type, type, field.fileOffset, offsets);
final Procedure getter = Procedure(field.name, ProcedureKind.Getter,
FunctionNode(getterStatement, returnType: field.type),
fileUri: field.fileUri,
reference: indexedClass?.lookupGetterReference(field.name))
..fileOffset = field.fileOffset
..isNonNullableByDefault = field.isNonNullableByDefault;
Procedure setter = null;
if (!field.isFinal) {
final VariableDeclaration argument =
VariableDeclaration('#v', type: field.type)
..fileOffset = field.fileOffset;
final setterStatement = _generateSetterStatement(
field.type, type, field.fileOffset, offsets, argument);
setter = Procedure(
field.name,
ProcedureKind.Setter,
FunctionNode(setterStatement,
returnType: VoidType(), positionalParameters: [argument]),
fileUri: field.fileUri,
reference: indexedClass?.lookupSetterReference(field.name))
..fileOffset = field.fileOffset
..isNonNullableByDefault = field.isNonNullableByDefault;
}
replacedGetters[field] = getter;
replacedSetters[field] = setter;
return [getter, if (setter != null) setter];
}
/// Sample output:
/// int #sizeOf => [24,24,16][_abi()];
void _replaceSizeOfMethod(
Class struct, Map<Abi, int> sizes, IndexedClass indexedClass) {
var name = Name("#sizeOf");
var getterReference = indexedClass?.lookupGetterReference(name);
final Field sizeOf = Field.immutable(name,
isStatic: true,
isFinal: true,
initializer: runtimeBranchOnLayout(sizes),
type: InterfaceType(intClass, Nullability.legacy),
fileUri: struct.fileUri,
getterReference: getterReference)
..fileOffset = struct.fileOffset;
_makeEntryPoint(sizeOf);
struct.addField(sizeOf);
}
int _sizeInBytes(NativeType type, Class clazz, Abi abi) {
if (type == NativeType.kStruct) {
final structLayout = structLayouts[clazz];
if (structLayout == null) {
// We have a cycle, so we don't know the size.
return 0;
}
return structLayout[abi].size;
}
final int size = nativeTypeSizes[type.index];
if (size == WORD_SIZE) {
return wordSize[abi];
}
return size;
}
int _alignmentOf(NativeType type, Class clazz, Abi abi) {
if (type == NativeType.kStruct) {
final structLayout = structLayouts[clazz];
if (structLayout == null) {
// We have a cycle, so we don't know the size.
return 0;
}
return structLayout[abi].alignment;
}
final int alignment = nonSizeAlignment[abi][type];
if (alignment != null) return alignment;
return _sizeInBytes(type, clazz, abi);
}
int _alignOffset(int offset, int alignment) {
final int remainder = offset % alignment;
if (remainder != 0) {
offset -= remainder;
offset += alignment;
}
return offset;
}
// Keep consistent with runtime/vm/compiler/ffi/native_type.cc
// NativeCompoundType::FromNativeTypes.
StructLayout _calculateStructLayout(
List<NativeType> types, List<Class> classes, Abi abi) {
int offset = 0;
final offsets = <int>[];
int structAlignment = 1;
for (int i = 0; i < types.length; i++) {
final int size = _sizeInBytes(types[i], classes[i], abi);
final int alignment = _alignmentOf(types[i], classes[i], abi);
offset = _alignOffset(offset, alignment);
offsets.add(offset);
offset += size;
structAlignment = math.max(structAlignment, alignment);
}
final int size = _alignOffset(offset, structAlignment);
return StructLayout(size, structAlignment, offsets);
}
void _makeEntryPoint(Annotatable node) {
node.addAnnotation(ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
pragmaName.getterReference: StringConstant("vm:entry-point"),
pragmaOptions.getterReference: NullConstant()
}),
InterfaceType(pragmaClass, Nullability.legacy, [])));
}
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);
}
}
/// The layout of a `Struct` in one [Abi].
class StructLayout {
/// Size of the entire struct.
final int size;
/// Alignment of struct when nested in other struct.
final int alignment;
/// Offset in bytes for each field, indexed by field number.
final List<int> offsets;
StructLayout(this.size, this.alignment, this.offsets);
}