blob: ae5fb348e30a8699c01327658004e72cbc652028 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:front_end/src/fasta/codes/fasta_codes.dart'
show
messageFfiAddressOfMustBeNative,
messageFfiAddressPosition,
messageFfiAddressReceiver,
messageFfiCreateOfStructOrUnion,
messageFfiExceptionalReturnNull,
messageFfiExpectedConstant,
templateFfiDartTypeMismatch,
templateFfiNativeCallableListenerReturnVoid,
templateFfiExpectedConstantArg,
templateFfiExpectedExceptionalReturn,
templateFfiExpectedNoExceptionalReturn,
templateFfiExtendsOrImplementsSealedClass,
templateFfiNotStatic;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/clone.dart';
import 'package:kernel/constructor_tearoff_lowering.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/library_index.dart' show LibraryIndex;
import 'package:kernel/names.dart';
import 'package:kernel/reference_from_index.dart';
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
import 'package:kernel/type_algebra.dart'
show FunctionTypeInstantiator, Substitution;
import 'package:kernel/type_environment.dart';
import 'common.dart'
show FfiStaticTypeError, FfiTransformer, NativeType, FfiTypeCheckDirection;
import 'definitions.dart' as definitions;
import 'finalizable.dart';
import 'native.dart' as native;
import 'native_type_cfe.dart';
/// Checks and replaces calls to dart:ffi compound fields and methods.
///
/// To reliably lower calls to methods like `sizeOf` and `Native.addressOf`,
/// this requires that the [definitions] and the [native] transformer already
/// ran on the libraries to transform.
void transformLibraries(
Component component,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
List<Library> libraries,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex) {
final index = LibraryIndex(component, [
"dart:ffi",
"dart:_internal",
"dart:typed_data",
"dart:nativewrappers",
"dart:isolate"
]);
if (!index.containsLibrary("dart:ffi")) {
// TODO: This check doesn't make sense: "dart:ffi" is always loaded/created
// for the VM target.
// If dart:ffi is not loaded, do not do the transformation.
return;
}
if (index.tryGetClass('dart:ffi', 'NativeFunction') == null) {
// If dart:ffi is not loaded (for real): do not do the transformation.
return;
}
final transformer = new _FfiUseSiteTransformer2(
index, coreTypes, hierarchy, diagnosticReporter, referenceFromIndex);
libraries.forEach(transformer.visitLibrary);
}
/// Combines [_FfiUseSiteTransformer] and [FinalizableTransformer] into a single
/// traversal.
///
/// This transformation is not AST-node preserving. [Expression]s and
/// [Statement]s can be replaced by other [Expression]s and [Statement]s
/// respectively. This means one cannot do `visitX() { super.visitX() as X }`.
class _FfiUseSiteTransformer2 extends FfiTransformer
with _FfiUseSiteTransformer, FinalizableTransformer {
_FfiUseSiteTransformer2(
LibraryIndex index,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex)
: super(index, coreTypes, hierarchy, diagnosticReporter,
referenceFromIndex);
}
/// Checks and replaces calls to dart:ffi compound fields and methods.
///
/// Designed to be mixed in. Calls super.visitXXX() to visit all nodes (except
/// the ones created by this transformation).
///
/// This transformation is not AST-node preserving. [Expression]s and
/// [Statement]s can be replaced by other [Expression]s and [Statement]s
/// respectively. This means one cannot do `visitX() { super.visitX() as X }`.
mixin _FfiUseSiteTransformer on FfiTransformer {
StaticTypeContext? get staticTypeContext;
bool _inFfiTearoff = false;
bool get isFfiLibrary => currentLibrary == ffiLibrary;
// Used to create private top-level fields with unique names for each
// callback.
int callbackCount = 0;
// Used to create private top-level trampoline methods with unique names
// for each call.
int callCount = 0;
@override
TreeNode visitLibrary(Library node) {
callbackCount = 0;
callCount = 0;
return super.visitLibrary(node);
}
@override
visitClass(Class node) {
try {
_ensureNotExtendsOrImplementsSealedClass(node);
return super.visitClass(node);
} on FfiStaticTypeError {
// It's OK to swallow the exception because the diagnostics issued will
// cause compilation to fail. By continuing, we can report more
// diagnostics before compilation ends.
return super.visitClass(node);
}
}
@override
visitConstructorInvocation(ConstructorInvocation node) {
if (_inFfiTearoff) {
return node;
}
final target = node.target;
if (hierarchy.isSubclassOf(target.enclosingClass, compoundClass) &&
target.enclosingClass != arrayClass &&
target.enclosingClass != compoundClass &&
target.name != Name("#fromTypedDataBase") &&
target.name != Name("#fromTypedData")) {
diagnosticReporter.report(messageFfiCreateOfStructOrUnion,
node.fileOffset, 1, node.location?.file);
}
return super.visitConstructorInvocation(node);
}
/// Transforms calls to Struct.create and Union.create.
///
/// Transforms `create<T>()` into
///
/// ```
/// Compound._fromTypedDataBase(Uint8List(sizeOf<T>()))
/// ```
///
/// and `create<T>(typedList)` into
///
/// ```
/// Compound._fromTypedData(typedList, sizeOf<T>())
/// ```
///
/// in subclasses of `Struct` and `Union`.
Expression _transformCompoundCreate(StaticInvocation node) {
final positionalArguments = node.arguments.positional;
final nativeType = (node.arguments.types.first as InterfaceType);
final constructors = nativeType.classNode.constructors;
final sizeOfExpression = inlineSizeOf(nativeType)!;
if (positionalArguments.isNotEmpty) {
// Check length of provided typed data, use checked constructor.
return ConstructorInvocation(
constructors.firstWhere((c) => c.name == Name("#fromTypedData")),
Arguments([
node.arguments.positional.first,
(positionalArguments.length >= 2
? positionalArguments[1]
: ConstantExpression(IntConstant(0))),
// Length in bytes to check the typedData against.
sizeOfExpression,
]),
);
}
// Correct-size typed data is allocated, use unchecked constructor.
return ConstructorInvocation(
constructors.firstWhere((c) => c.name == Name("#fromTypedDataBase")),
Arguments([
StaticInvocation(
uint8ListFactory,
Arguments([sizeOfExpression]),
),
ConstantExpression(IntConstant(0)),
]),
);
}
@override
visitProcedure(Procedure node) {
assert(_inFfiTearoff == false);
_inFfiTearoff = ((isFfiLibrary &&
node.isExtensionMember &&
(node == allocationTearoff ||
node == asFunctionTearoff ||
node == lookupFunctionTearoff ||
node == abiSpecificIntegerPointerElementAtTearoff ||
node == structPointerElementAtTearoff ||
node == unionPointerElementAtTearoff))) ||
// Dart2wasm uses enabledConstructorTearOffLowerings but these are not
// users trying to call constructors.
isConstructorTearOffLowering(node);
final result = super.visitProcedure(node);
_inFfiTearoff = false;
return result;
}
@override
TreeNode visitStaticInvocation(StaticInvocation node) {
final modifiedExpression = _visitStaticInvocation(node);
if (node == modifiedExpression) {
return super.visitStaticInvocation(node);
}
// We've just created this node. We're likely not going to need to transform
// this node itself. Visit its sub expressions.
return super.defaultExpression(modifiedExpression);
}
/// Replaces nodes if they match. Does not invoke any super visit.
Expression _visitStaticInvocation(StaticInvocation node) {
if (_inFfiTearoff) {
return node;
}
final Member target = node.target;
try {
if (target == abiSpecificIntegerArrayElemAt ||
target == abiSpecificIntegerArraySetElemAt) {
final pointer = node.arguments.positional[0];
final pointerType =
pointer.getStaticType(staticTypeContext!) as InterfaceType;
ensureNativeTypeValid(pointerType, pointer,
allowStructAndUnion: true, allowInlineArray: true);
final typeArg = pointerType.typeArguments.single;
final nativeTypeCfe =
NativeTypeCfe(this, typeArg) as AbiSpecificNativeTypeCfe;
final arrayVar = VariableDeclaration("#array",
initializer: NullCheck(node.arguments.positional[0]),
type: InterfaceType(arrayClass, Nullability.nonNullable),
isSynthesized: true)
..fileOffset = node.fileOffset;
final indexVar = VariableDeclaration("#index",
initializer: NullCheck(node.arguments.positional[1]),
type: coreTypes.intNonNullableRawType,
isSynthesized: true)
..fileOffset = node.fileOffset;
return BlockExpression(
Block([
arrayVar,
indexVar,
ExpressionStatement(InstanceInvocation(
InstanceAccessKind.Instance,
VariableGet(arrayVar),
arrayCheckIndex.name,
Arguments([VariableGet(indexVar)]),
interfaceTarget: arrayCheckIndex,
functionType: arrayCheckIndex.getterType as FunctionType,
)),
]),
abiSpecificLoadOrStoreExpression(
nativeTypeCfe,
typedDataBase: getCompoundTypedDataBaseField(
VariableGet(arrayVar),
node.fileOffset,
),
offsetInBytes: getCompoundOffsetInBytesField(
VariableGet(arrayVar),
node.fileOffset,
),
index: VariableGet(indexVar),
value: target == abiSpecificIntegerArraySetElemAt
? node.arguments.positional.last
: null,
fileOffset: node.fileOffset,
),
);
}
if (target == abiSpecificIntegerPointerGetValue ||
target == abiSpecificIntegerPointerSetValue ||
target == abiSpecificIntegerPointerElemAt ||
target == abiSpecificIntegerPointerSetElemAt) {
final pointer = node.arguments.positional[0];
final pointerType =
pointer.getStaticType(staticTypeContext!) as InterfaceType;
ensureNativeTypeValid(pointerType, pointer,
allowStructAndUnion: true, allowInlineArray: true);
final typeArg = pointerType.typeArguments.single;
final nativeTypeCfe =
NativeTypeCfe(this, typeArg) as AbiSpecificNativeTypeCfe;
return abiSpecificLoadOrStoreExpression(
nativeTypeCfe,
typedDataBase: node.arguments.positional[0],
index: (target == abiSpecificIntegerPointerElemAt ||
target == abiSpecificIntegerPointerSetElemAt)
? node.arguments.positional[1]
: null,
value: (target == abiSpecificIntegerPointerSetValue ||
target == abiSpecificIntegerPointerSetElemAt)
? node.arguments.positional.last
: null,
fileOffset: node.fileOffset,
);
}
if (target == structPointerGetRef ||
target == structPointerGetElemAt ||
target == unionPointerGetRef ||
target == unionPointerGetElemAt) {
final DartType nativeType = node.arguments.types[0];
ensureNativeTypeValid(nativeType, node, allowStructAndUnion: true);
return _replaceGetRef(node);
} else if (target == structPointerSetRef ||
target == structPointerSetElemAt ||
target == unionPointerSetRef ||
target == unionPointerSetElemAt) {
final DartType nativeType = node.arguments.types[0];
ensureNativeTypeValid(nativeType, node, allowStructAndUnion: true);
return _replaceSetRef(node);
} else if (target == abiSpecificIntegerPointerElementAt ||
target == structPointerElementAt ||
target == unionPointerElementAt ||
target == abiSpecificIntegerPointerPlusOperator ||
target == structPointerPlusOperator ||
target == unionPointerPlusOperator ||
target == abiSpecificIntegerPointerMinusOperator ||
target == structPointerMinusOperator ||
target == unionPointerMinusOperator) {
final pointer = node.arguments.positional[0];
final positiveOffset = node.arguments.positional[1];
final Expression offset;
if (target == abiSpecificIntegerPointerMinusOperator ||
target == structPointerMinusOperator ||
target == unionPointerMinusOperator) {
offset = InstanceInvocation(InstanceAccessKind.Instance,
positiveOffset, unaryMinusName, new Arguments([]),
interfaceTarget: coreTypes.intUnaryMinus,
functionType: coreTypes.intUnaryMinus.getterType as FunctionType);
} else {
offset = positiveOffset;
}
final pointerType =
pointer.getStaticType(staticTypeContext!) as InterfaceType;
ensureNativeTypeValid(pointerType, pointer,
allowStructAndUnion: true, allowInlineArray: true);
final DartType nativeType = node.arguments.types[0];
ensureNativeTypeValid(nativeType, node, allowStructAndUnion: true);
Expression? inlineSizeOf =
this.inlineSizeOf(nativeType as InterfaceType);
if (inlineSizeOf != null) {
// Generates `receiver.offsetBy(inlineSizeOfExpression)`.
return InstanceInvocation(InstanceAccessKind.Instance, pointer,
offsetByMethod.name, Arguments([multiply(offset, inlineSizeOf)]),
interfaceTarget: offsetByMethod,
functionType: Substitution.fromInterfaceType(pointerType)
.substituteType(offsetByMethod.getterType) as FunctionType);
}
} else if (target == structArrayElemAt || target == unionArrayElemAt) {
final DartType nativeType = node.arguments.types[0];
ensureNativeTypeValid(nativeType, node, allowStructAndUnion: true);
return _replaceRefArray(node);
} else if (target == arrayArrayElemAt) {
final DartType nativeType = node.arguments.types[0];
ensureNativeTypeValid(nativeType, node,
allowInlineArray: true, allowStructAndUnion: true);
return _replaceArrayArrayElemAt(node);
} else if (target == arrayArrayAssignAt) {
final DartType nativeType = node.arguments.types[0];
ensureNativeTypeValid(nativeType, node,
allowInlineArray: true, allowStructAndUnion: true);
return _replaceArrayArrayElemAt(node, setter: true);
} else if (target == sizeOfMethod) {
final DartType nativeType = node.arguments.types[0];
ensureNativeTypeValid(nativeType, node,
allowStructAndUnion: true, allowVoid: true);
if (nativeType is InterfaceType) {
Expression? inlineSizeOf = this.inlineSizeOf(nativeType);
if (inlineSizeOf != null) {
return inlineSizeOf;
}
}
} else if (target == lookupFunctionMethod) {
final nativeType = InterfaceType(nativeFunctionClass,
currentLibrary.nonNullable, [node.arguments.types[0]]);
final DartType dartType = node.arguments.types[1];
_ensureIsLeafIsConst(node);
final isLeaf = getIsLeafBoolean(node) ?? false;
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeMatch(
FfiTypeCheckDirection.nativeToDart,
nativeType,
dartType,
node,
allowHandle: true, // Handle-specific errors emitted below.
);
ensureLeafCallDoesNotUseHandles(
nativeType,
isLeaf,
reportErrorOn: node,
);
return _replaceLookupFunction(node);
} else if (target == asFunctionMethod) {
final dartType = node.arguments.types[1];
final InterfaceType nativeType = InterfaceType(nativeFunctionClass,
Nullability.nonNullable, [node.arguments.types[0]]);
_ensureIsLeafIsConst(node);
final isLeaf = getIsLeafBoolean(node) ?? false;
ensureNativeTypeValid(nativeType, node);
ensureNativeTypeMatch(
FfiTypeCheckDirection.nativeToDart,
nativeType,
dartType,
node,
allowHandle: true, // Handle-specific errors emitted below.
);
ensureLeafCallDoesNotUseHandles(
nativeType,
isLeaf,
reportErrorOn: node,
);
final DartType nativeSignature = nativeType.typeArguments[0];
return _replaceAsFunction(
functionPointer: node.arguments.positional[0],
pointerType: InterfaceType(
pointerClass, Nullability.nonNullable, [nativeType]),
nativeSignature: nativeSignature,
dartSignature: dartType as FunctionType,
isLeaf: isLeaf,
fileOffset: node.fileOffset,
);
} else if (target == fromFunctionMethod) {
return _verifyAndReplaceNativeCallableIsolateLocal(node,
fromFunction: true);
} else if (target == nativeCallableIsolateLocalConstructor) {
return _verifyAndReplaceNativeCallableIsolateLocal(node);
} else if (target == nativeCallableListenerConstructor) {
final DartType nativeType = InterfaceType(nativeFunctionClass,
currentLibrary.nonNullable, [node.arguments.types[0]]);
final Expression func = node.arguments.positional[0];
final DartType dartType = func.getStaticType(staticTypeContext!);
ensureNativeTypeValid(nativeType, node);
final ffiFuncType = ensureNativeTypeMatch(
FfiTypeCheckDirection.dartToNative, nativeType, dartType, node)
as FunctionType;
final funcType = dartType as FunctionType;
// Check return type.
if (ffiFuncType.returnType != VoidType()) {
diagnosticReporter.report(
templateFfiNativeCallableListenerReturnVoid.withArguments(
ffiFuncType.returnType,
currentLibrary.isNonNullableByDefault),
func.fileOffset,
1,
func.location?.file);
return node;
}
final replacement = _replaceNativeCallableListenerConstructor(node);
final compoundClasses = funcType.positionalParameters
.whereType<InterfaceType>()
.map((t) => t.classNode)
.where((c) =>
c.superclass == structClass || c.superclass == unionClass)
.toList();
return invokeCompoundConstructors(replacement, compoundClasses);
} else if (target == allocateMethod) {
final DartType nativeType = node.arguments.types[0];
ensureNativeTypeValid(nativeType, node,
allowStructAndUnion: true, allowVoid: true);
// Inline the body to get rid of a generic invocation of sizeOf.
// TODO(http://dartbug.com/39964): Add `alignmentOf<T>()` call.
Expression? sizeInBytes = inlineSizeOf(nativeType as InterfaceType);
if (sizeInBytes != null) {
if (node.arguments.positional.length == 2) {
sizeInBytes = multiply(node.arguments.positional[1], sizeInBytes);
}
final FunctionType allocateFunctionType =
allocatorAllocateMethod.getterType as FunctionType;
return InstanceInvocation(
InstanceAccessKind.Instance,
node.arguments.positional[0],
allocatorAllocateMethod.name,
Arguments([sizeInBytes], types: node.arguments.types),
interfaceTarget: allocatorAllocateMethod,
functionType: FunctionTypeInstantiator.instantiate(
allocateFunctionType, node.arguments.types));
}
} else if (target == structCreate || target == unionCreate) {
final nativeType = node.arguments.types.first;
ensureNativeTypeValid(nativeType, node, allowStructAndUnion: true);
return _transformCompoundCreate(node);
} else if (target == nativeAddressOf) {
return _replaceNativeAddressOf(node);
} else if (addressOfMethods.contains(target)) {
// The AST is visited recursively down. Handling of native invocations
// will inspect arguments for `<expr>.address` invocations. Any
// remaining invocations occur are places where `<expr>.address` is
// disallowed, so issue an error.
diagnosticReporter.report(
messageFfiAddressPosition, node.fileOffset, 1, node.location?.file);
} else {
final nativeAnnotation = memberGetNativeAnnotation(target);
if (nativeAnnotation != null && _isLeaf(nativeAnnotation)) {
return _replaceNativeCall(node, nativeAnnotation);
}
}
} on FfiStaticTypeError {
// It's OK to swallow the exception because the diagnostics issued will
// cause compilation to fail. By continuing, we can report more
// diagnostics before compilation ends.
}
return node;
}
bool _isLeaf(InstanceConstant native) {
return (native.fieldValues[nativeIsLeafField.fieldReference]
as BoolConstant)
.value;
}
/// Create Dart function which calls native code.
///
/// Adds a native effect invoking a compound constructors if this is used
/// as return type.
Expression _replaceAsFunction({
required Expression functionPointer,
required DartType pointerType,
required DartType nativeSignature,
required FunctionType dartSignature,
required bool isLeaf,
required int fileOffset,
}) {
assert(dartSignature.namedParameters.isEmpty);
final functionPointerVarName = '#ffiTarget$callCount';
final closureName = '#ffiClosure$callCount';
++callCount;
final pointerVar = VariableDeclaration(functionPointerVarName,
initializer: functionPointer, type: pointerType, isSynthesized: true);
final positionalParameters = [
for (int i = 0; i < dartSignature.positionalParameters.length; ++i)
VariableDeclaration(
'arg${i + 1}',
type: dartSignature.positionalParameters[i],
)
];
final closure = FunctionDeclaration(
VariableDeclaration(closureName,
type: dartSignature, isSynthesized: true)
..addAnnotation(ConstantExpression(
InstanceConstant(coreTypes.pragmaClass.reference, [], {
coreTypes.pragmaName.fieldReference:
StringConstant('vm:ffi:call-closure'),
coreTypes.pragmaOptions.fieldReference: InstanceConstant(
ffiCallClass.reference,
[nativeSignature],
{
ffiCallIsLeafField.fieldReference: BoolConstant(isLeaf),
},
),
}))),
FunctionNode(
Block([
for (final param in positionalParameters)
ExpressionStatement(StaticInvocation(
nativeEffectMethod, Arguments([VariableGet(param)]))),
ReturnStatement(StaticInvocation(
ffiCallMethod,
Arguments([
VariableGet(pointerVar),
], types: [
dartSignature.returnType,
]))
..fileOffset = fileOffset),
]),
positionalParameters: positionalParameters,
requiredParameterCount: dartSignature.requiredParameterCount,
returnType: dartSignature.returnType)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
final result = BlockExpression(
Block([
pointerVar,
closure,
]),
VariableGet(closure.variable));
final possibleCompoundReturn = findCompoundReturnType(dartSignature);
if (possibleCompoundReturn != null) {
return invokeCompoundConstructor(result, possibleCompoundReturn);
}
return result;
}
Expression invokeCompoundConstructors(
Expression nestedExpression, List<Class> compoundClasses) =>
compoundClasses
.distinct()
.fold(nestedExpression, invokeCompoundConstructor);
// We need to replace calls to 'DynamicLibrary.lookupFunction' with explicit
// Kernel, because we cannot have a generic call to 'asFunction' in its body.
//
// Above, in 'visitStaticInvocation', we ensure that the type arguments to
// 'lookupFunction' are constants, so by inlining the call to 'asFunction' at
// the call-site, we ensure that there are no generic calls to 'asFunction'.
Expression _replaceLookupFunction(StaticInvocation node) {
final DartType nativeSignature = node.arguments.types[0];
final DartType dartSignature = node.arguments.types[1];
final List<DartType> lookupTypeArgs = [
InterfaceType(
nativeFunctionClass, currentLibrary.nonNullable, [nativeSignature])
];
final Arguments lookupArgs =
Arguments([node.arguments.positional[1]], types: lookupTypeArgs);
final FunctionType lookupFunctionType =
libraryLookupMethod.getterType as FunctionType;
final lookupResult = InstanceInvocation(InstanceAccessKind.Instance,
node.arguments.positional[0], libraryLookupMethod.name, lookupArgs,
interfaceTarget: libraryLookupMethod,
functionType: FunctionTypeInstantiator.instantiate(
lookupFunctionType, lookupTypeArgs));
final isLeaf = getIsLeafBoolean(node) ?? false;
return _replaceAsFunction(
functionPointer: lookupResult,
pointerType: lookupResult.functionType.returnType,
nativeSignature: nativeSignature,
dartSignature: dartSignature as FunctionType,
isLeaf: isLeaf,
fileOffset: node.fileOffset,
);
}
// We need to rewrite calls to 'fromFunction' into two calls, representing the
// compile-time and run-time aspects of creating the closure:
//
// final dynamic _#ffiCallback0 = Pointer.fromFunction<T>(f, e) =>
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeCallbackFunction<T>(f, e), null, false);
//
// ... _#ffiCallback0 ...
//
// We must implement this as a Kernel rewrite because <T> must be a
// compile-time constant to any invocation of '_nativeCallbackFunction'.
//
// Creating this closure requires a runtime call, so we save the result in a
// synthetic top-level field to avoid recomputing it.
Expression _replaceFromFunction(
StaticInvocation node, Expression exceptionalReturn) {
final nativeFunctionType = InterfaceType(
nativeFunctionClass, currentLibrary.nonNullable, node.arguments.types);
var name = Name("_#ffiCallback${callbackCount++}", currentLibrary);
var getterReference = currentLibraryIndex?.lookupGetterReference(name);
final Field field = Field.immutable(name,
type: InterfaceType(
pointerClass, currentLibrary.nonNullable, [nativeFunctionType]),
initializer: StaticInvocation(
createNativeCallableIsolateLocalProcedure,
Arguments([
StaticInvocation(
nativeCallbackFunctionProcedure,
Arguments([
node.arguments.positional[0],
exceptionalReturn,
], types: node.arguments.types)),
NullLiteral(),
BoolLiteral(false),
], types: [
nativeFunctionType
])),
isStatic: true,
isFinal: true,
fileUri: currentLibrary.fileUri,
getterReference: getterReference)
..fileOffset = node.fileOffset;
currentLibrary.addField(field);
return StaticGet(field);
}
// NativeCallable<T>.isolateLocal(target, exceptionalReturn) calls become:
// isStaticFunction is false:
// _NativeCallableIsolateLocal<T>(
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeIsolateLocalCallbackFunction<T>(exceptionalReturn),
// target,
// true));
// isStaticFunction is true:
// _NativeCallableIsolateLocal<T>(
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
// _nativeCallbackFunction<T>(target, exceptionalReturn),
// null,
// true);
Expression _replaceNativeCallableIsolateLocalConstructor(
StaticInvocation node,
Expression exceptionalReturn,
bool isStaticFunction) {
final nativeFunctionType = InterfaceType(
nativeFunctionClass, currentLibrary.nonNullable, node.arguments.types);
final target = node.arguments.positional[0];
late StaticInvocation pointerValue;
if (isStaticFunction) {
pointerValue = StaticInvocation(
createNativeCallableIsolateLocalProcedure,
Arguments([
StaticInvocation(
nativeCallbackFunctionProcedure,
Arguments([
target,
exceptionalReturn,
], types: node.arguments.types)),
NullLiteral(),
BoolLiteral(true),
], types: [
nativeFunctionType,
]));
} else {
pointerValue = StaticInvocation(
createNativeCallableIsolateLocalProcedure,
Arguments([
StaticInvocation(nativeIsolateLocalCallbackFunctionProcedure,
Arguments([exceptionalReturn], types: node.arguments.types)),
target,
BoolLiteral(true),
], types: [
nativeFunctionType,
]));
}
return ConstructorInvocation(nativeCallablePrivateIsolateLocalConstructor,
Arguments([pointerValue], types: node.arguments.types));
}
// NativeCallable<T>.listener(target) calls become:
// void _handler(List args) => target(args[0], args[1], ...)
// final _callback = _NativeCallableListener<T>(_handler, debugName);
// _callback._pointer = _createNativeCallableListener<NativeFunction<T>>(
// _nativeAsyncCallbackFunction<T>(), _callback._rawPort);
// expression result: _callback;
Expression _replaceNativeCallableListenerConstructor(StaticInvocation node) {
final nativeFunctionType = InterfaceType(
nativeFunctionClass, currentLibrary.nonNullable, node.arguments.types);
final listType = InterfaceType(listClass, currentLibrary.nonNullable);
final nativeCallableType = InterfaceType(
nativeCallableClass, currentLibrary.nonNullable, node.arguments.types);
final targetType = node.arguments.types[0] as FunctionType;
// void _handler(List args) => target(args[0], args[1], ...)
final args = VariableDeclaration('args', type: listType, isFinal: true)
..fileOffset = node.fileOffset;
final targetArgs = <Expression>[];
for (int i = 0; i < targetType.positionalParameters.length; ++i) {
targetArgs.add(InstanceInvocation(InstanceAccessKind.Instance,
VariableGet(args), listElementAt.name, Arguments([IntLiteral(i)]),
interfaceTarget: listElementAt,
functionType: Substitution.fromInterfaceType(listType)
.substituteType(listElementAt.getterType) as FunctionType));
}
final target = node.arguments.positional[0];
final handlerBody = ExpressionStatement(FunctionInvocation(
FunctionAccessKind.FunctionType,
target,
Arguments(targetArgs),
functionType: targetType,
));
final handler = FunctionNode(handlerBody,
positionalParameters: [args], returnType: VoidType())
..fileOffset = node.fileOffset;
// final _callback = NativeCallable<T>._listener(_handler, debugName);
final nativeCallable = VariableDeclaration.forValue(
ConstructorInvocation(
nativeCallablePrivateListenerConstructor,
Arguments([
FunctionExpression(handler),
StringLiteral('NativeCallable($target)'),
], types: [
targetType,
])),
type: nativeCallableType,
isFinal: true)
..fileOffset = node.fileOffset;
// _callback._pointer = _createNativeCallableListener<NativeFunction<T>>(
// _nativeAsyncCallbackFunction<T>(), _callback._rawPort);
final pointerValue = StaticInvocation(
createNativeCallableListenerProcedure,
Arguments([
StaticInvocation(nativeAsyncCallbackFunctionProcedure,
Arguments([], types: [targetType])),
InstanceGet(InstanceAccessKind.Instance, VariableGet(nativeCallable),
nativeCallablePortField.name,
interfaceTarget: nativeCallablePortField,
resultType: nativeCallablePortField.getterType),
], types: [
nativeFunctionType,
]));
final pointerSetter = ExpressionStatement(InstanceSet(
InstanceAccessKind.Instance,
VariableGet(nativeCallable),
nativeCallablePointerField.name,
pointerValue,
interfaceTarget: nativeCallablePointerField,
));
// expression result: _callback;
return BlockExpression(
Block([
nativeCallable,
pointerSetter,
]),
VariableGet(nativeCallable));
}
Expression _verifyAndReplaceNativeCallableIsolateLocal(StaticInvocation node,
{bool fromFunction = false}) {
final DartType nativeType = InterfaceType(nativeFunctionClass,
currentLibrary.nonNullable, [node.arguments.types[0]]);
final Expression func = node.arguments.positional[0];
final DartType dartType = func.getStaticType(staticTypeContext!);
final isStaticFunction = _isStaticFunction(func);
if (fromFunction && !isStaticFunction) {
diagnosticReporter.report(
templateFfiNotStatic.withArguments(fromFunctionMethod.name.text),
func.fileOffset,
1,
func.location?.file);
return node;
}
ensureNativeTypeValid(nativeType, node);
final ffiFuncType = ensureNativeTypeMatch(
FfiTypeCheckDirection.dartToNative,
nativeType,
dartType,
node,
) as FunctionType;
final funcType = dartType as FunctionType;
// Check `exceptionalReturn`'s type.
final Class expectedReturnClass =
((node.arguments.types[0] as FunctionType).returnType as InterfaceType)
.classNode;
final NativeType? expectedReturn = getType(expectedReturnClass);
Expression exceptionalReturn = NullLiteral();
bool hasExceptionalReturn = false;
if (fromFunction) {
if (node.arguments.positional.length > 1) {
exceptionalReturn = node.arguments.positional[1];
hasExceptionalReturn = true;
}
} else {
if (node.arguments.named.isNotEmpty) {
exceptionalReturn = node.arguments.named[0].value;
hasExceptionalReturn = true;
}
}
if (expectedReturn == NativeType.kVoid ||
expectedReturn == NativeType.kPointer ||
expectedReturn == NativeType.kHandle ||
expectedReturnClass.superclass == structClass ||
expectedReturnClass.superclass == unionClass) {
if (hasExceptionalReturn) {
diagnosticReporter.report(
templateFfiExpectedNoExceptionalReturn.withArguments(
ffiFuncType.returnType, currentLibrary.isNonNullableByDefault),
node.fileOffset,
1,
node.location?.file);
return node;
}
} else {
// The exceptional return value is not optional for other return types.
if (!hasExceptionalReturn) {
diagnosticReporter.report(
templateFfiExpectedExceptionalReturn.withArguments(
ffiFuncType.returnType, currentLibrary.isNonNullableByDefault),
node.fileOffset,
1,
node.location?.file);
return node;
}
// The exceptional return value must be a constant so that it can be
// referenced by precompiled trampoline's object pool.
if (exceptionalReturn is! BasicLiteral &&
!(exceptionalReturn is ConstantExpression &&
exceptionalReturn.constant is PrimitiveConstant)) {
diagnosticReporter.report(messageFfiExpectedConstant, node.fileOffset,
1, node.location?.file);
return node;
}
// Moreover it may not be null.
if (exceptionalReturn is NullLiteral ||
(exceptionalReturn is ConstantExpression &&
exceptionalReturn.constant is NullConstant)) {
diagnosticReporter.report(messageFfiExceptionalReturnNull,
node.fileOffset, 1, node.location?.file);
return node;
}
final DartType returnType =
exceptionalReturn.getStaticType(staticTypeContext!);
if (!env.isSubtypeOf(returnType, funcType.returnType,
SubtypeCheckMode.ignoringNullabilities)) {
diagnosticReporter.report(
templateFfiDartTypeMismatch.withArguments(returnType,
funcType.returnType, currentLibrary.isNonNullableByDefault),
exceptionalReturn.fileOffset,
1,
exceptionalReturn.location?.file);
return node;
}
}
final replacement = fromFunction
? _replaceFromFunction(node, exceptionalReturn)
: _replaceNativeCallableIsolateLocalConstructor(
node, exceptionalReturn, isStaticFunction);
final compoundClasses = funcType.positionalParameters
.whereType<InterfaceType>()
.map((t) => t.classNode)
.where((c) => c.superclass == structClass || c.superclass == unionClass)
.toList();
return invokeCompoundConstructors(replacement, compoundClasses);
}
Expression _replaceGetRef(StaticInvocation node) {
final dartType = node.arguments.types[0];
final clazz = (dartType as InterfaceType).classNode;
final referencedStruct = ReferencedCompoundSubtypeCfe(clazz);
Expression pointer = NullCheck(node.arguments.positional[0]);
if (node.arguments.positional.length == 2) {
pointer = InstanceInvocation(
InstanceAccessKind.Instance,
pointer,
offsetByMethod.name,
Arguments([
multiply(node.arguments.positional[1], inlineSizeOf(dartType)!)
]),
interfaceTarget: offsetByMethod,
functionType:
Substitution.fromPairs(pointerClass.typeParameters, [dartType])
.substituteType(offsetByMethod.getterType) as FunctionType);
}
return referencedStruct.generateLoad(
dartType: dartType,
transformer: this,
typedDataBase: pointer,
offsetInBytes: ConstantExpression(IntConstant(0)),
fileOffset: node.fileOffset,
);
}
/// Replaces a `.ref=` or `[]=` on a compound pointer extension with a mem
/// copy call.
Expression _replaceSetRef(StaticInvocation node) {
final target = node.arguments.positional[0]; // Receiver of extension
final referencedStruct = ReferencedCompoundSubtypeCfe(
(node.arguments.types[0] as InterfaceType).classNode);
final Expression sourceStruct, targetOffset;
final DartType sourceStructType;
if (node.arguments.positional.length == 3) {
// []= call, args are (receiver, index, source)
sourceStruct = node.arguments.positional[2];
sourceStructType = node.arguments.types[0];
targetOffset = multiply(node.arguments.positional[1],
inlineSizeOf(node.arguments.types[0] as InterfaceType)!);
} else {
// .ref= call, args are (receiver, source)
sourceStruct = node.arguments.positional[1];
sourceStructType = node.arguments.types[0];
targetOffset = ConstantExpression(IntConstant(0));
}
final sourceVar = VariableDeclaration(
"#source",
initializer: sourceStruct,
type: sourceStructType,
isSynthesized: true,
)..fileOffset = node.fileOffset;
return BlockExpression(
Block([sourceVar]),
referencedStruct.generateStore(
sourceVar,
dartType: node.arguments.types[0],
offsetInBytes: targetOffset,
typedDataBase: target,
transformer: this,
fileOffset: node.fileOffset,
),
);
}
Expression _replaceRefArray(StaticInvocation node) {
final dartType = node.arguments.types[0];
final clazz = (dartType as InterfaceType).classNode;
final constructor = clazz.constructors
.firstWhere((c) => c.name == Name("#fromTypedDataBase"));
final arrayVar = VariableDeclaration("#array",
initializer: NullCheck(node.arguments.positional[0]),
type: InterfaceType(arrayClass, Nullability.nonNullable),
isSynthesized: true)
..fileOffset = node.fileOffset;
final indexVar = VariableDeclaration("#index",
initializer: NullCheck(node.arguments.positional[1]),
type: coreTypes.intNonNullableRawType,
isSynthesized: true)
..fileOffset = node.fileOffset;
return BlockExpression(
Block([
arrayVar,
indexVar,
ExpressionStatement(InstanceInvocation(
InstanceAccessKind.Instance,
VariableGet(arrayVar),
arrayCheckIndex.name,
Arguments([VariableGet(indexVar)]),
interfaceTarget: arrayCheckIndex,
functionType: arrayCheckIndex.getterType as FunctionType,
)),
]),
ConstructorInvocation(
constructor,
Arguments([
getCompoundTypedDataBaseField(
VariableGet(arrayVar),
node.fileOffset,
),
add(
getCompoundOffsetInBytesField(
VariableGet(arrayVar),
node.fileOffset,
),
multiply(VariableGet(indexVar), inlineSizeOf(dartType)!),
),
]),
),
);
}
/// Generates an expression that returns a new `Array<dartType>`.
///
/// Sample input getter:
/// ```
/// this<Array<T>>[index]
/// ```
///
/// Sample output getter:
///
/// ```
/// Array #array = this!;
/// int #index = index!;
/// #array._checkIndex(#index);
/// int #singleElementSize = inlineSizeOf<innermost(T)>();
/// int #elementSize = #array.nestedDimensionsFlattened * #singleElementSize;
/// int #offset = #elementSize * #index;
///
/// new Array<T>._(
/// #array._typedDataBase,
/// #offset,
/// #array.nestedDimensionsFirst,
/// #array.nestedDimensionsRest
/// )
/// ```
///
/// Sample input setter:
/// ```
/// this<Array<T>>[index] = value
/// ```
///
/// Sample output setter:
///
/// ```
/// Array #array = this!;
/// int #index = index!;
/// #array._checkIndex(#index);
/// int #singleElementSize = inlineSizeOf<innermost(T)>();
/// int #elementSize = #array.nestedDimensionsFlattened * #singleElementSize;
/// int #offset = #elementSize * #index;
///
/// _memCopy(
/// #array._typedDataBase, #offset, value._typedDataBase, 0, #elementSize)
/// ```
Expression _replaceArrayArrayElemAt(StaticInvocation node,
{bool setter = false}) {
final dartType = node.arguments.types[0];
final elementType = arraySingleElementType(dartType as InterfaceType);
final arrayVar = VariableDeclaration("#array",
initializer: NullCheck(node.arguments.positional[0]),
type: InterfaceType(arrayClass, Nullability.nonNullable),
isSynthesized: true)
..fileOffset = node.fileOffset;
final indexVar = VariableDeclaration("#index",
initializer: NullCheck(node.arguments.positional[1]),
type: coreTypes.intNonNullableRawType,
isSynthesized: true)
..fileOffset = node.fileOffset;
final singleElementSizeVar = VariableDeclaration("#singleElementSize",
initializer: inlineSizeOf(elementType as InterfaceType),
type: coreTypes.intNonNullableRawType,
isSynthesized: true)
..fileOffset = node.fileOffset;
final elementSizeVar = VariableDeclaration("#elementSize",
initializer: multiply(
VariableGet(singleElementSizeVar),
InstanceGet(InstanceAccessKind.Instance, VariableGet(arrayVar),
arrayNestedDimensionsFlattened.name,
interfaceTarget: arrayNestedDimensionsFlattened,
resultType: arrayNestedDimensionsFlattened.getterType)),
type: coreTypes.intNonNullableRawType,
isSynthesized: true)
..fileOffset = node.fileOffset;
final offsetVar = VariableDeclaration("#offset",
initializer:
multiply(VariableGet(elementSizeVar), VariableGet(indexVar)),
type: coreTypes.intNonNullableRawType,
isSynthesized: true)
..fileOffset = node.fileOffset;
final checkIndexAndLocalVars = [
arrayVar,
indexVar,
ExpressionStatement(InstanceInvocation(
InstanceAccessKind.Instance,
VariableGet(arrayVar),
arrayCheckIndex.name,
Arguments([VariableGet(indexVar)]),
interfaceTarget: arrayCheckIndex,
functionType: arrayCheckIndex.getterType as FunctionType,
)),
singleElementSizeVar,
elementSizeVar,
offsetVar,
];
if (!setter) {
// `[]`
return BlockExpression(
Block(checkIndexAndLocalVars),
ConstructorInvocation(
arrayConstructor,
Arguments([
getCompoundTypedDataBaseField(
VariableGet(arrayVar),
node.fileOffset,
),
add(
getCompoundOffsetInBytesField(
VariableGet(arrayVar),
node.fileOffset,
),
VariableGet(offsetVar),
),
InstanceGet(InstanceAccessKind.Instance, VariableGet(arrayVar),
arrayNestedDimensionsFirst.name,
interfaceTarget: arrayNestedDimensionsFirst,
resultType: arrayNestedDimensionsFirst.getterType),
InstanceGet(InstanceAccessKind.Instance, VariableGet(arrayVar),
arrayNestedDimensionsRest.name,
interfaceTarget: arrayNestedDimensionsRest,
resultType: arrayNestedDimensionsRest.getterType)
], types: [
dartType
])));
}
// `[]=`
final valueVar = VariableDeclaration(
"#value",
initializer: NullCheck(node.arguments.positional[2]),
type: InterfaceType(arrayClass, Nullability.nonNullable),
isSynthesized: true,
)..fileOffset = node.fileOffset;
return BlockExpression(
Block([
...checkIndexAndLocalVars,
valueVar,
]),
StaticInvocation(
memCopy,
Arguments([
getCompoundTypedDataBaseField(
VariableGet(arrayVar),
node.fileOffset,
),
add(
getCompoundOffsetInBytesField(
VariableGet(arrayVar),
node.fileOffset,
),
VariableGet(offsetVar),
),
getCompoundTypedDataBaseField(
VariableGet(valueVar),
node.fileOffset,
),
getCompoundOffsetInBytesField(
VariableGet(valueVar),
node.fileOffset,
),
VariableGet(elementSizeVar),
]))
..fileOffset = node.fileOffset);
}
bool _isStaticFunction(Expression node) =>
(node is StaticGet && node.target is Procedure) ||
(node is ConstantExpression && node.constant is StaticTearOffConstant);
/// Returns the class that should not be implemented or extended.
///
/// If the superclass is not sealed, returns `null`.
Class? _extendsOrImplementsSealedClass(Class klass) {
// Classes in dart:ffi themselves can extend FFI classes.
if (klass == arrayClass ||
klass == arraySizeClass ||
klass == compoundClass ||
klass == opaqueClass ||
klass == structClass ||
klass == unionClass ||
klass == abiSpecificIntegerClass ||
klass == varArgsClass ||
classNativeTypes[klass] != null) {
return null;
}
// The Opaque and Struct classes can be extended, but subclasses
// cannot be (nor implemented).
final onlyDirectExtendsClasses = [
opaqueClass,
structClass,
unionClass,
abiSpecificIntegerClass,
];
final superClass = klass.superclass;
for (final onlyDirectExtendsClass in onlyDirectExtendsClasses) {
if (hierarchy.isSubInterfaceOf(klass, onlyDirectExtendsClass)) {
if (superClass == onlyDirectExtendsClass) {
// Directly extending is fine.
return null;
} else {
return superClass;
}
}
}
return null;
}
void _ensureNotExtendsOrImplementsSealedClass(Class klass) {
final Class? extended = _extendsOrImplementsSealedClass(klass);
if (extended != null) {
diagnosticReporter.report(
templateFfiExtendsOrImplementsSealedClass
.withArguments(extended.name),
klass.fileOffset,
1,
klass.location?.file);
throw FfiStaticTypeError();
}
}
void _ensureIsLeafIsConst(StaticInvocation node) {
final isLeaf = getIsLeafBoolean(node);
if (isLeaf == null) {
diagnosticReporter.report(
templateFfiExpectedConstantArg.withArguments('isLeaf'),
node.fileOffset,
1,
node.location?.file);
// Throw so we don't get another error about not replacing
// `lookupFunction`, which will shadow the above error.
throw FfiStaticTypeError();
}
}
/// Replaces calls to `Native.addressOf(x)` by looking up the `@Native`
/// annotation of `x` and rewriting the expression to use
/// `_addressOf(annotation)`, which is implemented in the VM.
///
/// By implementing this in the VM, we can re-use the implementation used to
/// lookup addresses for `@Native` invocations. We could also replace the
/// call with an invocation to `Native._ffi_resolver_function` and wrap the
/// result in a `Pointer`. To support static linking of native functions in
/// the future, having the unresolved symbol in the assembly generated for
/// each call site in the VM will be necessary. So we're already doing this
/// in the VM today.
///
/// The transformation works by looking up the resolved `@Native` annotation
/// left by the native transformer in a pragma. Looking for a `@Native`
/// annotation on the argument wouldn't work because it's removed by the
/// native transformer and because it requires context from the library to
/// resolve the asset id.
StaticInvocation _replaceNativeAddressOf(StaticInvocation node) {
final arg = node.arguments.positional.single;
final nativeType = node.arguments.types.single;
final potentiallyNativeTarget = switch (arg) {
ConstantExpression(constant: StaticTearOffConstant method) =>
method.target,
StaticGet(:var targetReference) => targetReference.asMember,
_ => null,
};
Constant? nativeAnnotation =
memberGetNativeAnnotation(potentiallyNativeTarget);
if (nativeAnnotation == null) {
diagnosticReporter.report(messageFfiAddressOfMustBeNative, arg.fileOffset,
1, node.location?.file);
return node;
}
ensureNativeTypeValid(nativeType, node,
allowStructAndUnion: true, allowInlineArray: true);
ensureNativeTypeMatch(FfiTypeCheckDirection.nativeToDart, nativeType,
arg.getStaticType(staticTypeContext!), node,
allowArray: true);
return StaticInvocation(
nativePrivateAddressOf,
Arguments([ConstantExpression(nativeAnnotation)], types: [nativeType]),
)..fileOffset = arg.fileOffset;
}
InstanceConstant? memberGetNativeAnnotation(Member? member) {
if (member == null) {
return null;
}
for (final annotation in member.annotations) {
if (annotation
case ConstantExpression(constant: final InstanceConstant c)) {
if (c.classNode == coreTypes.pragmaClass) {
final name = c.fieldValues[coreTypes.pragmaName.fieldReference];
if (name is StringConstant &&
name.value == native.FfiNativeTransformer.nativeMarker) {
return c.fieldValues[coreTypes.pragmaOptions.fieldReference]
as InstanceConstant;
}
}
}
}
return null;
}
StaticInvocation _replaceNativeCall(
StaticInvocation node,
InstanceConstant targetNativeAnnotation,
) {
final target = node.target;
if (targetNativeAnnotation.typeArguments.length != 1) {
return node;
}
final annotationType = targetNativeAnnotation.typeArguments.single;
if (annotationType is! FunctionType) {
return node;
}
final parameterTypes = [
for (final varDecl in target.function.positionalParameters) varDecl.type,
];
final numParams = parameterTypes.length;
String methodPostfix = '';
final newArguments = <Expression>[];
final newParameters = <VariableDeclaration>[];
bool isTransformed = false;
for (int i = 0; i < numParams; i++) {
final parameter = target.function.positionalParameters[i];
final parameterType = parameterTypes[i];
final argument = node.arguments.positional[i];
final (
postFix,
newType,
newArgument,
) = _replaceNativeCallParameterAndArgument(
parameter,
parameterType,
argument,
node.fileOffset,
);
methodPostfix += postFix;
if (postFix == 'C' || postFix == 'E' || postFix == 'T') {
isTransformed = true;
}
newParameters.add(VariableDeclaration(
parameter.name,
type: newType,
));
newArguments.add(newArgument);
}
if (!isTransformed) {
return node;
}
final newName = '${target.name.text}#$methodPostfix';
final Procedure newTarget;
final parent = target.parent;
final members = switch (parent) {
Library _ => parent.members,
Class _ => parent.members,
_ => throw UnimplementedError('Unexpected parent: ${parent}'),
};
final existingNewTarget = members
.whereType<Procedure>()
.where((element) => element.name.text == newName)
.firstOrNull;
if (existingNewTarget != null) {
newTarget = existingNewTarget;
} else {
final cloner = CloneProcedureWithoutBody();
newTarget = cloner.cloneProcedure(target, null);
newTarget.name = Name(newName);
newTarget.function.positionalParameters = newParameters;
setParents(newParameters, newTarget.function);
switch (parent) {
case Library _:
parent.addProcedure(newTarget);
case Class _:
parent.addProcedure(newTarget);
}
}
return StaticInvocation(
newTarget,
Arguments(newArguments),
);
}
/// Converts a single parameter with argument for [_replaceNativeCall].
(
/// '' for non-Pointer.
/// 'P' for Pointer.
/// 'T' for TypedData.
/// 'C' for _Compound (TypedData/Pointer and offset in bytes).
/// 'E' for errors.
String methodPostFix,
DartType parameterType,
Expression argument,
) _replaceNativeCallParameterAndArgument(
VariableDeclaration parameter,
DartType parameterType,
Expression argument,
int fileOffset,
) {
if (parameterType is! InterfaceType ||
parameterType.classNode != pointerClass) {
// Parameter is non-pointer. Keep unchanged.
return ('', parameterType, argument);
}
if (argument is! StaticInvocation ||
!addressOfMethods.contains(argument.target)) {
// The argument has type Pointer, but it's not produced by any of the
// `.address` getters.
// Argument must be a Pointer object. Keep unchanged.
return ('P', parameterType, argument);
}
if (addressOfMethodsTypedData.contains(argument.target)) {
final subExpression = argument.arguments.positional.single;
// Argument is `typedData.address`.
final typedDataType = InterfaceType(
typedDataClass,
Nullability.nonNullable,
const <DartType>[],
);
return ('T', typedDataType, subExpression);
}
final subExpression = argument.arguments.positional.single;
if (addressOfMethodsCompound.contains(argument.target)) {
// Argument is `structOrUnionOrArray.address`.
return ('C', compoundType, subExpression);
}
assert(addressOfMethodsPrimitive.contains(argument.target));
// Argument is an `expr.address` where `expr` is typed `bool`, `int`, or
// `double`. Analyze `expr` further.
switch (subExpression) {
case InstanceGet _:
// Look for `structOrUnion.member.address`.
final interfaceTarget = subExpression.interfaceTarget;
final enclosingClass = interfaceTarget.enclosingClass!;
final targetSuperClass = enclosingClass.superclass;
if (targetSuperClass == unionClass) {
// `expr` is a union member access.
// Union members have no additional offset. Pass in unchanged.
return ('C', compoundType, subExpression.receiver);
}
if (targetSuperClass == structClass) {
final getterName = interfaceTarget.name.text;
final offsetOfName = '$getterName#offsetOf';
final offsetGetter = enclosingClass.procedures
.firstWhere((e) => e.name.text == offsetOfName);
// `expr` is a struct member access. Struct members have an offset.
// Pass in a newly constructed `_Compound`, with adjusted offset.
return (
'C',
compoundType,
_generateCompoundOffsetBy(
subExpression.receiver,
StaticGet(offsetGetter),
fileOffset,
variableName: "${parameter.name}#value",
),
);
}
// Error, unrecognized getter.
case StaticInvocation _:
// Look for `array[index].address`.
// Extensions have already been desugared, so no enclosing extension.
final target = subExpression.target;
if (!target.isExtensionMember) break;
final positionalParameters = target.function.positionalParameters;
if (positionalParameters.length != 2) break;
final firstParamType = positionalParameters.first.type;
if (firstParamType is! InterfaceType) break;
if (firstParamType.classNode != arrayClass) break;
// Extensions have already been desugared, so original name is lost.
if (!target.name.text.endsWith('|[]')) break;
final DartType arrayElementType;
if (subExpression.arguments.types.isNotEmpty) {
// AbiSpecificInteger.
arrayElementType = subExpression.arguments.types.single;
} else {
arrayElementType = firstParamType.typeArguments.single;
}
final arrayElementSize =
inlineSizeOf(arrayElementType as InterfaceType)!;
// Array element. Pass in a newly constructed `_Compound`, with
// adjusted offset.
return (
'C',
compoundType,
_generateCompoundOffsetBy(
subExpression.arguments.positional[0],
multiply(
arrayElementSize,
subExpression.arguments.positional[1], // index.
),
fileOffset,
variableName: "${parameter.name}#value",
),
);
case InstanceInvocation _:
// Look for `typedData[index].address`
final receiverType =
staticTypeContext!.getExpressionType(subExpression.receiver);
final implementsTypedData = TypeEnvironment(coreTypes, hierarchy)
.isSubtypeOf(
receiverType,
InterfaceType(typedDataClass, Nullability.nonNullable),
SubtypeCheckMode.withNullabilities);
if (!implementsTypedData) break;
if (receiverType is! InterfaceType) break;
final classNode = receiverType.classNode;
final elementSizeInBytes = _typedDataElementSizeInBytes(classNode);
if (elementSizeInBytes == null) break;
// Typed Data element off. Pass in new _Compound with extra
// offset.
return (
'C',
compoundType,
ConstructorInvocation(
compoundFromTypedDataBase,
Arguments([
subExpression.receiver,
multiply(
ConstantExpression(IntConstant(elementSizeInBytes)),
subExpression.arguments.positional.first, // index.
),
]),
),
);
default:
}
diagnosticReporter.report(
messageFfiAddressReceiver,
argument.fileOffset,
1,
argument.location?.file,
);
// Pass nullptr to prevent cascading error messages.
return (
'E', // Error.
pointerVoidType,
StaticInvocation(
fromAddressInternal,
Arguments(
<Expression>[ConstantExpression(IntConstant(0))],
types: <DartType>[voidType],
),
),
);
}
int? _typedDataElementSizeInBytes(Class classNode) {
final name = classNode.name;
if (name.contains('8')) {
return 1;
} else if (name.contains('16')) {
return 2;
} else if (name.contains('32')) {
return 4;
} else if (name.contains('64')) {
return 8;
}
return null;
}
/// Returns:
///
/// ```
/// _Compound._fromTypedDataBase(
/// compound._typedDataBase,
/// compound._offsetInBytes + offsetInBytes,
/// )
/// ```
Expression _generateCompoundOffsetBy(
Expression compound,
Expression offsetInBytes,
int fileOffset, {
String variableName = "#compoundOffset",
}) {
final compoundType = InterfaceType(
compoundClass,
Nullability.nonNullable,
const <DartType>[],
);
final valueVar = VariableDeclaration(
variableName,
initializer: compound,
type: compoundType,
isSynthesized: true,
)..fileOffset = fileOffset;
final newArgument = BlockExpression(
Block([
valueVar,
]),
ConstructorInvocation(
compoundFromTypedDataBase,
Arguments([
getCompoundTypedDataBaseField(
VariableGet(valueVar),
fileOffset,
),
add(
getCompoundOffsetInBytesField(
VariableGet(valueVar),
fileOffset,
),
offsetInBytes,
),
]),
),
);
return newArgument;
}
}
extension<T extends Object> on List<T> {
/// Order-preserved distinct elements.
List<T> distinct() {
final seen = <T>{};
return where((element) => seen.add(element)).toList();
}
}