Files
sdk/pkg/vm/lib/incremental_compiler.dart
T
Jens Johansen e491bc5d86 Allow to specify what dill file to initialize from
This CL updates the frontend_server to allow specifying what dill
file to initialize from.

Normally, it tries to initialize from the same dill file as is the
output. This is fine in many cases (e.g. flutter run always says it
should output to build/app.dill): The first time it compiles
everything, subsequent times (across restarts) it only recompiles
what's needed.

When running tests, however, the output is in a temporary directory,
so it doesn't work across restarts. The startup time for tests is
always rather high, because it always has to recompile everything.

This CL updates the frontend_server to allow specifying what dill
file to initialize from. Flutter can thus save the compiled test
file in a centralized location (e.g. build/test.dill) and load from
that across restarts.

Plumbing this into flutter yields the following result, when running
a single test (the one automatically created when creating a new
flutter project):

Before: ~5.5 seconds
Now, first run: ~5.5 second (unchanged)
Now, subsequent runs: ~3.4 seconds

Approximate breakdown:

Startup cost (e.g. if there's no test directory):        ~ 800 ms
Starting up the actual frontend server: ~150 ms
Reading source from old dill, loading all relevant files
and invalidating source:                                 ~ 300 ms
Compiling everything (or, rather, nothing as it's all
from the dill at this point):                            ~ 650 ms
Serializing:                                             ~ 250 ms
Copying files:                                           ~  50 ms
Running the actual test:                                 ~1100 ms

Total:                                                   ~3300 ms

`time flutter test --local-engine=host_debug --preview-dart-2`
says ~3.4 seconds...

To compare, --no-preview-dart-2 takes ~2.2 seconds,
so dart2 still has a (significantly) higher startup cost.

Moves the needle on https://github.com/flutter/flutter/issues/15767.

Change-Id: I36a2d05bc76e0193d369df42eb3b9f08416dd78f
Reviewed-on: https://dart-review.googlesource.com/51820
Reviewed-by: Kevin Millikin <kmillikin@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
2018-05-17 06:49:41 +00:00

107 lines
3.5 KiB
Dart

// Copyright (c) 2018, 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.
/// Defines wrapper class around incremental compiler to support
/// the flow, where incremental deltas can be rejected by VM.
import 'dart:async';
import 'package:front_end/src/api_prototype/compiler_options.dart';
import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
import 'package:kernel/kernel.dart';
const String kDebugProcedureName = ":Eval";
/// Wrapper around [IncrementalKernelGenerator] that keeps track of rejected
/// deltas and combines them together into resultant program until it is
/// accepted.
class IncrementalCompiler {
IncrementalKernelGenerator _generator;
List<Component> _pendingDeltas;
CompilerOptions _compilerOptions;
bool initialized = false;
bool fullComponent = false;
Uri initializeFromDillUri;
IncrementalCompiler(this._compilerOptions, Uri entryPoint,
{this.initializeFromDillUri}) {
_generator = new IncrementalKernelGenerator(
_compilerOptions, entryPoint, initializeFromDillUri);
_pendingDeltas = <Component>[];
}
/// Recompiles invalidated files, produces incremental component.
///
/// If [entryPoint] is specified, that points to new entry point for the
/// compilation. Otherwise, previously set entryPoint is used.
Future<Component> compile({Uri entryPoint}) async {
Component component = await _generator.computeDelta(
entryPoint: entryPoint, fullComponent: fullComponent);
initialized = true;
fullComponent = false;
final bool firstDelta = _pendingDeltas.isEmpty;
_pendingDeltas.add(component);
if (firstDelta) {
return component;
}
// If more than one delta is pending, we need to combine them.
Procedure mainMethod;
Map<Uri, Library> combined = <Uri, Library>{};
for (Component delta in _pendingDeltas) {
if (delta.mainMethod != null) {
mainMethod = delta.mainMethod;
}
for (Library library in delta.libraries) {
combined[library.importUri] = library;
}
}
return new Component(libraries: combined.values.toList())
..mainMethod = mainMethod;
}
/// This lets incremental compiler know that results of last [compile] call
/// were accepted, don't need to be included into subsequent [compile] calls
/// results.
accept() {
_pendingDeltas.clear();
}
/// This tells incremental compiler that it needs rescan [uri] file during
/// next [compile] call.
invalidate(Uri uri) {
_generator.invalidate(uri);
}
resetDeltaState() {
_pendingDeltas.clear();
fullComponent = true;
}
Future<Procedure> compileExpression(
String expression,
List<String> definitions,
List<String> typeDefinitions,
String libraryUri,
String klass,
bool isStatic) {
Map<String, DartType> completeDefinitions = {};
for (String name in definitions) {
if (!isLegalIdentifier(name)) continue;
completeDefinitions[name] = new DynamicType();
}
List<TypeParameter> typeParameters = [];
for (String name in typeDefinitions) {
if (!isLegalIdentifier(name)) continue;
typeParameters.add(new TypeParameter(name, new DynamicType()));
}
Uri library = Uri.parse(libraryUri);
if (library == null) return null;
return _generator.compileExpression(expression, completeDefinitions,
typeParameters, kDebugProcedureName, library, klass, isStatic);
}
}