blob: 861bec38d426b332a0bc900601475c2a48c87cb5 [file] [log] [blame]
// 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.
import 'dart:async';
import 'dart:convert';
import 'package:front_end/byte_store.dart';
import 'package:front_end/incremental_kernel_generator.dart';
import 'package:front_end/src/base/performance_logger.dart';
import 'package:front_end/src/base/processed_options.dart';
import 'package:front_end/src/byte_store/protected_file_byte_store.dart';
import 'package:front_end/src/fasta/uri_translator.dart';
import 'package:front_end/src/incremental/file_state.dart';
import 'package:front_end/src/incremental/kernel_driver.dart';
import 'package:kernel/kernel.dart';
import 'package:meta/meta.dart';
/// Implementation of [IncrementalKernelGenerator].
///
/// TODO(scheglov) Update the documentation.
///
/// Theory of operation: an instance of [IncrementalResolvedAstGenerator] is
/// used to obtain resolved ASTs, and these are fed into kernel code generation
/// logic.
class IncrementalKernelGeneratorImpl implements IncrementalKernelGenerator {
static const MSG_PENDING_COMPUTE =
'A computeDelta() invocation is still executing.';
static const MSG_NO_LAST_DELTA =
'The last delta has been already accepted or rejected.';
static const MSG_HAS_LAST_DELTA =
'The last delta must be either accepted or rejected.';
/// The logger to report compilation progress.
final PerformanceLog _logger;
/// The [ByteStore] used to cache results.
final ByteStore _byteStore;
/// The URI of the program entry point.
final Uri _entryPoint;
/// The function to notify when files become used or unused, or `null`.
final WatchUsedFilesFn _watchFn;
/// Whether we the generator is configured to use SDK outline.
bool _hasSdkOutlineBytes;
/// The [KernelDriver] that is used to compute kernels.
KernelDriver _driver;
/// Whether [computeDelta] is executing.
bool _isComputeDeltaExecuting = false;
/// The current signatures for libraries.
final Map<Uri, String> _currentSignatures = {};
/// The signatures for libraries produced by the last [computeDelta], or
/// `null` if the last delta was either accepted or rejected.
Map<Uri, String> _lastSignatures;
/// The object that provides additional information for tests.
_TestView _testView;
IncrementalKernelGeneratorImpl(ProcessedOptions options,
UriTranslator uriTranslator, List<int> sdkOutlineBytes, this._entryPoint,
{WatchUsedFilesFn watch})
: _logger = options.logger,
_byteStore = options.byteStore,
_watchFn = watch {
_hasSdkOutlineBytes = sdkOutlineBytes != null;
_testView = new _TestView(this);
Future<Null> onFileAdded(Uri uri) {
if (_watchFn != null) {
return _watchFn(uri, true);
}
return new Future.value();
}
_driver = new KernelDriver(options, uriTranslator,
sdkOutlineBytes: sdkOutlineBytes, fileAddedFn: onFileAdded);
}
/// Return the object that provides additional information for tests.
@visibleForTesting
_TestView get test => _testView;
@override
void acceptLastDelta() {
_throwIfNoLastDelta();
_updateProtectedFileByteStore();
_currentSignatures.addAll(_lastSignatures);
_lastSignatures = null;
}
@override
Future<DeltaProgram> computeDelta() {
if (_isComputeDeltaExecuting) {
throw new StateError(MSG_PENDING_COMPUTE);
}
if (_lastSignatures != null) {
throw new StateError(MSG_HAS_LAST_DELTA);
}
_lastSignatures = {};
_isComputeDeltaExecuting = true;
return _logger.runAsync('Compute delta', () async {
try {
KernelSequenceResult kernelResult =
await _driver.getKernelSequence(_entryPoint);
List<LibraryCycleResult> results = kernelResult.results;
// Exclude the SDK cycle if was not compiled.
if (_hasSdkOutlineBytes) {
results.removeWhere((cycle) => cycle.signature == '<sdk>');
}
// The file graph might have changed, perform GC.
await _gc();
// The set of affected library cycles (have different signatures).
final affectedLibraryCycles = new Set<LibraryCycle>();
for (LibraryCycleResult result in results) {
for (Library library in result.kernelLibraries) {
Uri uri = library.importUri;
if (_currentSignatures[uri] != result.signature) {
_lastSignatures[uri] = result.signature;
affectedLibraryCycles.add(result.cycle);
}
}
}
// The set of affected library cycles (have different signatures),
// or libraries that import or export affected libraries (so VM might
// have inlined some code from affected libraries into them).
final vmRequiredLibraryCycles = new Set<LibraryCycle>();
void gatherVmRequiredLibraryCycles(LibraryCycle cycle) {
if (vmRequiredLibraryCycles.add(cycle)) {
cycle.directUsers.forEach(gatherVmRequiredLibraryCycles);
}
}
affectedLibraryCycles.forEach(gatherVmRequiredLibraryCycles);
// Add required libraries.
Program program = new Program(nameRoot: kernelResult.nameRoot);
for (LibraryCycleResult result in results) {
if (vmRequiredLibraryCycles.contains(result.cycle)) {
program.uriToSource.addAll(result.uriToSource);
for (Library library in result.kernelLibraries) {
program.libraries.add(library);
library.parent = program;
}
}
}
// Set the main method.
if (program.libraries.isNotEmpty) {
for (Library library in results.last.kernelLibraries) {
if (library.importUri == _entryPoint) {
program.mainMethod = library.procedures.firstWhere(
(procedure) => procedure.name.name == 'main',
orElse: () => null);
break;
}
}
}
var stateString = _ExternalState.asString(_lastSignatures);
return new DeltaProgram(stateString, program);
} finally {
_isComputeDeltaExecuting = false;
}
});
}
@override
void invalidate(Uri uri) {
_driver.invalidate(uri);
}
@override
void rejectLastDelta() {
_throwIfNoLastDelta();
_lastSignatures = null;
}
@override
void reset() {
_currentSignatures.clear();
_lastSignatures = null;
}
@override
void setState(String state) {
if (_isComputeDeltaExecuting) {
throw new StateError(MSG_PENDING_COMPUTE);
}
var signatures = _ExternalState.fromString(state);
_currentSignatures.clear();
_currentSignatures.addAll(signatures);
}
/// Find files which are not referenced from the entry point and report
/// them to the watch function.
Future<Null> _gc() async {
var removedFiles = _driver.fsState.gc(_entryPoint);
if (removedFiles.isNotEmpty && _watchFn != null) {
for (var removedFile in removedFiles) {
await _watchFn(removedFile.fileUri, false);
}
}
}
/// Throw [StateError] if [_lastSignatures] is `null`, i.e. there is no
/// last delta - it either has not been computed yet, or has been already
/// accepted or rejected.
void _throwIfNoLastDelta() {
if (_isComputeDeltaExecuting) {
throw new StateError(MSG_PENDING_COMPUTE);
}
if (_lastSignatures == null) {
throw new StateError(MSG_NO_LAST_DELTA);
}
}
/// If [ProtectedFileByteStore] is used, update the protected keys.
void _updateProtectedFileByteStore() {
ByteStore byteStore = this._byteStore;
if (byteStore is ProtectedFileByteStore) {
// Compute the set of added and removed ByteStore keys.
// We use knowledge about KernelDriver implementation details.
var addedKeys = new Set<String>();
var removedKeys = new Set<String>();
for (var lastUri in _lastSignatures.keys) {
var currentSignature = _currentSignatures[lastUri];
var lastSignature = _lastSignatures[lastUri];
addedKeys.add('$lastSignature.kernel');
if (currentSignature != null && lastSignature != null) {
removedKeys.add('$currentSignature.kernel');
}
}
byteStore.updateProtectedKeys(
add: addedKeys.toList(), remove: removedKeys.toList());
}
}
}
class _ExternalState {
/// Return the JSON encoding of the [signatures].
static String asString(Map<Uri, String> signatures) {
var json = <String, String>{};
signatures.forEach((uri, signature) {
json[uri.toString()] = signature;
});
return JSON.encode(json);
}
/// Decode the given JSON [state] into the program state.
static Map<Uri, String> fromString(String state) {
var signatures = <Uri, String>{};
Map<String, String> json = JSON.decode(state);
json.forEach((uriStr, signature) {
var uri = Uri.parse(uriStr);
signatures[uri] = signature;
});
return signatures;
}
}
@visibleForTesting
class _TestView {
final IncrementalKernelGeneratorImpl _generator;
_TestView(this._generator);
/// The [KernelDriver] that is used to actually compile.
KernelDriver get driver => _generator._driver;
}