blob: 7545b089ea7b86e4addccedf9bd8765b9fbebde7 [file] [log] [blame]
// Copyright (c) 2021, 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 is a helper script which performs IL matching for AOT IL tests.
// See runtime/docs/infra/il_tests.md for more information.
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:mirrors';
import 'package:collection/collection.dart';
import 'package:vm/testing/il_matchers.dart';
void main(List<String> args) async {
getName = MirrorSystem.getName;
if (args.length < 2 || args.length > 3) {
throw 'Usage: compare_il <*_il_test.dart> <output.il> [<renames.json>]';
}
final testFile = args[0];
final ilFile = args[1];
final renamesFile = args.length == 3 ? args[2] : null;
final rename = _loadRenames(renamesFile);
final graphs = _loadGraphs(ilFile, rename);
final tests = await _loadTestCases(testFile);
Map<String, FlowGraph> findMatchingGraphs(String name, String? closureName) {
final closureSuffix = closureName != null ? '_${rename(closureName)}' : '';
final suffix = '_${rename(name)}${closureSuffix}';
return graphs.entries.firstWhere((f) => f.key.contains(suffix)).value;
}
for (var test in tests) {
test.run(findMatchingGraphs(test.name, test.closureName));
}
exit(0); // Success.
}
class TestCase {
final String name;
final String? closureName;
final String phasesFilter;
final LibraryMirror library;
late final phases =
phasesFilter.split(',').expand(_expandPhasePattern).toList();
TestCase({
required this.name,
this.closureName,
required this.phasesFilter,
required this.library,
});
late final fullName = name + (closureName != null ? '_$closureName' : '');
void run(Map<String, FlowGraph> graphs) {
print('matching IL (${phases.join(', ')}) for $fullName');
library.invoke(MirrorSystem.getSymbol('matchIL\$$fullName'),
phases.map((phase) => graphs[phase]!).toList());
print('... ok');
}
/// Parses phase filter components (same format as --compiler-passes flag).
static List<String> _expandPhasePattern(String pattern) {
bool printBefore = false, printAfter = false;
switch (pattern[0]) {
case '[':
printBefore = true;
break;
case ']':
printAfter = true;
break;
case '*':
printBefore = printAfter = true;
break;
}
final phaseName =
(printBefore || printAfter) ? pattern.substring(1) : pattern;
if (!printBefore && !printAfter) {
printAfter = true;
}
return [
if (printBefore) 'Before $phaseName',
if (printAfter) 'After $phaseName',
];
}
}
/// Extracts test cases from the given file by looking for functions
/// marked with @pragma('vm:testing:print-flow-graph', ...).
Future<Set<TestCase>> _loadTestCases(String testFile) async {
final mirrorSystem = currentMirrorSystem();
final library =
await mirrorSystem.isolate.loadUri(File(testFile).absolute.uri);
pragma? getPragma(DeclarationMirror decl, String name) => decl.metadata
.map((m) => m.reflectee)
.whereType<pragma>()
.firstWhereOrNull((p) => p.name == name);
final cases = LinkedHashSet<TestCase>(
equals: (a, b) => a.fullName == b.fullName,
hashCode: (a) => a.fullName.hashCode,
);
void processDeclaration(DeclarationMirror decl) {
TestCase? testCase;
pragma? p = getPragma(decl, 'vm:testing:print-flow-graph');
if (p != null) {
final name = MirrorSystem.getName(decl.simpleName);
testCase = TestCase(
name: name,
phasesFilter: (p.options as String?) ?? 'AllocateRegisters',
library: library,
);
}
p = getPragma(decl, 'vm:testing:match-inner-flow-graph');
if (p != null) {
final name = MirrorSystem.getName(decl.simpleName);
final closureName = p.options as String;
testCase = TestCase(
name: name,
closureName: closureName,
phasesFilter: 'AllocateRegisters',
library: library,
);
}
if (testCase != null) {
final added = cases.add(testCase);
if (!added) throw 'duplicate test case with name ${testCase.fullName}';
}
}
for (var decl in library.declarations.values) {
if (decl is ClassMirror) {
decl.declarations.values.forEach(processDeclaration);
} else {
processDeclaration(decl);
}
}
return cases;
}
Map<String, Map<String, FlowGraph>> _loadGraphs(String ilFile, Renamer rename) {
final graphs = <String, Map<String, FlowGraph>>{};
for (var graph in File(ilFile).readAsLinesSync()) {
final m = jsonDecode(graph) as Map<String, dynamic>;
graphs.putIfAbsent(m['f'], () => {})[m['p']] =
FlowGraph(m['b'], m['desc'], m['flags'], rename: rename);
}
return graphs;
}
Renamer _loadRenames(String? renamesFile) {
// Load renames map if present.
if (renamesFile == null) {
return (v) => v;
}
final list =
(jsonDecode(File(renamesFile).readAsStringSync()) as List).cast<String>();
final renamesMap = <String, String>{
for (var i = 0; i < list.length; i += 2) list[i]: list[i + 1],
};
return (v) => renamesMap[v] ?? v;
}