blob: 91e5398f3ce64e033084b034d7ca9985025b9f8f [file] [log] [blame]
// Copyright (c) 2023, 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';
//
// Configuration.
//
const nativeToDartType = {
'Int8': 'int',
'Int16': 'int',
'Int32': 'int',
'Int64': 'int',
'Uint8': 'int',
'Uint16': 'int',
'Uint32': 'int',
'Uint64': 'int',
'Float': 'double',
'Double': 'double',
'Pointer<Uint8>': 'Pointer<Uint8>',
'Handle': 'Object',
};
const generateFor = {
'Int8': [1],
'Int16': [1],
'Int32': [1, 2, 4, 10, 20],
'Int64': [1, 2, 4, 10, 20],
'Uint8': [1],
'Uint16': [1],
'Uint32': [1],
'Uint64': [1],
'Float': [1, 2, 4, 10, 20],
'Double': [1, 2, 4, 10, 20],
'Pointer<Uint8>': [1, 2, 4, 10, 20],
'Handle': [1, 2, 4, 10, 20],
};
const allNumbers = [1, 2, 4, 10, 20];
//
// Generator.
//
void main() {
final List<String> nativeTypes = nativeToDartType.keys.toList();
final List<String> dartTypes = nativeToDartType.values.toSet().toList();
final List<String> nativeIntTypes = nativeTypes.where(isInt).toList();
final List<String> nativeDoubleTypes = nativeTypes.where(isDouble).toList();
final List<String> nativePointerTypes = nativeTypes.where(isPointer).toList();
final List<String> nativeHandleTypes = nativeTypes.where(isHandle).toList();
final StringBuffer buffer = StringBuffer();
buffer.write(header);
generateTypedefs(buffer, 'Function', dartTypes, allNumbers);
generateTypedefs(buffer, 'NativeFunction', nativeTypes, allNumbers);
generateBenchmarkInt(buffer, nativeIntTypes);
generateBenchmarkDouble(buffer, nativeDoubleTypes);
generateBenchmarkPointer(buffer, nativePointerTypes);
generateBenchmarkHandle(buffer, nativeHandleTypes);
final path = Platform.script.resolve('dart/benchmark_generated.dart').path;
File(path).writeAsStringSync(buffer.toString());
print(Process.runSync('dart', ['format', path]).stderr);
}
const header = '''
// Copyright (c) 2023, 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 has been automatically generated. Please do not edit it manually.
// To regenerate the file, run the following script:
//
// > dart benchmarks/FfiCall/generate_benchmarks.dart
// Using part of, so that the library uri is identical to the main file.
// That way the FfiNativeResolver works for the main uri.
part of 'FfiCall.dart';
''';
void generateTypedefs(StringBuffer buffer, String namePrefix,
List<String> types, List<int> numbers) {
for (String type in types) {
for (int number in numbers) {
final String name = '$namePrefix$number${toIdentifier(type)}';
final String arguments = repeat(type, number, ', ');
buffer.write('typedef $name = $type Function($arguments);');
}
}
}
void generateBenchmarkInt(StringBuffer buffer, List<String> types) {
for (String type in types) {
final String typeName = toIdentifier(type);
final String dartType = toIdentifier(nativeToDartType[type]!);
for (int number in generateFor[type]!) {
final String name = '${typeName}x${'$number'.padLeft(2, '0')}';
final String expected = IntVariation(type, number).expectedValue(number);
final String functionType = 'Function$number$dartType';
final String functionNativeType = 'NativeFunction$number$typeName';
final String functionNameC = 'Function$number$typeName';
final String argument = IntVariation(type, number).argument;
final String arguments = repeat(argument, number, ', ');
final String functionNameDart = 'function$number$typeName';
final String dartArguments =
List.generate(number, (i) => '$dartType a$i').join(', ');
buffer.write('''
class $name extends FfiBenchmarkBase {
final $functionType f;
$name({bool isLeaf = false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: true)
: ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: false),
super('FfiCall.$name', isLeaf: isLeaf);
@override
void run() {
int x = 0;
for (int i = 0; i < N; i++) {
x += f($arguments);
}
expectEquals(x, $expected);
}
}
''');
for (bool isLeaf in [false, true]) {
final leaf = isLeaf ? 'Leaf' : '';
buffer.write('''
@Native<$functionNativeType>(symbol: '$functionNameC', isLeaf: $isLeaf)
external $dartType $functionNameDart$leaf($dartArguments);
class ${name}Native$leaf extends FfiBenchmarkBase {
${name}Native$leaf() : super('FfiCall.${name}Native', isLeaf: $isLeaf);
@override
void run() {
int x = 0;
for (int i = 0; i < N; i++) {
x += $functionNameDart$leaf($arguments);
}
expectEquals(x, $expected);
}
}
''');
}
}
}
}
void generateBenchmarkDouble(StringBuffer buffer, List<String> types) {
for (String type in types) {
final String typeName = toIdentifier(type);
final String dartType = toIdentifier(nativeToDartType[type]!);
for (int number in generateFor[type]!) {
final String name = '${typeName}x${'$number'.padLeft(2, '0')}';
final String expected = number == 1
? 'N + N * 42.0' // Do work with single arg.
: 'N * $number * ($number + 1) / 2 '; // The rest sums arguments.
final String functionType = 'Function$number$dartType';
final String functionNativeType = 'NativeFunction$number$typeName';
final String functionNameC = 'Function$number$typeName';
final List<double> argVals = List.generate(number, (i) => 1.0 * (i + 1));
final String arguments = argVals.join(', ');
final String functionNameDart = 'function$number$typeName';
final String dartArguments =
List.generate(number, (i) => '$dartType a$i').join(', ');
buffer.write('''
class $name extends FfiBenchmarkBase {
final $functionType f;
$name({bool isLeaf = false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: true)
: ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: false),
super('FfiCall.$name', isLeaf: isLeaf);
@override
void run() {
double x = 0;
for (int i = 0; i < N; i++) {
x += f($arguments);
}
final double expected = $expected;
expectApprox(x, expected);
}
}
''');
for (bool isLeaf in [false, true]) {
final leaf = isLeaf ? 'Leaf' : '';
buffer.write('''
@Native<$functionNativeType>(symbol: '$functionNameC', isLeaf: $isLeaf)
external $dartType $functionNameDart$leaf($dartArguments);
class ${name}Native$leaf extends FfiBenchmarkBase {
${name}Native$leaf() : super('FfiCall.${name}Native', isLeaf: $isLeaf);
@override
void run() {
double x = 0;
for (int i = 0; i < N; i++) {
x += $functionNameDart$leaf($arguments);
}
final double expected = $expected;
expectApprox(x, expected);
}
}
''');
}
}
}
}
void generateBenchmarkPointer(StringBuffer buffer, List<String> types) {
for (String type in types) {
if (type != 'Pointer<Uint8>') throw Exception('Not implemented for $type.');
final String typeName = toIdentifier(type);
final String dartType = nativeToDartType[type]!;
final String dartTypeName = toIdentifier(dartType);
for (int number in generateFor[type]!) {
final String name = '${typeName}x${'$number'.padLeft(2, '0')}';
final List<String> pointerNames =
List.generate(number, (i) => 'p${i + 1}');
final String pointers =
pointerNames.map((n) => '$type $n = nullptr;').join('\n');
final String setup = List.generate(
number - 1, (i) => 'p${i + 2} = p1.elementAt(${i + 1});').join();
final String functionType = 'Function$number$dartTypeName';
final String functionNativeType = 'NativeFunction$number$typeName';
final String functionNameC = 'Function$number$typeName';
final String arguments = pointerNames.skip(1).join(', ');
final String functionNameDart = 'function$number$typeName';
final String dartArguments =
List.generate(number, (i) => '$dartType a$i').join(', ');
buffer.write('''
class $name extends FfiBenchmarkBase {
final $functionType f;
$name({bool isLeaf = false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: true)
: ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: false),
super('FfiCall.$name', isLeaf: isLeaf);
$pointers
@override
void setup() {
p1 = calloc(N + 1);
$setup
}
@override
void teardown() {
calloc.free(p1);
}
@override
void run() {
$type x = p1;
for (int i = 0; i < N; i++) {
x = f(x, $arguments);
}
expectEquals(x.address, p1.address + N * sizeOf<Uint8>());
}
}
''');
for (bool isLeaf in [false, true]) {
final leaf = isLeaf ? 'Leaf' : '';
buffer.write('''
@Native<$functionNativeType>(symbol: '$functionNameC', isLeaf: $isLeaf)
external $dartType $functionNameDart$leaf($dartArguments);
class ${name}Native$leaf extends FfiBenchmarkBase {
${name}Native$leaf() : super('FfiCall.${name}Native', isLeaf: $isLeaf);
$pointers
@override
void setup() {
p1 = calloc(N + 1);
$setup
}
@override
void teardown() {
calloc.free(p1);
}
@override
void run() {
$type x = p1;
for (int i = 0; i < N; i++) {
x = $functionNameDart$leaf(x, $arguments);
}
expectEquals(x.address, p1.address + N * sizeOf<Uint8>());
}
}
''');
}
}
}
}
void generateBenchmarkHandle(StringBuffer buffer, List<String> types) {
for (String type in types) {
if (type != 'Handle') throw Exception('Not implemented for $type.');
final String typeName = toIdentifier(type);
final String dartType = toIdentifier(nativeToDartType[type]!);
for (int number in generateFor[type]!) {
final String name = '${typeName}x${'$number'.padLeft(2, '0')}';
final String setup =
List.generate(number + 1, (i) => 'final m$i = MyClass($i);')
.skip(2)
.join('\n');
final String functionType = 'Function$number$dartType';
final String functionNativeType = 'NativeFunction$number$typeName';
final String functionNameC = 'Function$number$typeName';
final String arguments =
List.generate(number - 1, (i) => 'm${i + 2}').join(', ');
final String functionNameDart = 'function$number$typeName';
final String dartArguments =
List.generate(number, (i) => '$dartType a$i').join(', ');
buffer.write('''
class $name extends FfiBenchmarkBase {
final $functionType f;
$name()
: f = ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: false),
super('FfiCall.$name', isLeaf: false);
@override
void run() {
final m1 = MyClass(123);
$setup
Object x = m1;
for (int i = 0; i < N; i++) {
x = f(x, $arguments);
}
expectIdentical(x, m1);
}
}
@Native<$functionNativeType>(symbol: '$functionNameC', isLeaf: false)
external $dartType $functionNameDart($dartArguments);
class ${name}Native extends FfiBenchmarkBase {
${name}Native() : super('FfiCall.${name}Native', isLeaf: false);
@override
void run() {
final m1 = MyClass(123);
$setup
Object x = m1;
for (int i = 0; i < N; i++) {
x = $functionNameDart(x, $arguments);
}
expectIdentical(x, m1);
}
}
''');
}
}
}
//
// Benchmark variations.
//
class IntVariation {
/// The argument passed to the C function, the same value is passed if the C
/// function has multiple parameters.
final String argument;
/// The expected value of summation of all return values.
final String Function(int number) expectedValue;
/// These benchmarks sum all arguments over all iterations.
IntVariation.LargeIntManyArguments()
: argument = 'i',
expectedValue = ((int number) => 'N * (N - 1) * $number / 2');
/// Benchmarks with only one argument return 42 added to the argument.
IntVariation.LargeIntOneArgument()
: argument = 'i',
expectedValue = ((int number) => 'N * (N - 1) / 2 + N * 42');
/// The benchmarks with small ints (`int8_t`, `uint8_t`, etc.) we pass an
/// arbitrary fixed argument between 0 and 127 to prevent truncation.
///
/// The C function returns 42 added to the argument.
IntVariation.SmallInt()
: argument = '17',
expectedValue = ((int number) => 'N * 17 + N * 42');
factory IntVariation(String type, int number) {
if (isSmallInt(type)) {
return IntVariation.SmallInt();
}
if (number == 1) {
return IntVariation.LargeIntOneArgument();
}
return IntVariation.LargeIntManyArguments();
}
}
//
// Helper functions.
//
String toIdentifier(String type) =>
type.replaceAll('<', '').replaceAll('>', '');
String repeat(String input, int n, String separator) {
if (n == 0) {
return '';
}
return (input + separator) * (n - 1) + input;
}
bool isInt(String type) => type.startsWith('Int') || type.startsWith('Uint');
/// True for `int8_t`, `uint8_t`, `int16_t`, and `uint16_t`.
bool isSmallInt(String type) =>
isInt(type) && (type.contains('8') || type.contains('16'));
bool isDouble(String type) =>
type.startsWith('Float') || type.startsWith('Double');
bool isPointer(String type) => type.startsWith('Pointer');
bool isHandle(String type) => type == 'Handle';