blob: aa7303c5090b6943d16e58cf4f819ee9bff70b79 [file] [log] [blame]
// 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 dart2js.compiler_base;
import 'dart:async' show Future;
import 'dart:convert' show jsonEncode;
import 'package:compiler/src/serialization/indexed_sink_source.dart';
import 'package:compiler/src/universe/use.dart' show StaticUse;
import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
import 'package:kernel/ast.dart' as ir;
import '../compiler_api.dart' as api;
import 'common.dart';
import 'common/codegen.dart';
import 'common/elements.dart' show ElementEnvironment;
import 'common/metrics.dart' show Metric;
import 'common/names.dart' show Selectors;
import 'common/tasks.dart'
show CompilerTask, GenericTask, GenericTaskWithMetrics, Measurer;
import 'common/work.dart' show WorkItem;
import 'deferred_load/deferred_load.dart' show DeferredLoadTask;
import 'deferred_load/output_unit.dart' show OutputUnitData;
import 'deferred_load/program_split_constraints/nodes.dart' as psc
show ConstraintData;
import 'deferred_load/program_split_constraints/parser.dart' as psc show Parser;
import 'diagnostics/diagnostic_listener.dart';
import 'diagnostics/messages.dart' show Message;
import 'dump_info.dart'
show
DumpInfoJsAstRegistry,
DumpInfoProgramData,
DumpInfoStateData,
DumpInfoTask;
import 'elements/entities.dart';
import 'enqueue.dart' show Enqueuer;
import 'environment.dart';
import 'inferrer/abstract_value_domain.dart';
import 'inferrer/abstract_value_strategy.dart';
import 'inferrer/computable.dart' show ComputableAbstractValueStrategy;
import 'inferrer/powersets/powersets.dart' show PowersetStrategy;
import 'inferrer/trivial.dart' show TrivialAbstractValueStrategy;
import 'inferrer/typemasks/masks.dart' show TypeMaskStrategy;
import 'inferrer/types.dart'
show GlobalTypeInferenceResults, GlobalTypeInferenceTask;
import 'inferrer/wrapped.dart' show WrappedAbstractValueStrategy;
import 'io/source_information.dart';
import 'js_backend/codegen_inputs.dart' show CodegenInputs;
import 'js_backend/enqueuer.dart';
import 'js_backend/inferred_data.dart';
import 'js_model/js_strategy.dart';
import 'js_model/js_world.dart';
import 'js_model/locals.dart';
import 'kernel/front_end_adapter.dart' show CompilerFileSystem;
import 'kernel/kernel_strategy.dart';
import 'kernel/kernel_world.dart';
import 'null_compiler_output.dart' show NullCompilerOutput;
import 'options.dart' show CompilerOptions, Dart2JSStage;
import 'phase/load_kernel.dart' as load_kernel;
import 'resolution/enqueuer.dart';
import 'serialization/serialization.dart';
import 'serialization/task.dart';
import 'serialization/strategies.dart';
import 'source_file_provider.dart';
import 'universe/selector.dart' show Selector;
import 'universe/codegen_world_builder.dart';
import 'universe/resolution_world_builder.dart';
import 'universe/world_impact.dart' show WorldImpact, WorldImpactBuilderImpl;
/// Implementation of the compiler using a [api.CompilerInput] for supplying
/// the sources.
class Compiler {
final Measurer measurer;
final api.CompilerInput provider;
final api.CompilerDiagnostics handler;
late final KernelFrontendStrategy frontendStrategy;
late final JsBackendStrategy backendStrategy;
late final DiagnosticReporter _reporter;
late final Map<Entity, WorldImpact> _impactCache;
late final GenericTask userHandlerTask;
late final GenericTask userProviderTask;
/// Options provided from command-line arguments.
final CompilerOptions options;
// These internal flags are used to stop compilation after a specific phase.
// Used only for debugging and testing purposes only.
bool stopAfterClosedWorldForTesting = false;
bool stopAfterGlobalTypeInferenceForTesting = false;
/// Output provider from user of Compiler API.
late final api.CompilerOutput _outputProvider;
api.CompilerOutput get outputProvider => _outputProvider;
late ir.Component componentForTesting;
late JClosedWorld? backendClosedWorldForTesting;
late ResolutionEnqueuer resolutionEnqueuerForTesting;
late CodegenEnqueuer codegenEnqueuerForTesting;
late DumpInfoStateData dumpInfoStateForTesting;
ir.Component? untrimmedComponentForDumpInfo;
DiagnosticReporter get reporter => _reporter;
Map<Entity, WorldImpact> get impactCache => _impactCache;
late final Environment environment;
final DataReadMetrics dataReadMetrics = DataReadMetrics();
late final List<CompilerTask> tasks;
late final GenericTask loadKernelTask;
fe.InitializedCompilerState? initializedCompilerState;
bool forceSerializationForTesting = false;
late final GlobalTypeInferenceTask globalInference;
late final CodegenWorldBuilder _codegenWorldBuilder;
late AbstractValueStrategy abstractValueStrategy;
late final GenericTask selfTask;
late final GenericTask enqueueTask;
late final DeferredLoadTask deferredLoadTask;
late final DumpInfoTask dumpInfoTask;
final DumpInfoJsAstRegistry dumpInfoRegistry;
late final SerializationTask serializationTask;
Progress progress = const Progress();
static const int RESOLUTION_STATUS_SCANNING = 0;
static const int RESOLUTION_STATUS_RESOLVING = 1;
static const int RESOLUTION_STATUS_DONE_RESOLVING = 2;
static const int RESOLUTION_STATUS_COMPILING = 3;
int? resolutionStatus;
Dart2JSStage get stage => options.stage;
bool compilationFailed = false;
psc.ConstraintData? programSplitConstraintsData;
// Callback function used for testing resolution enqueuing.
void Function()? onResolutionQueueEmptyForTesting;
// Callback function used for testing codegen enqueuing.
void Function()? onCodegenQueueEmptyForTesting;
Compiler(this.provider, api.CompilerOutput outputProvider, this.handler,
this.options)
// NOTE: allocating measurer is done upfront to ensure the wallclock is
// started before other computations.
: measurer = Measurer(enableTaskMeasurements: options.verbose),
dumpInfoRegistry = DumpInfoJsAstRegistry(options) {
options.deriveOptions();
options.validate();
environment = Environment(options.environment);
abstractValueStrategy = options.useTrivialAbstractValueDomain
? const TrivialAbstractValueStrategy()
: const TypeMaskStrategy();
if (options.experimentalWrapped || options.testMode) {
abstractValueStrategy =
WrappedAbstractValueStrategy(abstractValueStrategy);
} else if (options.experimentalPowersets) {
abstractValueStrategy = PowersetStrategy(abstractValueStrategy);
}
if (options.debugGlobalInference) {
abstractValueStrategy =
ComputableAbstractValueStrategy(abstractValueStrategy);
}
CompilerTask kernelFrontEndTask;
selfTask = GenericTaskWithMetrics('self', measurer, dataReadMetrics);
_outputProvider = _CompilerOutput(this, outputProvider);
_reporter = DiagnosticReporter(this);
kernelFrontEndTask = GenericTask('Front end', measurer);
frontendStrategy =
KernelFrontendStrategy(kernelFrontEndTask, options, reporter);
backendStrategy = createBackendStrategy();
_impactCache = <Entity, WorldImpact>{};
if (options.showInternalProgress) {
progress = InteractiveProgress();
}
tasks = [
// [enqueueTask] is created earlier because it contains the resolution
// world objects needed by other tasks.
enqueueTask = GenericTask('Enqueue', measurer),
loadKernelTask = GenericTask('kernel loader', measurer),
kernelFrontEndTask,
globalInference = GlobalTypeInferenceTask(this),
deferredLoadTask = frontendStrategy.createDeferredLoadTask(this),
dumpInfoTask = DumpInfoTask(options, measurer, _outputProvider, reporter),
selfTask,
serializationTask = SerializationTask(
options, reporter, provider, outputProvider, measurer),
...backendStrategy.tasks,
userHandlerTask = GenericTask('Diagnostic handler', measurer),
userProviderTask = GenericTask('Input provider', measurer)
];
initializedCompilerState = options.kernelInitializedCompilerState;
}
/// Creates the backend strategy.
///
/// Override this to mock the backend strategy for testing.
JsBackendStrategy createBackendStrategy() {
return JsBackendStrategy(this);
}
ResolutionWorldBuilder? resolutionWorldBuilderForTesting;
KClosedWorld? get frontendClosedWorldForTesting =>
resolutionWorldBuilderForTesting?.closedWorldForTesting;
CodegenWorldBuilder get codegenWorldBuilder => _codegenWorldBuilder;
CodegenWorld? codegenWorldForTesting;
bool get disableTypeInference =>
options.disableTypeInference || compilationFailed;
// Compiles the dart program as specified in [options].
//
// The resulting future will complete with true if the compilation
// succeeded.
Future<bool> run() => selfTask.measureSubtask("run", () async {
measurer.startWallClock();
var setupDuration = measurer.elapsedWallClock;
try {
await runInternal();
} catch (error, stackTrace) {
await _reporter.onError(options.compilationTarget, error, stackTrace);
} finally {
measurer.stopWallClock();
}
dataReadMetrics.addDataRead(provider);
if (options.verbose) {
var timings = StringBuffer();
computeTimings(setupDuration, timings);
logVerbose('$timings');
}
if (options.reportPrimaryMetrics || options.reportSecondaryMetrics) {
var metrics = StringBuffer();
collectMetrics(metrics);
logInfo('$metrics');
}
return !compilationFailed;
});
/// Dumps a list of unused [ir.Library]'s in the [KernelResult]. This *must*
/// be called before [setMainAndTrimComponent], because that method will
/// discard the unused [ir.Library]s.
void dumpUnusedLibraries(ir.Component component, Set<Uri> libraries) {
bool isUnused(ir.Library l) => !libraries.contains(l.importUri);
String libraryString(ir.Library library) {
return '${library.importUri}(${library.fileUri})';
}
var unusedLibraries =
component.libraries.where(isUnused).map(libraryString).toList();
unusedLibraries.sort();
var jsonLibraries = jsonEncode(unusedLibraries);
outputProvider.createOutputSink(options.outputUri!.pathSegments.last,
'unused.json', api.OutputType.dumpUnusedLibraries)
..add(jsonLibraries)
..close();
reporter.reportInfo(
reporter.createMessage(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
'text': "${unusedLibraries.length} unused libraries out of "
"${component.libraries.length}. Dumping to JSON."
}));
}
/// Trims a component down to only the provided library uris.
ir.Component trimComponent(
ir.Component component, Set<Uri> librariesToInclude) {
var irLibraryMap = <Uri, ir.Library>{};
var irLibraries = <ir.Library>[];
for (var library in component.libraries) {
irLibraryMap[library.importUri] = library;
}
for (var library in librariesToInclude) {
irLibraries.add(irLibraryMap[library]!);
}
var mainMethod = component.mainMethodName;
var componentMode = component.mode;
final trimmedComponent = ir.Component(
libraries: irLibraries,
uriToSource: component.uriToSource,
nameRoot: component.root);
trimmedComponent.setMainMethodAndMode(mainMethod, true, componentMode);
return trimmedComponent;
}
Future runInternal() async {
clearState();
var compilationTarget = options.compilationTarget;
reporter.log('Compiling $compilationTarget (${options.buildId})');
if (options.readProgramSplit != null) {
var constraintUri = options.readProgramSplit;
var constraintParser = psc.Parser();
var programSplitJson = await CompilerFileSystem(provider)
.entityForUri(constraintUri!)
.readAsString();
programSplitConstraintsData = constraintParser.read(programSplitJson);
}
await selfTask.measureSubtask("compileFromKernel", () async {
await runSequentialPhases();
});
}
/// Clear the internal compiler state to prevent memory leaks when invoking
/// the compiler multiple times (e.g. in batch mode).
// TODO(ahe): implement a better mechanism where we can store
// such caches in the compiler and get access to them through a
// suitably maintained static reference to the current compiler.
void clearState() {
Selector.canonicalizedValues.clear();
StaticUse.clearCache();
// The selector objects held in static fields must remain canonical.
for (Selector selector in Selectors.ALL) {
Selector.canonicalizedValues
.putIfAbsent(selector.hashCode, () => <Selector>[])
.add(selector);
}
}
JClosedWorld? computeClosedWorld(
ir.Component component, Uri rootLibraryUri, List<Uri> libraries) {
frontendStrategy.registerLoadedLibraries(component, libraries);
ResolutionEnqueuer resolutionEnqueuer = frontendStrategy
.createResolutionEnqueuer(enqueueTask, this)
..onEmptyForTesting = onResolutionQueueEmptyForTesting;
if (retainDataForTesting) {
resolutionEnqueuerForTesting = resolutionEnqueuer;
resolutionWorldBuilderForTesting = resolutionEnqueuer.worldBuilder;
}
frontendStrategy.onResolutionStart();
for (LibraryEntity library
in frontendStrategy.elementEnvironment.libraries) {
frontendStrategy.elementEnvironment.forEachClass(library,
(ClassEntity cls) {
// Register all classes eagerly to optimize closed world computation in
// `ClassWorldBuilder.isInheritedInSubtypeOf`.
resolutionEnqueuer.worldBuilder.registerClass(cls);
});
}
WorldImpactBuilderImpl mainImpact = WorldImpactBuilderImpl();
final mainFunction = frontendStrategy.computeMain(mainImpact);
// In order to see if a library is deferred, we must compute the
// compile-time constants that are metadata. This means adding
// something to the resolution queue. So we cannot wait with
// this until after the resolution queue is processed.
deferredLoadTask.beforeResolution(rootLibraryUri, libraries);
resolutionStatus = RESOLUTION_STATUS_RESOLVING;
resolutionEnqueuer.applyImpact(mainImpact);
if (options.showInternalProgress) reporter.log('Computing closed world');
processQueue(
frontendStrategy.elementEnvironment, resolutionEnqueuer, mainFunction,
onProgress: showResolutionProgress);
resolutionEnqueuer.logSummary(reporter.log);
_reporter.reportSuppressedMessagesSummary();
if (compilationFailed) {
return null;
}
checkQueue(resolutionEnqueuer);
JClosedWorld? closedWorld =
closeResolution(mainFunction!, resolutionEnqueuer.worldBuilder);
return closedWorld;
}
Future<load_kernel.Output?> loadKernel() async {
final input = load_kernel.Input(options, provider, reporter,
initializedCompilerState, forceSerializationForTesting);
load_kernel.Output? output =
await loadKernelTask.measure(() async => load_kernel.run(input));
reporter.log("Kernel load complete");
return output;
}
Future<load_kernel.Output?> produceKernel() async {
if (!stage.shouldReadClosedWorld) {
load_kernel.Output? output = await loadKernel();
if (output == null) return null;
if (compilationFailed) {
// Some tests still use the component, even if the CFE failed.
frontendStrategy.registerComponent(output.component);
return null;
}
ir.Component component = output.component;
if (retainDataForTesting) {
componentForTesting = component;
}
if (options.features.newDumpInfo.isEnabled && options.dumpInfo) {
untrimmedComponentForDumpInfo = component;
}
if (stage.shouldOnlyComputeDill) {
Set<Uri> includedLibraries = output.libraries!.toSet();
if (stage.shouldLoadFromDill) {
if (options.dumpUnusedLibraries) {
dumpUnusedLibraries(component, includedLibraries);
}
if (options.entryUri != null) {
component = trimComponent(component, includedLibraries);
}
}
serializationTask.serializeComponent(component,
includeSourceBytes: false);
}
return output.withNewComponent(component);
} else {
ir.Component component =
await serializationTask.deserializeComponentAndUpdateOptions();
if (retainDataForTesting) {
componentForTesting = component;
}
return load_kernel.Output(component, null, null, null);
}
}
bool shouldStopAfterLoadKernel(load_kernel.Output? output) =>
output == null || compilationFailed || stage.shouldOnlyComputeDill;
GlobalTypeInferenceResults performGlobalTypeInference(
JClosedWorld closedWorld) {
FunctionEntity mainFunction = closedWorld.elementEnvironment.mainFunction!;
reporter.log('Performing global type inference');
GlobalLocalsMap globalLocalsMap =
GlobalLocalsMap(closedWorld.closureDataLookup.getEnclosingMember);
InferredDataBuilder inferredDataBuilder =
InferredDataBuilderImpl(closedWorld.annotationsData);
return globalInference.runGlobalTypeInference(
mainFunction, closedWorld, globalLocalsMap, inferredDataBuilder);
}
int runCodegenEnqueuer(
CodegenResults codegenResults,
InferredData inferredData,
SourceLookup sourceLookup,
JClosedWorld closedWorld) {
CodegenInputs codegenInputs = codegenResults.codegenInputs;
CodegenEnqueuer codegenEnqueuer = backendStrategy.createCodegenEnqueuer(
enqueueTask, closedWorld, codegenInputs, codegenResults, sourceLookup)
..onEmptyForTesting = onCodegenQueueEmptyForTesting;
if (retainDataForTesting) {
codegenEnqueuerForTesting = codegenEnqueuer;
}
_codegenWorldBuilder = codegenEnqueuer.worldBuilder;
reporter.log('Compiling methods');
FunctionEntity mainFunction = closedWorld.elementEnvironment.mainFunction!;
processQueue(closedWorld.elementEnvironment, codegenEnqueuer, mainFunction,
onProgress: showCodegenProgress);
codegenEnqueuer.logSummary(reporter.log);
CodegenWorld codegenWorld = codegenWorldBuilder.close();
if (retainDataForTesting) {
codegenWorldForTesting = codegenWorld;
}
reporter.log('Emitting JavaScript');
int programSize = backendStrategy.assembleProgram(
closedWorld, inferredData, codegenInputs, codegenWorld);
backendStrategy.onCodegenEnd(codegenInputs);
checkQueue(codegenEnqueuer);
return programSize;
}
JClosedWorld closedWorldTestMode(JClosedWorld closedWorld) {
SerializationIndices indices = SerializationIndices(testMode: true);
final strategy =
const BytesInMemorySerializationStrategy(useDataKinds: true);
// TODO(natebiggs): Add when kernel offsets are consistent across
// serialization layer.
// List<int> irData = strategy
// .serializeComponent(closedWorld.elementMap.programEnv.mainComponent);
// final component = strategy.deserializeComponent(irData);
List<int> closedWorldData =
strategy.serializeClosedWorld(closedWorld, options, indices);
final component = closedWorld.elementMap.programEnv.mainComponent;
return strategy.deserializeClosedWorld(options, reporter,
abstractValueStrategy, component, closedWorldData, indices);
}
GlobalTypeInferenceResults globalTypeInferenceResultsTestMode(
GlobalTypeInferenceResults results) {
SerializationIndices indices = SerializationIndices(testMode: true);
final strategy =
const BytesInMemorySerializationStrategy(useDataKinds: true);
final closedWorld = results.closedWorld;
final component = closedWorld.elementMap.programEnv.mainComponent;
List<int> globalTypeInferenceResultsData =
strategy.serializeGlobalTypeInferenceResults(results, options, indices);
return strategy.deserializeGlobalTypeInferenceResults(
options,
reporter,
environment,
abstractValueStrategy,
component,
closedWorld,
globalTypeInferenceResultsData,
indices);
}
Future<JClosedWorld?> produceClosedWorld(
load_kernel.Output output, SerializationIndices indices) async {
ir.Component component = output.component;
JClosedWorld? closedWorld;
if (!stage.shouldReadClosedWorld) {
Uri rootLibraryUri = output.rootLibraryUri!;
List<Uri> libraries = output.libraries!;
closedWorld = computeClosedWorld(component, rootLibraryUri, libraries);
if (stage == Dart2JSStage.closedWorld && closedWorld != null) {
serializationTask.serializeClosedWorld(closedWorld, indices);
} else if (options.testMode && closedWorld != null) {
closedWorld = closedWorldTestMode(closedWorld);
backendStrategy.registerJClosedWorld(closedWorld);
}
} else {
closedWorld = await serializationTask.deserializeClosedWorld(
abstractValueStrategy, component, useDeferredSourceReads, indices);
}
if (retainDataForTesting) {
backendClosedWorldForTesting = closedWorld;
}
return closedWorld;
}
bool shouldStopAfterClosedWorld(JClosedWorld? closedWorld) =>
closedWorld == null ||
stage == Dart2JSStage.closedWorld ||
stage == Dart2JSStage.deferredLoadIds ||
stopAfterClosedWorldForTesting;
Future<GlobalTypeInferenceResults> produceGlobalTypeInferenceResults(
JClosedWorld closedWorld,
ir.Component component,
SerializationIndices indices) async {
GlobalTypeInferenceResults globalTypeInferenceResults;
if (!stage.shouldReadGlobalInference) {
globalTypeInferenceResults = performGlobalTypeInference(closedWorld);
if (stage == Dart2JSStage.globalInference) {
serializationTask.serializeGlobalTypeInference(
globalTypeInferenceResults, indices);
} else if (options.testMode) {
globalTypeInferenceResults =
globalTypeInferenceResultsTestMode(globalTypeInferenceResults);
}
} else {
globalTypeInferenceResults =
await serializationTask.deserializeGlobalTypeInferenceResults(
environment,
abstractValueStrategy,
closedWorld.elementMap.programEnv.mainComponent,
closedWorld,
useDeferredSourceReads,
indices);
}
return globalTypeInferenceResults;
}
bool get shouldStopAfterGlobalTypeInference =>
stage == Dart2JSStage.globalInference ||
stopAfterGlobalTypeInferenceForTesting;
CodegenInputs initializeCodegen(
GlobalTypeInferenceResults globalTypeInferenceResults) {
backendStrategy
.registerJClosedWorld(globalTypeInferenceResults.closedWorld);
resolutionStatus = RESOLUTION_STATUS_COMPILING;
return backendStrategy.onCodegenStart(globalTypeInferenceResults);
}
Future<CodegenResults> produceCodegenResults(
GlobalTypeInferenceResults globalTypeInferenceResults,
SourceLookup sourceLookup,
SerializationIndices indices) async {
CodegenInputs codegenInputs = initializeCodegen(globalTypeInferenceResults);
CodegenResults codegenResults;
if (!stage.shouldReadCodegenShards) {
codegenResults = OnDemandCodegenResults(
codegenInputs, backendStrategy.functionCompiler);
if (stage == Dart2JSStage.codegenSharded) {
serializationTask.serializeCodegen(
backendStrategy,
globalTypeInferenceResults.closedWorld.abstractValueDomain,
codegenResults,
indices);
}
} else {
codegenResults = await serializationTask.deserializeCodegen(
backendStrategy,
globalTypeInferenceResults.closedWorld,
codegenInputs,
useDeferredSourceReads,
sourceLookup,
indices);
}
return codegenResults;
}
bool get shouldStopAfterCodegen => stage == Dart2JSStage.codegenSharded;
// Only use deferred reads for the linker phase as most deferred entities will
// not be needed. In other stages we use most of this data so it's not worth
// deferring.
bool get useDeferredSourceReads => stage == Dart2JSStage.jsEmitter;
Future<void> runSequentialPhases() async {
// Load kernel.
final output = await produceKernel();
if (shouldStopAfterLoadKernel(output)) return;
final indices = SerializationIndices();
// Compute closed world.
JClosedWorld? closedWorld = await produceClosedWorld(output!, indices);
if (shouldStopAfterClosedWorld(closedWorld)) return;
// Run global analysis.
GlobalTypeInferenceResults globalTypeInferenceResults =
await produceGlobalTypeInferenceResults(
closedWorld!, output.component, indices);
if (shouldStopAfterGlobalTypeInference) return;
closedWorld = globalTypeInferenceResults.closedWorld;
// Allow the original references to these to be GCed and only hold
// references to them if we are actually running the dump info task later.
SerializationIndices? indicesForDumpInfo;
GlobalTypeInferenceResults? globalTypeInferenceResultsForDumpInfo;
AbstractValueDomain? abstractValueDomainForDumpInfo;
OutputUnitData? outputUnitDataForDumpInfo;
if (options.dumpInfoWriteUri != null ||
options.dumpInfoReadUri != null ||
options.dumpInfo) {
globalTypeInferenceResultsForDumpInfo = globalTypeInferenceResults;
abstractValueDomainForDumpInfo = closedWorld.abstractValueDomain;
outputUnitDataForDumpInfo = closedWorld.outputUnitData;
indicesForDumpInfo = indices;
}
// Run codegen.
final sourceLookup = SourceLookup(output.component);
CodegenResults codegenResults = await produceCodegenResults(
globalTypeInferenceResults, sourceLookup, indices);
if (shouldStopAfterCodegen) return;
final inferredData = globalTypeInferenceResults.inferredData;
if (options.dumpInfoReadUri != null) {
final dumpInfoData =
await serializationTask.deserializeDumpInfoProgramData(
backendStrategy,
abstractValueDomainForDumpInfo!,
outputUnitDataForDumpInfo!,
indicesForDumpInfo!);
await runDumpInfo(
codegenResults, globalTypeInferenceResultsForDumpInfo!, dumpInfoData);
} else {
// Link.
final programSize = runCodegenEnqueuer(
codegenResults, inferredData, sourceLookup, closedWorld);
if (options.dumpInfo || options.dumpInfoWriteUri != null) {
final dumpInfoData = DumpInfoProgramData.fromEmitterResults(
backendStrategy, dumpInfoRegistry, programSize);
dumpInfoRegistry.clear();
if (options.dumpInfoWriteUri != null) {
serializationTask.serializeDumpInfoProgramData(
backendStrategy,
dumpInfoData,
abstractValueDomainForDumpInfo!,
indicesForDumpInfo!);
} else {
await runDumpInfo(codegenResults,
globalTypeInferenceResultsForDumpInfo!, dumpInfoData);
}
}
}
}
Future<void> runDumpInfo(
CodegenResults codegenResults,
GlobalTypeInferenceResults globalTypeInferenceResults,
DumpInfoProgramData dumpInfoProgramData) async {
JClosedWorld closedWorld = globalTypeInferenceResults.closedWorld;
DumpInfoStateData dumpInfoState;
dumpInfoTask.registerDumpInfoProgramData(dumpInfoProgramData);
if (options.features.newDumpInfo.isEnabled) {
untrimmedComponentForDumpInfo ??= (await produceKernel())!.component;
dumpInfoState = await dumpInfoTask.dumpInfoNew(
untrimmedComponentForDumpInfo!,
closedWorld,
globalTypeInferenceResults);
} else {
dumpInfoState =
await dumpInfoTask.dumpInfo(closedWorld, globalTypeInferenceResults);
}
if (retainDataForTesting) {
dumpInfoStateForTesting = dumpInfoState;
}
}
/// Perform the steps needed to fully end the resolution phase.
JClosedWorld? closeResolution(FunctionEntity mainFunction,
ResolutionWorldBuilder resolutionWorldBuilder) {
resolutionStatus = RESOLUTION_STATUS_DONE_RESOLVING;
KClosedWorld kClosedWorld = resolutionWorldBuilder.closeWorld(reporter);
OutputUnitData result = deferredLoadTask.run(mainFunction, kClosedWorld);
if (options.stage == Dart2JSStage.deferredLoadIds) return null;
// Impact data is no longer needed.
if (!retainDataForTesting) {
_impactCache.clear();
}
JClosedWorld jClosedWorld =
backendStrategy.createJClosedWorld(kClosedWorld, result);
return jClosedWorld;
}
/// Empty the [enqueuer] queue.
void emptyQueue(Enqueuer enqueuer, {void onProgress(Enqueuer enqueuer)?}) {
selfTask.measureSubtask("emptyQueue", () {
enqueuer.forEach((WorkItem work) {
if (onProgress != null) {
onProgress(enqueuer);
}
reporter.withCurrentElement(
work.element,
() => selfTask.measureSubtask("applyImpact", () {
enqueuer.applyImpact(
selfTask.measureSubtask("work.run", () => work.run()));
}));
});
});
}
void processQueue(ElementEnvironment elementEnvironment, Enqueuer enqueuer,
FunctionEntity? mainMethod,
{void onProgress(Enqueuer enqueuer)?}) {
selfTask.measureSubtask("processQueue", () {
enqueuer.open(
mainMethod,
elementEnvironment.libraries
.map((LibraryEntity library) => library.canonicalUri));
progress.startPhase();
emptyQueue(enqueuer, onProgress: onProgress);
enqueuer.queueIsClosed = true;
enqueuer.close();
assert(compilationFailed ||
enqueuer.checkNoEnqueuedInvokedInstanceMethods(elementEnvironment));
});
}
/// Perform various checks of the queue. This includes checking that the
/// queues are empty (nothing was added after we stopped processing the
/// queues).
checkQueue(Enqueuer enqueuer) {
enqueuer.checkQueueIsEmpty();
}
void showResolutionProgress(Enqueuer enqueuer) {
assert(resolutionStatus == RESOLUTION_STATUS_RESOLVING,
'Unexpected phase: $resolutionStatus');
progress.showProgress(
'Resolved ', enqueuer.processedEntities.length, ' elements.');
}
void showCodegenProgress(Enqueuer enqueuer) {
progress.showProgress(
'Compiled ', enqueuer.processedEntities.length, ' methods.');
}
void reportDiagnostic(DiagnosticMessage message,
List<DiagnosticMessage> infos, api.Diagnostic kind) {
_reportDiagnosticMessage(message, kind);
for (DiagnosticMessage info in infos) {
_reportDiagnosticMessage(info, api.Diagnostic.CONTEXT);
}
}
void _reportDiagnosticMessage(
DiagnosticMessage diagnosticMessage, api.Diagnostic kind) {
var span = diagnosticMessage.sourceSpan;
var message = diagnosticMessage.message;
// If the message came from the CFE use the message code as the text
// so that tests can determine the cause of the message.
final messageText =
diagnosticMessage is DiagnosticCfeMessage && options.testMode
? diagnosticMessage.messageCode
: '$message';
if (span.isUnknown) {
callUserHandler(message, null, null, null, messageText, kind);
} else {
callUserHandler(
message, span.uri, span.begin, span.end, messageText, kind);
}
}
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;
}
}
void reportCrashInUserCode(String message, exception, stackTrace) {
reporter.onCrashInUserCode(message, exception, stackTrace);
}
/// Messages for which compile-time errors are reported but compilation
/// continues regardless.
static const List<MessageKind> BENIGN_ERRORS = <MessageKind>[
MessageKind.INVALID_METADATA,
MessageKind.INVALID_METADATA_GENERIC,
];
bool markCompilationAsFailed(DiagnosticMessage message, api.Diagnostic kind) {
if (options.testMode) {
// When in test mode, i.e. on the build-bot, we always stop compilation.
return true;
}
if (reporter.options.fatalWarnings) {
return true;
}
return !BENIGN_ERRORS.contains(message.message.kind);
}
void fatalDiagnosticReported(DiagnosticMessage message,
List<DiagnosticMessage> infos, api.Diagnostic kind) {
if (markCompilationAsFailed(message, kind)) {
compilationFailed = true;
}
}
/// Compute a [SourceSpan] from spannable using the [currentElement] as
/// context.
SourceSpan spanFromSpannable(Spannable spannable, Entity? currentElement) {
SourceSpan span;
if (resolutionStatus == Compiler.RESOLUTION_STATUS_COMPILING) {
span = backendStrategy.spanFromSpannable(spannable, currentElement);
} else {
span = frontendStrategy.spanFromSpannable(spannable, currentElement);
}
return span;
}
/// Helper for determining whether [element] is declared within 'user code'.
bool inUserCode(Entity? element) {
return element == null || _uriFromElement(element) != null;
}
/// Return a canonical URI for the source of [element].
///
/// For a package library with canonical URI 'package:foo/bar/baz.dart' the
/// return URI is 'package:foo'. For non-package libraries the returned URI is
/// the canonical URI of the library itself.
Uri? getCanonicalUri(Entity element) {
final libraryUri = _uriFromElement(element);
if (libraryUri == null) return null;
if (libraryUri.isScheme('package')) {
int slashPos = libraryUri.path.indexOf('/');
if (slashPos != -1) {
String packageName = libraryUri.path.substring(0, slashPos);
return Uri(scheme: 'package', path: packageName);
}
}
return libraryUri;
}
Uri? _uriFromElement(Entity element) {
if (element is LibraryEntity) {
return element.canonicalUri;
} else if (element is ClassEntity) {
return element.library.canonicalUri;
} else if (element is MemberEntity) {
return element.library.canonicalUri;
}
return null;
}
void logInfo(String message) {
callUserHandler(null, null, null, null, message, api.Diagnostic.INFO);
}
void logVerbose(String message) {
callUserHandler(
null, null, null, null, message, api.Diagnostic.VERBOSE_INFO);
}
String _formatMs(int ms) {
return (ms / 1000).toStringAsFixed(3) + 's';
}
void computeTimings(Duration setupDuration, StringBuffer timings) {
timings.writeln("Timings:");
var totalDuration = measurer.elapsedWallClock;
var asyncDuration = measurer.elapsedAsyncWallClock;
var cumulatedDuration = Duration.zero;
var timingData = <_TimingData>[];
for (final task in tasks) {
var running = task.isRunning ? "*" : " ";
var duration = task.duration;
if (duration != Duration.zero) {
cumulatedDuration += duration;
var milliseconds = duration.inMilliseconds;
timingData.add(_TimingData(' $running${task.name}:', milliseconds,
milliseconds * 100 / totalDuration.inMilliseconds));
for (String subtask in task.subtasks) {
var subtime = task.getSubtaskTime(subtask);
var running = task.getSubtaskIsRunning(subtask) ? "*" : " ";
timingData.add(_TimingData(' $running${task.name} > $subtask:',
subtime, subtime * 100 / totalDuration.inMilliseconds));
}
}
}
int longestDescription = timingData
.map((d) => d.description.length)
.fold(0, (a, b) => a < b ? b : a);
for (var data in timingData) {
var ms = _formatMs(data.milliseconds);
var padding =
" " * (longestDescription + 10 - data.description.length - ms.length);
var percentPadding = data.percent < 10 ? " " : "";
timings.writeln('${data.description}$padding $ms '
'$percentPadding(${data.percent.toStringAsFixed(1)}%)');
}
var unaccountedDuration =
totalDuration - cumulatedDuration - setupDuration - asyncDuration;
var percent =
unaccountedDuration.inMilliseconds * 100 / totalDuration.inMilliseconds;
timings.write(
' Total compile-time ${_formatMs(totalDuration.inMilliseconds)};'
' setup ${_formatMs(setupDuration.inMilliseconds)};'
' async ${_formatMs(asyncDuration.inMilliseconds)};'
' unaccounted ${_formatMs(unaccountedDuration.inMilliseconds)}'
' (${percent.toStringAsFixed(2)}%)');
}
void collectMetrics(StringBuffer buffer) {
buffer.writeln('Metrics:');
for (final task in tasks) {
var metrics = task.metrics;
var namespace = metrics.namespace;
if (namespace == '') {
namespace =
task.name.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '_');
}
void report(Metric metric) {
buffer
.writeln(' ${namespace}.${metric.name}: ${metric.formatValue()}');
}
for (final metric in metrics.primary) {
report(metric);
}
if (options.reportSecondaryMetrics) {
for (final metric in metrics.secondary) {
report(metric);
}
}
}
}
}
class _CompilerOutput implements api.CompilerOutput {
final Compiler _compiler;
final api.CompilerOutput _userOutput;
_CompilerOutput(this._compiler, api.CompilerOutput? output)
: this._userOutput = output ?? const NullCompilerOutput();
@override
api.OutputSink createOutputSink(
String name, String extension, api.OutputType type) {
if (_compiler.compilationFailed) {
// Ensure that we don't emit output when the compilation has failed.
return const NullCompilerOutput().createOutputSink(name, extension, type);
}
return _userOutput.createOutputSink(name, extension, type);
}
@override
api.BinaryOutputSink createBinarySink(Uri uri) {
return _userOutput.createBinarySink(uri);
}
}
class _TimingData {
final String description;
final int milliseconds;
final double percent;
_TimingData(this.description, this.milliseconds, this.percent);
}
/// Interface for showing progress during compilation.
class Progress {
const Progress();
/// Starts a new phase for which to show progress.
void startPhase() {}
/// Shows progress of the current phase if needed. The shown message is
/// computed as '$prefix$count$suffix'.
void showProgress(String prefix, int count, String suffix) {}
}
/// Progress implementations that prints progress to the [DiagnosticReporter]
/// with 500ms intervals.
class ProgressImpl implements Progress {
final DiagnosticReporter _reporter;
final Stopwatch _stopwatch = Stopwatch()..start();
ProgressImpl(this._reporter);
@override
void showProgress(String prefix, int count, String suffix) {
if (_stopwatch.elapsedMilliseconds > 500) {
_reporter.log('$prefix$count$suffix');
_stopwatch.reset();
}
}
@override
void startPhase() {
_stopwatch.reset();
}
}
/// Progress implementations that prints progress to the [DiagnosticReporter]
/// with 500ms intervals using escape sequences to keep the progress data on a
/// single line.
class InteractiveProgress implements Progress {
final Stopwatch _stopwatchPhase = Stopwatch()..start();
final Stopwatch _stopwatchInterval = Stopwatch()..start();
@override
void startPhase() {
print('');
_stopwatchPhase.reset();
_stopwatchInterval.reset();
}
@override
void showProgress(String prefix, int count, String suffix) {
if (_stopwatchInterval.elapsedMilliseconds > 500) {
var time = _stopwatchPhase.elapsedMilliseconds / 1000;
var rate = count / _stopwatchPhase.elapsedMilliseconds;
var s = StringBuffer('\x1b[1A\x1b[K') // go up and clear the line.
..write('\x1b[48;5;40m\x1b[30m==>\x1b[0m $prefix')
..write(count)
..write('$suffix Elapsed time: ')
..write(time.toStringAsFixed(2))
..write(' s. Rate: ')
..write(rate.toStringAsFixed(2))
..write(' units/ms');
print('$s');
_stopwatchInterval.reset();
}
}
}