// 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("IntPtr", "int", kDoNotEmit, kIntPtrElementSize), Config("Float", "double", "Float32List", 4), Config("Double", "double", "Float64List", 8), ]; // // Generator. // main(List arguments) { final args = argParser().parse(arguments); Uri path = Uri.parse(args['path']); generate(path, "ffi.g.dart", generatePublicExtension); generate(path, "ffi_patch.g.dart", generatePatchExtension); } void generate(Uri path, String fileName, Function(StringBuffer, Config, String) generator) { final buffer = StringBuffer(); generateHeader(buffer); configuration.forEach((Config c) => generator(buffer, c, "Pointer")); configuration.forEach((Config c) => generator(buffer, c, "Array")); generateFooter(buffer); final fullPath = path.resolve(fileName).path; File(fullPath).writeAsStringSync(buffer.toString()); final fmtResult = Process.runSync(dartfmtPath().path, ["-w", fullPath]); if (fmtResult.exitCode != 0) { throw Exception( "Formatting failed:\n${fmtResult.stdout}\n${fmtResult.stderr}\n"); } print("Generated $fullPath."); } void generateHeader(StringBuffer buffer) { const header = """ // // The following code is generated, do not edit by hand. // // Code generated by `runtime/tools/ffi/sdk_lib_ffi_generator.dart`. // """; buffer.write(header); } void generatePublicExtension( StringBuffer buffer, Config config, String container) { final nativeType = config.nativeType; final dartType = config.dartType; final typedListType = config.typedListType; final elementSize = config.elementSize; final bits = sizeOfBits(elementSize); // final sizeInBytes = // "${sizeOf(elementSize)} byte${elementSize != 1 ? "s" : ""}"; 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 { property = "double"; } 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 sizeTimes = elementSize != 1 ? '${bracketOr(sizeOf(elementSize))} * ' : ''; 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 + ${sizeTimes}length`. /// /// The user has to ensure the memory range is accessible while using the /// returned list. $alignment external $typedListType asTypedList(int length); """; if (container == "Pointer") { buffer.write(""" /// Extension on [Pointer] specialized for the type argument [$nativeType]. 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 + ${sizeTimes}index`. $platform$truncate$alignment external $dartType operator [](int index); /// The $property at `address + ${sizeTimes}index`. $platform$truncate$alignment external void operator []=(int index, $dartType value); $asTypedList } """); } else { buffer.write(""" /// Bounds checking indexing methods on [Array]s of [$nativeType]. extension ${nativeType}Array on Array<$nativeType> { external $dartType operator [](int index); external void operator []=(int index, $dartType value); } """); } } void generatePatchExtension( StringBuffer buffer, Config config, String 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 $typedListType asTypedList(int elements) => _asExternalTypedData(this, elements); """; if (container == "Pointer") { buffer.write(""" extension ${nativeType}Pointer on Pointer<$nativeType> { @patch $dartType get value => _load$nativeType(this, 0); @patch set value($dartType value) => _store$nativeType(this, 0, value); @patch $dartType operator [](int index) => _load$nativeType(this, ${sizeTimes}index); @patch operator []=(int index, $dartType value) => _store$nativeType(this, ${sizeTimes}index, value); $asTypedList } """); } else { buffer.write(""" extension ${nativeType}Array on Array<$nativeType> { @patch $dartType operator [](int index) { _checkIndex(index); return _load$nativeType(_typedDataBase, ${sizeTimes}index); } @patch operator []=(int index, $dartType value) { _checkIndex(index); return _store$nativeType(_typedDataBase, ${sizeTimes}index, value); } } """); } } void generateFooter(StringBuffer buffer) { final footer = """ // // End of generated code. // """; buffer.write(footer); } // // Helper functions. // bool _isInt(String type) => type.startsWith("Int") || type.startsWith("Uint"); bool _isSigned(String type) => type.startsWith("Int"); String sizeOf(int size) { switch (size) { case kIntPtrElementSize: return "4 or 8"; default: return "$size"; } } String sizeOfBits(int size) { switch (size) { case kIntPtrElementSize: return "32 or 64"; default: return "${size * 8}"; } } String sizeOfIntPtrSize(int size) { switch (size) { case kIntPtrElementSize: return "_intPtrSize"; default: return "$size"; } } String bracketOr(String input) { if (input.contains("or")) { return "($input)"; } return input; } 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; } Uri dartfmtPath() { // TODO(dacoharkes): Use "../../../tools/sdks/dart-sdk/bin/dartfmt" when the // pinned fully supports extension methods. return Uri.parse("dartfmt"); } class Config { final String nativeType; final String dartType; final String typedListType; final int elementSize; const Config( this.nativeType, this.dartType, this.typedListType, this.elementSize); } const String kDoNotEmit = "donotemit"; const int kIntPtrElementSize = -1;