e30cd0322c
This separates VariableDeclaration from Statement. VariableDeclaration no longer implements Statement and variable declared in a block or in a for-statement are now wrapped by a VariableStatement. Currently there are two VariableStatement implementations; LegacyVariableStatement for variables in the current model, called LegacyVariable, and VariableInitialization for variables used in the new, still experimental, encoding that supports scope computation. This CL is a step towards realigning the AST nodes to the new model in which each kind of variable has its own distinct subclass. (LocalVariable, PositionalParameter, NamedParameter, SyntheticVariable, etc.) Note that it is not the intent to use VariableStatement in ForStatement going forward but that will be handled in a follow-up. TEST=existing. Change-Id: I5b309cd62c9b138f95b74fb054686edffa49a393 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502681 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
2466 lines
80 KiB
Dart
2466 lines
80 KiB
Dart
// 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 imports 'codes/cfe_codes.dart' instead of 'api_prototype/codes.dart' to
|
|
// avoid cyclic dependency between `package:vm/modular` and `package:front_end`.
|
|
import 'package:front_end/src/api_prototype/constant_evaluator.dart'
|
|
show ConstantEvaluator, SimpleErrorReporter;
|
|
import 'package:front_end/src/codes/cfe_codes.dart' show LocatedMessage;
|
|
import 'package:front_end/src/codes/diagnostic.dart' as diag;
|
|
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, Target;
|
|
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';
|
|
|
|
enum _FfiUseSiteTransformMode { libraries, expressionEvaluation }
|
|
|
|
class _SilentErrorReporter extends SimpleErrorReporter {
|
|
const _SilentErrorReporter();
|
|
|
|
@override
|
|
void report(LocatedMessage message, [List<LocatedMessage>? context]) {}
|
|
}
|
|
|
|
/// 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(
|
|
Target target,
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
List<Library> libraries,
|
|
DiagnosticReporter diagnosticReporter,
|
|
ReferenceFromIndex? referenceFromIndex,
|
|
Map<String, String>? environmentDefines,
|
|
) {
|
|
final index = _createIndexIfFfiLoaded(component);
|
|
if (index == null) {
|
|
return;
|
|
}
|
|
final transformer = _createFfiUseSiteTransformer(
|
|
target,
|
|
component,
|
|
coreTypes,
|
|
hierarchy,
|
|
diagnosticReporter,
|
|
referenceFromIndex,
|
|
environmentDefines,
|
|
index,
|
|
mode: _FfiUseSiteTransformMode.libraries,
|
|
);
|
|
libraries.forEach(transformer.visitLibrary);
|
|
}
|
|
|
|
/// Checks and replaces dart:ffi use-site APIs inside [procedure] only.
|
|
///
|
|
/// This is used for expression compilation where the enclosing libraries are
|
|
/// already transformed and only the synthetic procedure should be processed.
|
|
void transformProcedure(
|
|
Target target,
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
Procedure procedure,
|
|
DiagnosticReporter diagnosticReporter,
|
|
ReferenceFromIndex? referenceFromIndex,
|
|
Map<String, String>? environmentDefines,
|
|
) {
|
|
final index = _createIndexIfFfiLoaded(component);
|
|
if (index == null) {
|
|
return;
|
|
}
|
|
final transformer = _createFfiUseSiteTransformer(
|
|
target,
|
|
component,
|
|
coreTypes,
|
|
hierarchy,
|
|
diagnosticReporter,
|
|
referenceFromIndex,
|
|
environmentDefines,
|
|
index,
|
|
mode: _FfiUseSiteTransformMode.expressionEvaluation,
|
|
);
|
|
// Procedure-only traversal starts at `visitProcedure`, so `visitLibrary` is not
|
|
// invoked and `currentLibrary` / `currentLibraryIndex` would otherwise be null.
|
|
// FFI rewrites depend on that library context (e.g. nullability, name binding,
|
|
// and reference lookups), so set it explicitly for this procedure.
|
|
transformer.initCurrentLibrary(procedure.enclosingLibrary);
|
|
procedure.accept(transformer);
|
|
}
|
|
|
|
LibraryIndex? _createIndexIfFfiLoaded(Component component) {
|
|
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 null;
|
|
}
|
|
if (index.tryGetClass('dart:ffi', 'NativeFunction') == null) {
|
|
// If dart:ffi is not loaded (for real): do not do the transformation.
|
|
return null;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
_FfiUseSiteTransformer2 _createFfiUseSiteTransformer(
|
|
Target target,
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
DiagnosticReporter diagnosticReporter,
|
|
ReferenceFromIndex? referenceFromIndex,
|
|
Map<String, String>? environmentDefines,
|
|
LibraryIndex index, {
|
|
required _FfiUseSiteTransformMode mode,
|
|
}) {
|
|
return _FfiUseSiteTransformer2(
|
|
index,
|
|
coreTypes,
|
|
hierarchy,
|
|
diagnosticReporter,
|
|
referenceFromIndex,
|
|
ConstantEvaluator(
|
|
target.dartLibrarySupport,
|
|
target.constantsBackend,
|
|
component,
|
|
environmentDefines,
|
|
TypeEnvironment(coreTypes, hierarchy),
|
|
const _SilentErrorReporter(),
|
|
errorOnUnevaluatedConstant: true,
|
|
),
|
|
mode: mode,
|
|
);
|
|
}
|
|
|
|
/// 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 {
|
|
final ConstantEvaluator _constantEvaluator;
|
|
final _FfiUseSiteTransformMode _mode;
|
|
_FfiUseSiteTransformer2(
|
|
LibraryIndex index,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
DiagnosticReporter diagnosticReporter,
|
|
ReferenceFromIndex? referenceFromIndex,
|
|
this._constantEvaluator, {
|
|
required _FfiUseSiteTransformMode mode,
|
|
}) : _mode = mode,
|
|
super(
|
|
index,
|
|
coreTypes,
|
|
hierarchy,
|
|
diagnosticReporter,
|
|
referenceFromIndex,
|
|
);
|
|
|
|
bool get allowEnclosingMutation =>
|
|
_mode == _FfiUseSiteTransformMode.libraries;
|
|
}
|
|
|
|
/// 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 get allowEnclosingMutation;
|
|
|
|
bool _inFfiTearoff = false;
|
|
ConstantEvaluator get _constantEvaluator;
|
|
|
|
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;
|
|
|
|
Never _reportEnclosingMutationInProcedureMode(
|
|
TreeNode node,
|
|
String operation,
|
|
) {
|
|
diagnosticReporter.report(
|
|
diag.ffiExpressionEvaluationNotSupported.withArguments(
|
|
operation: operation,
|
|
),
|
|
node.fileOffset,
|
|
1,
|
|
node.location?.file,
|
|
);
|
|
throw FfiStaticTypeError();
|
|
}
|
|
|
|
@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(
|
|
diag.ffiCreateOfStructOrUnion,
|
|
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 == structPointerRefWithFinalizerTearoff ||
|
|
node == unionPointerElementAtTearoff ||
|
|
node == unionPointerRefWithFinalizerTearoff))) ||
|
|
// 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([
|
|
VariableStatement(arrayVar),
|
|
VariableStatement(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 == structPointerRefWithFinalizer ||
|
|
target == unionPointerRefWithFinalizer) {
|
|
final DartType nativeType = node.arguments.types[0];
|
|
|
|
ensureNativeTypeValid(nativeType, node, allowStructAndUnion: true);
|
|
|
|
return _replaceRefWithFinalizer(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) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
final DartType nativeType = node.arguments.types[0];
|
|
|
|
ensureNativeTypeValid(nativeType, node, allowStructAndUnion: true);
|
|
|
|
return _replaceRefArray(node);
|
|
} else if (target == structArrayElements ||
|
|
target == unionArrayElements) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
final DartType nativeType = node.arguments.types[0];
|
|
|
|
ensureNativeTypeValid(nativeType, node, allowStructAndUnion: true);
|
|
|
|
return _replaceArrayElements(node);
|
|
} else if (target == arrayArrayElemAt) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
final DartType nativeType = node.arguments.types[0];
|
|
|
|
ensureNativeTypeValid(
|
|
nativeType,
|
|
node,
|
|
allowInlineArray: true,
|
|
allowStructAndUnion: true,
|
|
);
|
|
|
|
return _replaceArrayArrayElemAt(node);
|
|
} else if (target == abiSpecificIntegerArrayElements) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
final DartType nativeType = node.arguments.types[0];
|
|
|
|
ensureNativeTypeValid(
|
|
nativeType,
|
|
node,
|
|
allowStructAndUnion: true,
|
|
allowInlineArray: true,
|
|
);
|
|
|
|
return _replaceAbiSpecificIntegerElements(node);
|
|
} else if (target == arrayArrayElements) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
final DartType nativeType = node.arguments.types[0];
|
|
|
|
ensureNativeTypeValid(
|
|
nativeType,
|
|
node,
|
|
allowInlineArray: true,
|
|
allowStructAndUnion: true,
|
|
);
|
|
|
|
return _replaceArrayArrayElements(node);
|
|
} else if (target == arrayArrayAssignAt) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
final DartType nativeType = node.arguments.types[0];
|
|
|
|
ensureNativeTypeValid(
|
|
nativeType,
|
|
node,
|
|
allowInlineArray: true,
|
|
allowStructAndUnion: true,
|
|
);
|
|
|
|
return _replaceArrayArrayElemAt(node, setter: true);
|
|
} else if (target == sizeOfMethod) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
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) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
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) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
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 == nativeCallableIsolateGroupBoundConstructor) {
|
|
return _verifyAndReplaceNativeCallable(
|
|
node,
|
|
replacement: _replaceNativeCallableIsolateGroupBoundConstructor,
|
|
);
|
|
} else if (target == nativeCallableListenerConstructor) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
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(
|
|
diag.ffiNativeCallableListenerReturnVoid.withArguments(
|
|
returnType: ffiFuncType.returnType,
|
|
),
|
|
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) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
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) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
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(
|
|
diag.ffiAddressPosition,
|
|
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;
|
|
}
|
|
|
|
/// Returns true if the [node] invocation is missing arguments.
|
|
///
|
|
/// If a call is missing arguments, the front-end has already identified
|
|
/// compilation errors. In such cases, it is safe to early-return the
|
|
/// untransformed [node] because the transformation logic (which assumes valid
|
|
/// arguments) would crash, and the program is guaranteed to fail compilation.
|
|
static bool _isMissingArguments(StaticInvocation node) {
|
|
final target = node.target;
|
|
return node.arguments.types.length <
|
|
target.function.typeParameters.length ||
|
|
node.arguments.positional.length <
|
|
target.function.requiredParameterCount;
|
|
}
|
|
|
|
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,
|
|
isFinal: 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([VariableStatement(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,
|
|
) {
|
|
if (!allowEnclosingMutation) {
|
|
_reportEnclosingMutationInProcedureMode(node, 'Pointer.fromFunction');
|
|
}
|
|
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 true:
|
|
// _NativeCallableIsolateLocal<T>(
|
|
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
|
|
// _nativeCallbackFunction<T>(target, exceptionalReturn),
|
|
// null,
|
|
// true);
|
|
// isStaticFunction is false:
|
|
// _NativeCallableIsolateLocal<T>(
|
|
// _createNativeCallableIsolateLocal<NativeFunction<T>>(
|
|
// _nativeIsolateLocalCallbackFunction<T>(exceptionalReturn),
|
|
// target,
|
|
// 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._port);
|
|
// 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._port);
|
|
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([VariableStatement(nativeCallable), pointerSetter]),
|
|
VariableGet(nativeCallable),
|
|
);
|
|
}
|
|
|
|
// NativeCallable<T>.isolateGroupBound(target, exceptionalReturn) calls become:
|
|
// isStaticFunction is true:
|
|
// _NativeCallableIsolateGroupBound<T>(
|
|
// _createNativeCallableIsolateGroupBound<NativeFunction<T>>(
|
|
// _nativeIsolateGroupBoundCallbackFunction<T>(target, exceptionalReturn),
|
|
// null);
|
|
// isStaticFunction is false:
|
|
// _NativeCallableIsolateGroupBound<T>(
|
|
// _createNativeCallableIsolateGroupBound<NativeFunction<T>>(
|
|
// _nativeIsolateGroupBoundClosureFunction<T>(exceptionalReturn),
|
|
// target));
|
|
Expression _replaceNativeCallableIsolateGroupBoundConstructor(
|
|
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(
|
|
createNativeCallableIsolateGroupBoundProcedure,
|
|
Arguments(
|
|
[
|
|
StaticInvocation(
|
|
nativeIsolateGroupBoundCallbackFunctionProcedure,
|
|
Arguments([
|
|
target,
|
|
exceptionalReturn,
|
|
], types: node.arguments.types),
|
|
),
|
|
NullLiteral(),
|
|
],
|
|
types: [nativeFunctionType],
|
|
),
|
|
);
|
|
} else {
|
|
pointerValue = StaticInvocation(
|
|
createNativeCallableIsolateGroupBoundProcedure,
|
|
Arguments(
|
|
[
|
|
StaticInvocation(
|
|
nativeIsolateGroupBoundClosureFunctionProcedure,
|
|
Arguments([exceptionalReturn], types: node.arguments.types),
|
|
),
|
|
target,
|
|
],
|
|
types: [nativeFunctionType],
|
|
),
|
|
);
|
|
}
|
|
|
|
return ConstructorInvocation(
|
|
nativeCallablePrivateIsolateGroupBoundConstructor,
|
|
Arguments([pointerValue], types: node.arguments.types),
|
|
);
|
|
}
|
|
|
|
Expression _verifyAndReplaceNativeCallable(
|
|
StaticInvocation node, {
|
|
bool fromFunction = false,
|
|
required Expression Function(StaticInvocation, Expression, bool)
|
|
replacement,
|
|
}) {
|
|
if (_isMissingArguments(node)) {
|
|
return node;
|
|
}
|
|
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(
|
|
diag.ffiNotStatic.withArguments(name: 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(
|
|
diag.ffiExpectedNoExceptionalReturn.withArguments(
|
|
returnType: ffiFuncType.returnType,
|
|
),
|
|
node.fileOffset,
|
|
1,
|
|
node.location?.file,
|
|
);
|
|
return node;
|
|
}
|
|
} else {
|
|
// The exceptional return value is not optional for other return types.
|
|
if (!hasExceptionalReturn) {
|
|
diagnosticReporter.report(
|
|
diag.ffiExpectedExceptionalReturn.withArguments(
|
|
returnType: ffiFuncType.returnType,
|
|
),
|
|
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.
|
|
final exceptionalReturnValue = _constantEvaluator.evaluate(
|
|
staticTypeContext!,
|
|
exceptionalReturn,
|
|
);
|
|
if (exceptionalReturnValue is UnevaluatedConstant) {
|
|
diagnosticReporter.report(
|
|
diag.ffiExpectedConstant,
|
|
node.fileOffset,
|
|
1,
|
|
node.location?.file,
|
|
);
|
|
return node;
|
|
}
|
|
|
|
// Moreover it may not be null.
|
|
if (exceptionalReturnValue is NullConstant) {
|
|
diagnosticReporter.report(
|
|
diag.ffiExceptionalReturnNull,
|
|
node.fileOffset,
|
|
1,
|
|
node.location?.file,
|
|
);
|
|
return node;
|
|
}
|
|
|
|
final DartType returnType = exceptionalReturn.getStaticType(
|
|
staticTypeContext!,
|
|
);
|
|
|
|
if (!env.isSubtypeOf(returnType, funcType.returnType)) {
|
|
diagnosticReporter.report(
|
|
diag.ffiDartTypeMismatch.withArguments(
|
|
actualType: returnType,
|
|
expectedType: funcType.returnType,
|
|
),
|
|
exceptionalReturn.fileOffset,
|
|
1,
|
|
exceptionalReturn.location?.file,
|
|
);
|
|
return node;
|
|
}
|
|
|
|
exceptionalReturn = ConstantExpression(
|
|
exceptionalReturnValue,
|
|
returnType,
|
|
);
|
|
}
|
|
|
|
final compoundClasses = funcType.positionalParameters
|
|
.whereType<InterfaceType>()
|
|
.map((t) => t.classNode)
|
|
.where((c) => c.superclass == structClass || c.superclass == unionClass)
|
|
.toList();
|
|
return invokeCompoundConstructors(
|
|
replacement(node, exceptionalReturn, isStaticFunction),
|
|
compoundClasses,
|
|
);
|
|
}
|
|
|
|
Expression _verifyAndReplaceNativeCallableIsolateLocal(
|
|
StaticInvocation node, {
|
|
bool fromFunction = false,
|
|
}) {
|
|
return _verifyAndReplaceNativeCallable(
|
|
node,
|
|
fromFunction: fromFunction,
|
|
replacement: fromFunction
|
|
? (node, exceptionalReturn, _) =>
|
|
_replaceFromFunction(node, exceptionalReturn)
|
|
: _replaceNativeCallableIsolateLocalConstructor,
|
|
);
|
|
}
|
|
|
|
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([VariableStatement(sourceVar)]),
|
|
referencedStruct.generateStore(
|
|
sourceVar,
|
|
dartType: node.arguments.types[0],
|
|
offsetInBytes: targetOffset,
|
|
typedDataBase: target,
|
|
transformer: this,
|
|
fileOffset: node.fileOffset,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Replaces a `.refWithFinalizer` on a compound pointer extension with a
|
|
/// typed data view.
|
|
Expression _replaceRefWithFinalizer(StaticInvocation node) {
|
|
final dartType = node.arguments.types[0] as InterfaceType;
|
|
final constructors = dartType.classNode.constructors;
|
|
final fromTypedDataCtor = constructors.firstWhere(
|
|
(c) => c.name == Name("#fromTypedData"),
|
|
);
|
|
// args: (receiver, finalizer, {token})
|
|
final pointer = node.arguments.positional[0];
|
|
final finalizer = NamedExpression(
|
|
'finalizer',
|
|
node.arguments.positional[1],
|
|
);
|
|
NamedExpression? token;
|
|
if (node.arguments.named.isNotEmpty) {
|
|
token = node.arguments.named[0];
|
|
}
|
|
|
|
final cast = InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
pointer,
|
|
castMethod.name,
|
|
Arguments(const [], types: [uint8Type]),
|
|
interfaceTarget: castMethod,
|
|
functionType: FunctionTypeInstantiator.instantiate(
|
|
castMethod.getterType as FunctionType,
|
|
[uint8Type],
|
|
),
|
|
);
|
|
|
|
return ConstructorInvocation(
|
|
fromTypedDataCtor,
|
|
Arguments([
|
|
StaticInvocation(
|
|
uint8PointerAsTypedList,
|
|
Arguments(
|
|
[cast, inlineSizeOf(dartType)!],
|
|
named: [finalizer, if (token != null) token],
|
|
),
|
|
),
|
|
ConstantExpression(IntConstant(0)),
|
|
inlineSizeOf(dartType)!,
|
|
]),
|
|
);
|
|
}
|
|
|
|
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([
|
|
VariableStatement(arrayVar),
|
|
VariableStatement(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)!),
|
|
),
|
|
]),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Replaces `StructArray<MyCompound>.elements` and `UnionArray<MyCompound>.elements` with:
|
|
///
|
|
/// ```
|
|
/// _ArrayList<MyCompound>(this._array, sizeOf(MyCompound), MyCompound.#fromTypedDataBase);
|
|
/// ```
|
|
Expression _replaceArrayElements(StaticInvocation node) {
|
|
final dartType = node.arguments.types[0];
|
|
final clazz = (dartType as InterfaceType).classNode;
|
|
final constructor = clazz.constructors.firstWhere(
|
|
(c) => c.name == Name("#fromTypedDataBase"),
|
|
);
|
|
|
|
return ConstructorInvocation(
|
|
arrayListConstructor,
|
|
Arguments(
|
|
[
|
|
node.arguments.positional[0],
|
|
inlineSizeOf(dartType)!,
|
|
ConstantExpression(
|
|
dartType.typeArguments.isNotEmpty
|
|
? InstantiationConstant(
|
|
ConstructorTearOffConstant(constructor),
|
|
dartType.typeArguments,
|
|
)
|
|
: ConstructorTearOffConstant(constructor),
|
|
),
|
|
],
|
|
types: [dartType],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Replaces `ArrayArray<Type>.elements` with:
|
|
///
|
|
/// ```
|
|
/// _ArrayArrayList<Type>(this._array, sizeOf(Type));
|
|
/// ```
|
|
Expression _replaceArrayArrayElements(StaticInvocation node) {
|
|
final dartType = node.arguments.types[0];
|
|
final elementType = arraySingleElementType(dartType as InterfaceType);
|
|
|
|
return ConstructorInvocation(
|
|
arrayArrayListConstructor,
|
|
Arguments(
|
|
[
|
|
node.arguments.positional[0],
|
|
inlineSizeOf(elementType as InterfaceType)!,
|
|
],
|
|
types: [dartType],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Replaces `AbiSpecificIntegerArray<Type>.elements` with:
|
|
///
|
|
/// ```
|
|
/// AbiSpecificIntegerArrayList<Type>(
|
|
/// this._array,
|
|
/// (Array<Type> array, int index) {
|
|
/// return _loadAbiSpecificIntAtIndex(array._typedDataBase, array._offsetInBytes, index);
|
|
/// },
|
|
/// (Array<Type> array, int index, int value) {
|
|
/// return _storeAbiSpecificIntAtIndex(array._typedDataBase, array._offsetInBytes, index, value);
|
|
/// }
|
|
/// );
|
|
/// ```
|
|
Expression _replaceAbiSpecificIntegerElements(StaticInvocation node) {
|
|
final typeArg = node.arguments.types[0];
|
|
final nativeTypeCfe =
|
|
NativeTypeCfe(this, typeArg) as AbiSpecificNativeTypeCfe;
|
|
|
|
final arrayLoadVar = VariableDeclaration(
|
|
"#array",
|
|
type: InterfaceType(arrayClass, Nullability.nonNullable, [typeArg]),
|
|
isSynthesized: true,
|
|
)..fileOffset = node.fileOffset;
|
|
final indexLoadVar = VariableDeclaration(
|
|
"#index",
|
|
type: coreTypes.intNonNullableRawType,
|
|
isSynthesized: true,
|
|
)..fileOffset = node.fileOffset;
|
|
final loadClosure = FunctionExpression(
|
|
FunctionNode(
|
|
ReturnStatement(
|
|
abiSpecificLoadOrStoreExpression(
|
|
nativeTypeCfe,
|
|
typedDataBase: getCompoundTypedDataBaseField(
|
|
VariableGet(arrayLoadVar),
|
|
node.fileOffset,
|
|
),
|
|
offsetInBytes: getCompoundOffsetInBytesField(
|
|
VariableGet(arrayLoadVar),
|
|
node.fileOffset,
|
|
),
|
|
index: VariableGet(indexLoadVar),
|
|
fileOffset: node.fileOffset,
|
|
),
|
|
),
|
|
positionalParameters: [arrayLoadVar, indexLoadVar],
|
|
returnType: coreTypes.intNonNullableRawType,
|
|
),
|
|
);
|
|
|
|
final arrayStoreVar = VariableDeclaration(
|
|
"#array",
|
|
type: InterfaceType(arrayClass, Nullability.nonNullable, [typeArg]),
|
|
isSynthesized: true,
|
|
)..fileOffset = node.fileOffset;
|
|
final indexStoreVar = VariableDeclaration(
|
|
"#index",
|
|
type: coreTypes.intNonNullableRawType,
|
|
isSynthesized: true,
|
|
)..fileOffset = node.fileOffset;
|
|
final valueStoreVar = VariableDeclaration(
|
|
"#value",
|
|
type: coreTypes.intNullableRawType,
|
|
isSynthesized: true,
|
|
)..fileOffset = node.fileOffset;
|
|
final storeClosure = FunctionExpression(
|
|
FunctionNode(
|
|
ReturnStatement(
|
|
abiSpecificLoadOrStoreExpression(
|
|
nativeTypeCfe,
|
|
typedDataBase: getCompoundTypedDataBaseField(
|
|
VariableGet(arrayStoreVar),
|
|
node.fileOffset,
|
|
),
|
|
offsetInBytes: getCompoundOffsetInBytesField(
|
|
VariableGet(arrayStoreVar),
|
|
node.fileOffset,
|
|
),
|
|
index: VariableGet(indexStoreVar),
|
|
value: VariableGet(valueStoreVar),
|
|
fileOffset: node.fileOffset,
|
|
),
|
|
),
|
|
positionalParameters: [arrayStoreVar, indexStoreVar, valueStoreVar],
|
|
returnType: coreTypes.intNonNullableRawType,
|
|
),
|
|
);
|
|
|
|
return ConstructorInvocation(
|
|
abiSpecificIntegerArrayListConstructor,
|
|
Arguments(
|
|
[node.arguments.positional[0], loadClosure, storeClosure],
|
|
types: [typeArg],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 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 = [
|
|
VariableStatement(arrayVar),
|
|
VariableStatement(indexVar),
|
|
ExpressionStatement(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(arrayVar),
|
|
arrayCheckIndex.name,
|
|
Arguments([VariableGet(indexVar)]),
|
|
interfaceTarget: arrayCheckIndex,
|
|
functionType: arrayCheckIndex.getterType as FunctionType,
|
|
),
|
|
),
|
|
VariableStatement(singleElementSizeVar),
|
|
VariableStatement(elementSizeVar),
|
|
VariableStatement(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),
|
|
arrayVariableLengthField.name,
|
|
interfaceTarget: arrayVariableLengthField,
|
|
resultType: arrayVariableLengthField.type,
|
|
),
|
|
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, VariableStatement(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(
|
|
diag.ffiExtendsOrImplementsSealedClass.withArguments(
|
|
sealedClassName: extended.name,
|
|
),
|
|
klass.fileOffset,
|
|
1,
|
|
klass.location?.file,
|
|
);
|
|
throw FfiStaticTypeError();
|
|
}
|
|
}
|
|
|
|
void _ensureIsLeafIsConst(StaticInvocation node) {
|
|
final isLeaf = getIsLeafBoolean(node);
|
|
if (isLeaf == null) {
|
|
diagnosticReporter.report(
|
|
diag.ffiExpectedConstantArg.withArguments(name: '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) {
|
|
if (_isMissingArguments(node)) {
|
|
return 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(
|
|
diag.ffiAddressOfMustBeNative,
|
|
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 {
|
|
if (!allowEnclosingMutation) {
|
|
_reportEnclosingMutationInProcedureMode(
|
|
node,
|
|
'@Native(isLeaf: true) function invocation',
|
|
);
|
|
}
|
|
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))
|
|
..parent = parent;
|
|
}
|
|
|
|
/// 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 InvalidExpression && argument.expression is AsExpression) {
|
|
final parent = argument.expression as AsExpression;
|
|
final (
|
|
_,
|
|
_,
|
|
transformedArgument,
|
|
) = _replaceNativeCallParameterAndArgument(
|
|
parameter,
|
|
parameterType,
|
|
parent.operand,
|
|
fileOffset,
|
|
);
|
|
return (
|
|
'E',
|
|
parameterType,
|
|
InvalidExpression('Invalid Type', transformedArgument),
|
|
);
|
|
}
|
|
if (argument is InstanceInvocation &&
|
|
argument.interfaceTarget == castMethod) {
|
|
// Argument is .address.cast(), so truncating .cast()
|
|
final subExpression = argument.receiver;
|
|
return _replaceNativeCallParameterAndArgument(
|
|
parameter,
|
|
parameterType,
|
|
subExpression,
|
|
fileOffset,
|
|
);
|
|
}
|
|
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),
|
|
);
|
|
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(
|
|
diag.ffiAddressReceiver,
|
|
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([VariableStatement(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();
|
|
}
|
|
}
|