blob: ad1add7c6e2cb5db9ce0abe1f2eb0e144d519953 [file] [log] [blame] [edit]
// 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.
import 'dart:async';
import 'dart:typed_data';
// ignore: implementation_imports
import 'package:_js_interop_checks/src/transformations/static_interop_class_eraser.dart';
import 'package:collection/collection.dart';
// ignore: implementation_imports
import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
import 'package:kernel/class_hierarchy.dart' as ir;
import 'package:kernel/core_types.dart' as ir;
import 'package:kernel/kernel.dart' hide LibraryDependency, Combinator;
import 'package:kernel/target/targets.dart' hide DiagnosticReporter;
import 'package:kernel/type_environment.dart' as ir;
import 'package:kernel/verifier.dart';
import '../../compiler_api.dart' as api;
import '../common.dart';
import '../diagnostics/diagnostic_listener.dart';
import '../environment.dart';
import '../ir/annotations.dart';
import '../ir/constants.dart';
import '../kernel/dart2js_target.dart'
show
Dart2jsConstantsBackend,
Dart2jsDartLibrarySupport,
Dart2jsTarget,
implicitlyUsedLibraries;
import '../kernel/front_end_adapter.dart';
import '../kernel/transformations/global/transform.dart' as global_transforms;
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;
final fe.InitializedCompilerState? initializedCompilerState;
Output withNewComponent(ir.Component component) =>
Output(component, rootLibraryUri, libraries, initializedCompilerState);
Output(
this.component,
this.rootLibraryUri,
this.libraries,
this.initializedCompilerState,
);
}
Library _findEntryLibrary(Component component, Uri entryUri) {
final entryLibrary = component.libraries.firstWhereOrNull(
(l) => l.fileUri == entryUri,
);
if (entryLibrary == null) {
throw ArgumentError('Entry uri $entryUri not found in dill.');
}
return entryLibrary;
}
ir.Reference _findMainMethod(Library entryLibrary) {
var mainMethod = entryLibrary.procedures.firstWhereOrNull(
(p) => p.name.text == 'main',
);
// 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.firstWhereOrNull(
(p) => p.canonicalName?.name == 'main',
);
} else {
mainMethodReference = mainMethod.reference;
}
if (mainMethodReference == null) {
throw ArgumentError(
'Entry uri ${entryLibrary.fileUri} has no main method.',
);
}
return mainMethodReference;
}
String _getPlatformFilename(String targetName) => "${targetName}_platform.dill";
class _LoadFromKernelResult {
final ir.Component? component;
final Library? entryLibrary;
_LoadFromKernelResult(this.component, this.entryLibrary);
}
void _simplifyConstConditionals(
ir.Component component,
CompilerOptions options,
ir.ClassHierarchy classHierarchy,
DiagnosticReporter reporter,
) {
void reportMessage(
fe.LocatedMessage message,
List<fe.LocatedMessage>? context,
) {
reportLocatedMessage(reporter, message, context);
}
bool shouldNotInline(ir.TreeNode node) {
if (node is! ir.Annotatable) {
return false;
}
return computePragmaAnnotationDataFromIr(node).any(
(PragmaAnnotationData pragma) =>
pragma == const PragmaAnnotationData('noInline') ||
pragma == const PragmaAnnotationData('never-inline'),
);
}
fe.ConstConditionalSimplifier(
const Dart2jsDartLibrarySupport(),
const Dart2jsConstantsBackend(supportsUnevaluatedConstants: false),
component,
reportMessage,
environmentDefines: options.environment,
classHierarchy: classHierarchy,
shouldNotInline: shouldNotInline,
removeAsserts: !options.enableUserAssertions,
).run();
}
// Perform any backend-specific transforms here that can be done on both
// serialized components and components from source.
void _doTransformsOnKernelLoad(
Component component,
CompilerOptions options,
DiagnosticReporter reporter,
) {
if (options.stage.shouldRunGlobalTransforms) {
ir.CoreTypes coreTypes = ir.CoreTypes(component);
// Ignore ambiguous supertypes.
final classHierarchy = ir.ClassHierarchy(
component,
coreTypes,
onAmbiguousSupertypes: (_, _, _) {},
);
ir.TypeEnvironment typeEnvironment = ir.TypeEnvironment(
coreTypes,
classHierarchy,
);
final constantsEvaluator = Dart2jsConstantEvaluator(
component,
typeEnvironment,
(fe.LocatedMessage message, List<fe.LocatedMessage>? context) =>
reportLocatedMessage(reporter, message, context),
environment: Environment(options.environment),
);
StaticInteropClassEraser(coreTypes).visitComponent(component);
global_transforms.transformLibraries(
component.libraries,
constantsEvaluator,
coreTypes,
options,
);
_simplifyConstConditionals(component, options, classHierarchy, reporter);
}
}
Future<_LoadFromKernelResult> _loadFromKernel(
CompilerOptions options,
api.CompilerInput compilerInput,
String targetName,
DiagnosticReporter reporter,
) async {
Library? entryLibrary;
var resolvedUri = options.compilationTarget;
ir.Component component = ir.Component();
Future<void> read(Uri uri) async {
api.Input<Uint8List> input = await compilerInput.readFromUri(
uri,
inputKind: api.InputKind.binary,
);
BinaryBuilder(input.data).readComponent(component);
}
await read(resolvedUri);
if (options.platformBinaries != null &&
options.stage.shouldReadPlatformBinaries) {
var platformUri = options.platformBinaries?.resolve(
_getPlatformFilename(targetName),
);
// 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);
}
_doTransformsOnKernelLoad(component, options, reporter);
registerSources(component, compilerInput);
return _LoadFromKernelResult(component, entryLibrary);
}
class _LoadFromSourceResult {
final ir.Component? component;
final fe.InitializedCompilerState initializedCompilerState;
_LoadFromSourceResult(this.component, this.initializedCompilerState);
}
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,
supportsUnevaluatedConstants: !cfeConstants,
);
fe.FileSystem fileSystem = CompilerFileSystem(compilerInput);
fe.Verbosity verbosity = options.verbosity;
void onDiagnostic(fe.CfeDiagnosticMessage message) {
if (fe.Verbosity.shouldPrint(verbosity, message)) {
reportFrontEndMessage(reporter, message);
}
}
List<Uri> sources = [options.compilationTarget];
List<Uri> dependencies = [];
if (options.platformBinaries != null) {
dependencies.add(
options.platformBinaries!.resolve(_getPlatformFilename(targetName)),
);
}
if (options.dillDependencies != null) {
dependencies.addAll(options.dillDependencies!);
}
initializedCompilerState = fe.initializeCompiler(
initializedCompilerState,
target,
options.librariesSpecificationUri,
dependencies,
options.packageConfig,
explicitExperimentalFlags: options.explicitExperimentalFlags,
environmentDefines: environment,
invocationModes: options.cfeInvocationModes,
verbosity: verbosity,
);
ir.Component? component = await fe.compile(
initializedCompilerState,
verbose,
fileSystem,
onDiagnostic,
sources,
);
if (component != null) {
assert(() {
verifyComponent(
target,
VerificationStage.afterModularTransformations,
component,
);
return true;
}());
_doTransformsOnKernelLoad(component, options, reporter);
registerSources(component, compilerInput);
}
return _LoadFromSourceResult(component, initializedCompilerState);
}
Output _createOutput(
CompilerOptions options,
DiagnosticReporter reporter,
Library? entryLibrary,
ir.Component component,
fe.InitializedCompilerState? initializedCompilerState,
) {
Uri? rootLibraryUri;
Iterable<ir.Library> libraries = component.libraries;
if (component.mainMethod == null) {
// TODO(sigmund): move this so that we use the same error template
// from the CFE.
reporter.reportError(
reporter.createMessage(noLocationSpannable, 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 = <Library>{};
void 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(),
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;
fe.InitializedCompilerState? initializedCompilerState =
input.initializedCompilerState;
if (options.shouldLoadFromDill) {
_LoadFromKernelResult result = await _loadFromKernel(
options,
compilerInput,
targetName,
reporter,
);
component = result.component;
entryLibrary = result.entryLibrary;
} else {
_LoadFromSourceResult result = await _loadFromSource(
options,
compilerInput,
reporter,
input.initializedCompilerState,
targetName,
);
component = result.component;
initializedCompilerState = result.initializedCompilerState;
}
if (component == null) return null;
if (input.forceSerialization) {
// TODO(johnniwinther): Remove this when #34942 is fixed.
Uint8List data = fe.serializeComponent(component);
component = ir.Component();
BinaryBuilder(data).readComponent(component);
// Ensure we use the new deserialized entry point library.
entryLibrary = _findEntryLibrary(component, options.entryUri!);
}
return _createOutput(
options,
reporter,
entryLibrary,
component,
initializedCompilerState,
);
}
/// Registers with the dart2js compiler all sources embedded in a kernel
/// component. This may include sources that were read from disk directly as
/// files, but also sources that were embedded in binary `.dill` files (like the
/// platform kernel file).
///
/// This registration improves how locations are presented when errors
/// or crashes are reported by the dart2js compiler.
void registerSources(ir.Component component, api.CompilerInput compilerInput) {
component.uriToSource.forEach((uri, source) {
compilerInput.registerUtf8ContentsForDiagnostics(uri, source.source);
});
}