d45fd0f9d5
Adds support for single dimension inline arrays in structs. Multi- dimensional arrays will be supported in a future CL. This CL adds: - CFE static error checks for inline arrays. - CFE transformations for inline arrays. - VM consumption of inline array fields for NativeType. - Test generator support for inline arrays + generated tests. Previous CLs added support for inline arrays in: - analyzer https://dart-review.googlesource.com/c/sdk/+/183684 - updated in this CL to new API. - ABI calculation https://dart-review.googlesource.com/c/sdk/+/183682 Closes: https://github.com/dart-lang/sdk/issues/35763 Open issue: https://github.com/dart-lang/sdk/issues/45101 CFE transformations are tested with expectation files: TEST=pkg/front_end/testcases/(.*)/ffi_struct_inline_array.dart Trampolines and CArray API are tested with end-to-end Dart tests: TEST=tests/ffi(_2)/(.*)by_value(.*)test.dart TEST=tests/ffi(_2)/inline_array_test.dart Compile-time errors (both CFE and analyzer) are tested in: TEST=tests/ffi(_2)/vmspecific_static_checks_test.dart Change-Id: I014c0e4153f1b885638adce80de6ab3cac8e6bb2 Cq-Include-Trybots: luci.dart.try:dart-sdk-linux-try,dart-sdk-mac-try,dart-sdk-win-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-mac-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-linux-debug-x64-try,vm-kernel-nnbd-linux-debug-x64-try,vm-kernel-nnbd-linux-debug-ia32-try,vm-kernel-nnbd-mac-release-x64-try,vm-kernel-nnbd-win-debug-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-debug-simarm_x64-try,vm-kernel-precomp-nnbd-linux-debug-x64-try,vm-kernel-precomp-win-release-x64-try,vm-kernel-reload-linux-debug-x64-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-msan-linux-release-x64-try,vm-kernel-precomp-msan-linux-release-x64-try,vm-kernel-precomp-android-release-arm_x64-try,analyzer-analysis-server-linux-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/183640 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Clement Skau <cskau@google.com>
346 lines
9.0 KiB
Dart
346 lines
9.0 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 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<String> 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;
|