e40cad30a3
Part of removing uses of 'fasta'. TEST=existing Change-Id: Ibf144a126d8ff4ba15b6cfd2c4fb898e9a499362 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/371685 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Jens Johansen <jensj@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Srujan Gaddam <srujzs@google.com>
972 lines
33 KiB
Dart
972 lines
33 KiB
Dart
// Copyright (c) 2021, 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/codes/cfe_codes.dart'
|
|
show
|
|
messageFfiDefaultAssetDuplicate,
|
|
messageFfiNativeDuplicateAnnotations,
|
|
messageFfiNativeFieldMissingType,
|
|
messageFfiNativeFieldMustBeStatic,
|
|
messageFfiNativeFieldType,
|
|
messageFfiNativeMustBeExternal,
|
|
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
|
|
templateCantHaveNamedParameters,
|
|
templateCantHaveOptionalParameters,
|
|
templateFfiNativeUnexpectedNumberOfParameters,
|
|
templateFfiNativeUnexpectedNumberOfParametersWithReceiver,
|
|
templateFfiTypeInvalid;
|
|
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' show ReferenceFromIndex;
|
|
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
|
|
import 'package:kernel/type_environment.dart';
|
|
|
|
import 'common.dart'
|
|
show FfiStaticTypeError, FfiTransformer, NativeType, FfiTypeCheckDirection;
|
|
import 'native_type_cfe.dart';
|
|
|
|
/// Transform @Native annotated functions into FFI native function pointer
|
|
/// functions.
|
|
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'
|
|
]);
|
|
// Skip if dart:ffi isn't loaded (e.g. during incremental compile).
|
|
if (index.tryGetClass('dart:ffi', 'Native') == null) {
|
|
return;
|
|
}
|
|
final transformer = FfiNativeTransformer(
|
|
index, coreTypes, hierarchy, diagnosticReporter, referenceFromIndex);
|
|
libraries.forEach(transformer.visitLibrary);
|
|
}
|
|
|
|
class FfiNativeTransformer extends FfiTransformer {
|
|
final DiagnosticReporter diagnosticReporter;
|
|
final ReferenceFromIndex? referenceFromIndex;
|
|
final Class assetClass;
|
|
final Class nativeClass;
|
|
final Class nativeFunctionClass;
|
|
final Field assetAssetField;
|
|
final Field nativeSymbolField;
|
|
final Field nativeAssetField;
|
|
final Field resolverField;
|
|
|
|
StringConstant? currentAsset;
|
|
|
|
// VariableDeclaration names can be null or empty string, in which case
|
|
// they're automatically assigned a "temporary" name like `#t0`.
|
|
static const variableDeclarationTemporaryName = null;
|
|
|
|
FfiNativeTransformer(
|
|
LibraryIndex index,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
this.diagnosticReporter,
|
|
this.referenceFromIndex)
|
|
: assetClass = index.getClass('dart:ffi', 'DefaultAsset'),
|
|
nativeClass = index.getClass('dart:ffi', 'Native'),
|
|
nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
|
|
assetAssetField = index.getField('dart:ffi', 'DefaultAsset', 'id'),
|
|
nativeSymbolField = index.getField('dart:ffi', 'Native', 'symbol'),
|
|
nativeAssetField = index.getField('dart:ffi', 'Native', 'assetId'),
|
|
resolverField = index.getField('dart:ffi', 'Native', '_ffi_resolver'),
|
|
super(index, coreTypes, hierarchy, diagnosticReporter,
|
|
referenceFromIndex);
|
|
|
|
@override
|
|
TreeNode visitLibrary(Library node) {
|
|
assert(currentAsset == null);
|
|
final annotation = tryGetAssetAnnotationOrWarnOnDuplicates(node);
|
|
if (annotation != null) {
|
|
currentAsset = (annotation.constant as InstanceConstant)
|
|
.fieldValues[assetAssetField.fieldReference] as StringConstant;
|
|
}
|
|
final result = super.visitLibrary(node);
|
|
currentAsset = null;
|
|
return result;
|
|
}
|
|
|
|
List<ConstantExpression> tryGetAnnotation(
|
|
Annotatable node, Class instanceOf) {
|
|
final annotations = <ConstantExpression>[];
|
|
|
|
for (final Expression annotation in node.annotations) {
|
|
if (annotation is! ConstantExpression) {
|
|
continue;
|
|
}
|
|
final annotationConstant = annotation.constant;
|
|
if (annotationConstant is! InstanceConstant) {
|
|
continue;
|
|
}
|
|
if (annotationConstant.classNode == instanceOf) {
|
|
annotations.add(annotation);
|
|
}
|
|
}
|
|
|
|
return annotations;
|
|
}
|
|
|
|
ConstantExpression? tryGetAssetAnnotationOrWarnOnDuplicates(Library node) {
|
|
ConstantExpression? assetAnnotation;
|
|
|
|
for (final annotation in tryGetAnnotation(node, assetClass)) {
|
|
if (assetAnnotation != null) {
|
|
// Duplicate @DefaultAsset annotations are forbidden.
|
|
diagnosticReporter.report(messageFfiDefaultAssetDuplicate,
|
|
annotation.fileOffset, 1, annotation.location?.file);
|
|
return null;
|
|
} else {
|
|
assetAnnotation = annotation;
|
|
}
|
|
}
|
|
|
|
return assetAnnotation;
|
|
}
|
|
|
|
ConstantExpression? tryGetNativeAnnotationOrWarnOnDuplicates(Member node) {
|
|
ConstantExpression? nativeAnnotation;
|
|
|
|
for (final annotation in tryGetAnnotation(node, nativeClass)) {
|
|
if (nativeAnnotation != null) {
|
|
// Duplicate @Native annotations are forbidden.
|
|
diagnosticReporter.report(messageFfiNativeDuplicateAnnotations,
|
|
node.fileOffset, 1, node.location?.file);
|
|
return null;
|
|
} else {
|
|
nativeAnnotation = annotation;
|
|
}
|
|
}
|
|
|
|
return nativeAnnotation;
|
|
}
|
|
|
|
bool _extendsNativeFieldWrapperClass1(DartType type) {
|
|
if (type is InterfaceType) {
|
|
Class? cls = type.classNode;
|
|
while (cls != null) {
|
|
if (cls == nativeFieldWrapperClass1Class) {
|
|
return true;
|
|
}
|
|
cls = cls.superclass;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
StringConstant _resolveNativeSymbolName(
|
|
Member member, InstanceConstant native) {
|
|
final nativeFunctionConst =
|
|
native.fieldValues[nativeSymbolField.fieldReference];
|
|
return nativeFunctionConst is StringConstant
|
|
? nativeFunctionConst
|
|
: StringConstant(member.name.text);
|
|
}
|
|
|
|
StringConstant? _assetNameFromAnnotation(InstanceConstant native) {
|
|
final assetConstant = native.fieldValues[nativeAssetField.fieldReference];
|
|
return assetConstant is StringConstant ? assetConstant : null;
|
|
}
|
|
|
|
bool _isLeaf(InstanceConstant native) {
|
|
return (native.fieldValues[nativeIsLeafField.fieldReference]
|
|
as BoolConstant)
|
|
.value;
|
|
}
|
|
|
|
// Replaces parameters with Pointer if:
|
|
// 1) they extend NativeFieldWrapperClass1, and
|
|
// 2) the corresponding FFI parameter is Pointer.
|
|
DartType _wrapArgumentType(
|
|
DartType dartParameterType, DartType ffiParameterType) {
|
|
if (dartParameterType is InterfaceType) {
|
|
if (_extendsNativeFieldWrapperClass1(dartParameterType) &&
|
|
env.isSubtypeOf(ffiParameterType, pointerVoidType,
|
|
SubtypeCheckMode.ignoringNullabilities)) {
|
|
return pointerVoidType;
|
|
}
|
|
}
|
|
return dartParameterType;
|
|
}
|
|
|
|
// Replaces return type with Object if it is Handle.
|
|
DartType _wrapReturnType(DartType dartReturnType, DartType ffiReturnType) {
|
|
if (env.isSubtypeOf(
|
|
ffiReturnType,
|
|
handleClass.getThisType(coreTypes, Nullability.nonNullable),
|
|
SubtypeCheckMode.ignoringNullabilities) &&
|
|
dartReturnType is! VoidType) {
|
|
return objectClass.getThisType(coreTypes, dartReturnType.nullability);
|
|
}
|
|
return dartReturnType;
|
|
}
|
|
|
|
// Compute synthetic FFI function type, accounting for Objects passed as
|
|
// Pointer, and Objects returned as Handles.
|
|
FunctionType _wrapFunctionType(
|
|
FunctionType dartFunctionType, FunctionType ffiFunctionType) {
|
|
return FunctionType(
|
|
<DartType>[
|
|
for (var i = 0; i < dartFunctionType.positionalParameters.length; i++)
|
|
_wrapArgumentType(dartFunctionType.positionalParameters[i],
|
|
ffiFunctionType.positionalParameters[i]),
|
|
],
|
|
_wrapReturnType(dartFunctionType.returnType, ffiFunctionType.returnType),
|
|
dartFunctionType.nullability,
|
|
);
|
|
}
|
|
|
|
// Whether a parameter of [dartParameterType], passed as [ffiParameterType],
|
|
// needs to be converted to Pointer.
|
|
bool _requiresPointerConversion(
|
|
DartType dartParameterType, DartType ffiParameterType) {
|
|
return (env.isSubtypeOf(ffiParameterType, pointerVoidType,
|
|
SubtypeCheckMode.ignoringNullabilities) &&
|
|
!env.isSubtypeOf(dartParameterType, pointerVoidType,
|
|
SubtypeCheckMode.ignoringNullabilities));
|
|
}
|
|
|
|
VariableDeclaration _declareTemporary(Expression initializer,
|
|
DartType dartParameterType, DartType ffiParameterType) {
|
|
final wrappedType =
|
|
(_requiresPointerConversion(dartParameterType, ffiParameterType)
|
|
? nativeFieldWrapperClass1Type
|
|
: dartParameterType);
|
|
return VariableDeclaration(variableDeclarationTemporaryName,
|
|
initializer: initializer,
|
|
type: wrappedType,
|
|
isFinal: true,
|
|
isSynthesized: true);
|
|
}
|
|
|
|
Expression _getTemporary(
|
|
VariableDeclaration temporary,
|
|
DartType dartParameterType,
|
|
DartType ffiParameterType, {
|
|
required bool checkForNullptr,
|
|
}) {
|
|
if (_requiresPointerConversion(dartParameterType, ffiParameterType)) {
|
|
Expression pointerAddress = StaticInvocation(getNativeFieldFunction,
|
|
Arguments(<Expression>[VariableGet(temporary)]));
|
|
|
|
if (checkForNullptr) {
|
|
final pointerAddressVar = VariableDeclaration("#pointerAddress",
|
|
initializer: pointerAddress,
|
|
type: coreTypes.intNonNullableRawType,
|
|
isSynthesized: true);
|
|
pointerAddress = BlockExpression(
|
|
Block([
|
|
pointerAddressVar,
|
|
IfStatement(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(pointerAddressVar),
|
|
objectEquals.name,
|
|
Arguments([ConstantExpression(IntConstant(0))]),
|
|
interfaceTarget: objectEquals,
|
|
functionType: objectEquals.getterType as FunctionType,
|
|
),
|
|
ExpressionStatement(StaticInvocation(
|
|
stateErrorThrowNewFunction,
|
|
Arguments([
|
|
ConstantExpression(StringConstant(
|
|
'A Dart object attempted to access a native peer, '
|
|
'but the native peer has been collected (nullptr). '
|
|
'This is usually the result of calling methods on a '
|
|
'native-backed object when the native resources have '
|
|
'already been disposed.',
|
|
))
|
|
]),
|
|
)),
|
|
EmptyStatement(),
|
|
)
|
|
]),
|
|
VariableGet(pointerAddressVar),
|
|
);
|
|
}
|
|
|
|
return StaticInvocation(fromAddressInternal,
|
|
Arguments(<Expression>[pointerAddress], types: <DartType>[voidType]));
|
|
}
|
|
return VariableGet(temporary);
|
|
}
|
|
|
|
// Native calls that pass objects extending NativeFieldWrapperClass1
|
|
// should be passed as Pointer instead so we don't have the overhead of
|
|
// converting Handles.
|
|
// If we find a NativeFieldWrapperClass1 object being passed to an Native
|
|
// signature taking a Pointer, we automatically wrap the argument in a call to
|
|
// `Pointer.fromAddress(_getNativeField(obj))`.
|
|
//
|
|
// Example:
|
|
// passAsPointer(ClassWithNativeField());
|
|
//
|
|
// Becomes, roughly:
|
|
// {
|
|
// final NativeFieldWrapperClass1#t0 = ClassWithNativeField();
|
|
// final #t1 = passAsPointer(Pointer.fromAddress(_getNativeField(#t0)));
|
|
// reachabilityFence(#t0);
|
|
// } => #t1
|
|
Expression _wrapArgumentsAndReturn({
|
|
required StaticInvocation invocation,
|
|
required FunctionType dartFunctionType,
|
|
required FunctionType ffiFunctionType,
|
|
bool checkReceiverForNullptr = false,
|
|
}) {
|
|
List<DartType> ffiParameters = ffiFunctionType.positionalParameters;
|
|
List<DartType> dartParameters = dartFunctionType.positionalParameters;
|
|
// Create lists of temporary variables for arguments potentially being
|
|
// wrapped, and the (potentially) wrapped arguments to be passed.
|
|
final temporariesForArguments = [];
|
|
final callArguments = <Expression>[];
|
|
final fencedArguments = [];
|
|
for (int i = 0; i < invocation.arguments.positional.length; i++) {
|
|
final temporary = _declareTemporary(invocation.arguments.positional[i],
|
|
dartParameters[i], ffiParameters[i]);
|
|
// Note: We also evaluate, and assign temporaries for, non-wrapped
|
|
// arguments as we need to preserve the original evaluation order.
|
|
temporariesForArguments.add(temporary);
|
|
callArguments.add(_getTemporary(
|
|
temporary,
|
|
dartParameters[i],
|
|
ffiParameters[i],
|
|
checkForNullptr: checkReceiverForNullptr && i == 0,
|
|
));
|
|
if (_requiresPointerConversion(dartParameters[i], ffiParameters[i])) {
|
|
fencedArguments.add(temporary);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Expression resultInitializer = invocation;
|
|
if (env.isSubtypeOf(
|
|
ffiFunctionType.returnType,
|
|
handleClass.getThisType(coreTypes, Nullability.nonNullable),
|
|
SubtypeCheckMode.ignoringNullabilities)) {
|
|
resultInitializer = StaticInvocation(unsafeCastMethod,
|
|
Arguments([invocation], types: [dartFunctionType.returnType]));
|
|
}
|
|
|
|
// final T #t1 = foo(Pointer.fromAddress(_getNativeField(#t0)));
|
|
final result = VariableDeclaration(variableDeclarationTemporaryName,
|
|
initializer: resultInitializer,
|
|
type: dartFunctionType.returnType,
|
|
isFinal: true,
|
|
isSynthesized: true);
|
|
|
|
invocation.arguments = Arguments(callArguments);
|
|
|
|
// {
|
|
// final NativeFieldWrapperClass1 #t0 = ClassWithNativeField();
|
|
// .. #t1 ..
|
|
// reachabilityFence(#t0);
|
|
// } => #t1
|
|
final resultBlock = BlockExpression(
|
|
Block(<Statement>[
|
|
...temporariesForArguments,
|
|
result,
|
|
for (final argument in fencedArguments)
|
|
ExpressionStatement(StaticInvocation(reachabilityFenceFunction,
|
|
Arguments(<Expression>[VariableGet(argument)])))
|
|
]),
|
|
VariableGet(result),
|
|
);
|
|
|
|
return resultBlock;
|
|
}
|
|
|
|
// Verify the Dart and FFI parameter types are compatible.
|
|
bool _verifyParameter(DartType dartParameterType, DartType ffiParameterType,
|
|
int annotationOffset, Uri? file) {
|
|
// Only NativeFieldWrapperClass1 instances can be passed as pointer.
|
|
if (_requiresPointerConversion(dartParameterType, ffiParameterType) &&
|
|
!_extendsNativeFieldWrapperClass1(dartParameterType)) {
|
|
diagnosticReporter.report(
|
|
messageFfiNativeOnlyNativeFieldWrapperClassCanBePointer,
|
|
annotationOffset,
|
|
1,
|
|
file);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Verify the signatures of the Dart function and the accompanying FfiNative
|
|
// annotation matches.
|
|
bool _verifySignatures(Procedure node, FunctionType dartFunctionType,
|
|
FunctionType ffiFunctionType, int annotationOffset) {
|
|
if (ffiFunctionType.namedParameters.isNotEmpty) {
|
|
diagnosticReporter.report(
|
|
templateCantHaveNamedParameters.withArguments('FfiNative'),
|
|
annotationOffset,
|
|
0,
|
|
node.location?.file);
|
|
return false;
|
|
}
|
|
|
|
if (ffiFunctionType.positionalParameters.length >
|
|
ffiFunctionType.requiredParameterCount) {
|
|
diagnosticReporter.report(
|
|
templateCantHaveOptionalParameters.withArguments('FfiNative'),
|
|
annotationOffset,
|
|
0,
|
|
node.location?.file);
|
|
return false;
|
|
}
|
|
|
|
if (dartFunctionType.positionalParameters.length !=
|
|
ffiFunctionType.positionalParameters.length) {
|
|
final template = (node.isStatic
|
|
? templateFfiNativeUnexpectedNumberOfParameters
|
|
: templateFfiNativeUnexpectedNumberOfParametersWithReceiver);
|
|
diagnosticReporter.report(
|
|
template.withArguments(dartFunctionType.positionalParameters.length,
|
|
ffiFunctionType.positionalParameters.length),
|
|
annotationOffset,
|
|
1,
|
|
node.location?.file);
|
|
return false;
|
|
}
|
|
|
|
var validSignature = true;
|
|
for (var i = 0; i < dartFunctionType.positionalParameters.length; i++) {
|
|
final dartParameterType = dartFunctionType.positionalParameters[i];
|
|
if (dartParameterType is InterfaceType) {
|
|
if (!_verifyParameter(
|
|
dartParameterType,
|
|
ffiFunctionType.positionalParameters[i],
|
|
annotationOffset,
|
|
node.location?.file)) {
|
|
validSignature = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return validSignature;
|
|
}
|
|
|
|
static const _vmFfiNative = 'vm:ffi:native';
|
|
|
|
/// A pragma annotation added to methods previously annotated with `@Native`
|
|
/// during the transformation so that the use site transformer can find valid
|
|
/// targets.
|
|
static const nativeMarker = 'cfe:ffi:native-marker';
|
|
|
|
Procedure _transformProcedure(
|
|
Procedure node,
|
|
StringConstant nativeFunctionName,
|
|
StringConstant? overriddenAssetName,
|
|
bool isLeaf,
|
|
int annotationOffset,
|
|
FunctionType dartFunctionType,
|
|
FunctionType ffiFunctionType,
|
|
List<Expression> argumentList, {
|
|
required bool checkReceiverForNullptr,
|
|
}) {
|
|
final wrappedDartFunctionType = checkFfiType(
|
|
node, dartFunctionType, ffiFunctionType, isLeaf, annotationOffset);
|
|
|
|
if (wrappedDartFunctionType == null) {
|
|
// It's OK to continue because the diagnostics issued will cause
|
|
// compilation to fail. By continuing, we can report more diagnostics
|
|
// before compilation ends.
|
|
return node;
|
|
}
|
|
|
|
final resolvedNative = _generateResolvedNativeConstant(
|
|
nativeType: ffiFunctionType,
|
|
nativeName: nativeFunctionName,
|
|
isLeaf: isLeaf,
|
|
overriddenAssetName: overriddenAssetName,
|
|
);
|
|
final pragmaConstant = ConstantExpression(
|
|
InstanceConstant(pragmaClass.reference, [], {
|
|
pragmaName.fieldReference: StringConstant(_vmFfiNative),
|
|
pragmaOptions.fieldReference: resolvedNative,
|
|
}),
|
|
InterfaceType(
|
|
pragmaClass,
|
|
Nullability.nonNullable,
|
|
[],
|
|
),
|
|
);
|
|
|
|
// The marker annotation is always added to the original method so that the
|
|
// use-sites transformer can find it when a tear-off is passed to e.g.
|
|
// Native.addressOf.
|
|
node.addAnnotation(ConstantExpression(
|
|
InstanceConstant(pragmaClass.reference, [], {
|
|
pragmaName.fieldReference: StringConstant(nativeMarker),
|
|
pragmaOptions.fieldReference: resolvedNative,
|
|
}),
|
|
InterfaceType(
|
|
pragmaClass,
|
|
Nullability.nonNullable,
|
|
[],
|
|
),
|
|
));
|
|
|
|
final possibleCompoundReturn = findCompoundReturnType(dartFunctionType);
|
|
if (dartFunctionType == wrappedDartFunctionType &&
|
|
node.isStatic &&
|
|
possibleCompoundReturn == null) {
|
|
// We are not wrapping/unwrapping arguments or return value.
|
|
node.addAnnotation(pragmaConstant);
|
|
return node;
|
|
}
|
|
|
|
// Introduce a new function as external with the annotation and give the
|
|
// current function a body that does the wrapping/unwrapping.
|
|
node.isExternal = false;
|
|
|
|
addPragmaPreferInline(node);
|
|
|
|
final parent = node.parent;
|
|
final fileUri = node.fileUri;
|
|
|
|
final name = Name(
|
|
'_${node.name.text}\$${node.kind.name}\$FfiNative', currentLibrary);
|
|
final reference = currentLibraryIndex?.lookupGetterReference(name);
|
|
|
|
int varCounter = 0;
|
|
final nonWrappedFfiNative = Procedure(
|
|
name,
|
|
ProcedureKind.Method,
|
|
FunctionNode(
|
|
/*body=*/ null,
|
|
requiredParameterCount: wrappedDartFunctionType.requiredParameterCount,
|
|
positionalParameters: [
|
|
for (final positionalParameter
|
|
in wrappedDartFunctionType.positionalParameters)
|
|
VariableDeclaration(
|
|
/*name=*/ '#t${varCounter++}',
|
|
type: positionalParameter,
|
|
)..fileOffset = node.fileOffset,
|
|
],
|
|
returnType: wrappedDartFunctionType.returnType,
|
|
)..fileOffset = node.fileOffset,
|
|
fileUri: fileUri,
|
|
isStatic: true,
|
|
isExternal: true,
|
|
reference: reference,
|
|
)..fileOffset = node.fileOffset;
|
|
nonWrappedFfiNative.addAnnotation(pragmaConstant);
|
|
|
|
// Add procedure to the parent the FfiNative function belongs to.
|
|
if (parent is Class) {
|
|
parent.addProcedure(nonWrappedFfiNative);
|
|
} else if (parent is Library) {
|
|
parent.addProcedure(nonWrappedFfiNative);
|
|
} else {
|
|
throw 'Unexpected parent of @Native function. '
|
|
'Expected Class or Library, but found ${parent}.';
|
|
}
|
|
|
|
final nonWrappedInvocation = StaticInvocation(
|
|
nonWrappedFfiNative,
|
|
Arguments(argumentList),
|
|
)..fileOffset = node.fileOffset;
|
|
|
|
Expression result = (wrappedDartFunctionType == dartFunctionType
|
|
? nonWrappedInvocation
|
|
: _wrapArgumentsAndReturn(
|
|
invocation: nonWrappedInvocation,
|
|
dartFunctionType: dartFunctionType,
|
|
ffiFunctionType: ffiFunctionType,
|
|
checkReceiverForNullptr: checkReceiverForNullptr,
|
|
));
|
|
if (possibleCompoundReturn != null) {
|
|
result = invokeCompoundConstructor(result, possibleCompoundReturn);
|
|
}
|
|
|
|
node.function.body = ReturnStatement(result)..parent = node.function;
|
|
|
|
return node;
|
|
}
|
|
|
|
/// Creates a `Native` constant with all fields resolved.
|
|
///
|
|
/// Re-using the constant from the original `@Native` annotation doesn't work
|
|
/// because the asset field may be inferred from the library.
|
|
InstanceConstant _generateResolvedNativeConstant({
|
|
required DartType nativeType,
|
|
required StringConstant nativeName,
|
|
required bool isLeaf,
|
|
StringConstant? overriddenAssetName,
|
|
}) {
|
|
return InstanceConstant(
|
|
nativeClass.reference,
|
|
[nativeType],
|
|
{
|
|
nativeSymbolField.fieldReference: nativeName,
|
|
nativeAssetField.fieldReference: overriddenAssetName ??
|
|
currentAsset ??
|
|
StringConstant(currentLibrary.importUri.toString()),
|
|
nativeIsLeafField.fieldReference: BoolConstant(isLeaf),
|
|
},
|
|
);
|
|
}
|
|
|
|
// Transform Native instance methods.
|
|
//
|
|
// Example:
|
|
//
|
|
// class MyNativeClass extends NativeFieldWrapperClass1 {
|
|
// @Native<IntPtr Function(Pointer<Void>, IntPtr)>(symbol: 'MyClass_MyMethod')
|
|
// external int myMethod(int x);
|
|
// }
|
|
//
|
|
// Becomes, roughly:
|
|
//
|
|
// ... {
|
|
// @pragma(
|
|
// 'vm:ffi:native',
|
|
// Native<IntPtr Function(Pointer<Void>, IntPtr)>(
|
|
// symbol: 'MyClass_MyMethod',
|
|
// assetId: '<library uri>',
|
|
// ),
|
|
// )
|
|
// external int _myFunction$FfiNative(Pointer<Void> self, int x);
|
|
//
|
|
// @pragma('vm:prefer-inline')
|
|
// int myMethod(int x) => _myMethod$FfiNative(_getNativeField(this), x);
|
|
// }
|
|
Procedure _transformInstanceMethod(
|
|
Procedure node,
|
|
FunctionType ffiFunctionType,
|
|
StringConstant nativeFunctionName,
|
|
StringConstant? assetName,
|
|
bool isLeaf,
|
|
int annotationOffset) {
|
|
final dartFunctionType = FunctionType([
|
|
(node.parent as Class).getThisType(coreTypes, Nullability.nonNullable),
|
|
for (final parameter in node.function.positionalParameters) parameter.type
|
|
], node.function.returnType, Nullability.nonNullable);
|
|
|
|
final argumentList = <Expression>[
|
|
ThisExpression(),
|
|
for (final parameter in node.function.positionalParameters)
|
|
VariableGet(parameter)
|
|
];
|
|
|
|
return _transformProcedure(
|
|
node,
|
|
nativeFunctionName,
|
|
assetName,
|
|
isLeaf,
|
|
annotationOffset,
|
|
dartFunctionType,
|
|
ffiFunctionType,
|
|
argumentList,
|
|
checkReceiverForNullptr: true,
|
|
);
|
|
}
|
|
|
|
// Transform Native static functions.
|
|
//
|
|
// Example:
|
|
//
|
|
// @Native<IntPtr Function(Pointer<Void>, IntPtr)>(symbol: 'MyFunction')
|
|
// external int myFunction(MyNativeClass obj, int x);
|
|
//
|
|
// Becomes, roughly:
|
|
//
|
|
// @pragma(
|
|
// 'vm:ffi:native',
|
|
// Native<IntPtr Function(Pointer<Void>, IntPtr)>(
|
|
// symbol: 'MyFunction',
|
|
// assetId: '<library uri>',
|
|
// ),
|
|
// )
|
|
// external int _myFunction$FfiNative(Pointer<Void> self, int x);
|
|
//
|
|
// @pragma('vm:prefer-inline')
|
|
// int myFunction(MyNativeClass obj, int x)
|
|
// => _myFunction$FfiNative(
|
|
// Pointer<Void>.fromAddress(_getNativeField(obj)), x);
|
|
Procedure _transformStaticFunction(
|
|
Procedure node,
|
|
FunctionType ffiFunctionType,
|
|
StringConstant nativeFunctionName,
|
|
StringConstant? overriddenAssetName,
|
|
bool isLeaf,
|
|
int annotationOffset) {
|
|
final dartFunctionType =
|
|
node.function.computeThisFunctionType(Nullability.nonNullable);
|
|
|
|
final argumentList = <Expression>[
|
|
for (final parameter in node.function.positionalParameters)
|
|
VariableGet(parameter)
|
|
];
|
|
|
|
return _transformProcedure(
|
|
node,
|
|
nativeFunctionName,
|
|
overriddenAssetName,
|
|
isLeaf,
|
|
annotationOffset,
|
|
dartFunctionType,
|
|
ffiFunctionType,
|
|
argumentList,
|
|
checkReceiverForNullptr: false,
|
|
);
|
|
}
|
|
|
|
Expression _generateAddressOfField(
|
|
Member node, InterfaceType ffiType, InstanceConstant native) {
|
|
return StaticInvocation(
|
|
nativePrivateAddressOf,
|
|
Arguments([ConstantExpression(native)], types: [ffiType]),
|
|
)..fileOffset = node.fileOffset;
|
|
}
|
|
|
|
(DartType, NativeTypeCfe) _validateOrInferNativeFieldType(
|
|
Member node, DartType ffiType, DartType dartType) {
|
|
if (ffiType is DynamicType) {
|
|
// If no type argument is given on the @Native annotation, try to infer
|
|
// it.
|
|
final inferred = convertDartTypeToNativeType(dartType);
|
|
if (inferred != null) {
|
|
ffiType = inferred;
|
|
} else {
|
|
diagnosticReporter.report(messageFfiNativeFieldMissingType,
|
|
node.fileOffset, 1, node.location?.file);
|
|
throw FfiStaticTypeError();
|
|
}
|
|
}
|
|
|
|
ensureNativeTypeValid(
|
|
ffiType,
|
|
node,
|
|
allowStructAndUnion: true,
|
|
// Handles are not currently supported, but checking them separately and
|
|
// allowing them here yields a more specific error message.
|
|
allowHandle: true,
|
|
allowInlineArray: true,
|
|
);
|
|
ensureNativeTypeMatch(
|
|
FfiTypeCheckDirection.nativeToDart, ffiType, dartType, node,
|
|
allowHandle: true, allowArray: true);
|
|
|
|
// Array types must have an @Array annotation denoting its size.
|
|
if (isArrayType(ffiType)) {
|
|
final dimensions = ensureArraySizeAnnotation(node, ffiType);
|
|
return (
|
|
ffiType,
|
|
NativeTypeCfe.withoutLayout(this, dartType, arrayDimensions: dimensions)
|
|
);
|
|
}
|
|
|
|
// Apart from arrays, compound subtypes, pointers and numeric types are
|
|
// supported for fields.
|
|
if (!isStructOrUnionSubtype(ffiType) &&
|
|
!isAbiSpecificIntegerSubtype(ffiType)) {
|
|
final type = switch (ffiType) {
|
|
InterfaceType(:var classNode) => getType(classNode),
|
|
_ => null,
|
|
};
|
|
if (type == null ||
|
|
type == NativeType.kNativeFunction ||
|
|
type == NativeType.kHandle ||
|
|
isArrayType(ffiType)) {
|
|
diagnosticReporter.report(
|
|
messageFfiNativeFieldType, node.fileOffset, 1, node.location?.file);
|
|
throw FfiStaticTypeError();
|
|
}
|
|
}
|
|
|
|
return (ffiType, NativeTypeCfe.withoutLayout(this, ffiType));
|
|
}
|
|
|
|
@override
|
|
TreeNode visitField(Field node) {
|
|
final nativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node);
|
|
if (nativeAnnotation == null) {
|
|
return node;
|
|
}
|
|
// @Native annotations on fields are valid if the field is external. But
|
|
// external fields are represented as a getter/setter pair in Kernel, so
|
|
// only visit fields to verify that no native annotation is present.
|
|
assert(!node.isExternal);
|
|
diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset,
|
|
1, node.location?.file);
|
|
return node;
|
|
}
|
|
|
|
@override
|
|
visitProcedure(Procedure node) {
|
|
// Only transform functions that are external and have Native annotation:
|
|
// @Native<Double Function(Double)>(symbol: 'Math_sqrt')
|
|
// external double _square_root(double x);
|
|
final ffiNativeAnnotation = tryGetNativeAnnotationOrWarnOnDuplicates(node);
|
|
if (ffiNativeAnnotation == null) {
|
|
return node;
|
|
}
|
|
|
|
if (!node.isExternal) {
|
|
diagnosticReporter.report(messageFfiNativeMustBeExternal, node.fileOffset,
|
|
1, node.location?.file);
|
|
return node;
|
|
}
|
|
|
|
node.annotations.remove(ffiNativeAnnotation);
|
|
|
|
final ffiConstant = ffiNativeAnnotation.constant as InstanceConstant;
|
|
var nativeType = ffiConstant.typeArguments[0];
|
|
|
|
final nativeName = _resolveNativeSymbolName(node, ffiConstant);
|
|
final overriddenAssetName = _assetNameFromAnnotation(ffiConstant);
|
|
final isLeaf = _isLeaf(ffiConstant);
|
|
|
|
if (nativeType is FunctionType) {
|
|
try {
|
|
final nativeFunctionType = InterfaceType(
|
|
nativeFunctionClass, Nullability.nonNullable, [nativeType]);
|
|
ensureNativeTypeValid(nativeFunctionType, ffiNativeAnnotation,
|
|
allowStructAndUnion: true, allowHandle: true);
|
|
} on FfiStaticTypeError {
|
|
// We've already reported an error.
|
|
return node;
|
|
}
|
|
final ffiFunctionType = ffiConstant.typeArguments[0] as FunctionType;
|
|
|
|
if (!node.isStatic) {
|
|
return _transformInstanceMethod(node, ffiFunctionType, nativeName,
|
|
overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset);
|
|
}
|
|
|
|
return _transformStaticFunction(node, ffiFunctionType, nativeName,
|
|
overriddenAssetName, isLeaf, ffiNativeAnnotation.fileOffset);
|
|
} else if (node.kind == ProcedureKind.Getter ||
|
|
node.kind == ProcedureKind.Setter) {
|
|
if (!node.isStatic) {
|
|
diagnosticReporter.report(messageFfiNativeFieldMustBeStatic,
|
|
node.fileOffset, 1, node.location?.file);
|
|
}
|
|
|
|
DartType dartType;
|
|
NativeTypeCfe nativeTypeCfe;
|
|
try {
|
|
dartType = node.kind == ProcedureKind.Getter
|
|
? node.function.returnType
|
|
: node.function.positionalParameters[0].type;
|
|
(nativeType, nativeTypeCfe) =
|
|
_validateOrInferNativeFieldType(node, nativeType, dartType);
|
|
} on FfiStaticTypeError {
|
|
return node;
|
|
}
|
|
final resolved = _generateResolvedNativeConstant(
|
|
nativeType: nativeType,
|
|
nativeName: nativeName,
|
|
isLeaf: isLeaf,
|
|
overriddenAssetName: overriddenAssetName,
|
|
);
|
|
node.isExternal = false;
|
|
|
|
final zeroOffset = ConstantExpression(IntConstant(0));
|
|
|
|
if (node.kind == ProcedureKind.Getter) {
|
|
node.function.body = ReturnStatement(nativeTypeCfe.generateLoad(
|
|
dartType: dartType,
|
|
fileOffset: node.fileOffset,
|
|
typedDataBase: _generateAddressOfField(
|
|
node, nativeType as InterfaceType, resolved),
|
|
transformer: this,
|
|
offsetInBytes: zeroOffset,
|
|
));
|
|
node.annotations.add(ConstantExpression(
|
|
InstanceConstant(pragmaClass.reference, [], {
|
|
pragmaName.fieldReference: StringConstant(nativeMarker),
|
|
pragmaOptions.fieldReference: resolved,
|
|
}),
|
|
InterfaceType(
|
|
pragmaClass,
|
|
Nullability.nonNullable,
|
|
[],
|
|
),
|
|
));
|
|
} else {
|
|
node.function.body = ExpressionStatement(nativeTypeCfe.generateStore(
|
|
node.function.positionalParameters[0],
|
|
dartType: dartType,
|
|
fileOffset: node.fileOffset,
|
|
typedDataBase: _generateAddressOfField(
|
|
node, nativeType as InterfaceType, resolved),
|
|
transformer: this,
|
|
offsetInBytes: zeroOffset,
|
|
));
|
|
}
|
|
} else {
|
|
// This function is not annotated with a native function type, which is
|
|
// invalid.
|
|
diagnosticReporter.report(
|
|
templateFfiTypeInvalid.withArguments(nativeType),
|
|
node.fileOffset,
|
|
1,
|
|
node.location?.file);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/// Checks whether the FFI function type is valid and reports any errors.
|
|
/// Returns the Dart function type for the FFI function if the type is valid.
|
|
///
|
|
/// For example, for FFI function type `Int8 Function(Double)`, this returns
|
|
/// `int Function(double)`.
|
|
FunctionType? checkFfiType(
|
|
Procedure node,
|
|
FunctionType dartFunctionType,
|
|
FunctionType ffiFunctionTypeWithPossibleVarArgs,
|
|
bool isLeaf,
|
|
int annotationOffset) {
|
|
final ffiFunctionType = flattenVarargs(ffiFunctionTypeWithPossibleVarArgs);
|
|
if (!_verifySignatures(
|
|
node, dartFunctionType, ffiFunctionType, annotationOffset)) {
|
|
return null;
|
|
}
|
|
|
|
// int Function(Pointer<Void>)
|
|
final wrappedDartFunctionType =
|
|
_wrapFunctionType(dartFunctionType, ffiFunctionType);
|
|
|
|
final nativeType = InterfaceType(
|
|
nativeFunctionClass, Nullability.nonNullable, [ffiFunctionType]);
|
|
|
|
try {
|
|
ensureNativeTypeValid(nativeType, node);
|
|
ensureNativeTypeMatch(
|
|
FfiTypeCheckDirection.nativeToDart,
|
|
nativeType,
|
|
wrappedDartFunctionType,
|
|
node,
|
|
allowHandle: true, // Handle-specific errors emitted below.
|
|
);
|
|
ensureLeafCallDoesNotUseHandles(
|
|
nativeType,
|
|
isLeaf,
|
|
reportErrorOn: 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 null;
|
|
}
|
|
|
|
return wrappedDartFunctionType;
|
|
}
|
|
}
|