| // 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); |