blob: 2818b2865f33a2322980ef5c1bdea15ec97a0fc6 [file] [log] [blame]
// Copyright (c) 2024, 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.
import 'dart:io';
import 'c_types.dart';
import 'utils.dart';
import 'structs_by_value_tests_generator.dart';
void main() async {
await Future.wait([
writeC(),
for (final container in Container.values) writeDart(container),
writeDartShared()
]);
}
class Container {
/// The name of the container in Dart code.
final String name;
/// The copyright year for the generated test file for this container.
final int copyrightYear;
/// How this containers' name shows up in tests.
final String Function(PointerType) testName;
final Iterable<Test> tests;
final Iterable<FundamentalType> elementTypes;
const Container(
this.name,
this.copyrightYear,
this.testName,
this.tests,
this.elementTypes,
);
static final array = Container(
'Array',
2024,
(pointerType) => '${pointerType.pointerTo}Array',
[
Test.self,
Test.elementAt,
],
[
int8,
int16,
int32,
int64,
uint8,
uint16,
uint32,
uint64,
float,
double_,
bool_,
],
);
static final struct = Container(
'Struct',
2024,
(pointerType) => '${pointerType.pointerTo}Struct',
[
Test.field,
],
[
int8,
int16,
int32,
int64,
uint8,
uint16,
uint32,
uint64,
float,
double_,
bool_,
],
);
static final typedData = Container(
'TypedData',
2024,
(pointerType) => pointerType.dartTypedData,
[
Test.self,
Test.elementAt,
Test.view,
Test.viewMany,
],
[
int8,
int16,
int32,
int64,
uint8,
uint16,
uint32,
uint64,
float,
double_,
],
);
static final values = [
array,
struct,
typedData,
];
}
class Test {
final String name;
const Test(this.name);
String testName(Container container, CType elementType) {
final containerName = container.testName(PointerType(elementType));
return 'testAddressOf$containerName$name';
}
static const elementAt = Test('ElementAt');
static const field = Test('Field');
static const self = Test('');
static const view = Test('View');
static const viewMany = Test('ViewMany');
}
const generatorPath = 'tests/ffi/generator/address_of_test_generator.dart';
Future<void> writeDart(Container container) async {
final StringBuffer buffer = StringBuffer();
buffer.write(headerDart(
copyrightYear: container.copyrightYear,
));
buffer.write("""
void main() {${'''
// Force dlopen so @Native lookups in DynamicLibrary.process() succeed.
dlopenGlobalPlatformSpecific('ffi_test_functions');
'''}
for (int i = 0; i < 100; ++i) {
""");
for (final elementType in container.elementTypes) {
for (final test in container.tests) {
buffer.write("""
${test.testName(container, elementType)}();
""");
}
}
if (container == Container.struct) {
buffer.write('''
testAddressOfStructPointerMany();
''');
}
buffer.write("""
}
}
""");
for (final elementType in container.elementTypes) {
final pointerType = PointerType(elementType);
final testName = container.testName(pointerType);
final varName = container.name.lowerCaseFirst();
String value = elementType.isSigned ? 'i % 2 == 0 ? i : -i' : 'i';
if (elementType.isFloatingPoint) {
value = '($value).toDouble()';
}
final equals = elementType.isFloatingPoint ? 'approxEquals' : 'equals';
if (container == Container.array) {
buffer.write("""
final class ${elementType.dartCType}ArrayStruct extends Struct {
@Array(20)
external Array<${elementType.dartCType}> array;
}
Array<${elementType.dartCType}> make${elementType.dartCType}Array(int length){
assert(length == 20);
final typedData = make${pointerType.dartTypedData}(length);
final struct = Struct.create<${elementType.dartCType}ArrayStruct>(typedData);
return struct.array;
}
""");
}
if (container == Container.struct) {
buffer.write("""
final class ${elementType.dartCType}Struct extends Struct {
${[
for (int i = 0; i < manyCount; i++)
'''
@${elementType.dartCType}()
external ${elementType.dartType} a$i;
'''
].join()}
}
${elementType.dartCType}Struct make${elementType.dartCType}Struct(int length){
assert(length == 20);
final typedData = make${pointerType.dartTypedData}(length);
final struct = Struct.create<${elementType.dartCType}Struct>(typedData);
return struct;
}
""");
}
for (final test in container.tests) {
final methodName = test.testName(container, elementType);
switch (test) {
case Test.self:
buffer.write("""
void $methodName() {
const length = 20;
final $varName = make${testName}(length);
final expectedResult = makeExpectedResult${elementType.dartCType}(0, length);
final result = take${elementType.dartCType}Pointer($varName.address, length);
Expect.$equals(expectedResult, result);
}
""");
case Test.elementAt:
buffer.write("""
void $methodName() {
const length = $manyCount;
final $varName = make${testName}(length);
final expectedResult = makeExpectedResult${elementType.dartCType}(0, length);
final result = take${elementType.dartCType}PointerMany(${[
for (int i = 0; i < manyCount; i++) '$varName[$i].address,'
].join()});
Expect.$equals(expectedResult, result);
}
""");
case Test.view:
buffer.write("""
void $methodName() {
const sourceLength = 30;
const viewStart = 10;
const viewLength = 10;
final viewEnd = viewStart + viewLength;
final source = make${pointerType.dartTypedData}(sourceLength);
final view = ${pointerType.dartTypedData}.sublistView(source, viewStart, viewEnd);
final expectedResult = makeExpectedResult${elementType.dartCType}(viewStart, viewEnd);
final result = take${elementType.dartCType}Pointer(view.address, view.length);
Expect.$equals(expectedResult, result);
}
""");
case Test.viewMany:
buffer.write("""
void $methodName() {
const length = $manyCount;
final typedData = make${pointerType.dartTypedData}(length);
final expectedResult = makeExpectedResult${elementType.dartCType}(0, length);
final result = take${elementType.dartCType}PointerMany(${[
for (int i = 0; i < manyCount; i++)
'${pointerType.dartTypedData}.sublistView(typedData, $i, $i + 1).address,'
].join()});
Expect.$equals(expectedResult, result);
}
""");
case Test.field:
buffer.write("""
void $methodName() {
const length = $manyCount;
final $varName = make${testName}(length);
final expectedResult = makeExpectedResult${elementType.dartCType}(0, length);
final result = take${elementType.dartCType}PointerMany(${[
for (int i = 0; i < manyCount; i++) '$varName.a$i.address,'
].join()});
Expect.$equals(expectedResult, result);
}
""");
}
}
}
if (container == Container.struct) {
final elementType = int16;
for (final compoundType in [
StructType([elementType]),
UnionType([elementType])
]) {
final compoundKind = compoundType is StructType ? 'Struct' : 'Union';
final pointerType = PointerType(compoundType);
buffer.write(compoundType.dartClass());
buffer.write('''
@Native<${elementType.dartCType} Function(
${[for (int i = 0; i < manyCount; i++) '${pointerType.dartCType},'].join()}
)>(symbol: 'Take${compoundKind}2BytesIntPointerMany', isLeaf: true)
external ${elementType.dartType} take${compoundKind}2BytesIntPointerMany(
${[
for (int i = 0; i < manyCount; i++)
'${pointerType.dartCType} pointer$i,',
].join()}
);
void testAddressOf${compoundKind}PointerMany() {
const length = $manyCount;
final typedData = makeInt16List(length);
${[
for (int i = 0; i < manyCount; i++)
'final struct$i = ${compoundKind}.create<${compoundKind}2BytesInt>(typedData, $i);'
].join()}
final expectedResult = makeExpectedResult${elementType.dartCType}(0, length);
final result = take${compoundKind}2BytesIntPointerMany(${[
for (int i = 0; i < manyCount; i++) 'struct$i.address,'
].join()});
Expect.equals(expectedResult, result);
}
''');
}
}
final path = testPath(container);
await File(path).writeAsString(buffer.toString());
await runProcess(Platform.resolvedExecutable, ["format", path]);
}
Future<void> writeDartShared() async {
final StringBuffer buffer = StringBuffer();
buffer.write(headerDart(
copyrightYear: 2024,
));
for (final elementType in Container.struct.elementTypes) {
final pointerType = PointerType(elementType);
String value = elementType.isSigned ? 'i % 2 == 0 ? i : -i' : 'i';
if (elementType.isFloatingPoint) {
value = '($value).toDouble()';
}
buffer.write("""
@Native<${elementType.dartCType} Function(${pointerType.dartCType}, Size)>(symbol: 'Take${elementType.dartCType}Pointer', isLeaf: true)
external ${elementType.dartType} take${elementType.dartCType}Pointer(${pointerType.dartCType} pointer, int length);
@Native<${elementType.dartCType} Function(
${[for (int i = 0; i < manyCount; i++) '${pointerType.dartCType},'].join()}
)>(symbol: 'Take${elementType.dartCType}PointerMany', isLeaf: true)
external ${elementType.dartType} take${elementType.dartCType}PointerMany(
${[
for (int i = 0; i < manyCount; i++) '${pointerType.dartCType} pointer$i,',
].join()}
);
""");
if (elementType != bool_) {
buffer.write("""
${pointerType.dartTypedData} make${pointerType.dartTypedData}(int length) {
final typedData = ${pointerType.dartTypedData}(length);
for (int i = 0; i < length; i++) {
final value = $value;
typedData[i] = value;
}
return typedData;
}
${elementType.dartType} makeExpectedResult${elementType.dartCType}(int start, int end) {
${elementType.dartType} expectedResult = 0;
for (int i = start; i < end; i++) {
final value = $value;
expectedResult += value;
}
return expectedResult;
}
""");
}
}
final path = Platform.script
.resolve("../../ffi/address_of_generated_shared.dart")
.toFilePath();
;
await File(path).writeAsString(buffer.toString());
await runProcess(Platform.resolvedExecutable, ["format", path]);
}
String testPath(Container container) {
final lowerCase = container.name.toLowerCase();
return Platform.script
.resolve("../../ffi/address_of_${lowerCase}_generated_test.dart")
.toFilePath();
}
String headerDart({
required int copyrightYear,
}) {
return """
${headerCommon(copyrightYear: copyrightYear, generatorPath: generatorPath)}
//
// SharedObjects=ffi_test_functions
// VMOptions=
// VMOptions=--deterministic --optimization-counter-threshold=90
// VMOptions=--use-slow-path
// VMOptions=--use-slow-path --stacktrace-every=100
// ignore_for_file: unused_import
import 'dart:ffi';
import 'dart:typed_data';
import 'package:expect/expect.dart';
import 'address_of_generated_shared.dart';
import 'address_of_shared.dart';
import 'dylib_utils.dart';
final ffiTestFunctions = dlopenPlatformSpecific('ffi_test_functions');
""";
}
const manyCount = 20;
Future<void> writeC() async {
final StringBuffer buffer = StringBuffer();
buffer.write(headerC(copyrightYear: 2024, generatorPath: generatorPath));
for (final elementType in Container.struct.elementTypes) {
final pointerType = PointerType(elementType);
String coutCast(String input) {
if (elementType == uint8 || elementType == int8 || elementType == bool_) {
return "static_cast<int>($input)";
}
return input;
}
String opCast(String input) {
if (elementType == bool_) {
return "static_cast<int>($input)";
}
return input;
}
buffer.write('''
DART_EXPORT ${elementType.cType} Take${elementType.dartCType}Pointer(${pointerType.cType} data, size_t length) {
${elementType.cType} result = ${elementType.zero};
if (length > 100) {
std::cout << "Mangled arguments\\n";
return result;
}
for (size_t i = 0; i < length; i++) {
std::cout << "data[" << i << "] = " << ${coutCast('data[i]')} << "\\n";
result ${elementType.addAssignOp} ${opCast('data[i]')};
}
return result;
}
DART_EXPORT ${elementType.cType} Take${elementType.dartCType}PointerMany(
${[
for (int i = 0; i < manyCount; i++) '${pointerType.cType} data$i'
].join(',')}
) {
${elementType.cType} result = ${elementType.zero};
${[
for (int i = 0; i < manyCount; i++)
'''
std::cout << "data$i[0] = " << ${coutCast('data$i[0]')} << "\\n";
result ${elementType.addAssignOp} ${opCast('data$i[0]')};''',
].join('\n')}
return result;
}
''');
}
final elementType = int16;
for (final compoundType in [
StructType([elementType]),
UnionType([elementType])
]) {
final pointerType = PointerType(compoundType);
buffer.write(compoundType.cDefinition);
buffer.write('''
DART_EXPORT ${elementType.cType} Take${compoundType.dartCType}PointerMany(
${[
for (int i = 0; i < manyCount; i++) '${pointerType.cType} data$i'
].join(',')}
) {
${elementType.cType} result = ${elementType.zero};
${[
for (int i = 0; i < manyCount; i++)
'''
std::cout << "data$i->a0 = " << ${'data$i->a0'} << "\\n";
result += data$i->a0;''',
].join('\n')}
return result;
}
''');
}
buffer.write(footerC);
await File(ccPath).writeAsString(buffer.toString());
await runProcess("clang-format", ["-i", ccPath]);
}
final ccPath = Platform.script
.resolve("../../../runtime/bin/ffi_test/ffi_test_functions_generated_2.cc")
.toFilePath();