395e5f6037
I added TODOs for removing the old code after the constants update has landed. Change-Id: Icde8d68e4e7c4b51e9536f09ce4e514e3b00336d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98661 Auto-Submit: Daco Harkes <dacoharkes@google.com> Reviewed-by: Kevin Millikin <kmillikin@google.com>
416 lines
13 KiB
Dart
416 lines
13 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.
|
|
|
|
library vm.transformations.ffi_definitions;
|
|
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:front_end/src/api_unstable/vm.dart'
|
|
show
|
|
templateFfiFieldAnnotation,
|
|
templateFfiStructAnnotation,
|
|
templateFfiTypeMismatch,
|
|
templateFfiFieldInitializer;
|
|
|
|
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/target/targets.dart' show DiagnosticReporter;
|
|
|
|
import 'ffi.dart'
|
|
show
|
|
ReplacedMembers,
|
|
NativeType,
|
|
FfiTransformer,
|
|
nativeTypeSizes,
|
|
WORD_SIZE;
|
|
|
|
/// Checks and expands the dart:ffi @struct and field annotations.
|
|
///
|
|
/// Sample input:
|
|
/// @ffi.struct
|
|
/// class Coord extends ffi.Pointer<Void> {
|
|
/// @ffi.Double()
|
|
/// double x;
|
|
///
|
|
/// @ffi.Double()
|
|
/// double y;
|
|
///
|
|
/// @ffi.Pointer()
|
|
/// Coord next;
|
|
///
|
|
/// external static int sizeOf();
|
|
/// }
|
|
///
|
|
/// Sample output:
|
|
/// class Coordinate extends ffi.Pointer<ffi.Void> {
|
|
/// ffi.Pointer<ffi.Double> get _xPtr => cast();
|
|
/// set x(double v) => _xPtr.store(v);
|
|
/// double get x => _xPtr.load();
|
|
///
|
|
/// ffi.Pointer<ffi.Double> get _yPtr =>
|
|
/// offsetBy(ffi.sizeOf<ffi.Double>() * 1).cast();
|
|
/// set y(double v) => _yPtr.store(v);
|
|
/// double get y => _yPtr.load();
|
|
///
|
|
/// ffi.Pointer<Coordinate> get _nextPtr =>
|
|
/// offsetBy(ffi.sizeOf<ffi.Double>() * 2).cast();
|
|
/// set next(Coordinate v) => _nextPtr.store(v);
|
|
/// Coordinate get next => _nextPtr.load();
|
|
///
|
|
/// static int sizeOf() => 24;
|
|
/// }
|
|
ReplacedMembers transformLibraries(
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
List<Library> libraries,
|
|
DiagnosticReporter diagnosticReporter) {
|
|
final LibraryIndex index = LibraryIndex(
|
|
component, const ["dart:ffi", "dart:_internal", "dart:core"]);
|
|
if (!index.containsLibrary("dart:ffi")) {
|
|
// if dart:ffi is not loaded, do not do the transformation
|
|
return ReplacedMembers({}, {});
|
|
}
|
|
final transformer = new _FfiDefinitionTransformer(
|
|
index, coreTypes, hierarchy, diagnosticReporter);
|
|
libraries.forEach(transformer.visitLibrary);
|
|
return ReplacedMembers(
|
|
transformer.replacedGetters, transformer.replacedSetters);
|
|
}
|
|
|
|
/// Checks and expands the dart:ffi @struct and field annotations.
|
|
class _FfiDefinitionTransformer extends FfiTransformer {
|
|
final LibraryIndex index;
|
|
final Field _internalIs64Bit;
|
|
final Constructor _unimplementedErrorCtor;
|
|
static const String _errorOn32BitMessage =
|
|
"Code-gen for FFI structs is not supported on 32-bit platforms.";
|
|
|
|
Map<Field, Procedure> replacedGetters = {};
|
|
Map<Field, Procedure> replacedSetters = {};
|
|
|
|
_FfiDefinitionTransformer(this.index, CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy, DiagnosticReporter diagnosticReporter)
|
|
: _internalIs64Bit = index.getTopLevelMember('dart:_internal', 'is64Bit'),
|
|
_unimplementedErrorCtor =
|
|
index.getMember('dart:core', 'UnimplementedError', ''),
|
|
super(index, coreTypes, hierarchy, diagnosticReporter) {}
|
|
|
|
Statement guardOn32Bit(Statement body) {
|
|
final Throw error = Throw(ConstructorInvocation(_unimplementedErrorCtor,
|
|
Arguments([StringLiteral(_errorOn32BitMessage)])));
|
|
return IfStatement(
|
|
StaticGet(_internalIs64Bit), body, ExpressionStatement(error));
|
|
}
|
|
|
|
@override
|
|
visitClass(Class node) {
|
|
if (node == pointerClass || !hierarchy.isSubtypeOf(node, pointerClass)) {
|
|
return node;
|
|
}
|
|
|
|
// Because subtypes of Pointer are only allocated by allocate<Pointer<..>>()
|
|
// and fromAddress<Pointer<..>>() which are not recognized as constructor
|
|
// calls, we need to prevent these classes from being tree shaken out.
|
|
_preventTreeShaking(node);
|
|
|
|
_checkFieldAnnotations(node);
|
|
_checkConstructors(node);
|
|
|
|
bool isStruct = _checkStructAnnotation(node);
|
|
if (isStruct) {
|
|
int size = _replaceFields(node);
|
|
_replaceSizeOfMethod(node, size);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
bool _checkStructAnnotation(Class node) {
|
|
bool isStruct = _hasAnnotation(node);
|
|
if (!isStruct && node.fields.isNotEmpty) {
|
|
diagnosticReporter.report(
|
|
templateFfiStructAnnotation.withArguments(node.name),
|
|
node.fileOffset,
|
|
1,
|
|
node.fileUri);
|
|
}
|
|
return isStruct;
|
|
}
|
|
|
|
void _checkFieldAnnotations(Class node) {
|
|
for (Field f in node.fields) {
|
|
if (f.initializer is! NullLiteral) {
|
|
diagnosticReporter.report(
|
|
templateFfiFieldInitializer.withArguments(f.name.name),
|
|
f.fileOffset,
|
|
f.name.name.length,
|
|
f.fileUri);
|
|
}
|
|
List<NativeType> annos = _getAnnotations(f).toList();
|
|
if (annos.length != 1) {
|
|
diagnosticReporter.report(
|
|
templateFfiFieldAnnotation.withArguments(f.name.name),
|
|
f.fileOffset,
|
|
f.name.name.length,
|
|
f.fileUri);
|
|
} else {
|
|
DartType dartType = f.type;
|
|
DartType nativeType =
|
|
InterfaceType(nativeTypesClasses[annos.first.index]);
|
|
DartType shouldBeDartType = convertNativeTypeToDartType(nativeType);
|
|
if (!env.isSubtypeOf(dartType, shouldBeDartType)) {
|
|
diagnosticReporter.report(
|
|
templateFfiTypeMismatch.withArguments(
|
|
dartType, shouldBeDartType, nativeType),
|
|
f.fileOffset,
|
|
1,
|
|
f.location.file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void _checkConstructors(Class node) {
|
|
List<Initializer> toRemove = [];
|
|
for (Constructor c in node.constructors) {
|
|
for (Initializer i in c.initializers) {
|
|
if (i is FieldInitializer) {
|
|
toRemove.add(i);
|
|
diagnosticReporter.report(
|
|
templateFfiFieldInitializer.withArguments(i.field.name.name),
|
|
i.fileOffset,
|
|
1,
|
|
i.location.file);
|
|
}
|
|
}
|
|
}
|
|
// Remove initializers referring to fields to prevent cascading errors.
|
|
for (Initializer i in toRemove) {
|
|
i.remove();
|
|
}
|
|
}
|
|
|
|
/// Computes the field offsets in the struct and replaces the fields with
|
|
/// getters and setters using these offsets.
|
|
///
|
|
/// Returns the total size of the struct.
|
|
int _replaceFields(Class node) {
|
|
List<Field> fields = [];
|
|
List<NativeType> types = [];
|
|
|
|
for (Field f in node.fields) {
|
|
List<NativeType> annos = _getAnnotations(f).toList();
|
|
if (annos.length == 1) {
|
|
NativeType t = annos.first;
|
|
fields.add(f);
|
|
types.add(t);
|
|
}
|
|
}
|
|
|
|
List<int> offsets = _calculateOffsets(types);
|
|
int size = _calculateSize(offsets, types);
|
|
|
|
for (int i = 0; i < fields.length; i++) {
|
|
List<Procedure> methods =
|
|
_generateMethodsForField(fields[i], types[i], offsets[i]);
|
|
for (Procedure p in methods) {
|
|
node.addMember(p);
|
|
}
|
|
}
|
|
|
|
for (Field f in fields) {
|
|
f.remove();
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/// Sample output:
|
|
/// ffi.Pointer<ffi.Double> get _xPtr => cast();
|
|
/// double get x => _xPtr.load();
|
|
/// set x(double v) => _xPtr.store(v);
|
|
List<Procedure> _generateMethodsForField(
|
|
Field field, NativeType type, int offset) {
|
|
DartType nativeType = type == NativeType.kPointer
|
|
? field.type
|
|
: InterfaceType(nativeTypesClasses[type.index]);
|
|
DartType pointerType = InterfaceType(pointerClass, [nativeType]);
|
|
Name pointerName = Name('#_ptr_${field.name.name}');
|
|
|
|
// Sample output for primitives:
|
|
// ffi.Pointer<ffi.Double> get _xPtr => cast<ffi.Pointer<ffi.Double>>();
|
|
// Sample output for structs:
|
|
// ffi.Pointer<Coordinate> get _xPtr => offsetBy(16).cast<...>();
|
|
Expression offsetExpression = ThisExpression();
|
|
if (offset != 0) {
|
|
offsetExpression = MethodInvocation(offsetExpression, offsetByMethod.name,
|
|
Arguments([IntLiteral(offset)]), offsetByMethod);
|
|
}
|
|
Procedure pointerGetter = Procedure(
|
|
pointerName,
|
|
ProcedureKind.Getter,
|
|
FunctionNode(
|
|
guardOn32Bit(ReturnStatement(MethodInvocation(
|
|
offsetExpression,
|
|
castMethod.name,
|
|
Arguments([], types: [pointerType]),
|
|
castMethod))),
|
|
returnType: pointerType));
|
|
|
|
// Sample output:
|
|
// double get x => _xPtr.load<double>();
|
|
Procedure getter = Procedure(
|
|
field.name,
|
|
ProcedureKind.Getter,
|
|
FunctionNode(
|
|
guardOn32Bit(ReturnStatement(MethodInvocation(
|
|
PropertyGet(ThisExpression(), pointerName, pointerGetter),
|
|
loadMethod.name,
|
|
Arguments([], types: [field.type]),
|
|
loadMethod))),
|
|
returnType: field.type));
|
|
|
|
// Sample output:
|
|
// set x(double v) => _xPtr.store(v);
|
|
VariableDeclaration argument = VariableDeclaration('#v', type: field.type);
|
|
Procedure setter = Procedure(
|
|
field.name,
|
|
ProcedureKind.Setter,
|
|
FunctionNode(
|
|
guardOn32Bit(ReturnStatement(MethodInvocation(
|
|
PropertyGet(ThisExpression(), pointerName, pointerGetter),
|
|
storeMethod.name,
|
|
Arguments([VariableGet(argument)]),
|
|
storeMethod))),
|
|
returnType: VoidType(),
|
|
positionalParameters: [argument]));
|
|
|
|
replacedGetters[field] = getter;
|
|
replacedSetters[field] = setter;
|
|
|
|
return [pointerGetter, getter, setter];
|
|
}
|
|
|
|
/// Sample input:
|
|
/// external static int sizeOf();
|
|
///
|
|
/// Sample output:
|
|
/// static int sizeOf() => 24;
|
|
void _replaceSizeOfMethod(Class struct, int size) {
|
|
Procedure sizeOf = _findProcedure(struct, 'sizeOf');
|
|
if (sizeOf == null || !sizeOf.isExternal || !sizeOf.isStatic) {
|
|
return;
|
|
}
|
|
|
|
// replace in place to avoid going over use sites
|
|
sizeOf.function = FunctionNode(
|
|
guardOn32Bit(ReturnStatement(IntLiteral(size))),
|
|
returnType: InterfaceType(intClass));
|
|
sizeOf.isExternal = false;
|
|
}
|
|
|
|
// TODO(dacoharkes): move to VM, take into account architecture
|
|
// https://github.com/dart-lang/sdk/issues/35768
|
|
int _sizeInBytes(NativeType t) {
|
|
int size = nativeTypeSizes[t.index];
|
|
if (size == WORD_SIZE) {
|
|
size = 8;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
int _align(int offset, int size) {
|
|
int remainder = offset % size;
|
|
if (remainder != 0) {
|
|
offset -= remainder;
|
|
offset += size;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
// TODO(dacoharkes): move to VM, take into account architecture
|
|
// https://github.com/dart-lang/sdk/issues/35768
|
|
List<int> _calculateOffsets(List<NativeType> types) {
|
|
int offset = 0;
|
|
List<int> offsets = [];
|
|
for (NativeType t in types) {
|
|
int size = _sizeInBytes(t);
|
|
offset = _align(offset, size);
|
|
offsets.add(offset);
|
|
offset += size;
|
|
}
|
|
return offsets;
|
|
}
|
|
|
|
// TODO(dacoharkes): move to VM, take into account architecture
|
|
// https://github.com/dart-lang/sdk/issues/35768
|
|
int _calculateSize(List<int> offsets, List<NativeType> types) {
|
|
if (offsets.isEmpty) {
|
|
return 0;
|
|
}
|
|
int largestElement = types.map((e) => _sizeInBytes(e)).reduce(math.max);
|
|
int highestOffsetIndex = types.length - 1;
|
|
int highestOffset = offsets[highestOffsetIndex];
|
|
int highestOffsetSize = _sizeInBytes(types[highestOffsetIndex]);
|
|
return _align(highestOffset + highestOffsetSize, largestElement);
|
|
}
|
|
|
|
bool _hasAnnotation(Class node) {
|
|
// Pre constant 2018 update.
|
|
// TODO(dacoharkes): Remove pre constant 2018 after constants change landed.
|
|
for (Expression e in node.annotations) {
|
|
if (e is StaticGet) {
|
|
if (e.target == structField) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
Iterable<Class> postConstant2018 = node.annotations
|
|
.whereType<ConstantExpression>()
|
|
.map((expr) => expr.constant)
|
|
.whereType<InstanceConstant>()
|
|
.map((constant) => constant.classNode);
|
|
return postConstant2018.contains(structClass);
|
|
}
|
|
|
|
void _preventTreeShaking(Class node) {
|
|
node.addAnnotation(ConstructorInvocation(
|
|
pragmaConstructor, Arguments([StringLiteral("vm:entry-point")])));
|
|
}
|
|
|
|
NativeType _getFieldType(Class c) {
|
|
NativeType fieldType = getType(c);
|
|
|
|
if (fieldType == NativeType.kVoid) {
|
|
// Fields cannot have Void types.
|
|
return null;
|
|
}
|
|
return fieldType;
|
|
}
|
|
|
|
Iterable<NativeType> _getAnnotations(Field node) {
|
|
Iterable<NativeType> preConstant2018 = node.annotations
|
|
.whereType<ConstructorInvocation>()
|
|
.map((expr) => expr.target.parent)
|
|
.map((klass) => _getFieldType(klass))
|
|
.where((type) => type != null);
|
|
Iterable<NativeType> postConstant2018 = node.annotations
|
|
.whereType<ConstantExpression>()
|
|
.map((expr) => expr.constant)
|
|
.whereType<InstanceConstant>()
|
|
.map((constant) => constant.classNode)
|
|
.map((klass) => _getFieldType(klass))
|
|
.where((type) => type != null);
|
|
// TODO(dacoharkes): Remove preConstant2018 after constants change landed.
|
|
return postConstant2018.followedBy(preConstant2018);
|
|
}
|
|
}
|
|
|
|
/// Finds procedure with name, otherwise returns null.
|
|
Procedure _findProcedure(Class c, String name) =>
|
|
c.procedures.firstWhere((p) => p.name.name == name, orElse: () => null);
|