blob: b60c1cfea78a9f20e108c7d419472b594d3fbec1 [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.
//
// Generates both the dart and dart2 version of this benchmark.
import 'dart:io';
import 'dart:math';
import 'package:path/path.dart' as path;
const String benchmarkName = 'SubtypeTestCache';
const List<int> assertionCounts = [
1,
5,
10,
25,
50,
75,
100,
250,
500,
750,
1000
];
void generateBenchmarkClassesAndUtilities(IOSink output) {
final maxCount = assertionCounts.reduce(max);
output.writeln('''
// 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 benchmark suite measures the overhead of looking up elements of
// SubtypeTestCaches, which are used when a type testing stub cannot determine
// whether a given type is assignable.
import 'package:benchmark_harness/benchmark_harness.dart';
void main() {''');
// We must run the benchmarks from smallest count to largest, since a single
// STC is shared across all the benchmarks (due to the single call site in
// [check]). This ensures that benchmarks that are testing counts small
// enough for a linear STC use a linear STC.
final sortedCounts = assertionCounts.toList(growable: false);
sortedCounts.sort();
for (final count in sortedCounts) {
output.write('''
const STC$count().report();
''');
}
// We need to run the STCSame<max> benchmark only after running all the
// STC<count> benchmarks, so that we ensure the shared STC is properly primed.
output.write('''
const STCSame$maxCount().report();
''');
output.writeln('''
}
class STCBenchmarkBase extends BenchmarkBase {
final int count;
const STCBenchmarkBase(String name, this.count) : super(name);
// Normalize the cost across the benchmarks by number of type tests.
@override
void report() => emitter.emit(name, measure() / count);
}
''');
for (final count in assertionCounts) {
output.write('''
class STC$count extends STCBenchmarkBase {
const STC$count() : super('$benchmarkName.STC$count', $count);
@override
void run() {
''');
for (int i = 0; i < count; i++) {
output.write('''
check<int>(instances[$i]);
''');
}
output.writeln('''
}
}
''');
}
output.write('''
class STCSame$maxCount extends STCBenchmarkBase {
const STCSame$maxCount() : super('$benchmarkName.STCSame$maxCount', $maxCount);
@override
void run() {
// Do $maxCount AssertAssignable checks for the last type checked in the
// STC$maxCount benchmark.
''');
for (int i = 0; i < maxCount; i++) {
output.write('''
check<int>(instances[${maxCount - 1}]);
''');
}
output.writeln('''
}
}
@pragma('vm:never-inline')
@pragma('wasm:never-inline')
@pragma('dart2js:never-inline')
void check<S>(dynamic s) => s as C<S> Function();
class C<T> {}
''');
for (int i = 0; i < maxCount; i++) {
output.write('''
class C$i<T> extends C<T> {}
C$i<S> closure$i<S>() => C$i<S>();
''');
}
// We create constant tearoffs of the closures above to use for our values
// in the `as` checks. We could make constant instances of the classes, but
// the specialized TTS for the `C` class hierarchy means that we'll never
// actually hit the SubtypeTestCache!
//
// Using closures both avoids the likelihood of eventually optimizing the TTS
// for this check and making this benchmark outdated and also ensures the VM
// performs the most intensive check for each STC entry, i.e., the
// Subtype7TestCache stub is called.
output.write('''
const instances = <dynamic>[
''');
for (int i = 0; i < maxCount; i++) {
output.write('''
closure$i<int>,
''');
}
output.write('''
];
''');
}
void main() {
final dartFilePath = path.join(
path.dirname(Platform.script.path), 'dart', '$benchmarkName.dart');
final dartSink = File(dartFilePath).openWrite();
generateBenchmarkClassesAndUtilities(dartSink);
dartSink..flush();
}