blob: 2b512b2873610366c89422f0453bbcec3c261509 [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;
import 'package:front_end/src/fasta/codes/fasta_codes.dart'
show
messageFfiLeafCallMustNotReturnHandle,
messageFfiLeafCallMustNotTakeHandle,
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, SubtypeCheckMode;
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,
}
/// [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 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 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 structPointerGetElemAt;
final Procedure structPointerSetElemAt;
final Procedure structPointerElementAt;
final Procedure structPointerPlusOperator;
final Procedure structPointerMinusOperator;
final Procedure structPointerElementAtTearoff;
final Procedure unionPointerGetRef;
final Procedure unionPointerSetRef;
final Procedure unionPointerGetElemAt;
final Procedure unionPointerSetElemAt;
final Procedure unionPointerElementAt;
final Procedure unionPointerPlusOperator;
final Procedure unionPointerMinusOperator;
final Procedure unionPointerElementAtTearoff;
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 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 nativeIsolateLocalCallbackFunctionProcedure;
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 Constructor nativeCallablePrivateIsolateLocalConstructor;
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 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'),
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'),
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'),
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'),
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'),
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'),
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'),
nativeCallbackFunctionProcedure =
index.getTopLevelProcedure('dart:ffi', '_nativeCallbackFunction'),
nativeAsyncCallbackFunctionProcedure = index.getTopLevelProcedure(
'dart:ffi', '_nativeAsyncCallbackFunction'),
nativeIsolateLocalCallbackFunctionProcedure =
index.getTopLevelProcedure(
'dart:ffi', '_nativeIsolateLocalCallbackFunction'),
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'),
nativeCallablePrivateIsolateLocalConstructor =
index.getConstructor('dart:ffi', '_NativeCallableIsolateLocal', ''),
nativeCallableListenerConstructor =
index.getProcedure('dart:ffi', 'NativeCallable', 'listener'),
nativeCallablePrivateListenerConstructor =
index.getConstructor('dart:ffi', '_NativeCallableListener', ''),
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;
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]
/// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
/// where DartRepresentationOf(Tn) -> Sn
DartType? convertNativeTypeToDartType(
DartType nativeType, {
bool allowStructAndUnion = false,
bool allowHandle = false,
bool allowInlineArray = false,
bool allowVoid = false,
}) {
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 InterfaceType(intClass, Nullability.legacy);
}
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 InterfaceType(intClass, Nullability.legacy);
}
if (nativeType_ == NativeType.kFloat || nativeType_ == NativeType.kDouble) {
return InterfaceType(doubleClass, Nullability.legacy);
}
if (nativeType_ == NativeType.kBool) {
return InterfaceType(boolClass, Nullability.legacy);
}
if (nativeType_ == NativeType.kVoid) {
if (!allowVoid) {
return null;
}
return VoidType();
}
if (nativeType_ == NativeType.kHandle && allowHandle) {
return InterfaceType(objectClass, Nullability.legacy);
}
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,
);
if (returnType == null) return null;
final argumentTypes = <DartType>[];
for (final paramDartType in flattenVarargs(fun).positionalParameters) {
argumentTypes.add(
convertNativeTypeToDartType(
paramDartType,
allowStructAndUnion: true,
allowHandle: true,
) ??
dummyDartType,
);
}
if (argumentTypes.contains(dummyDartType)) return null;
return FunctionType(argumentTypes, returnType, Nullability.legacy);
}
/// 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) {
if (isPointerType(dartType) ||
isStructOrUnionSubtype(dartType) ||
isArrayType(dartType)) {
return dartType;
} else {
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() => InterfaceType(
listClass, Nullability.legacy, [coreTypes.intLegacyRawType]);
ConstantExpression intListConstantExpression(List<int?> values) =>
ConstantExpression(
ListConstant(coreTypes.intLegacyRawType, [
for (var v in values)
if (v != null) IntConstant(v) else NullConstant()
]),
_listOfIntType());
/// Expression that queries VM internals at runtime to figure out on which ABI
/// we are.
Expression runtimeBranchOnLayout(Map<Abi, int?> values) {
final result = InstanceInvocation(
InstanceAccessKind.Instance,
intListConstantExpression([
for (final abi in Abi.values) values[abi],
]),
listElementAt.name,
Arguments([StaticInvocation(abiMethod, Arguments([]))]),
interfaceTarget: listElementAt,
functionType: Substitution.fromInterfaceType(_listOfIntType())
.substituteType(listElementAt.getterType) as FunctionType);
if (values.isPartial) {
return checkAbiSpecificIntegerMapping(result);
}
return result;
}
Expression checkAbiSpecificIntegerMapping(Expression nullableExpression) =>
StaticInvocation(
checkAbiSpecificIntegerMappingFunction,
Arguments(
[nullableExpression],
types: [InterfaceType(intClass, Nullability.nonNullable)],
),
);
bool isPrimitiveType(DartType type) {
if (type is InvalidType) {
return false;
}
if (type is NullType) {
return false;
}
if (!env.isSubtypeOf(
type, nativeTypeType, SubtypeCheckMode.ignoringNullabilities)) {
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, SubtypeCheckMode.ignoringNullabilities);
}
bool isArrayType(DartType type) {
if (type is InvalidType) {
return false;
}
if (type is NullType) {
return false;
}
return env.isSubtypeOf(
type,
InterfaceType(arrayClass, Nullability.legacy, [nativeTypeType]),
SubtypeCheckMode.ignoringNullabilities);
}
/// 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) {
final sizeAnnotations = getArraySizeAnnotations(node);
List<int> dimensions;
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;
if (arrayDimensions(type) != dimensions.length) {
diagnosticReporter.report(
templateFfiSizeAnnotationDimensions.withArguments(node.name.text),
node.fileOffset,
node.name.text.length,
node.fileUri);
}
for (var dimension in dimensions) {
if (dimension <= 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>> 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> _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;
}
/// 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.legacy),
SubtypeCheckMode.ignoringNullabilities);
}
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.legacy),
SubtypeCheckMode.ignoringNullabilities);
}
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 = convertNativeTypeToDartType(
nativeType,
allowStructAndUnion: true,
allowHandle: allowHandle,
allowInlineArray: allowArray,
allowVoid: allowVoid,
)!;
if (dartType == correspondingDartType) return correspondingDartType;
switch (direction) {
case FfiTypeCheckDirection.nativeToDart:
if (env.isSubtypeOf(correspondingDartType, dartType,
SubtypeCheckMode.ignoringNullabilities)) {
// 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:
if (env.isSubtypeOf(dartType, correspondingDartType,
SubtypeCheckMode.ignoringNullabilities)) {
return correspondingDartType;
}
}
diagnosticReporter.report(
templateFfiTypeMismatch.withArguments(dartType, correspondingDartType,
nativeType, currentLibrary.isNonNullableByDefault),
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 (!_nativeTypeValid(nativeType,
allowStructAndUnion: allowStructAndUnion,
allowHandle: allowHandle,
allowInlineArray: allowInlineArray,
allowVoid: allowVoid)) {
diagnosticReporter.report(
templateFfiTypeInvalid.withArguments(
nativeType, currentLibrary.isNonNullableByDefault),
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 _nativeTypeValid(
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(Procedure node) {
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 {}