| // 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(')', ''); |
| } |