blob: 8746e087d149325a7850d0e9da5f1106c87a521f [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.
// This file contains logic which is shared between the ffi_definition and
// ffi_use_site transformers.
library vm.transformations.ffi;
// This imports 'codes/cfe_codes.dart' instead of 'api_prototype/codes.dart' to
// avoid cyclic dependency between `package:vm/modular` and `package:front_end`.
import 'package:front_end/src/codes/cfe_codes.dart'
show
messageFfiLeafCallMustNotReturnHandle,
messageFfiLeafCallMustNotTakeHandle,
messageFfiVariableLengthArrayNotLast,
messageNegativeVariableDimension,
messageNonPositiveArrayDimensions,
templateFfiSizeAnnotation,
templateFfiSizeAnnotationDimensions,
templateFfiTypeInvalid,
templateFfiTypeMismatch;
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/targets.dart' show DiagnosticReporter;
import 'package:kernel/type_algebra.dart' show Substitution;
import 'package:kernel/type_environment.dart' show TypeEnvironment;
import 'package:kernel/util/graph.dart' as kernelGraph;
import 'abi.dart';
import 'native_type_cfe.dart';
/// Represents the (instantiated) ffi.NativeType.
enum NativeType {
kNativeType,
kNativeInteger,
kNativeDouble,
kPointer,
kNativeFunction,
kInt8,
kInt16,
kInt32,
kInt64,
kUint8,
kUint16,
kUint32,
kUint64,
kFloat,
kDouble,
kVoid,
kOpaque,
kStruct,
kHandle,
kBool,
}
const Set<NativeType> nativeIntTypesFixedSize = <NativeType>{
NativeType.kInt8,
NativeType.kInt16,
NativeType.kInt32,
NativeType.kInt64,
NativeType.kUint8,
NativeType.kUint16,
NativeType.kUint32,
NativeType.kUint64,
};
/// The [NativeType] class names.
const Map<NativeType, String> nativeTypeClassNames = <NativeType, String>{
NativeType.kNativeType: 'NativeType',
NativeType.kNativeInteger: '_NativeInteger',
NativeType.kNativeDouble: '_NativeDouble',
NativeType.kPointer: 'Pointer',
NativeType.kNativeFunction: 'NativeFunction',
NativeType.kInt8: 'Int8',
NativeType.kInt16: 'Int16',
NativeType.kInt32: 'Int32',
NativeType.kInt64: 'Int64',
NativeType.kUint8: 'Uint8',
NativeType.kUint16: 'Uint16',
NativeType.kUint32: 'Uint32',
NativeType.kUint64: 'Uint64',
NativeType.kFloat: 'Float',
NativeType.kDouble: 'Double',
NativeType.kVoid: 'Void',
NativeType.kOpaque: 'Opaque',
NativeType.kStruct: 'Struct',
NativeType.kHandle: 'Handle',
NativeType.kBool: 'Bool',
};
const int UNKNOWN = 0;
const int WORD_SIZE = -1;
/// The [NativeType] sizes in bytes.
const Map<NativeType, int> nativeTypeSizes = <NativeType, int>{
NativeType.kNativeType: UNKNOWN,
NativeType.kNativeInteger: UNKNOWN,
NativeType.kNativeDouble: UNKNOWN,
NativeType.kPointer: WORD_SIZE,
NativeType.kNativeFunction: UNKNOWN,
NativeType.kInt8: 1,
NativeType.kInt16: 2,
NativeType.kInt32: 4,
NativeType.kInt64: 8,
NativeType.kUint8: 1,
NativeType.kUint16: 2,
NativeType.kUint32: 4,
NativeType.kUint64: 8,
NativeType.kFloat: 4,
NativeType.kDouble: 8,
NativeType.kVoid: UNKNOWN,
NativeType.kOpaque: UNKNOWN,
NativeType.kStruct: UNKNOWN,
NativeType.kHandle: WORD_SIZE,
NativeType.kBool: 1,
};
/// Load and store are rewired to their static type for these types.
const List<NativeType> optimizedTypes = [
NativeType.kBool,
NativeType.kInt8,
NativeType.kInt16,
NativeType.kInt32,
NativeType.kInt64,
NativeType.kUint8,
NativeType.kUint16,
NativeType.kUint32,
NativeType.kUint64,
NativeType.kFloat,
NativeType.kDouble,
NativeType.kPointer,
];
const List<NativeType> unalignedLoadsStores = [
NativeType.kFloat,
NativeType.kDouble,
];
const List<String> addressOfExtensionsTypedData = [
'Float32List',
'Float64List',
'Int16List',
'Int32List',
'Int64List',
'Int8List',
'Uint16List',
'Uint32List',
'Uint64List',
'Uint8List',
];
const List<String> addressOfExtensionsCompound = ['Array', 'Struct', 'Union'];
const List<String> addressOfExtensionsPrimitive = ['Bool', 'Double', 'Int'];
const List<String> addressOfExtensions = [
...addressOfExtensionsCompound,
...addressOfExtensionsPrimitive,
...addressOfExtensionsTypedData,
];
enum FfiTypeCheckDirection {
// Passing a value from native code to Dart code.
nativeToDart,
// Passing a value from Dart code to native code.
dartToNative,
}
/// Converting mode of `Handle` in [convertNativeTypeToDartType].
enum HandleToDartTypeConversionMode {
/// The variance-based conversion mode, where `Handle` is converted to
/// `Never` if it appears in covariant positions of the type being converted,
/// or to `Object?` when it appears in contravariant positions of the type.
/// This mode is used when the type being converted appears as the subtype of
/// a type check.
varianceBasedBehaviorForSubtype,
/// The variance-based conversion mode, where `Handle` is converted to
/// `Object?` if it appears in covariant positions of the type being
/// converted, or to `Never` when it appears in contravariant positions of
/// the type. This mode is used when the type being converted appears as the
/// supertype of a type check.
varianceBasedBehaviorForSupertype,
}
/// [FfiTransformer] contains logic which is shared between
/// _FfiUseSiteTransformer and _FfiDefinitionTransformer.
class FfiTransformer extends Transformer {
final TypeEnvironment env;
final CoreTypes coreTypes;
final LibraryIndex index;
final ClassHierarchy hierarchy;
final DiagnosticReporter diagnosticReporter;
final ReferenceFromIndex? referenceFromIndex;
final Class objectClass;
final Class intClass;
final Class doubleClass;
final Class boolClass;
final Class listClass;
final Class typeClass;
final Procedure unsafeCastMethod;
final Procedure nativeEffectMethod;
final Class typedDataClass;
final Procedure typedDataBufferGetter;
final Procedure typedDataOffsetInBytesGetter;
final Procedure byteBufferAsUint8List;
final Procedure uint8ListFactory;
final Class pragmaClass;
final Field pragmaName;
final Field pragmaOptions;
final Procedure listElementAt;
final Procedure numAddition;
final Procedure numMultiplication;
final Procedure objectEquals;
final Procedure stateErrorThrowNewFunction;
final Library ffiLibrary;
final Class allocatorClass;
final Class nativeFunctionClass;
final Class handleClass;
final Class opaqueClass;
final Class arrayClass;
final Class arraySizeClass;
final Field arraySizeDimension1Field;
final Field arraySizeDimension2Field;
final Field arraySizeDimension3Field;
final Field arraySizeDimension4Field;
final Field arraySizeDimension5Field;
final Field arraySizeDimensionsField;
final Field arraySizeVariableDimensionField;
final Class pointerClass;
final Class compoundClass;
final Class structClass;
final Class unionClass;
final Class abiSpecificIntegerClass;
final Class abiSpecificIntegerMappingClass;
final Class varArgsClass;
final Class nativeFieldWrapperClass1Class;
final Class ffiStructLayoutClass;
final Field ffiStructLayoutTypesField;
final Field ffiStructLayoutPackingField;
final Class ffiAbiSpecificMappingClass;
final Field ffiAbiSpecificMappingNativeTypesField;
final Class ffiInlineArrayClass;
final Field ffiInlineArrayElementTypeField;
final Field ffiInlineArrayLengthField;
final Field ffiInlineArrayVariableLengthField;
final Class packedClass;
final Field packedMemberAlignmentField;
final Procedure allocateMethod;
final Procedure allocatorAllocateMethod;
final Procedure castMethod;
final Procedure offsetByMethod;
final Procedure addressGetter;
final Procedure structPointerGetRef;
final Procedure structPointerSetRef;
final Procedure structPointerRefWithFinalizer;
final Procedure structPointerRefWithFinalizerTearoff;
final Procedure structPointerGetElemAt;
final Procedure structPointerSetElemAt;
final Procedure structPointerElementAt;
final Procedure structPointerPlusOperator;
final Procedure structPointerMinusOperator;
final Procedure structPointerElementAtTearoff;
final Procedure unionPointerGetRef;
final Procedure unionPointerSetRef;
final Procedure unionPointerRefWithFinalizer;
final Procedure unionPointerRefWithFinalizerTearoff;
final Procedure unionPointerGetElemAt;
final Procedure unionPointerSetElemAt;
final Procedure unionPointerElementAt;
final Procedure unionPointerPlusOperator;
final Procedure unionPointerMinusOperator;
final Procedure unionPointerElementAtTearoff;
final Procedure uint8PointerAsTypedList;
final Constructor arrayListConstructor;
final Constructor arrayArrayListConstructor;
final Constructor abiSpecificIntegerArrayListConstructor;
final Procedure arrayArrayElements;
final Procedure structArrayElements;
final Procedure unionArrayElements;
final Procedure abiSpecificIntegerArrayElements;
final Procedure structArrayElemAt;
final Procedure unionArrayElemAt;
final Procedure arrayArrayElemAt;
final Procedure arrayArrayAssignAt;
final Procedure abiSpecificIntegerPointerGetValue;
final Procedure abiSpecificIntegerPointerSetValue;
final Procedure abiSpecificIntegerPointerElemAt;
final Procedure abiSpecificIntegerPointerSetElemAt;
final Procedure abiSpecificIntegerPointerElementAt;
final Procedure abiSpecificIntegerPointerPlusOperator;
final Procedure abiSpecificIntegerPointerMinusOperator;
final Procedure abiSpecificIntegerPointerElementAtTearoff;
final Procedure abiSpecificIntegerArrayElemAt;
final Procedure abiSpecificIntegerArraySetElemAt;
final Procedure asFunctionMethod;
final Procedure ffiCallMethod;
final Procedure sizeOfMethod;
final Procedure lookupFunctionMethod;
final Procedure fromFunctionMethod;
final Field compoundTypedDataBaseField;
final Field compoundOffsetInBytesField;
final Field arraySizeField;
final Field arrayVariableLengthField;
final Field arrayNestedDimensionsField;
final Procedure arrayCheckIndex;
final Procedure arrayNestedDimensionsFlattened;
final Procedure arrayNestedDimensionsFirst;
final Procedure arrayNestedDimensionsRest;
final Procedure structCreate;
final Procedure unionCreate;
final Constructor compoundFromTypedDataBase;
final Constructor structFromTypedDataBase;
final Constructor unionFromTypedDataBase;
final Constructor structFromTypedData;
final Constructor unionFromTypedData;
final Constructor arrayConstructor;
final Procedure fromAddressInternal;
final Procedure libraryLookupMethod;
final Procedure abiMethod;
final Procedure createNativeCallableListenerProcedure;
final Procedure nativeCallbackFunctionProcedure;
final Procedure nativeAsyncCallbackFunctionProcedure;
final Procedure createNativeCallableIsolateLocalProcedure;
final Procedure createNativeCallableIsolateGroupBoundProcedure;
final Procedure nativeIsolateLocalCallbackFunctionProcedure;
final Procedure nativeIsolateGroupBoundCallbackFunctionProcedure;
final Procedure nativeIsolateGroupBoundClosureFunctionProcedure;
final Map<NativeType, Procedure> loadMethods;
final Map<NativeType, Procedure> loadUnalignedMethods;
final Map<NativeType, Procedure> storeMethods;
final Map<NativeType, Procedure> storeUnalignedMethods;
final Procedure loadAbiSpecificIntMethod;
final Procedure loadAbiSpecificIntAtIndexMethod;
final Procedure storeAbiSpecificIntMethod;
final Procedure storeAbiSpecificIntAtIndexMethod;
final Procedure abiCurrentMethod;
final Map<Constant, Abi> constantAbis;
final Class intptrClass;
late AbiSpecificNativeTypeCfe intptrNativeTypeCfe;
final Procedure memCopy;
final Procedure allocationTearoff;
final Procedure asFunctionTearoff;
final Procedure lookupFunctionTearoff;
final Procedure getNativeFieldFunction;
final Class finalizableClass;
final Procedure reachabilityFenceFunction;
final Procedure checkAbiSpecificIntegerMappingFunction;
final Class rawRecvPortClass;
final Class nativeCallableClass;
final Procedure nativeCallableIsolateLocalConstructor;
final Procedure nativeCallableIsolateGroupBoundConstructor;
final Constructor nativeCallablePrivateIsolateLocalConstructor;
final Constructor nativeCallablePrivateIsolateGroupBoundConstructor;
final Procedure nativeCallableListenerConstructor;
final Constructor nativeCallablePrivateListenerConstructor;
final Field nativeCallablePortField;
final Field nativeCallablePointerField;
final Procedure nativeAddressOf;
final Procedure nativePrivateAddressOf;
final List<Procedure> addressOfMethods;
final List<Procedure> addressOfMethodsCompound;
final List<Procedure> addressOfMethodsPrimitive;
final List<Procedure> addressOfMethodsTypedData;
final Class ffiCallClass;
final Field ffiCallIsLeafField;
final Field nativeIsLeafField;
late final InterfaceType nativeFieldWrapperClass1Type;
late final InterfaceType voidType;
late final InterfaceType pointerVoidType;
// The instantiated to bounds type argument for the Pointer class.
late final InterfaceType nativeTypeType;
// The Pointer type when instantiated to bounds.
late final InterfaceType pointerNativeTypeType;
late final InterfaceType uint8Type;
late final InterfaceType compoundType;
/// Classes corresponding to [NativeType], indexed by [NativeType].
final Map<NativeType, Class> nativeTypesClasses;
final Map<Class, NativeType> classNativeTypes;
Library? _currentLibrary;
Library get currentLibrary => _currentLibrary!;
IndexedLibrary? currentLibraryIndex;
FfiTransformer(
this.index,
this.coreTypes,
this.hierarchy,
this.diagnosticReporter,
this.referenceFromIndex,
) : env = TypeEnvironment(coreTypes, hierarchy),
objectClass = coreTypes.objectClass,
intClass = coreTypes.intClass,
doubleClass = coreTypes.doubleClass,
boolClass = coreTypes.boolClass,
listClass = coreTypes.listClass,
typeClass = coreTypes.typeClass,
unsafeCastMethod = index.getTopLevelProcedure(
'dart:_internal',
'unsafeCast',
),
nativeEffectMethod = index.getTopLevelProcedure(
'dart:_internal',
'_nativeEffect',
),
typedDataClass = index.getClass('dart:typed_data', 'TypedData'),
typedDataBufferGetter = index.getProcedure(
'dart:typed_data',
'TypedData',
'get:buffer',
),
typedDataOffsetInBytesGetter = index.getProcedure(
'dart:typed_data',
'TypedData',
'get:offsetInBytes',
),
byteBufferAsUint8List = index.getProcedure(
'dart:typed_data',
'ByteBuffer',
'asUint8List',
),
uint8ListFactory = index.getProcedure('dart:typed_data', 'Uint8List', ''),
pragmaClass = coreTypes.pragmaClass,
pragmaName = coreTypes.pragmaName,
pragmaOptions = coreTypes.pragmaOptions,
listElementAt = coreTypes.index.getProcedure('dart:core', 'List', '[]'),
numAddition = coreTypes.index.getProcedure('dart:core', 'num', '+'),
numMultiplication = coreTypes.index.getProcedure('dart:core', 'num', '*'),
objectEquals = coreTypes.index.getProcedure('dart:core', 'Object', '=='),
stateErrorThrowNewFunction = coreTypes.index.getProcedure(
'dart:core',
'StateError',
'_throwNew',
),
ffiLibrary = index.getLibrary('dart:ffi'),
allocatorClass = index.getClass('dart:ffi', 'Allocator'),
nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
handleClass = index.getClass('dart:ffi', 'Handle'),
opaqueClass = index.getClass('dart:ffi', 'Opaque'),
arrayClass = index.getClass('dart:ffi', 'Array'),
arraySizeClass = index.getClass('dart:ffi', '_ArraySize'),
arraySizeDimension1Field = index.getField(
'dart:ffi',
'_ArraySize',
'dimension1',
),
arraySizeDimension2Field = index.getField(
'dart:ffi',
'_ArraySize',
'dimension2',
),
arraySizeDimension3Field = index.getField(
'dart:ffi',
'_ArraySize',
'dimension3',
),
arraySizeDimension4Field = index.getField(
'dart:ffi',
'_ArraySize',
'dimension4',
),
arraySizeDimension5Field = index.getField(
'dart:ffi',
'_ArraySize',
'dimension5',
),
arraySizeDimensionsField = index.getField(
'dart:ffi',
'_ArraySize',
'dimensions',
),
arraySizeVariableDimensionField = index.getField(
'dart:ffi',
'_ArraySize',
'variableDimension',
),
pointerClass = index.getClass('dart:ffi', 'Pointer'),
compoundClass = index.getClass('dart:ffi', '_Compound'),
structClass = index.getClass('dart:ffi', 'Struct'),
unionClass = index.getClass('dart:ffi', 'Union'),
abiSpecificIntegerClass = index.getClass(
'dart:ffi',
'AbiSpecificInteger',
),
abiSpecificIntegerMappingClass = index.getClass(
'dart:ffi',
'AbiSpecificIntegerMapping',
),
varArgsClass = index.getClass('dart:ffi', 'VarArgs'),
nativeFieldWrapperClass1Class = index.getClass(
'dart:nativewrappers',
'NativeFieldWrapperClass1',
),
ffiStructLayoutClass = index.getClass('dart:ffi', '_FfiStructLayout'),
ffiStructLayoutTypesField = index.getField(
'dart:ffi',
'_FfiStructLayout',
'fieldTypes',
),
ffiStructLayoutPackingField = index.getField(
'dart:ffi',
'_FfiStructLayout',
'packing',
),
ffiAbiSpecificMappingClass = index.getClass(
'dart:ffi',
'_FfiAbiSpecificMapping',
),
ffiAbiSpecificMappingNativeTypesField = index.getField(
'dart:ffi',
'_FfiAbiSpecificMapping',
'nativeTypes',
),
ffiInlineArrayClass = index.getClass('dart:ffi', '_FfiInlineArray'),
ffiInlineArrayElementTypeField = index.getField(
'dart:ffi',
'_FfiInlineArray',
'elementType',
),
ffiInlineArrayLengthField = index.getField(
'dart:ffi',
'_FfiInlineArray',
'length',
),
ffiInlineArrayVariableLengthField = index.getField(
'dart:ffi',
'_FfiInlineArray',
'variableLength',
),
packedClass = index.getClass('dart:ffi', 'Packed'),
packedMemberAlignmentField = index.getField(
'dart:ffi',
'Packed',
'memberAlignment',
),
allocateMethod = index.getProcedure('dart:ffi', 'AllocatorAlloc', 'call'),
allocatorAllocateMethod = index.getProcedure(
'dart:ffi',
'Allocator',
'allocate',
),
castMethod = index.getProcedure('dart:ffi', 'Pointer', 'cast'),
offsetByMethod = index.getProcedure('dart:ffi', 'Pointer', '_offsetBy'),
addressGetter = index.getProcedure('dart:ffi', 'Pointer', 'get:address'),
compoundTypedDataBaseField = index.getField(
'dart:ffi',
'_Compound',
'_typedDataBase',
),
compoundOffsetInBytesField = index.getField(
'dart:ffi',
'_Compound',
'_offsetInBytes',
),
arraySizeField = index.getField('dart:ffi', 'Array', '_size'),
arrayVariableLengthField = index.getField(
'dart:ffi',
'Array',
'_variableLength',
),
arrayNestedDimensionsField = index.getField(
'dart:ffi',
'Array',
'_nestedDimensions',
),
arrayCheckIndex = index.getProcedure('dart:ffi', 'Array', '_checkIndex'),
arrayNestedDimensionsFlattened = index.getProcedure(
'dart:ffi',
'Array',
'get:_nestedDimensionsFlattened',
),
arrayNestedDimensionsFirst = index.getProcedure(
'dart:ffi',
'Array',
'get:_nestedDimensionsFirst',
),
arrayNestedDimensionsRest = index.getProcedure(
'dart:ffi',
'Array',
'get:_nestedDimensionsRest',
),
structCreate = index.getProcedure('dart:ffi', 'Struct', 'create'),
unionCreate = index.getProcedure('dart:ffi', 'Union', 'create'),
compoundFromTypedDataBase = index.getConstructor(
'dart:ffi',
'_Compound',
'_fromTypedDataBase',
),
structFromTypedDataBase = index.getConstructor(
'dart:ffi',
'Struct',
'_fromTypedDataBase',
),
unionFromTypedDataBase = index.getConstructor(
'dart:ffi',
'Union',
'_fromTypedDataBase',
),
structFromTypedData = index.getConstructor(
'dart:ffi',
'Struct',
'_fromTypedData',
),
unionFromTypedData = index.getConstructor(
'dart:ffi',
'Union',
'_fromTypedData',
),
arrayConstructor = index.getConstructor('dart:ffi', 'Array', '_'),
fromAddressInternal = index.getTopLevelProcedure(
'dart:ffi',
'_fromAddress',
),
structPointerGetRef = index.getProcedure(
'dart:ffi',
'StructPointer',
'get:ref',
),
structPointerSetRef = index.getProcedure(
'dart:ffi',
'StructPointer',
'set:ref',
),
structPointerRefWithFinalizer = index.getProcedure(
'dart:ffi',
'StructPointer',
'refWithFinalizer',
),
structPointerRefWithFinalizerTearoff = index.getProcedure(
'dart:ffi',
'StructPointer',
LibraryIndex.tearoffPrefix + 'refWithFinalizer',
),
structPointerGetElemAt = index.getProcedure(
'dart:ffi',
'StructPointer',
'[]',
),
structPointerSetElemAt = index.getProcedure(
'dart:ffi',
'StructPointer',
'[]=',
),
structPointerElementAt = index.getProcedure(
'dart:ffi',
'StructPointer',
'elementAt',
),
structPointerPlusOperator = index.getProcedure(
'dart:ffi',
'StructPointer',
'+',
),
structPointerMinusOperator = index.getProcedure(
'dart:ffi',
'StructPointer',
'-',
),
structPointerElementAtTearoff = index.getProcedure(
'dart:ffi',
'StructPointer',
LibraryIndex.tearoffPrefix + 'elementAt',
),
unionPointerGetRef = index.getProcedure(
'dart:ffi',
'UnionPointer',
'get:ref',
),
unionPointerSetRef = index.getProcedure(
'dart:ffi',
'UnionPointer',
'set:ref',
),
unionPointerRefWithFinalizer = index.getProcedure(
'dart:ffi',
'UnionPointer',
'refWithFinalizer',
),
unionPointerRefWithFinalizerTearoff = index.getProcedure(
'dart:ffi',
'UnionPointer',
LibraryIndex.tearoffPrefix + 'refWithFinalizer',
),
unionPointerGetElemAt = index.getProcedure(
'dart:ffi',
'UnionPointer',
'[]',
),
unionPointerSetElemAt = index.getProcedure(
'dart:ffi',
'UnionPointer',
'[]=',
),
unionPointerElementAt = index.getProcedure(
'dart:ffi',
'UnionPointer',
'elementAt',
),
unionPointerPlusOperator = index.getProcedure(
'dart:ffi',
'UnionPointer',
'+',
),
unionPointerMinusOperator = index.getProcedure(
'dart:ffi',
'UnionPointer',
'-',
),
unionPointerElementAtTearoff = index.getProcedure(
'dart:ffi',
'UnionPointer',
LibraryIndex.tearoffPrefix + 'elementAt',
),
uint8PointerAsTypedList = index.getProcedure(
'dart:ffi',
'Uint8Pointer',
'asTypedList',
),
arrayArrayElements = index.getProcedure(
'dart:ffi',
'ArrayArray',
'get:elements',
),
structArrayElements = index.getProcedure(
'dart:ffi',
'StructArray',
'get:elements',
),
unionArrayElements = index.getProcedure(
'dart:ffi',
'UnionArray',
'get:elements',
),
abiSpecificIntegerArrayElements = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerArray',
'get:elements',
),
arrayArrayListConstructor = index.getConstructor(
'dart:ffi',
'_ArrayArrayList',
'',
),
arrayListConstructor = index.getConstructor('dart:ffi', '_ArrayList', ''),
abiSpecificIntegerArrayListConstructor = index.getConstructor(
'dart:ffi',
'_AbiSpecificIntegerArrayList',
'',
),
structArrayElemAt = index.getProcedure('dart:ffi', 'StructArray', '[]'),
unionArrayElemAt = index.getProcedure('dart:ffi', 'UnionArray', '[]'),
arrayArrayElemAt = index.getProcedure('dart:ffi', 'ArrayArray', '[]'),
arrayArrayAssignAt = index.getProcedure('dart:ffi', 'ArrayArray', '[]='),
abiSpecificIntegerPointerGetValue = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerPointer',
'get:value',
),
abiSpecificIntegerPointerSetValue = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerPointer',
'set:value',
),
abiSpecificIntegerPointerElemAt = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerPointer',
'[]',
),
abiSpecificIntegerPointerSetElemAt = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerPointer',
'[]=',
),
abiSpecificIntegerPointerElementAt = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerPointer',
'elementAt',
),
abiSpecificIntegerPointerPlusOperator = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerPointer',
'+',
),
abiSpecificIntegerPointerMinusOperator = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerPointer',
'-',
),
abiSpecificIntegerPointerElementAtTearoff = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerPointer',
LibraryIndex.tearoffPrefix + 'elementAt',
),
abiSpecificIntegerArrayElemAt = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerArray',
'[]',
),
abiSpecificIntegerArraySetElemAt = index.getProcedure(
'dart:ffi',
'AbiSpecificIntegerArray',
'[]=',
),
asFunctionMethod = index.getProcedure(
'dart:ffi',
'NativeFunctionPointer',
'asFunction',
),
ffiCallMethod = index.getTopLevelProcedure('dart:ffi', '_ffiCall'),
sizeOfMethod = index.getTopLevelProcedure('dart:ffi', 'sizeOf'),
lookupFunctionMethod = index.getProcedure(
'dart:ffi',
'DynamicLibraryExtension',
'lookupFunction',
),
fromFunctionMethod = index.getProcedure(
'dart:ffi',
'Pointer',
'fromFunction',
),
libraryLookupMethod = index.getProcedure(
'dart:ffi',
'DynamicLibrary',
'lookup',
),
abiMethod = index.getTopLevelProcedure('dart:ffi', '_abi'),
createNativeCallableListenerProcedure = index.getTopLevelProcedure(
'dart:ffi',
'_createNativeCallableListener',
),
createNativeCallableIsolateLocalProcedure = index.getTopLevelProcedure(
'dart:ffi',
'_createNativeCallableIsolateLocal',
),
createNativeCallableIsolateGroupBoundProcedure = index
.getTopLevelProcedure(
'dart:ffi',
'_createNativeCallableIsolateGroupBound',
),
nativeCallbackFunctionProcedure = index.getTopLevelProcedure(
'dart:ffi',
'_nativeCallbackFunction',
),
nativeAsyncCallbackFunctionProcedure = index.getTopLevelProcedure(
'dart:ffi',
'_nativeAsyncCallbackFunction',
),
nativeIsolateLocalCallbackFunctionProcedure = index.getTopLevelProcedure(
'dart:ffi',
'_nativeIsolateLocalCallbackFunction',
),
nativeIsolateGroupBoundCallbackFunctionProcedure = index
.getTopLevelProcedure(
'dart:ffi',
'_nativeIsolateGroupBoundCallbackFunction',
),
nativeIsolateGroupBoundClosureFunctionProcedure = index
.getTopLevelProcedure(
'dart:ffi',
'_nativeIsolateGroupBoundClosureFunction',
),
nativeTypesClasses = nativeTypeClassNames.map(
(nativeType, name) =>
MapEntry(nativeType, index.getClass('dart:ffi', name)),
),
classNativeTypes = nativeTypeClassNames.map(
(nativeType, name) =>
MapEntry(index.getClass('dart:ffi', name), nativeType),
),
loadMethods = Map.fromIterable(
optimizedTypes,
value: (t) {
final name = nativeTypeClassNames[t];
return index.getTopLevelProcedure('dart:ffi', "_load$name");
},
),
loadUnalignedMethods = Map.fromIterable(
unalignedLoadsStores,
value: (t) {
final name = nativeTypeClassNames[t];
return index.getTopLevelProcedure(
'dart:ffi',
"_load${name}Unaligned",
);
},
),
storeMethods = Map.fromIterable(
optimizedTypes,
value: (t) {
final name = nativeTypeClassNames[t];
return index.getTopLevelProcedure('dart:ffi', "_store$name");
},
),
storeUnalignedMethods = Map.fromIterable(
unalignedLoadsStores,
value: (t) {
final name = nativeTypeClassNames[t];
return index.getTopLevelProcedure(
'dart:ffi',
"_store${name}Unaligned",
);
},
),
loadAbiSpecificIntMethod = index.getTopLevelProcedure(
'dart:ffi',
"_loadAbiSpecificInt",
),
loadAbiSpecificIntAtIndexMethod = index.getTopLevelProcedure(
'dart:ffi',
"_loadAbiSpecificIntAtIndex",
),
storeAbiSpecificIntMethod = index.getTopLevelProcedure(
'dart:ffi',
"_storeAbiSpecificInt",
),
storeAbiSpecificIntAtIndexMethod = index.getTopLevelProcedure(
'dart:ffi',
"_storeAbiSpecificIntAtIndex",
),
abiCurrentMethod = index.getProcedure('dart:ffi', 'Abi', 'current'),
constantAbis = abiNames.map(
(abi, name) => MapEntry(
(index.getField('dart:ffi', 'Abi', name).initializer
as ConstantExpression)
.constant,
abi,
),
),
intptrClass = index.getClass('dart:ffi', 'IntPtr'),
memCopy = index.getTopLevelProcedure('dart:ffi', '_memCopy'),
allocationTearoff = index.getProcedure(
'dart:ffi',
'AllocatorAlloc',
LibraryIndex.tearoffPrefix + 'call',
),
asFunctionTearoff = index.getProcedure(
'dart:ffi',
'NativeFunctionPointer',
LibraryIndex.tearoffPrefix + 'asFunction',
),
lookupFunctionTearoff = index.getProcedure(
'dart:ffi',
'DynamicLibraryExtension',
LibraryIndex.tearoffPrefix + 'lookupFunction',
),
getNativeFieldFunction = index.getTopLevelProcedure(
'dart:nativewrappers',
'_getNativeField',
),
finalizableClass = index.getClass('dart:ffi', 'Finalizable'),
reachabilityFenceFunction = index.getTopLevelProcedure(
'dart:_internal',
'reachabilityFence',
),
checkAbiSpecificIntegerMappingFunction = index.getTopLevelProcedure(
'dart:ffi',
"_checkAbiSpecificIntegerMapping",
),
rawRecvPortClass = index.getClass('dart:isolate', 'RawReceivePort'),
nativeCallableClass = index.getClass('dart:ffi', 'NativeCallable'),
nativeCallableIsolateLocalConstructor = index.getProcedure(
'dart:ffi',
'NativeCallable',
'isolateLocal',
),
nativeCallableIsolateGroupBoundConstructor = index.getProcedure(
'dart:ffi',
'NativeCallable',
'isolateGroupBound',
),
nativeCallablePrivateIsolateLocalConstructor = index.getConstructor(
'dart:ffi',
'_NativeCallableIsolateLocal',
'',
),
nativeCallableListenerConstructor = index.getProcedure(
'dart:ffi',
'NativeCallable',
'listener',
),
nativeCallablePrivateListenerConstructor = index.getConstructor(
'dart:ffi',
'_NativeCallableListener',
'',
),
nativeCallablePrivateIsolateGroupBoundConstructor = index.getConstructor(
'dart:ffi',
'_NativeCallableIsolateGroupBound',
'',
),
nativeCallablePortField = index.getField(
'dart:ffi',
'_NativeCallableListener',
'_port',
),
nativeCallablePointerField = index.getField(
'dart:ffi',
'_NativeCallableBase',
'_pointer',
),
nativeAddressOf =
index.getMember('dart:ffi', 'Native', 'addressOf') as Procedure,
nativePrivateAddressOf =
index.getMember('dart:ffi', 'Native', '_addressOf') as Procedure,
addressOfMethods = [
for (final name in addressOfExtensions)
index.getProcedure('dart:ffi', '${name}Address', 'get:address'),
],
addressOfMethodsPrimitive = [
for (final name in addressOfExtensionsPrimitive)
index.getProcedure('dart:ffi', '${name}Address', 'get:address'),
],
addressOfMethodsCompound = [
for (final name in addressOfExtensionsCompound)
index.getProcedure('dart:ffi', '${name}Address', 'get:address'),
],
addressOfMethodsTypedData = [
for (final name in addressOfExtensionsTypedData)
index.getProcedure('dart:ffi', '${name}Address', 'get:address'),
],
ffiCallClass = index.getClass('dart:ffi', '_FfiCall'),
ffiCallIsLeafField = index.getField('dart:ffi', '_FfiCall', 'isLeaf'),
nativeIsLeafField = index.getField('dart:ffi', 'Native', 'isLeaf') {
nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
coreTypes,
Nullability.nonNullable,
);
voidType = nativeTypesClasses[NativeType.kVoid]!.getThisType(
coreTypes,
Nullability.nonNullable,
);
pointerVoidType = InterfaceType(pointerClass, Nullability.nonNullable, [
voidType,
]);
nativeTypeType = nativeTypesClasses[NativeType.kNativeType]!.getThisType(
coreTypes,
Nullability.nonNullable,
);
pointerNativeTypeType = InterfaceType(
pointerClass,
Nullability.nonNullable,
[nativeTypeType],
);
intptrNativeTypeCfe =
NativeTypeCfe(this, InterfaceType(intptrClass, Nullability.nonNullable))
as AbiSpecificNativeTypeCfe;
uint8Type = InterfaceType(
nativeTypesClasses[NativeType.kUint8]!,
Nullability.nonNullable,
);
compoundType = InterfaceType(
compoundClass,
Nullability.nonNullable,
const <DartType>[],
);
}
@override
TreeNode visitLibrary(Library node) {
assert(_currentLibrary == null);
_currentLibrary = node;
currentLibraryIndex = referenceFromIndex?.lookupLibrary(node);
final result = super.visitLibrary(node);
_currentLibrary = null;
return result;
}
/// Computes the Dart type corresponding to a ffi.[NativeType], returns null
/// if it is not a valid NativeType.
///
/// ```
/// [Int8] -> [int]
/// [Int16] -> [int]
/// [Int32] -> [int]
/// [Int64] -> [int]
/// [Uint8] -> [int]
/// [Uint16] -> [int]
/// [Uint32] -> [int]
/// [Uint64] -> [int]
/// [IntPtr] -> [int]
/// T extends [AbiSpecificInteger] -> [int]
/// [Double] -> [double]
/// [Float] -> [double]
/// [Bool] -> [bool]
/// [Void] -> [void]
/// [Pointer]<T> -> [Pointer]<T>
/// T extends [Struct] -> T
/// T extends [Union] -> T
/// [Handle] -> [Object?]|[Never]
/// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
/// where DartRepresentationOf(Tn) -> Sn
/// ```
///
/// If [mode] is [HandleToDartTypeConversionMode.oldNativeBehavior], `Handle`
/// is converted to `Object?`. Otherwise, `Handle` is converted to either
/// `Object?` or `Never`. Since `Handle` represents any possible Dart object,
/// it should match any type. To achieve that, in the subtype checks where
/// `Handle` appears as the subtype (as indicated by [mode] being
/// [HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype]), it
/// should be converted depending on the variance of its position: `Never` for
/// covariant positions, and `Object?` for contravariant ones. If `Handle`
/// appears as the supertype in the check (as indicated by [mode] being
/// [HandleToDartTypeConversionMode.varianceBasedBehaviorForSupertype]), the
/// treatment of covariant and contravariant positions is reversed. For more
/// details, see https://github.com/dart-lang/sdk/issues/49518.
///
/// [isCovariant] is the flag that helps compute the variance of the position
/// in [nativeType]. It's updated in the recursive invocations of
/// [convertNativeTypeToDartType] and should be omitted in regular call sites.
/// [isCovariant] is ignored if [mode] is
/// [HandleToDartTypeConversionMode.oldNativeBehavior].
DartType? convertNativeTypeToDartType(
DartType nativeType, {
bool allowStructAndUnion = false,
bool allowHandle = false,
bool allowInlineArray = false,
bool allowVoid = false,
HandleToDartTypeConversionMode mode =
HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype,
Variance variance = Variance.invariant,
}) {
if (nativeType is! InterfaceType) {
return null;
}
final InterfaceType native = nativeType;
final Class nativeClass = native.classNode;
final NativeType? nativeType_ = getType(nativeClass);
if (nativeClass == arrayClass) {
if (!allowInlineArray) {
return null;
}
final nested = convertNativeTypeToDartType(
nativeType.typeArguments.single,
allowInlineArray: true,
allowStructAndUnion: true,
);
if (nested == null) {
return null;
}
return nativeType;
}
if (hierarchy.isSubclassOf(nativeClass, abiSpecificIntegerClass)) {
if (nativeClass == abiSpecificIntegerClass) {
return null;
}
return coreTypes.intNonNullableRawType;
}
if (hierarchy.isSubclassOf(nativeClass, compoundClass)) {
if (nativeClass == structClass || nativeClass == unionClass) {
return null;
}
return allowStructAndUnion ? nativeType : null;
}
if (nativeType_ == null) {
return null;
}
if (nativeType_ == NativeType.kPointer) {
return nativeType;
}
if (nativeIntTypesFixedSize.contains(nativeType_)) {
return coreTypes.intNonNullableRawType;
}
if (nativeType_ == NativeType.kFloat || nativeType_ == NativeType.kDouble) {
return coreTypes.doubleNonNullableRawType;
}
if (nativeType_ == NativeType.kBool) {
return coreTypes.boolNonNullableRawType;
}
if (nativeType_ == NativeType.kVoid) {
if (!allowVoid) {
return null;
}
return VoidType();
}
if (nativeType_ == NativeType.kHandle && allowHandle) {
switch (mode) {
case HandleToDartTypeConversionMode.varianceBasedBehaviorForSubtype:
return switch (variance) {
Variance.contravariant => coreTypes.objectNullableRawType,
Variance.covariant ||
Variance.invariant ||
Variance.unrelated => const NeverType.nonNullable(),
};
case HandleToDartTypeConversionMode.varianceBasedBehaviorForSupertype:
return switch (variance) {
Variance.contravariant => const NeverType.nonNullable(),
Variance.covariant ||
Variance.invariant ||
Variance.unrelated => coreTypes.objectNullableRawType,
};
}
}
if (nativeType_ != NativeType.kNativeFunction ||
native.typeArguments[0] is! FunctionType) {
return null;
}
final FunctionType fun = native.typeArguments[0] as FunctionType;
if (fun.namedParameters.isNotEmpty) return null;
if (fun.positionalParameters.length != fun.requiredParameterCount) {
return null;
}
if (fun.typeParameters.isNotEmpty) return null;
final DartType? returnType = convertNativeTypeToDartType(
fun.returnType,
allowStructAndUnion: true,
allowHandle: true,
allowVoid: true,
mode: mode,
variance: variance,
);
if (returnType == null) return null;
final argumentTypes = <DartType>[];
for (final paramDartType in flattenVarargs(fun).positionalParameters) {
argumentTypes.add(
convertNativeTypeToDartType(
paramDartType,
allowStructAndUnion: true,
allowHandle: true,
mode: mode,
variance: variance.combine(Variance.contravariant),
) ??
dummyDartType,
);
}
if (argumentTypes.contains(dummyDartType)) return null;
return FunctionType(argumentTypes, returnType, Nullability.nonNullable);
}
/// Finds a native type for the given [dartType] if there is only one possible
/// native type.
///
/// This is impossible for some types (like [int] which needs a specific ffi
/// type to denote the width in C). This method returns `null` for those
/// types.
///
/// For types where this returns a non-null value, this is the inverse of
/// [convertNativeTypeToDartType].
DartType? convertDartTypeToNativeType(
DartType dartType, {
bool allowVoid = false,
}) {
if (allowVoid && dartType is VoidType) return voidType;
if (isArrayType(dartType) ||
isPointerType(dartType) ||
isStructOrUnionSubtype(dartType)) {
return dartType;
}
if (dartType is FunctionType) {
if (dartType.namedParameters.isNotEmpty) return null;
if (dartType.positionalParameters.length !=
dartType.requiredParameterCount) {
return null;
}
if (dartType.typeParameters.isNotEmpty) return null;
final returnType = convertDartTypeToNativeType(
dartType.returnType,
allowVoid: true,
);
if (returnType == null) return null;
final argumentTypes = <DartType>[];
for (final paramDartType
in flattenVarargs(dartType).positionalParameters) {
argumentTypes.add(
convertDartTypeToNativeType(paramDartType) ?? dummyDartType,
);
}
if (argumentTypes.contains(dummyDartType)) return null;
return FunctionType(argumentTypes, returnType, Nullability.nonNullable);
}
return null;
}
/// Removes the VarArgs from a DartType list.
///
/// ```
/// [Int8, Int8] -> [Int8, Int8]
/// [Int8, VarArgs<(Int8,)>] -> [Int8, Int8]
/// [Int8, VarArgs<(Int8, Int8)>] -> [Int8, Int8, Int8]
/// ```
FunctionType flattenVarargs(FunctionType functionTypeWithPossibleVarArgs) {
final positionalParameters =
functionTypeWithPossibleVarArgs.positionalParameters;
if (positionalParameters.isEmpty) {
return functionTypeWithPossibleVarArgs;
}
final lastPositionalParameter = positionalParameters.last;
if (lastPositionalParameter is InterfaceType &&
lastPositionalParameter.classNode == varArgsClass) {
final typeArgument = lastPositionalParameter.typeArguments.single;
if (typeArgument is! RecordType) {
return functionTypeWithPossibleVarArgs;
}
if (typeArgument.named.isNotEmpty) {
// Named record fields are not supported.
return functionTypeWithPossibleVarArgs;
}
final positionalParameters = [
...functionTypeWithPossibleVarArgs.positionalParameters.sublist(
0,
functionTypeWithPossibleVarArgs.positionalParameters.length - 1,
),
for (final paramDartType in typeArgument.positional) paramDartType,
];
return FunctionType(
positionalParameters,
functionTypeWithPossibleVarArgs.returnType,
functionTypeWithPossibleVarArgs.declaredNullability,
namedParameters: functionTypeWithPossibleVarArgs.namedParameters,
typeParameters: functionTypeWithPossibleVarArgs.typeParameters,
requiredParameterCount: positionalParameters.length,
);
}
return functionTypeWithPossibleVarArgs;
}
/// The [NativeType] corresponding to [c]. Returns `null` for user-defined
/// structs.
NativeType? getType(Class c) {
return classNativeTypes[c];
}
InterfaceType _listOfIntType(Nullability elementNullability) => InterfaceType(
listClass,
Nullability.nonNullable,
[coreTypes.intRawType(elementNullability)],
);
ConstantExpression intListConstantExpression(
List<int?> values,
Nullability elementNullability,
) => ConstantExpression(
ListConstant(coreTypes.intRawType(elementNullability), [
for (var v in values)
if (v != null) IntConstant(v) else NullConstant(),
]),
_listOfIntType(elementNullability),
);
/// Expression that queries VM internals at runtime to figure out on which ABI
/// we are.
Expression runtimeBranchOnLayout(Map<Abi, int?> values) {
final elementNullability =
values.isPartial ? Nullability.nullable : Nullability.nonNullable;
final result = InstanceInvocation(
InstanceAccessKind.Instance,
intListConstantExpression([
for (final abi in Abi.values) values[abi],
], elementNullability),
listElementAt.name,
Arguments([StaticInvocation(abiMethod, Arguments([]))]),
interfaceTarget: listElementAt,
functionType:
Substitution.fromInterfaceType(
_listOfIntType(elementNullability),
).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)],
),
);
bool isPrimitiveType(DartType type) {
if (type is InvalidType) {
return false;
}
if (type is NullType) {
return false;
}
if (!env.isSubtypeOf(type, nativeTypeType)) {
return false;
}
if (isPointerType(type)) {
return false;
}
if (type is InterfaceType) {
final nativeType = getType(type.classNode);
return nativeType != null;
}
return false;
}
bool isPointerType(DartType type) {
if (type is InvalidType) {
return false;
}
if (type is NullType) {
return false;
}
return env.isSubtypeOf(type, pointerNativeTypeType);
}
bool isArrayType(DartType type) {
if (type is InvalidType) {
return false;
}
if (type is NullType) {
return false;
}
return env.isSubtypeOf(
type,
InterfaceType(arrayClass, Nullability.nonNullable, [nativeTypeType]),
);
}
/// Returns the single element type nested type argument of `Array`.
///
/// `Array<Array<Array<Int8>>>` -> `Int8`.
///
/// `Array<Array<Array<Unknown>>>` -> [InvalidType].
DartType arraySingleElementType(DartType dartType) {
if (dartType is! InterfaceType) {
return InvalidType();
}
InterfaceType elementType = dartType;
while (elementType.classNode == arrayClass) {
final elementTypeAny = elementType.typeArguments[0];
if (elementTypeAny is! InterfaceType) {
return InvalidType();
}
elementType = elementTypeAny;
}
return elementType;
}
/// Ensures that [node] has an `Array` annotation with valid dimensions
/// matching its [type].
///
/// Throws an [FfiStaticTypeError] otherwise.
List<int> ensureArraySizeAnnotation(
Member node,
DartType type,
bool allowVariableLength,
) {
final sizeAnnotations = getArraySizeAnnotations(node);
List<int> dimensions;
bool variableLength;
var success = true;
if (sizeAnnotations.length == 1) {
final singleElementType = arraySingleElementType(type);
if (singleElementType is! InterfaceType) {
assert(singleElementType is InvalidType);
throw FfiStaticTypeError();
} else {
dimensions = sizeAnnotations.single.$1;
variableLength = sizeAnnotations.single.$2;
if (arrayDimensions(type) != dimensions.length) {
diagnosticReporter.report(
templateFfiSizeAnnotationDimensions.withArguments(node.name.text),
node.fileOffset,
node.name.text.length,
node.fileUri,
);
}
if (variableLength) {
if (!allowVariableLength) {
diagnosticReporter.report(
messageFfiVariableLengthArrayNotLast,
node.fileOffset,
node.name.text.length,
node.fileUri,
);
}
}
for (var i = 0; i < dimensions.length; i++) {
// First dimension is variable.
if (i == 0 && variableLength) {
// Variable dimension can't be negative.
if (dimensions[0] < 0) {
diagnosticReporter.report(
messageNegativeVariableDimension,
node.fileOffset,
node.name.text.length,
node.fileUri,
);
}
continue;
}
if (dimensions[i] <= 0) {
diagnosticReporter.report(
messageNonPositiveArrayDimensions,
node.fileOffset,
node.name.text.length,
node.fileUri,
);
success = false;
}
}
}
} else {
diagnosticReporter.report(
templateFfiSizeAnnotation.withArguments(node.name.text),
node.fileOffset,
node.name.text.length,
node.fileUri,
);
throw FfiStaticTypeError();
}
if (!success) {
throw FfiStaticTypeError();
}
return dimensions;
}
Iterable<(List<int>, bool)> getArraySizeAnnotations(Member node) {
return node.annotations
.whereType<ConstantExpression>()
.map((e) => e.constant)
.whereType<InstanceConstant>()
.where((e) => e.classNode == arraySizeClass)
.map(_arraySize);
}
/// Reads the dimensions from a constant instance of `_ArraySize`.
(List<int>, bool) _arraySize(InstanceConstant constant) {
final variableDimension =
constant.fieldValues[arraySizeVariableDimensionField.fieldReference];
final variableLength = variableDimension is IntConstant;
final dimensions =
constant.fieldValues[arraySizeDimensionsField.fieldReference];
if (dimensions is ListConstant) {
final result = dimensions.entries.whereType<IntConstant>().map(
(e) => e.value,
);
return (
[if (variableLength) variableDimension.value, ...result],
variableLength,
);
}
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, variableLength);
}
/// Returns the number of dimensions of `Array`.
///
/// `Array<Array<Array<Int8>>>` -> 3.
///
/// `Array<Array<Array<Unknown>>>` -> 3.
int arrayDimensions(DartType dartType) {
DartType elementType = dartType;
int dimensions = 0;
while (elementType is InterfaceType &&
elementType.classNode == arrayClass) {
elementType = elementType.typeArguments[0];
dimensions++;
}
return dimensions;
}
bool isAbiSpecificIntegerSubtype(DartType type) {
if (type is InvalidType) {
return false;
}
if (type is NullType) {
return false;
}
if (type is InterfaceType) {
if (type.classNode == abiSpecificIntegerClass) {
return false;
}
}
return env.isSubtypeOf(
type,
InterfaceType(abiSpecificIntegerClass, Nullability.nonNullable),
);
}
bool isStructOrUnionSubtype(DartType type) {
if (type is InvalidType) {
return false;
}
if (type is NullType) {
return false;
}
if (type is InterfaceType) {
if (type.classNode == structClass ||
type.classNode == unionClass ||
type.classNode == arrayClass) {
return false;
}
}
return env.isSubtypeOf(
type,
InterfaceType(compoundClass, Nullability.nonNullable),
);
}
Expression getCompoundTypedDataBaseField(
Expression receiver,
int fileOffset,
) {
return InstanceGet(
InstanceAccessKind.Instance,
receiver,
compoundTypedDataBaseField.name,
interfaceTarget: compoundTypedDataBaseField,
resultType: compoundTypedDataBaseField.type,
)..fileOffset = fileOffset;
}
Expression getCompoundOffsetInBytesField(
Expression receiver,
int fileOffset,
) {
return InstanceGet(
InstanceAccessKind.Instance,
receiver,
compoundOffsetInBytesField.name,
interfaceTarget: compoundOffsetInBytesField,
resultType: compoundOffsetInBytesField.type,
)..fileOffset = fileOffset;
}
Expression add(Expression a, Expression b) {
return InstanceInvocation(
InstanceAccessKind.Instance,
a,
numAddition.name,
Arguments([b]),
interfaceTarget: numAddition,
functionType: numAddition.getterType as FunctionType,
);
}
Expression multiply(Expression a, Expression b) {
return InstanceInvocation(
InstanceAccessKind.Instance,
a,
numMultiplication.name,
Arguments([b]),
interfaceTarget: numMultiplication,
functionType: numMultiplication.getterType as FunctionType,
);
}
MapConstant? getAbiSpecificIntegerMappingAnnotation(Class node) {
final annotations =
node.annotations
.whereType<ConstantExpression>()
.map((e) => e.constant)
.whereType<InstanceConstant>()
.where((e) => e.classNode == abiSpecificIntegerMappingClass)
.map(
(instanceConstant) =>
instanceConstant.fieldValues.values.single as MapConstant,
)
.toList();
// There can be at most one annotation (checked by `_FfiDefinitionTransformer`)
if (annotations.length == 1) {
return annotations[0];
}
return null;
}
Expression? inlineSizeOf(InterfaceType nativeType) {
final Class nativeClass = nativeType.classNode;
final NativeType? nt = getType(nativeClass);
if (nt == null) {
// User-defined compounds.
final Procedure sizeOfGetter = nativeClass.procedures.firstWhere(
(function) => function.name == Name('#sizeOf'),
);
return StaticGet(sizeOfGetter);
}
final int size = nativeTypeSizes[nt]!;
if (size == WORD_SIZE) {
return runtimeBranchOnLayout(wordSize);
}
if (size != UNKNOWN) {
return ConstantExpression(
IntConstant(size),
InterfaceType(intClass, currentLibrary.nonNullable),
);
}
// Size unknown.
return null;
}
/// Generates an expression performing an Abi specific integer load or store.
///
/// If [value] is provided, it is a store, otherwise a load.
///
/// Provide either [index], or [offsetInBytes], or none for an offset of 0.
///
/// Generates an expression:
///
/// ```dart
/// _storeAbiSpecificInt(
/// [8, 8, 4][_abi()],
/// typedDataBase,
/// index * [8, 8, 4][_abi()],
/// value,
/// )
/// ```
Expression abiSpecificLoadOrStoreExpression(
AbiSpecificNativeTypeCfe nativeTypeCfe, {
required Expression typedDataBase,
Expression? offsetInBytes,
Expression? index,
Expression? value,
required fileOffset,
}) {
final method = () {
if (value != null) {
if (index != null) {
return storeAbiSpecificIntAtIndexMethod;
}
return storeAbiSpecificIntMethod;
}
if (index != null) {
return loadAbiSpecificIntAtIndexMethod;
}
return loadAbiSpecificIntMethod;
}();
return StaticInvocation(
method,
Arguments(
[
typedDataBase,
offsetInBytes ?? ConstantExpression(IntConstant(0)),
if (index != null) index,
if (value != null) value,
],
types: [InterfaceType(nativeTypeCfe.clazz, Nullability.nonNullable)],
),
)..fileOffset = fileOffset;
}
/// Prevents the struct from being tree-shaken in TFA by invoking its
/// constructor in a `_nativeEffect` expression.
Expression invokeCompoundConstructor(
Expression nestedExpression,
Class compoundClass,
) {
final constructor = compoundClass.constructors.firstWhere(
(c) => c.name == Name("#fromTypedDataBase"),
);
return BlockExpression(
Block([
ExpressionStatement(
StaticInvocation(
nativeEffectMethod,
Arguments([
ConstructorInvocation(
constructor,
Arguments([
StaticInvocation(
uint8ListFactory,
Arguments([ConstantExpression(IntConstant(1))]),
)..fileOffset = nestedExpression.fileOffset,
ConstantExpression(IntConstant(0)),
]),
)..fileOffset = nestedExpression.fileOffset,
]),
),
),
]),
nestedExpression,
)..fileOffset = nestedExpression.fileOffset;
}
/// Returns the compound [Class] if a compound is returned, otherwise `null`.
Class? findCompoundReturnType(DartType dartSignature) {
if (dartSignature is! FunctionType) {
return null;
}
final returnType = dartSignature.returnType;
if (returnType is! InterfaceType) {
return null;
}
final clazz = returnType.classNode;
if (clazz.superclass == structClass || clazz.superclass == unionClass) {
return clazz;
}
return null;
}
/// Returns
/// - `true` if leaf
/// - `false` if not leaf
/// - `null` if the expression is not valid (e.g. non-const bool, null)
bool? getIsLeafBoolean(StaticInvocation node) {
for (final named in node.arguments.named) {
if (named.name == 'isLeaf') {
final expr = named.value;
if (expr is BoolLiteral) {
return expr.value;
} else if (expr is ConstantExpression) {
final constant = expr.constant;
if (constant is BoolConstant) {
return constant.value;
}
}
// isLeaf is passed some invalid value.
return null;
}
}
// isLeaf defaults to false.
return false;
}
void ensureLeafCallDoesNotUseHandles(
InterfaceType nativeType,
bool isLeaf, {
required TreeNode reportErrorOn,
}) {
// Handles are only disallowed for leaf calls.
if (isLeaf == false) {
return;
}
bool error = false;
// Check if return type is Handle.
final functionType = nativeType.typeArguments[0];
if (functionType is FunctionType) {
final returnType = functionType.returnType;
if (returnType is InterfaceType) {
if (returnType.classNode == handleClass) {
diagnosticReporter.report(
messageFfiLeafCallMustNotReturnHandle,
reportErrorOn.fileOffset,
1,
reportErrorOn.location?.file,
);
error = true;
}
}
// Check if any of the argument types are Handle.
for (DartType param in functionType.positionalParameters) {
if ((param as InterfaceType).classNode == handleClass) {
diagnosticReporter.report(
messageFfiLeafCallMustNotTakeHandle,
reportErrorOn.fileOffset,
1,
reportErrorOn.location?.file,
);
error = true;
}
}
}
if (error) {
throw FfiStaticTypeError();
}
}
DartType ensureNativeTypeMatch(
FfiTypeCheckDirection direction,
DartType nativeType,
DartType dartType,
TreeNode reportErrorOn, {
bool allowHandle = false,
bool allowVoid = false,
bool allowArray = false,
}) {
final DartType correspondingDartType;
switch (direction) {
case FfiTypeCheckDirection.nativeToDart:
correspondingDartType =
convertNativeTypeToDartType(
nativeType,
allowStructAndUnion: true,
allowHandle: allowHandle,
allowInlineArray: allowArray,
allowVoid: allowVoid,
mode:
HandleToDartTypeConversionMode
.varianceBasedBehaviorForSubtype,
variance: Variance.covariant,
)!;
if (env.isSubtypeOf(correspondingDartType, dartType)) {
// If subtype, manually check the return type is not void.
if (correspondingDartType is FunctionType) {
if (dartType is FunctionType) {
if ((dartType.returnType is VoidType) ==
(correspondingDartType.returnType is VoidType)) {
return correspondingDartType;
}
// One of the return types is void, the other isn't, report error.
} else {
// One is a function type, the other isn't, report error.
}
} else {
return correspondingDartType;
}
}
case FfiTypeCheckDirection.dartToNative:
correspondingDartType =
convertNativeTypeToDartType(
nativeType,
allowStructAndUnion: true,
allowHandle: allowHandle,
allowInlineArray: allowArray,
allowVoid: allowVoid,
mode:
HandleToDartTypeConversionMode
.varianceBasedBehaviorForSupertype,
variance: Variance.covariant,
)!;
if (env.isSubtypeOf(dartType, correspondingDartType)) {
return correspondingDartType;
}
}
diagnosticReporter.report(
templateFfiTypeMismatch.withArguments(
dartType,
correspondingDartType,
nativeType,
),
reportErrorOn.fileOffset,
1,
reportErrorOn.location?.file,
);
throw FfiStaticTypeError();
}
void ensureNativeTypeValid(
DartType nativeType,
TreeNode reportErrorOn, {
bool allowHandle = false,
bool allowStructAndUnion = false,
bool allowInlineArray = false,
bool allowVoid = false,
}) {
if (!isNativeTypeValid(
nativeType,
allowStructAndUnion: allowStructAndUnion,
allowHandle: allowHandle,
allowInlineArray: allowInlineArray,
allowVoid: allowVoid,
)) {
diagnosticReporter.report(
templateFfiTypeInvalid.withArguments(nativeType),
reportErrorOn.fileOffset,
1,
reportErrorOn.location?.file,
);
throw FfiStaticTypeError();
}
}
/// The Dart type system does not enforce that NativeFunction return and
/// parameter types are only NativeTypes, so we need to check this.
bool isNativeTypeValid(
DartType nativeType, {
bool allowStructAndUnion = false,
bool allowHandle = false,
bool allowInlineArray = false,
bool allowVoid = false,
}) {
return convertNativeTypeToDartType(
nativeType,
allowStructAndUnion: allowStructAndUnion,
allowHandle: allowHandle,
allowInlineArray: allowInlineArray,
allowVoid: allowVoid,
) !=
null;
}
void addPragmaPreferInline(Member node) {
assert(node is Procedure || node is Constructor);
node.addAnnotation(
ConstantExpression(
InstanceConstant(pragmaClass.reference, [], {
pragmaName.fieldReference: StringConstant("vm:prefer-inline"),
pragmaOptions.fieldReference: NullConstant(),
}),
),
);
}
}
/// Returns all libraries including the ones from component except for platform
/// libraries that are only in component.
Set<Library> _getAllRelevantLibraries(
Component component,
List<Library> libraries,
) {
Set<Library> allLibs = {};
allLibs.addAll(libraries);
for (Library lib in component.libraries) {
// Skip real dart: libraries. dart:core imports dart:ffi, but that doesn't
// mean we have to transform anything.
if (lib.importUri.isScheme("dart") && !lib.isSynthetic) continue;
allLibs.add(lib);
}
return allLibs;
}
/// Checks if any library depends on dart:ffi.
Library? importsFfi(Component component, List<Library> libraries) {
final Uri dartFfiUri = Uri.parse("dart:ffi");
Set<Library> allLibs = _getAllRelevantLibraries(component, libraries);
for (Library lib in allLibs) {
for (LibraryDependency dependency in lib.dependencies) {
Library targetLibrary = dependency.targetLibrary;
if (targetLibrary.importUri == dartFfiUri) {
return targetLibrary;
}
}
}
return null;
}
/// Calculates the libraries in [libraries] that transitively imports dart:ffi.
///
/// Returns null if dart:ffi is not imported.
List<Library>? calculateTransitiveImportsOfDartFfiIfUsed(
Component component,
List<Library> libraries,
) {
Set<Library> allLibs = _getAllRelevantLibraries(component, libraries);
final Uri dartFfiUri = Uri.parse("dart:ffi");
Library? dartFfi;
canFind:
for (Library lib in allLibs) {
for (LibraryDependency dependency in lib.dependencies) {
Library targetLibrary = dependency.targetLibrary;
if (targetLibrary.importUri == dartFfiUri) {
dartFfi = targetLibrary;
break canFind;
}
}
}
if (dartFfi == null) return null;
kernelGraph.LibraryGraph graph = new kernelGraph.LibraryGraph(allLibs);
Set<Library> result = kernelGraph.calculateTransitiveDependenciesOf(graph, {
dartFfi,
});
return (result..retainAll(libraries)).toList();
}
extension on Map<Abi, Object?> {
bool get isPartial =>
[for (final abi in Abi.values) this[abi]].contains(null);
}
/// Used internally for abnormal control flow to prevent cascading error
/// messages.
class FfiStaticTypeError implements Exception {}