blob: f654265d11c1aefc5011a7e9ef693bf085f70cf7 [file] [log] [blame]
// Copyright (c) 2022, 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.
// @dart = 2.10
import 'dart:async';
import 'package:front_end/src/fasta/kernel/utils.dart';
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
import 'package:kernel/kernel.dart' hide LibraryDependency, Combinator;
import 'package:kernel/target/targets.dart' hide DiagnosticReporter;
import '../../compiler.dart' as api;
import '../commandline_options.dart';
import '../common.dart';
import '../kernel/front_end_adapter.dart';
import '../kernel/dart2js_target.dart'
show Dart2jsTarget, implicitlyUsedLibraries;
import '../kernel/transformations/clone_mixin_methods_with_super.dart'
as transformMixins show transformLibraries;
import '../options.dart';
class Input {
final CompilerOptions options;
final api.CompilerInput compilerInput;
final DiagnosticReporter reporter;
/// Shared state between compilations. Only used when loading from source.
final fe.InitializedCompilerState initializedCompilerState;
// TODO(johnniwinther): Remove this when #34942 is fixed.
/// Force in-memory serialization/deserialization of the loaded component.
///
/// This is used for testing.
final bool forceSerialization;
Input(this.options, this.compilerInput, this.reporter,
this.initializedCompilerState, this.forceSerialization);
}
/// Result of invoking the CFE to produce the kernel IR.
class Output {
final ir.Component component;
/// The [Uri] of the root library containing main.
/// Note: rootLibraryUri will be null for some modules, for example in the
/// case of dependent libraries processed modularly.
final Uri rootLibraryUri;
/// Returns the [Uri]s of all libraries that have been loaded that are
/// reachable from the [rootLibraryUri].
///
/// Note that [component] may contain some libraries that are excluded here.
final List<Uri> libraries;
/// When running only dart2js modular analysis, returns the [Uri]s for
/// libraries loaded in the input module.
///
/// This excludes other libraries reachable from them that were loaded as
/// dependencies. The result of [moduleLibraries] is always a subset of
/// [libraries].
final List<Uri> moduleLibraries;
final fe.InitializedCompilerState initializedCompilerState;
Output withNewComponent(ir.Component component) => Output(component,
rootLibraryUri, libraries, moduleLibraries, initializedCompilerState);
Output(this.component, this.rootLibraryUri, this.libraries,
this.moduleLibraries, this.initializedCompilerState);
}
Library _findEntryLibrary(Component component, Uri entryUri) {
var entryLibrary = component.libraries
.firstWhere((l) => l.fileUri == entryUri, orElse: () => null);
if (entryLibrary == null) {
throw ArgumentError('Entry uri $entryUri not found in dill.');
}
return entryLibrary;
}
ir.Reference _findMainMethod(Library entryLibrary) {
var mainMethod = entryLibrary.procedures
.firstWhere((p) => p.name.text == 'main', orElse: () => null);
// In some cases, a main method is defined in another file, and then
// exported. In these cases, we search for the main method in
// [additionalExports].
ir.Reference mainMethodReference;
if (mainMethod == null) {
mainMethodReference = entryLibrary.additionalExports
.firstWhere((p) => p.canonicalName.name == 'main', orElse: () => null);
} else {
mainMethodReference = mainMethod.reference;
}
if (mainMethodReference == null) {
throw ArgumentError(
'Entry uri ${entryLibrary.fileUri} has no main method.');
}
return mainMethodReference;
}
String _getPlatformFilename(CompilerOptions options, String targetName) {
String unsoundMarker = options.useLegacySubtyping ? "_unsound" : "";
return "${targetName}_platform$unsoundMarker.dill";
}
void _inferNullSafetyMode(CompilerOptions options, bool isSound) {
if (options.nullSafetyMode == NullSafetyMode.unspecified) {
options.nullSafetyMode =
isSound ? NullSafetyMode.sound : NullSafetyMode.unsound;
}
}
void _validateNullSafetyMode(CompilerOptions options) {
assert(options.nullSafetyMode != NullSafetyMode.unspecified);
}
class _LoadFromKernelResult {
final ir.Component component;
final Library entryLibrary;
final List<Uri> moduleLibraries;
_LoadFromKernelResult(
this.component, this.entryLibrary, this.moduleLibraries);
}
void _doGlobalTransforms(Component component) {
transformMixins.transformLibraries(component.libraries);
}
Future<_LoadFromKernelResult> _loadFromKernel(CompilerOptions options,
api.CompilerInput compilerInput, String targetName) async {
Library entryLibrary;
var resolvedUri = options.compilationTarget;
ir.Component component = ir.Component();
List<Uri> moduleLibraries = [];
Future<void> read(Uri uri) async {
api.Input input =
await compilerInput.readFromUri(uri, inputKind: api.InputKind.binary);
BinaryBuilder(input.data).readComponent(component);
}
await read(resolvedUri);
if (options.modularMode) {
moduleLibraries = component.libraries.map((lib) => lib.importUri).toList();
}
var isStrongDill =
component.mode == ir.NonNullableByDefaultCompiledMode.Strong;
var incompatibleNullSafetyMode =
isStrongDill ? NullSafetyMode.unsound : NullSafetyMode.sound;
if (options.nullSafetyMode == incompatibleNullSafetyMode) {
var dillMode = isStrongDill ? 'sound' : 'unsound';
var option = isStrongDill ? Flags.noSoundNullSafety : Flags.soundNullSafety;
throw ArgumentError("$resolvedUri was compiled with $dillMode null "
"safety and is incompatible with the '$option' option");
}
_inferNullSafetyMode(options, isStrongDill);
_validateNullSafetyMode(options);
// When compiling modularly, a dill for the SDK will be provided. In those
// cases we ignore the implicit platform binary.
bool platformBinariesIncluded =
options.modularMode || options.hasModularAnalysisInputs;
if (options.platformBinaries != null && !platformBinariesIncluded) {
var platformUri = options.platformBinaries
.resolve(_getPlatformFilename(options, targetName));
// Modular analysis can be run on the sdk by providing directly the
// path to the platform.dill file. In that case, we do not load the
// platform file implicitly.
// TODO(joshualitt): Change how we detect this case so it is less
// brittle.
if (platformUri != resolvedUri) await read(platformUri);
}
// Concatenate dills.
if (options.dillDependencies != null) {
for (Uri dependency in options.dillDependencies) {
await read(dependency);
}
}
if (options.entryUri != null) {
entryLibrary = _findEntryLibrary(component, options.entryUri);
var mainMethod = _findMainMethod(entryLibrary);
component.setMainMethodAndMode(mainMethod, true, component.mode);
}
// We apply global transforms when running phase 0.
if (options.cfeOnly) {
_doGlobalTransforms(component);
}
return _LoadFromKernelResult(component, entryLibrary, moduleLibraries);
}
class _LoadFromSourceResult {
final ir.Component component;
final fe.InitializedCompilerState initializedCompilerState;
final List<Uri> moduleLibraries;
_LoadFromSourceResult(
this.component, this.initializedCompilerState, this.moduleLibraries);
}
Future<_LoadFromSourceResult> _loadFromSource(
CompilerOptions options,
api.CompilerInput compilerInput,
DiagnosticReporter reporter,
fe.InitializedCompilerState initializedCompilerState,
String targetName) async {
bool verbose = false;
bool cfeConstants = options.features.cfeConstants.isEnabled;
Map<String, String> environment = cfeConstants ? options.environment : null;
Target target = Dart2jsTarget(targetName, TargetFlags(),
options: options,
canPerformGlobalTransforms: true,
supportsUnevaluatedConstants: !cfeConstants);
fe.FileSystem fileSystem = CompilerFileSystem(compilerInput);
fe.Verbosity verbosity = options.verbosity;
fe.DiagnosticMessageHandler onDiagnostic = (fe.DiagnosticMessage message) {
if (fe.Verbosity.shouldPrint(verbosity, message)) {
reportFrontEndMessage(reporter, message);
}
};
// If we are passed a list of sources, then we are performing a modular
// compile. In this case, we cannot infer null safety from the source files
// and must instead rely on the options passed in on the command line.
bool isModularCompile = false;
List<Uri> sources = [];
if (options.sources != null) {
isModularCompile = true;
sources.addAll(options.sources);
} else {
fe.CompilerOptions feOptions = fe.CompilerOptions()
..target = target
..librariesSpecificationUri = options.librariesSpecificationUri
..packagesFileUri = options.packageConfig
..explicitExperimentalFlags = options.explicitExperimentalFlags
..environmentDefines = environment
..verbose = verbose
..fileSystem = fileSystem
..onDiagnostic = onDiagnostic
..verbosity = verbosity;
Uri resolvedUri = options.compilationTarget;
bool isLegacy =
await fe.uriUsesLegacyLanguageVersion(resolvedUri, feOptions);
_inferNullSafetyMode(options, !isLegacy);
sources.add(options.compilationTarget);
}
// If we are performing a modular compile, we expect the platform binary to be
// supplied along with other dill dependencies.
List<Uri> dependencies = [];
if (options.platformBinaries != null && !isModularCompile) {
dependencies.add(options.platformBinaries
.resolve(_getPlatformFilename(options, targetName)));
}
if (options.dillDependencies != null) {
dependencies.addAll(options.dillDependencies);
}
initializedCompilerState = fe.initializeCompiler(
initializedCompilerState,
target,
options.librariesSpecificationUri,
dependencies,
options.packageConfig,
explicitExperimentalFlags: options.explicitExperimentalFlags,
environmentDefines: environment,
nnbdMode:
options.useLegacySubtyping ? fe.NnbdMode.Weak : fe.NnbdMode.Strong,
invocationModes: options.cfeInvocationModes,
verbosity: verbosity);
ir.Component component = await fe.compile(initializedCompilerState, verbose,
fileSystem, onDiagnostic, sources, isModularCompile);
_validateNullSafetyMode(options);
// We have to compute canonical names on the component here to avoid missing
// canonical names downstream.
if (isModularCompile) {
component.computeCanonicalNames();
}
return _LoadFromSourceResult(
component, initializedCompilerState, isModularCompile ? sources : []);
}
Output _createOutput(
CompilerOptions options,
DiagnosticReporter reporter,
Library entryLibrary,
ir.Component component,
List<Uri> moduleLibraries,
fe.InitializedCompilerState initializedCompilerState) {
Uri rootLibraryUri = null;
Iterable<ir.Library> libraries = component.libraries;
if (!options.modularMode) {
// For non-modular builds we should always have a [mainMethod] at this
// point.
if (component.mainMethod == null) {
// TODO(sigmund): move this so that we use the same error template
// from the CFE.
reporter.reportError(reporter.createMessage(NO_LOCATION_SPANNABLE,
MessageKind.GENERIC, {'text': "No 'main' method found."}));
}
// If we are building from dill and are passed an [entryUri], then we use
// that to find the appropriate [entryLibrary]. Otherwise, we fallback to
// the [enclosingLibrary] of the [mainMethod].
// NOTE: Under some circumstances, the [entryLibrary] exports the
// [mainMethod] from another library, and thus the [enclosingLibrary] of
// the [mainMethod] may not be the same as the [entryLibrary].
var root = entryLibrary ?? component.mainMethod.enclosingLibrary;
rootLibraryUri = root.importUri;
// Filter unreachable libraries: [Component] was built by linking in the
// entire SDK libraries, not all of them are used. We include anything
// that is reachable from `main`. Note that all internal libraries that
// the compiler relies on are reachable from `dart:core`.
var seen = Set<Library>();
search(ir.Library current) {
if (!seen.add(current)) return;
for (ir.LibraryDependency dep in current.dependencies) {
search(dep.targetLibrary);
}
}
search(root);
// Libraries dependencies do not show implicit imports to certain internal
// libraries.
const Set<String> alwaysInclude = {
'dart:_internal',
'dart:core',
'dart:async',
...implicitlyUsedLibraries,
};
for (String uri in alwaysInclude) {
Library library = component.libraries.firstWhere((lib) {
return '${lib.importUri}' == uri;
});
search(library);
}
libraries = libraries.where(seen.contains);
}
return Output(
component,
rootLibraryUri,
libraries.map((lib) => lib.importUri).toList(),
moduleLibraries,
initializedCompilerState);
}
/// Loads an entire Kernel [Component] from a file on disk.
Future<Output> run(Input input) async {
CompilerOptions options = input.options;
api.CompilerInput compilerInput = input.compilerInput;
DiagnosticReporter reporter = input.reporter;
String targetName = options.compileForServer ? "dart2js_server" : "dart2js";
Library entryLibrary;
ir.Component component;
List<Uri> moduleLibraries = const [];
fe.InitializedCompilerState initializedCompilerState =
input.initializedCompilerState;
if (options.fromDill) {
_LoadFromKernelResult result =
await _loadFromKernel(options, compilerInput, targetName);
component = result.component;
entryLibrary = result.entryLibrary;
moduleLibraries = result.moduleLibraries;
} else {
_LoadFromSourceResult result = await _loadFromSource(options, compilerInput,
reporter, input.initializedCompilerState, targetName);
component = result.component;
initializedCompilerState = result.initializedCompilerState;
moduleLibraries = result.moduleLibraries;
}
if (component == null) return null;
if (input.forceSerialization) {
// TODO(johnniwinther): Remove this when #34942 is fixed.
List<int> data = serializeComponent(component);
component = ir.Component();
BinaryBuilder(data).readComponent(component);
}
return _createOutput(options, reporter, entryLibrary, component,
moduleLibraries, initializedCompilerState);
}