| // 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 file generates the extension methods public API and the extension | |
| // methods patch file for all integers, double, and float. | |
| // The PointerPointer and PointerStruct extension are written by hand since | |
| // those are not repetitive. | |
| import 'dart:io'; | |
| import 'package:args/args.dart'; | |
| // | |
| // Configuration. | |
| // | |
| const configuration = [ | |
| Config("Int8", "int", "Int8List", 1), | |
| Config("Int16", "int", "Int16List", 2), | |
| Config("Int32", "int", "Int32List", 4), | |
| Config("Int64", "int", "Int64List", 8), | |
| Config("Uint8", "int", "Uint8List", 1), | |
| Config("Uint16", "int", "Uint16List", 2), | |
| Config("Uint32", "int", "Uint32List", 4), | |
| Config("Uint64", "int", "Uint64List", 8), | |
| Config("Float", "double", "Float32List", 4), | |
| Config("Double", "double", "Float64List", 8), | |
| Config("Bool", "bool", kDoNotEmit, 1, since: Version(2, 15)), | |
| ]; | |
| /// A container to generate the extension for. | |
| class Container { | |
| final String name; | |
| /// Since annotation for the extension. | |
| final Version? since; | |
| const Container(this.name, this.since); | |
| static const pointer = Container('Pointer', null); | |
| static const array = Container('Array', Version(2, 13)); | |
| static const typedData = Container('TypedData', Version(3, 5)); | |
| static const values = [pointer, array, typedData]; | |
| } | |
| // | |
| // Generator. | |
| // | |
| void main() { | |
| update(Uri.file('sdk/lib/ffi/ffi.dart'), generatePublicExtension); | |
| update( | |
| Uri.file('sdk/lib/_internal/vm/lib/ffi_patch.dart'), | |
| generatePatchExtension, | |
| ); | |
| } | |
| void update(Uri fileName, Function(StringBuffer, Config, Container) generator) { | |
| final file = File.fromUri(fileName); | |
| if (!file.existsSync()) { | |
| print('$fileName does not exist, run from the root of the SDK.'); | |
| return; | |
| } | |
| final fileContents = file.readAsStringSync(); | |
| final split1 = fileContents.split(header); | |
| if (split1.length != 2) { | |
| print('$fileName has unexpected contents.'); | |
| print(split1.length); | |
| return; | |
| } | |
| final split2 = split1[1].split(footer); | |
| if (split2.length != 2) { | |
| print('$fileName has unexpected contents 2.'); | |
| print(split2.length); | |
| return; | |
| } | |
| final buffer = StringBuffer(); | |
| buffer.write(split1[0]); | |
| buffer.write(header); | |
| for (final container in Container.values) { | |
| for (final config in configuration) { | |
| generator(buffer, config, container); | |
| } | |
| } | |
| buffer.write(footer); | |
| buffer.write(split2[1]); | |
| file.writeAsStringSync(buffer.toString()); | |
| final fmtResult = Process.runSync(Platform.resolvedExecutable, [ | |
| "format", | |
| fileName.toFilePath(), | |
| ]); | |
| if (fmtResult.exitCode != 0) { | |
| stderr.writeln( | |
| "Formatting failed:\n${fmtResult.stdout}\n${fmtResult.stderr}\n", | |
| ); | |
| exit(1); | |
| } | |
| print("Updated $fileName."); | |
| } | |
| const header = """ | |
| // | |
| // Generated code, do not edit! | |
| // | |
| // Code generated by `runtime/tools/ffi/sdk_lib_ffi_generator.dart`. | |
| // | |
| """; | |
| void generateHeader(StringBuffer buffer) { | |
| buffer.write(header); | |
| } | |
| void generatePublicExtension( | |
| StringBuffer buffer, | |
| Config config, | |
| Container container, | |
| ) { | |
| final nativeType = config.nativeType; | |
| final dartType = config.dartType; | |
| final typedListType = config.typedListType; | |
| final elementSize = config.elementSize; | |
| final bits = sizeOfBits(elementSize); | |
| String property; | |
| if (_isInt(nativeType)) { | |
| if (_isSigned(nativeType)) { | |
| property = "$bits-bit two's complement integer"; | |
| } else { | |
| property = "$bits-bit unsigned integer"; | |
| } | |
| } else if (nativeType == "Float") { | |
| property = "float"; | |
| } else if (nativeType == "Double") { | |
| property = "double"; | |
| } else if (nativeType == "Bool") { | |
| property = "bool"; | |
| } else { | |
| throw "Unexpected type: $nativeType"; | |
| } | |
| const platformIntPtr = """ | |
| /// | |
| /// On 32-bit platforms this is a 32-bit integer, and on 64-bit platforms | |
| /// this is a 64-bit integer. | |
| """; | |
| final platform = nativeType == "IntPtr" ? platformIntPtr : ""; | |
| final intSignedTruncate = | |
| """ | |
| /// | |
| /// A Dart integer is truncated to $bits bits (as if by `.toSigned($bits)`) before | |
| /// being stored, and the $bits-bit value is sign-extended when it is loaded. | |
| """; | |
| const intPtrTruncate = """ | |
| /// | |
| /// On 32-bit platforms a Dart integer is truncated to 32 bits (as if by | |
| /// `.toSigned(32)`) before being stored, and the 32-bit value is | |
| /// sign-extended when it is loaded. | |
| """; | |
| final intUnsignedTruncate = | |
| """ | |
| /// | |
| /// A Dart integer is truncated to $bits bits (as if by `.toUnsigned($bits)`) before | |
| /// being stored, and the $bits-bit value is zero-extended when it is loaded. | |
| """; | |
| const floatTruncate = """ | |
| /// | |
| /// A Dart double loses precision before being stored, and the float value is | |
| /// converted to a double when it is loaded. | |
| """; | |
| String truncate = ""; | |
| if (nativeType == "IntPtr") { | |
| truncate = intPtrTruncate; | |
| } else if (_isInt(nativeType) && elementSize != 8) { | |
| truncate = _isSigned(nativeType) ? intSignedTruncate : intUnsignedTruncate; | |
| } else if (nativeType == "Float") { | |
| truncate = floatTruncate; | |
| } | |
| final alignmentDefault = | |
| """ | |
| /// | |
| /// The [address] must be ${sizeOf(elementSize)}-byte aligned. | |
| """; | |
| const alignmentIntptr = """ | |
| /// | |
| /// On 32-bit platforms the [address] must be 4-byte aligned, and on 64-bit | |
| /// platforms the [address] must be 8-byte aligned. | |
| """; | |
| String alignment = ""; | |
| if (nativeType == "IntPtr") { | |
| alignment = alignmentIntptr; | |
| } else if (elementSize != 1) { | |
| alignment = alignmentDefault; | |
| } | |
| final asTypedList = typedListType == kDoNotEmit | |
| ? "" | |
| : """ | |
| /// Creates a typed list view backed by memory in the address space. | |
| /// | |
| /// The returned view will allow access to the memory range from [address] | |
| /// to `address + sizeOf<$nativeType>() * length`. | |
| /// | |
| /// The user has to ensure the memory range is accessible while using the | |
| /// returned list. | |
| /// | |
| /// If provided, [finalizer] will be run on the pointer once the typed list | |
| /// is GCed. If provided, [token] will be passed to [finalizer], otherwise | |
| /// the this pointer itself will be passed. | |
| $alignment external $typedListType asTypedList( | |
| int length, { | |
| @Since('3.1') Pointer<NativeFinalizerFunction>? finalizer, | |
| @Since('3.1') Pointer<Void>? token, | |
| }); | |
| """; | |
| final since = | |
| Version.latest(config.since, container.since)?.sinceAnnotation ?? ''; | |
| switch (container) { | |
| case Container.pointer: | |
| buffer.write(""" | |
| /// Extension on [Pointer] specialized for the type argument [$nativeType]. | |
| $since extension ${nativeType}Pointer on Pointer<$nativeType> { | |
| /// The $property at [address]. | |
| $platform$truncate$alignment external $dartType get value; | |
| external void set value($dartType value); | |
| /// The $property at `address + sizeOf<$nativeType>() * index`. | |
| $platform$truncate$alignment external $dartType operator [](int index); | |
| /// The $property at `address + sizeOf<$nativeType>() * index`. | |
| $platform$truncate$alignment external void operator []=(int index, $dartType value); | |
| /// Pointer arithmetic (takes element size into account). | |
| @Deprecated('Use operator + instead') | |
| Pointer<$nativeType> elementAt(int index) => Pointer.fromAddress(address + sizeOf<$nativeType>() * index); | |
| /// A pointer to the [offset]th [$nativeType] after this one. | |
| /// | |
| /// Returns a pointer to the [$nativeType] whose address is | |
| /// [offset] times the size of `$nativeType` after the address of this pointer. | |
| /// That is `(this + offset).address == this.address + offset * sizeOf<$nativeType>()`. | |
| /// | |
| /// Also `(this + offset).value` is equivalent to `this[offset]`, | |
| /// and similarly for setting. | |
| @Since('3.3') | |
| @pragma("vm:prefer-inline") | |
| Pointer<$nativeType> operator +(int offset) => Pointer.fromAddress(address + sizeOf<$nativeType>() * offset); | |
| /// A pointer to the [offset]th [$nativeType] before this one. | |
| /// | |
| /// Equivalent to `this + (-offset)`. | |
| /// | |
| /// Returns a pointer to the [$nativeType] whose address is | |
| /// [offset] times the size of `$nativeType` before the address of this pointer. | |
| /// That is, `(this - offset).address == this.address - offset * sizeOf<$nativeType>()`. | |
| /// | |
| /// Also, `(this - offset).value` is equivalent to `this[-offset]`, | |
| /// and similarly for setting, | |
| @Since('3.3') | |
| @pragma("vm:prefer-inline") | |
| Pointer<$nativeType> operator -(int offset) => Pointer.fromAddress(address - sizeOf<$nativeType>() * offset); | |
| $asTypedList | |
| } | |
| """); | |
| case Container.array: | |
| final elementsGetterReturnType = _isBool(nativeType) | |
| ? 'List<$dartType>' | |
| : typedListType; | |
| buffer.write(""" | |
| /// Bounds checking indexing methods on [Array]s of [$nativeType]. | |
| $since extension ${nativeType}Array on Array<$nativeType> { | |
| /// Loads a Dart value from this array at [index]. | |
| /// | |
| /// This extension method must be invoked on a receiver of type `Array<T>` | |
| /// where `T` is a compile-time constant type. | |
| external $dartType operator [](int index); | |
| /// Stores a Dart value in this array at [index]. | |
| /// | |
| /// This extension method must be invoked on a receiver of type `Array<T>` | |
| /// where `T` is a compile-time constant type. | |
| external void operator []=(int index, $dartType value); | |
| /// A list view of the bytes of this array. | |
| /// | |
| /// Has the same length and elements (as accessed using the index operator) | |
| /// as this array, and writes to either the list or this arrary are visible | |
| /// in both. | |
| @Since('3.8') | |
| external $elementsGetterReturnType get elements; | |
| } | |
| """); | |
| case Container.typedData: | |
| if (typedListType != kDoNotEmit) { | |
| buffer.write(""" | |
| $since extension ${typedListType}Address on $typedListType { | |
| /// The memory address of the underlying data. | |
| /// | |
| /// An expression of the form `expression.address` denoting this `address` | |
| /// can only occurr as an entire argument expression in the invocation of | |
| /// a leaf [Native] external function. | |
| /// | |
| /// Example: | |
| /// | |
| /// ```dart import:typed_data | |
| /// @Native<Void Function(Pointer<$nativeType>)>(isLeaf: true) | |
| /// external void myFunction(Pointer<$nativeType> pointer); | |
| /// | |
| /// void main() { | |
| /// final list = $typedListType(10); | |
| /// myFunction(list.address); | |
| /// } | |
| /// ``` | |
| /// | |
| /// The expression before `.address` is evaluated like the left-hand-side of | |
| /// an assignment, to something that gives access to the storage behind the | |
| /// expression, which can be used both for reading and writing. The `.address` | |
| /// then gives a native pointer to that storage. | |
| /// | |
| /// The `.address` is evaluated just before calling into native code when | |
| /// invoking a leaf [Native] external function. This ensures the Dart garbage | |
| /// collector will not move the object that the address points in to. | |
| external Pointer<$nativeType> get address; | |
| } | |
| """); | |
| } | |
| } | |
| } | |
| void generatePatchExtension( | |
| StringBuffer buffer, | |
| Config config, | |
| Container container, | |
| ) { | |
| final nativeType = config.nativeType; | |
| final dartType = config.dartType; | |
| final typedListType = config.typedListType; | |
| final elementSize = config.elementSize; | |
| final sizeTimes = elementSize != 1 | |
| ? '${sizeOfIntPtrSize(elementSize)} * ' | |
| : ''; | |
| final asTypedList = typedListType == kDoNotEmit | |
| ? "" | |
| : """ | |
| @patch | |
| @pragma("vm:prefer-inline") | |
| $typedListType asTypedList( | |
| int length, { | |
| Pointer<NativeFinalizerFunction>? finalizer, | |
| Pointer<Void>? token, | |
| }) { | |
| ArgumentError.checkNotNull(this, "Pointer<$nativeType>"); | |
| ArgumentError.checkNotNull(length, "length"); | |
| _checkExternalTypedDataLength(length, $elementSize); | |
| _checkPointerAlignment(address, $elementSize); | |
| final result = _asExternalTypedData$nativeType(this, length); | |
| if (finalizer != null) { | |
| _attachAsTypedListFinalizer(finalizer, result, token ?? this, $sizeTimes length); | |
| } | |
| return result; | |
| } | |
| """; | |
| switch (container) { | |
| case Container.pointer: | |
| buffer.write(""" | |
| @patch | |
| extension ${nativeType}Pointer on Pointer<$nativeType> { | |
| @patch | |
| @pragma("vm:prefer-inline") | |
| $dartType get value => _load$nativeType(this, 0); | |
| @patch | |
| @pragma("vm:prefer-inline") | |
| set value($dartType value) => _store$nativeType(this, 0, value); | |
| @patch | |
| @pragma("vm:prefer-inline") | |
| $dartType operator [](int index) => _load$nativeType(this, ${sizeTimes}index); | |
| @patch | |
| @pragma("vm:prefer-inline") | |
| operator []=(int index, $dartType value) => _store$nativeType(this, ${sizeTimes}index, value); | |
| $asTypedList | |
| } | |
| """); | |
| case Container.array: | |
| final String elementsGetter; | |
| final String listHelperClass; | |
| if (_isBool(nativeType)) { | |
| elementsGetter = | |
| """ | |
| List<$dartType> get elements => _${nativeType}ArrayList(this); | |
| """; | |
| listHelperClass = | |
| """ | |
| class _${nativeType}ArrayList with ListMixin<$dartType>, FixedLengthListMixin<$dartType> { | |
| _${nativeType}ArrayList(this._array); | |
| final Array<$nativeType> _array; | |
| @override | |
| int get length => _array._size; | |
| @override | |
| $dartType operator [](int index) => _array[index]; | |
| @override | |
| operator []=(int index, $dartType value) { | |
| _array[index] = value; | |
| } | |
| } | |
| """; | |
| } else { | |
| elementsGetter = | |
| """ | |
| $typedListType get elements { | |
| assert(_nestedDimensionsFlattened == 1); | |
| final length = _size; | |
| final typedDataBase = _typedDataBase; | |
| if (typedDataBase is Pointer) { | |
| return typedDataBase | |
| ._offsetBy(_offsetInBytes) | |
| .cast<$nativeType>() | |
| .asTypedList(length); | |
| } | |
| final typedData = _typedDataBase as TypedData; | |
| final start = typedData.offsetInBytes + _offsetInBytes; | |
| return $typedListType.view(typedData.buffer, start, length); | |
| } | |
| """; | |
| listHelperClass = ""; | |
| } | |
| buffer.write(""" | |
| @patch | |
| extension ${nativeType}Array on Array<$nativeType> { | |
| @patch | |
| @pragma("vm:prefer-inline") | |
| $dartType operator [](int index) { | |
| _checkIndex(index); | |
| return _load$nativeType(_typedDataBase, _offsetInBytes + ${sizeTimes}index,); | |
| } | |
| @patch | |
| @pragma("vm:prefer-inline") | |
| operator []=(int index, $dartType value) { | |
| _checkIndex(index); | |
| return _store$nativeType(_typedDataBase, _offsetInBytes + ${sizeTimes}index , value,); | |
| } | |
| @patch | |
| $elementsGetter | |
| } | |
| $listHelperClass | |
| """); | |
| case Container.typedData: | |
| // Nothing to do, all rewritten in the CFE. | |
| } | |
| } | |
| final footer = """ | |
| // | |
| // End of generated code. | |
| // | |
| """; | |
| void generateFooter(StringBuffer buffer) { | |
| buffer.write(footer); | |
| } | |
| // | |
| // Helper functions. | |
| // | |
| bool _isInt(String type) => type.startsWith("Int") || type.startsWith("Uint"); | |
| bool _isSigned(String type) => type.startsWith("Int"); | |
| bool _isBool(String type) => type == "Bool"; | |
| String sizeOf(int size) => "$size"; | |
| String sizeOfBits(int size) => "${size * 8}"; | |
| String sizeOfIntPtrSize(int size) => "$size"; | |
| final Uri _containingFolder = File.fromUri(Platform.script).parent.uri; | |
| ArgParser argParser() { | |
| final parser = ArgParser(allowTrailingOptions: false); | |
| parser.addOption( | |
| 'path', | |
| abbr: 'p', | |
| help: 'Path to generate the files at.', | |
| defaultsTo: _containingFolder.toString(), | |
| ); | |
| parser.addFlag( | |
| 'help', | |
| abbr: 'h', | |
| help: 'Display usage information.', | |
| callback: (help) { | |
| if (help) print(parser.usage); | |
| }, | |
| ); | |
| return parser; | |
| } | |
| class Config { | |
| final String nativeType; | |
| final String dartType; | |
| final String typedListType; | |
| final int elementSize; | |
| final Version? since; | |
| const Config( | |
| this.nativeType, | |
| this.dartType, | |
| this.typedListType, | |
| this.elementSize, { | |
| Version? since, | |
| }) : since = since; | |
| } | |
| const String kDoNotEmit = "donotemit"; | |
| class Version { | |
| final int major; | |
| final int minor; | |
| const Version(this.major, this.minor); | |
| @override | |
| String toString() => '$major.$minor'; | |
| static Version? latest(Version? a, Version? b) { | |
| if (a == null) return b; | |
| if (b == null) return a; | |
| if (a.major > b.major) return a; | |
| if (b.major > a.major) return b; | |
| if (a.minor > b.minor) return a; | |
| return b; | |
| } | |
| String get sinceAnnotation => "@Since('$this')"; | |
| } |