| 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');
|
| +}
|
|
|