|  | // 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 + ${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'; |