blob: 6de0872dae9c9b327837a63f8599be153d4cd801 [file] [log] [blame]
// Copyright (c) 2016, 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.
library kernel.type_propagation.dump;
import 'package:kernel/kernel.dart';
import 'package:kernel/text/ast_to_text.dart';
import 'package:kernel/type_propagation/builder.dart';
import 'package:kernel/type_propagation/solver.dart';
import 'package:kernel/type_propagation/visualizer.dart';
import 'package:kernel/type_propagation/constraints.dart';
import 'package:kernel/type_propagation/type_propagation.dart';
import 'package:args/args.dart';
import 'dart:io';
ArgParser parser = new ArgParser()
..addFlag('graph', help: 'Generate graphviz dot files')
..addOption('graph-filter',
valueHelp: 'name',
help: 'Only print graph for members whose name contains the given string')
..addFlag('text', help: 'Generate annotated kernel text files')
..addFlag('escape', help: 'Dump information from escape analysis')
..addFlag('stats',
help: 'Print times and constraint system size', defaultsTo: true)
..addFlag('solve', help: 'Solve the constraint system', defaultsTo: true);
String get usage => """
Usage: dump [options] FILE.dill
Options:
${parser.usage}
""";
const String outputDir = 'typegraph';
main(List<String> args) {
if (args.length == 0) {
print(usage);
exit(1);
}
ArgResults options = parser.parse(args);
if (options.rest.length != 1) {
print('Exactly one file must be given');
exit(1);
}
String path = options.rest.single;
bool printGraphviz = options['graph'];
bool printText = options['text'];
bool printEscape = options['escape'];
bool useVisualizer = printGraphviz || printText || printEscape;
Program program = loadProgramFromBinary(path);
Stopwatch watch = new Stopwatch()..start();
Visualizer visualizer = useVisualizer ? new Visualizer(program) : null;
Builder builder = new Builder(program, visualizer: visualizer, verbose: true);
int buildTime = watch.elapsedMilliseconds;
watch.reset();
var solver = new Solver(builder);
if (options['solve']) {
solver.solve();
}
int solveTime = watch.elapsedMilliseconds;
visualizer?.solver = solver;
ConstraintSystem constraints = builder.constraints;
if (printEscape) {
for (int value = 0; value <= constraints.numberOfValues; ++value) {
TreeNode node;
if (value < builder.hierarchy.classes.length) {
node = builder.hierarchy.classes[value];
} else {
FunctionNode function = visualizer.getFunctionFromValue(value);
if (function == null || function.parent is! Member) continue;
node = function.parent;
}
int escape = solver.getEscapeContext(value);
String escapeString = (escape == constraints.latticePointOfValue[value])
? 'no escape'
: visualizer.getLatticePointName(escape);
print('$node -> $escapeString');
}
}
if (printText) {
print('Printing kernel text files...');
new Directory(outputDir).createSync();
StringBuffer buffer = new StringBuffer();
Printer printer =
new Printer(buffer, annotator: visualizer.getTextAnnotator());
printer.writeProgramFile(program);
String path = '$outputDir/program.txt';
new File(path).writeAsStringSync('$buffer');
}
if (printGraphviz) {
print('Printing graphviz dot files...');
String filter = options['graph-filter'];
new Directory(outputDir).createSync();
void dumpMember(Member member) {
if (filter != null && !'$member'.contains(filter)) return;
String name = sanitizeFilename('$member');
String path = '$outputDir/$name.dot';
String dotCode = visualizer.dumpMember(member);
new File(path).writeAsStringSync(dotCode);
}
for (var library in program.libraries) {
library.members.forEach(dumpMember);
for (var class_ in library.classes) {
class_.members.forEach(dumpMember);
}
}
}
if (options['stats']) {
var constraints = solver.constraints;
int numberOfConstraints = constraints.numberOfAssignments +
constraints.numberOfLoads +
constraints.numberOfStores;
int numberOfTransfers = numberOfConstraints * solver.iterations;
double transfersPerSecond =
(numberOfConstraints * solver.iterations) / (solveTime / 1000);
Iterable<int> outputVariables = [
builder.global.fields.values,
builder.global.returns.values,
builder.global.parameters.values
].expand((x) => x);
int outputCount = outputVariables.length;
int inferredUnknown = 0;
int inferredNothing = 0;
int inferredNullable = 0;
int inferredNonNullable = 0;
int inferredOnlyNull = 0;
for (int variable in outputVariables) {
int values = solver.getVariableValue(variable);
int bitmask = solver.getVariableBitmask(variable);
if (values == Solver.bottom && bitmask == 0) {
++inferredNothing;
} else if (values == Solver.bottom && bitmask == ValueBit.null_) {
++inferredOnlyNull;
} else if (values == Solver.rootClass && bitmask == ValueBit.all) {
++inferredUnknown;
} else if (bitmask & ValueBit.null_ != 0) {
++inferredNullable;
} else {
++inferredNonNullable;
}
}
print("""
Build time: $buildTime ms
Solve time: $solveTime ms
Iterations: ${solver.iterations}
Classes: ${builder.hierarchy.classes.length}
Values: ${constraints.numberOfValues}
Unions: ${constraints.numberOfLatticePoints}
Variables: ${constraints.numberOfVariables}
Fields: ${builder.fieldNames.length}
Assignments: ${constraints.numberOfAssignments}
Loads: ${constraints.numberOfLoads}
Stores: ${constraints.numberOfStores}
Transfers: $numberOfTransfers (${(transfersPerSecond / 1000000).toStringAsFixed(1)} M/s)
Outputs: $outputCount
Unknown: $inferredUnknown (${percent(inferredUnknown, outputCount)})
Nullable: $inferredNullable (${percent(inferredNullable, outputCount)})
Non-nullable: $inferredNonNullable (${percent(inferredNonNullable, outputCount)})
Only null: $inferredOnlyNull (${percent(inferredOnlyNull, outputCount)})
Nothing: $inferredNothing (${percent(inferredNothing, outputCount)})
""");
}
}
String percent(int amount, int total) {
if (total == 0) return '0%';
return (amount / total * 100).toStringAsFixed(1) + '%';
}
String sanitizeFilename(String name) {
return name
.replaceAll('::', '.')
.replaceAll('/', r'$div')
.replaceAll('(', '')
.replaceAll(')', '');
}