Index: pkg/front_end/tool/fasta_perf.dart |
diff --git a/pkg/front_end/tool/fasta_perf.dart b/pkg/front_end/tool/fasta_perf.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ab8043028a1c4c4b8032c767d4a3cd5a0972a77f |
--- /dev/null |
+++ b/pkg/front_end/tool/fasta_perf.dart |
@@ -0,0 +1,298 @@ |
+// Copyright (c) 2017, 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. |
+ |
+/// An entrypoint used to run portions of fasta and measure its performance. |
+library front_end.tool.fasta_perf; |
+ |
+import 'dart:async'; |
+import 'dart:io'; |
+ |
+import 'package:front_end/src/fasta/analyzer/ast_builder.dart'; |
+import 'package:front_end/src/fasta/ast_kind.dart' show AstKind; |
+import 'package:front_end/src/fasta/dill/dill_target.dart' show DillTarget; |
+import 'package:front_end/src/fasta/kernel/kernel_target.dart' |
+ show KernelTarget; |
+import 'package:front_end/src/fasta/parser.dart'; |
+import 'package:front_end/src/fasta/scanner.dart'; |
+import 'package:front_end/src/fasta/scanner/io.dart' show readBytesFromFileSync; |
+import 'package:front_end/src/fasta/scanner/precedence.dart'; |
+import 'package:front_end/src/fasta/source/scope_listener.dart' show Scope; |
+import 'package:front_end/src/fasta/ticker.dart' show Ticker; |
+import 'package:front_end/src/fasta/translate_uri.dart' show TranslateUri; |
+import 'package:front_end/src/fasta/translate_uri.dart'; |
+ |
+/// Cumulative total number of chars scanned. |
+int inputSize = 0; |
+ |
+/// Cumulative time spent scanning. |
+Stopwatch scanTimer = new Stopwatch(); |
+ |
+main(List<String> args) async { |
+ // TODO(sigmund): provide sdk folder as well. |
+ if (args.length < 2) { |
+ print('usage: fasta_perf.dart <bench-id> <entry.dart>'); |
+ exit(1); |
+ } |
+ var bench = args[0]; |
+ var entryUri = Uri.base.resolve(args[1]); |
+ |
+ await setup(entryUri); |
+ |
+ Map<Uri, List<int>> files = await scanReachableFiles(entryUri); |
+ var handlers = { |
+ 'scan': () async => scanFiles(files), |
+ // TODO(sigmund): enable when we can run the ast-builder standalone. |
+ // 'parse': () async => parseFiles(files), |
+ 'kernel_gen_e2e': () async { |
+ await generateKernel(entryUri); |
+ }, |
+ // TODO(sigmund): enable once we add a build step to create the |
+ // platform.dill files. |
+ // 'kernel_gen_e2e_sum': () async { |
+ // await generateKernel(entryUri, compileSdk: false); |
+ // }, |
+ }; |
+ |
+ var handler = handlers[bench]; |
+ if (handler == null) { |
+ // TODO(sigmund): implement the remaining benchmarks. |
+ print('unsupported bench-id: $bench. Please specify one of the following: ' |
+ '${handlers.keys.join(", ")}'); |
+ exit(1); |
+ } |
+ |
+ // TODO(sigmund): replace the warmup with instrumented snapshots. |
+ int iterations = bench.contains('kernel_gen') ? 2 : 10; |
+ for (int i = 0; i < iterations; i++) { |
+ var totalTimer = new Stopwatch()..start(); |
+ print('== iteration $i'); |
+ await handler(); |
+ totalTimer.stop(); |
+ report('total', totalTimer.elapsedMicroseconds); |
+ } |
+} |
+ |
+/// Translates `dart:*` and `package:*` URIs to resolved URIs. |
+TranslateUri uriResolver; |
+ |
+/// Preliminary set up to be able to correctly resolve URIs on the given |
+/// program. |
+Future setup(Uri entryUri) async { |
+ // TODO(sigmund): use `perf.dart::_findSdkPath` here when fasta can patch the |
+ // sdk directly. |
+ var sdkRoot = |
+ Uri.base.resolve(Platform.resolvedExecutable).resolve('patched_sdk/'); |
+ uriResolver = await TranslateUri.parse(sdkRoot); |
+} |
+ |
+/// Scan [contents] and return the first token produced by the scanner. |
+Token tokenize(List<int> contents) { |
+ scanTimer.start(); |
+ var token = scan(contents).tokens; |
+ scanTimer.stop(); |
+ return token; |
+} |
+ |
+/// Scans every file in [files] and reports the time spent doing so. |
+void scanFiles(Map<Uri, List<int>> files) { |
+ scanTimer = new Stopwatch(); |
+ for (var source in files.values) { |
+ tokenize(source); |
+ } |
+ report('scan', scanTimer.elapsedMicroseconds); |
+} |
+ |
+/// Load and scans all files we need to process: files reachable from the |
+/// entrypoint and all core libraries automatically included by the VM. |
+Future<Map<Uri, List<int>>> scanReachableFiles(Uri entryUri) async { |
+ var files = <Uri, List<int>>{}; |
+ var loadTimer = new Stopwatch()..start(); |
+ scanTimer = new Stopwatch(); |
+ var entrypoints = [ |
+ entryUri, |
+ // These extra libraries are added to match the same set of libraries |
+ // scanned by default by the VM and the other benchmarks. |
+ Uri.parse('dart:async'), |
+ Uri.parse('dart:collection'), |
+ Uri.parse('dart:convert'), |
+ Uri.parse('dart:core'), |
+ Uri.parse('dart:developer'), |
+ Uri.parse('dart:_internal'), |
+ Uri.parse('dart:io'), |
+ Uri.parse('dart:isolate'), |
+ Uri.parse('dart:math'), |
+ Uri.parse('dart:mirrors'), |
+ Uri.parse('dart:typed_data'), |
+ ]; |
+ for (var entry in entrypoints) { |
+ await collectSources(entry, files); |
+ } |
+ loadTimer.stop(); |
+ |
+ inputSize = 0; |
+ // adjust size because there is a null-terminator on the contents. |
+ for (var source in files.values) inputSize += (source.length - 1); |
+ print('input size: ${inputSize} chars'); |
+ var loadTime = loadTimer.elapsedMicroseconds - scanTimer.elapsedMicroseconds; |
+ report('load', loadTime); |
+ report('scan', scanTimer.elapsedMicroseconds); |
+ return files; |
+} |
+ |
+/// Add to [files] all sources reachable from [start]. |
+Future<Null> collectSources(Uri start, Map<Uri, List<int>> files) async { |
+ helper(Uri uri) { |
+ uri = uriResolver.translate(uri) ?? uri; |
+ if (uri == null) return; |
+ if (files.containsKey(uri)) return; |
+ var contents = readBytesFromFileSync(uri); |
+ files[uri] = contents; |
+ for (var directiveUri in extractDirectiveUris(contents)) { |
+ helper(uri.resolve(directiveUri)); |
+ } |
+ } |
+ |
+ helper(start); |
+} |
+ |
+/// Parse [contents] as a Dart program and return the URIs that appear in its |
+/// import, export, and part directives. |
+Set<String> extractDirectiveUris(List<int> contents) { |
+ var listener = new DirectiveListener(); |
+ new DirectiveParser(listener).parseUnit(tokenize(contents)); |
+ return listener.uris; |
+} |
+ |
+/// Diet parser that stops eagerly at the first sign that we have seen all the |
+/// import, export, and part directives. |
+class DirectiveParser extends ClassMemberParser { |
+ DirectiveParser(listener) : super(listener); |
+ |
+ static final _endToken = new SymbolToken(EOF_INFO, -1); |
+ |
+ Token parseClassOrNamedMixinApplication(Token token) => _endToken; |
+ Token parseEnum(Token token) => _endToken; |
+ parseTypedef(token) => _endToken; |
+ parseTopLevelMember(Token token) => _endToken; |
+} |
+ |
+/// Listener that records the URIs from imports, exports, and part directives. |
+class DirectiveListener extends Listener { |
+ bool _inDirective = false; |
+ Set<String> uris = new Set<String>(); |
+ |
+ void _enterDirective() { |
+ _inDirective = true; |
+ } |
+ |
+ void _exitDirective() { |
+ _inDirective = false; |
+ } |
+ |
+ beginImport(_) => _enterDirective(); |
+ beginExport(_) => _enterDirective(); |
+ beginPart(_) => _enterDirective(); |
+ |
+ endExport(export, semicolon) => _exitDirective(); |
+ endImport(import, deferred, asKeyword, semicolon) => _exitDirective(); |
+ endPart(part, semicolon) => _exitDirective(); |
+ |
+ void beginLiteralString(Token token) { |
+ if (_inDirective) { |
+ var quotedString = token.value; |
+ uris.add(quotedString.substring(1, quotedString.length - 1)); |
+ } |
+ } |
+} |
+ |
+/// Parses every file in [files] and reports the time spent doing so. |
+void parseFiles(Map<Uri, List<int>> files) { |
+ scanTimer = new Stopwatch(); |
+ var parseTimer = new Stopwatch()..start(); |
+ files.forEach((uri, source) { |
+ parseFull(uri, source); |
+ }); |
+ parseTimer.stop(); |
+ |
+ report('scan', scanTimer.elapsedMicroseconds); |
+ report( |
+ 'parse', parseTimer.elapsedMicroseconds - scanTimer.elapsedMicroseconds); |
+} |
+ |
+/// Parse the full body of [source]. |
+parseFull(Uri uri, List<int> source) { |
+ var tokens = tokenize(source); |
+ Parser parser = new Parser(new _PartialAstBuilder(uri)); |
+ parser.parseUnit(tokens); |
+} |
+ |
+class _EmptyScope extends Scope { |
+ _EmptyScope() : super({}, null); |
+} |
+ |
+// Note: AstBuilder doesn't build compilation-units or classes, only method |
+// bodies. So this listener is not feature complete. |
+class _PartialAstBuilder extends AstBuilder { |
+ final Uri uri; |
+ _PartialAstBuilder(this.uri) : super(null, null, null, new _EmptyScope()); |
+ |
+ // Note: this method converts the body to kernel, so we skip that here. |
+ @override |
+ finishFunction(formals, asyncModifier, body) {} |
+} |
+ |
+// Invoke the fasta kernel generator for the program starting in [entryUri] |
+// TODO(sigmund): update to uyse the frontend api once fasta is beind hit. |
+generateKernel(Uri entryUri, {bool compileSdk: true}) async { |
+ // TODO(sigmund): this is here only to compute the input size, |
+ // we should extract the input size from the frontend instead. |
+ scanReachableFiles(entryUri); |
+ |
+ var timer = new Stopwatch()..start(); |
+ final Ticker ticker = new Ticker(); |
+ final DillTarget dillTarget = new DillTarget(ticker, uriResolver); |
+ final KernelTarget kernelTarget = new KernelTarget(dillTarget, uriResolver); |
+ var entrypoints = [ |
+ entryUri, |
+ // These extra libraries are added to match the same set of libraries |
+ // scanned by default by the VM and the other benchmarks. |
+ Uri.parse('dart:async'), |
+ Uri.parse('dart:collection'), |
+ Uri.parse('dart:convert'), |
+ Uri.parse('dart:core'), |
+ Uri.parse('dart:developer'), |
+ Uri.parse('dart:_internal'), |
+ Uri.parse('dart:io'), |
+ Uri.parse('dart:isolate'), |
+ Uri.parse('dart:math'), |
+ Uri.parse('dart:mirrors'), |
+ Uri.parse('dart:typed_data'), |
+ ]; |
+ entrypoints.forEach(kernelTarget.read); |
+ |
+ if (!compileSdk) { |
+ dillTarget.read( |
+ Uri.base.resolve(Platform.resolvedExecutable).resolve('platform.dill')); |
+ } |
+ await dillTarget.writeOutline(null); |
+ var program = await kernelTarget.writeOutline(null); |
+ program = await kernelTarget.writeProgram(null, AstKind.Kernel); |
+ if (kernelTarget.errors.isNotEmpty) { |
+ throw kernelTarget.errors.first; |
+ } |
+ timer.stop(); |
+ report('kernel_gen_e2e', timer.elapsedMicroseconds); |
+ return program; |
+} |
+ |
+/// Report that metric [name] took [time] micro-seconds to process |
+/// [inputSize] characters. |
+void report(String name, int time) { |
+ var sb = new StringBuffer(); |
+ var padding = ' ' * (20 - name.length); |
+ sb.write('$name:$padding $time us, ${time ~/ 1000} ms'); |
+ var invSpeed = (time * 1000 / inputSize).toStringAsFixed(2); |
+ sb.write(', $invSpeed ns/char'); |
+ print('$sb'); |
+} |