09ff1b56f7
This also: - replaces --use-kernel with --use-kernel-in-ssa - replaces --load-dill with --use-kernel - makes --use-kernel support both: compiling from .dill or from .dart - change the default of other options that are currently disabled when --use-kernel is on. - adds .status lines for all tests in language/corelib/dart2js_extra/dart2js_native R=efortuna@google.com Note: this CL is built on top of another change (https://codereview.chromium.org/2981403002/) that adds preview_dart_2 to test.dart Review-Url: https://codereview.chromium.org/2989453002 .
428 lines
16 KiB
Dart
428 lines
16 KiB
Dart
// Copyright (c) 2012, 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 leg_apiimpl;
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:package_config/packages.dart';
|
|
import 'package:package_config/packages_file.dart' as pkgs;
|
|
import 'package:package_config/src/packages_impl.dart'
|
|
show MapPackages, NonFilePackagesDirectoryPackages;
|
|
import 'package:package_config/src/util.dart' show checkValidPackageUri;
|
|
|
|
import '../compiler_new.dart' as api;
|
|
import 'common/tasks.dart' show GenericTask, Measurer;
|
|
import 'common.dart';
|
|
import 'compiler.dart';
|
|
import 'diagnostics/messages.dart' show Message;
|
|
import 'elements/elements.dart' as elements;
|
|
import 'environment.dart';
|
|
import 'library_loader.dart';
|
|
import 'io/source_file.dart';
|
|
import 'options.dart' show CompilerOptions;
|
|
import 'platform_configuration.dart' as platform_configuration;
|
|
import 'resolved_uri_translator.dart';
|
|
import 'script.dart';
|
|
|
|
/// Implements the [Compiler] using a [api.CompilerInput] for supplying the
|
|
/// sources.
|
|
class CompilerImpl extends Compiler {
|
|
final Measurer measurer;
|
|
api.CompilerInput provider;
|
|
api.CompilerDiagnostics handler;
|
|
Packages packages;
|
|
|
|
bool get mockableLibraryUsed => resolvedUriTranslator.isSet
|
|
? resolvedUriTranslator.mockableLibraryUsed
|
|
: false;
|
|
|
|
ForwardingResolvedUriTranslator resolvedUriTranslator;
|
|
|
|
GenericTask userHandlerTask;
|
|
GenericTask userProviderTask;
|
|
GenericTask userPackagesDiscoveryTask;
|
|
|
|
Uri get libraryRoot => options.platformConfigUri.resolve(".");
|
|
|
|
CompilerImpl(this.provider, api.CompilerOutput outputProvider, this.handler,
|
|
CompilerOptions options,
|
|
{MakeReporterFunction makeReporter})
|
|
// NOTE: allocating measurer is done upfront to ensure the wallclock is
|
|
// started before other computations.
|
|
: measurer = new Measurer(enableTaskMeasurements: options.verbose),
|
|
resolvedUriTranslator = new ForwardingResolvedUriTranslator(),
|
|
super(
|
|
options: options,
|
|
outputProvider: outputProvider,
|
|
environment: new _Environment(options.environment),
|
|
makeReporter: makeReporter) {
|
|
_Environment env = environment;
|
|
env.compiler = this;
|
|
tasks.addAll([
|
|
userHandlerTask = new GenericTask('Diagnostic handler', measurer),
|
|
userProviderTask = new GenericTask('Input provider', measurer),
|
|
userPackagesDiscoveryTask =
|
|
new GenericTask('Package discovery', measurer),
|
|
]);
|
|
}
|
|
|
|
void log(message) {
|
|
callUserHandler(
|
|
null, null, null, null, message, api.Diagnostic.VERBOSE_INFO);
|
|
}
|
|
|
|
/// Report [exception] reading [uri]. Use [element] and [node] to compute
|
|
/// the error location.
|
|
void _reportReadError(
|
|
Uri uri, elements.Element element, Spannable node, exception) {
|
|
if (element == null || node == null) {
|
|
reporter.reportErrorMessage(new SourceSpan(uri, 0, 0),
|
|
MessageKind.READ_SELF_ERROR, {'uri': uri, 'exception': exception});
|
|
} else {
|
|
reporter.withCurrentElement(element, () {
|
|
reporter.reportErrorMessage(node, MessageKind.READ_URI_ERROR,
|
|
{'uri': uri, 'exception': exception});
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the script designated by [readableUri].
|
|
*/
|
|
Future<Script> readScript(Uri readableUri, [Spannable node]) {
|
|
if (!readableUri.isAbsolute) {
|
|
if (node == null) node = NO_LOCATION_SPANNABLE;
|
|
reporter.internalError(
|
|
node, 'Relative uri $readableUri provided to readScript(Uri).');
|
|
}
|
|
|
|
// We need to store the current element since we are reporting read errors
|
|
// asynchronously and therefore need to restore the current element for
|
|
// [node] to be valid.
|
|
elements.Element element = currentElement;
|
|
|
|
Uri resourceUri = translateUri(node, readableUri);
|
|
if (resourceUri == null) return _synthesizeScript(readableUri);
|
|
if (resourceUri.scheme == 'dart-ext') {
|
|
if (!options.allowNativeExtensions) {
|
|
reporter.withCurrentElement(element, () {
|
|
reporter.reportErrorMessage(node, MessageKind.DART_EXT_NOT_SUPPORTED);
|
|
});
|
|
}
|
|
return _synthesizeScript(readableUri);
|
|
}
|
|
|
|
// TODO(johnniwinther): Wrap the result from [provider] in a specialized
|
|
// [Future] to ensure that we never execute an asynchronous action without
|
|
// setting up the current element of the compiler.
|
|
return new Future.sync(
|
|
() => callUserProvider(resourceUri, api.InputKind.utf8))
|
|
.then((api.Input sourceFile) {
|
|
// We use [readableUri] as the URI for the script since need to preserve
|
|
// the scheme in the script because [Script.uri] is used for resolving
|
|
// relative URIs mentioned in the script. See the comment on
|
|
// [LibraryLoader] for more details.
|
|
return new Script(readableUri, resourceUri, sourceFile);
|
|
}).catchError((error) {
|
|
_reportReadError(readableUri, element, node, error);
|
|
return _synthesizeScript(readableUri);
|
|
});
|
|
}
|
|
|
|
Future<Script> _synthesizeScript(Uri readableUri) {
|
|
return new Future.value(new Script.synthetic(readableUri));
|
|
}
|
|
|
|
Future<Binary> readBinary(Uri resourceUri, [Spannable node]) {
|
|
if (!resourceUri.isAbsolute) {
|
|
if (node == null) node = NO_LOCATION_SPANNABLE;
|
|
reporter.internalError(
|
|
node, 'Relative uri $resourceUri provided to readBinary(Uri).');
|
|
}
|
|
|
|
// We need to store the current element since we are reporting read errors
|
|
// asynchronously and therefore need to restore the current element for
|
|
// [node] to be valid.
|
|
elements.Element element = currentElement;
|
|
|
|
return new Future.sync(
|
|
() => callUserProvider(resourceUri, api.InputKind.binary))
|
|
.catchError((error) {
|
|
_reportReadError(resourceUri, element, node, error);
|
|
return new Binary(resourceUri, null);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Translates a readable URI into a resource URI.
|
|
*
|
|
* See [LibraryLoader] for terminology on URIs.
|
|
*/
|
|
Uri translateUri(Spannable node, Uri uri) =>
|
|
uri.scheme == 'package' ? translatePackageUri(node, uri) : uri;
|
|
|
|
Uri translatePackageUri(Spannable node, Uri uri) {
|
|
try {
|
|
checkValidPackageUri(uri);
|
|
} on ArgumentError catch (e) {
|
|
reporter.reportErrorMessage(node, MessageKind.INVALID_PACKAGE_URI,
|
|
{'uri': uri, 'exception': e.message});
|
|
return null;
|
|
}
|
|
return packages.resolve(uri, notFound: (Uri notFound) {
|
|
reporter.reportErrorMessage(
|
|
node, MessageKind.LIBRARY_NOT_FOUND, {'resolvedUri': uri});
|
|
return null;
|
|
});
|
|
}
|
|
|
|
Future<elements.LibraryElement> analyzeUri(Uri uri,
|
|
{bool skipLibraryWithPartOfTag: true}) {
|
|
Future setupFuture = new Future.value();
|
|
if (resolvedUriTranslator.isNotSet) {
|
|
setupFuture = setupFuture.then((_) => setupSdk());
|
|
}
|
|
if (packages == null) {
|
|
setupFuture = setupFuture.then((_) => setupPackages(uri));
|
|
}
|
|
return setupFuture.then((_) {
|
|
return super
|
|
.analyzeUri(uri, skipLibraryWithPartOfTag: skipLibraryWithPartOfTag);
|
|
});
|
|
}
|
|
|
|
Future setupPackages(Uri uri) {
|
|
if (options.packageRoot != null) {
|
|
// Use "non-file" packages because the file version requires a [Directory]
|
|
// and we can't depend on 'dart:io' classes.
|
|
packages = new NonFilePackagesDirectoryPackages(options.packageRoot);
|
|
} else if (options.packageConfig != null) {
|
|
Future<Binary> future =
|
|
callUserProvider(options.packageConfig, api.InputKind.binary);
|
|
return future.then((Binary binary) {
|
|
packages =
|
|
new MapPackages(pkgs.parse(binary.data, options.packageConfig));
|
|
}).catchError((error) {
|
|
reporter.reportErrorMessage(
|
|
NO_LOCATION_SPANNABLE,
|
|
MessageKind.INVALID_PACKAGE_CONFIG,
|
|
{'uri': options.packageConfig, 'exception': error});
|
|
packages = Packages.noPackages;
|
|
});
|
|
} else {
|
|
if (options.packagesDiscoveryProvider == null) {
|
|
packages = Packages.noPackages;
|
|
} else {
|
|
return callUserPackagesDiscovery(uri).then((p) {
|
|
packages = p;
|
|
});
|
|
}
|
|
}
|
|
return new Future.value();
|
|
}
|
|
|
|
Future<Null> setupSdk() {
|
|
Future future = new Future.value(null);
|
|
if (options.resolutionInputs != null) {
|
|
future = Future.forEach(options.resolutionInputs, (Uri resolutionInput) {
|
|
reporter.log('Reading serialized data from ${resolutionInput}');
|
|
Future<SourceFile> future =
|
|
callUserProvider(resolutionInput, api.InputKind.utf8);
|
|
return future.then((SourceFile sourceFile) {
|
|
serialization.deserializeFromText(
|
|
resolutionInput, sourceFile.slowText());
|
|
});
|
|
});
|
|
}
|
|
if (resolvedUriTranslator.isNotSet) {
|
|
future = future.then((_) {
|
|
return platform_configuration
|
|
.load(options.platformConfigUri, provider)
|
|
.then((Map<String, Uri> mapping) {
|
|
resolvedUriTranslator.resolvedUriTranslator =
|
|
new ResolvedUriTranslator(
|
|
mapping, reporter, options.platformConfigUri);
|
|
});
|
|
});
|
|
}
|
|
// TODO(johnniwinther): This does not apply anymore.
|
|
// The incremental compiler sets up the sdk before run.
|
|
// Therefore this will be called a second time.
|
|
return future;
|
|
}
|
|
|
|
Future<bool> run(Uri uri) {
|
|
Duration setupDuration = measurer.wallClock.elapsed;
|
|
return selfTask.measureSubtask("CompilerImpl.run", () {
|
|
log('Using platform configuration at ${options.platformConfigUri}');
|
|
|
|
return setupSdk().then((_) => setupPackages(uri)).then((_) {
|
|
assert(resolvedUriTranslator.isSet);
|
|
assert(packages != null);
|
|
|
|
return super.run(uri);
|
|
}).then((bool success) {
|
|
if (options.verbose) {
|
|
StringBuffer timings = new StringBuffer();
|
|
computeTimings(setupDuration, timings);
|
|
log("$timings");
|
|
}
|
|
return success;
|
|
});
|
|
});
|
|
}
|
|
|
|
void computeTimings(Duration setupDuration, StringBuffer timings) {
|
|
timings.writeln("Timings:");
|
|
Duration totalDuration = measurer.wallClock.elapsed;
|
|
Duration asyncDuration = measurer.asyncWallClock.elapsed;
|
|
Duration cumulatedDuration = Duration.ZERO;
|
|
for (final task in tasks) {
|
|
String running = task.isRunning ? "*" : "";
|
|
Duration duration = task.duration;
|
|
if (duration != Duration.ZERO) {
|
|
cumulatedDuration += duration;
|
|
timings.writeln(' $running${task.name} took'
|
|
' ${duration.inMilliseconds}msec');
|
|
for (String subtask in task.subtasks) {
|
|
int subtime = task.getSubtaskTime(subtask);
|
|
String running = task.getSubtaskIsRunning(subtask) ? "*" : "";
|
|
timings.writeln(
|
|
' $running${task.name} > $subtask took ${subtime}msec');
|
|
}
|
|
}
|
|
}
|
|
Duration unaccountedDuration =
|
|
totalDuration - cumulatedDuration - setupDuration - asyncDuration;
|
|
double percent =
|
|
unaccountedDuration.inMilliseconds * 100 / totalDuration.inMilliseconds;
|
|
timings.write(' Total compile-time ${totalDuration.inMilliseconds}msec;'
|
|
' setup ${setupDuration.inMilliseconds}msec;'
|
|
' async ${asyncDuration.inMilliseconds}msec;'
|
|
' unaccounted ${unaccountedDuration.inMilliseconds}msec'
|
|
' (${percent.toStringAsFixed(2)}%)');
|
|
}
|
|
|
|
void reportDiagnostic(DiagnosticMessage message,
|
|
List<DiagnosticMessage> infos, api.Diagnostic kind) {
|
|
_reportDiagnosticMessage(message, kind);
|
|
for (DiagnosticMessage info in infos) {
|
|
_reportDiagnosticMessage(info, api.Diagnostic.INFO);
|
|
}
|
|
}
|
|
|
|
void _reportDiagnosticMessage(
|
|
DiagnosticMessage diagnosticMessage, api.Diagnostic kind) {
|
|
// [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For
|
|
// instance in the [Types] constructor in typechecker.dart.
|
|
SourceSpan span = diagnosticMessage.sourceSpan;
|
|
Message message = diagnosticMessage.message;
|
|
if (span == null || span.uri == null) {
|
|
callUserHandler(message, null, null, null, '$message', kind);
|
|
} else {
|
|
callUserHandler(
|
|
message, span.uri, span.begin, span.end, '$message', kind);
|
|
}
|
|
}
|
|
|
|
bool get isMockCompilation =>
|
|
mockableLibraryUsed && options.allowMockCompilation;
|
|
|
|
void callUserHandler(Message message, Uri uri, int begin, int end,
|
|
String text, api.Diagnostic kind) {
|
|
try {
|
|
userHandlerTask.measure(() {
|
|
handler.report(message, uri, begin, end, text, kind);
|
|
});
|
|
} catch (ex, s) {
|
|
reportCrashInUserCode('Uncaught exception in diagnostic handler', ex, s);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<api.Input> callUserProvider(Uri uri, api.InputKind inputKind) {
|
|
try {
|
|
return userProviderTask
|
|
.measureIo(() => provider.readFromUri(uri, inputKind: inputKind));
|
|
} catch (ex, s) {
|
|
reportCrashInUserCode('Uncaught exception in input provider', ex, s);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<Packages> callUserPackagesDiscovery(Uri uri) {
|
|
try {
|
|
return userPackagesDiscoveryTask
|
|
.measureIo(() => options.packagesDiscoveryProvider(uri));
|
|
} catch (ex, s) {
|
|
reportCrashInUserCode('Uncaught exception in package discovery', ex, s);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Uri resolvePatchUri(String libraryName) {
|
|
return LibraryLoaderTask.resolvePatchUri(
|
|
libraryName, options.platformConfigUri);
|
|
}
|
|
}
|
|
|
|
class _Environment implements Environment {
|
|
final Map<String, String> definitions;
|
|
|
|
// TODO(sigmund): break the circularity here: Compiler needs an environment to
|
|
// initialize the library loader, but the environment here needs to know about
|
|
// how the sdk is set up and about whether the backend supports mirrors.
|
|
CompilerImpl compiler;
|
|
|
|
_Environment(this.definitions);
|
|
|
|
String valueOf(String name) {
|
|
assert(compiler.resolvedUriTranslator != null,
|
|
failedAt(NO_LOCATION_SPANNABLE, "setupSdk() has not been run"));
|
|
|
|
var result = definitions[name];
|
|
if (result != null || definitions.containsKey(name)) return result;
|
|
if (!name.startsWith(_dartLibraryEnvironmentPrefix)) return null;
|
|
|
|
String libraryName = name.substring(_dartLibraryEnvironmentPrefix.length);
|
|
|
|
// Private libraries are not exposed to the users.
|
|
if (libraryName.startsWith("_")) return null;
|
|
|
|
Uri libraryUri = compiler.resolvedUriTranslator.sdkLibraries[libraryName];
|
|
if (libraryUri != null && libraryUri.scheme != "unsupported") {
|
|
// Dart2js always "supports" importing 'dart:mirrors' but will abort
|
|
// the compilation at a later point if the backend doesn't support
|
|
// mirrors. In this case 'mirrors' should not be in the environment.
|
|
if (libraryName == 'mirrors') {
|
|
return compiler.backend.supportsReflection ? "true" : null;
|
|
}
|
|
return "true";
|
|
}
|
|
|
|
// Note: we return null on `dart:io` here, even if we allow users to
|
|
// unconditionally import it.
|
|
//
|
|
// In the past it was invalid to import `dart:io` for client apps. We just
|
|
// made it valid to import it as a stopgap measure to support packages like
|
|
// `http`. This is temporary until we support config-imports in the
|
|
// language.
|
|
//
|
|
// Because it is meant to be temporary and because the returned `dart:io`
|
|
// implementation will throw on most APIs, we still preserve that
|
|
// when compiling client apps the `dart:io` library is technically not
|
|
// supported, and so `const bool.fromEnvironment(dart.library.io)` is false.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// For every 'dart:' library, a corresponding environment variable is set
|
|
/// to "true". The environment variable's name is the concatenation of
|
|
/// this prefix and the name (without the 'dart:'.
|
|
///
|
|
/// For example 'dart:html' has the environment variable 'dart.library.html' set
|
|
/// to "true".
|
|
const String _dartLibraryEnvironmentPrefix = 'dart.library.';
|