blob: ffe1cb49a24a1e5bbcb348e369a20b1e66e95443 [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;
import 'dart:async' show Future;
import 'dart:convert' show jsonEncode;
import 'dart:typed_data';
import 'package:compiler/src/universe/use.dart' show StaticUse;
// ignore: implementation_imports
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/trivial.dart' show TrivialAbstractValueStrategy;
import 'inferrer/typemasks/masks.dart' show TypeMaskStrategy;
import 'inferrer/types.dart'
show GlobalTypeInferenceResults, GlobalTypeInferenceTask;
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, CompilerStage;
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;
enum _ResolutionStatus { resolving, doneResolving, compiling }
/// 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();
_ResolutionStatus? _resolutionStatus;
CompilerStage 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.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(noLocationSpannable, 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;
final trimmedComponent = ir.Component(
libraries: irLibraries,
uriToSource: component.uriToSource,
nameRoot: component.root,
);
trimmedComponent.setMainMethodAndMode(mainMethod, true);
return trimmedComponent;
}
Future<void> 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 = _ResolutionStatus.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 && stage.emitsDumpInfo) {
untrimmedComponentForDumpInfo = component;
}
if (stage.shouldOnlyComputeDill) {
Set<Uri> includedLibraries = output.libraries!.toSet();
if (options.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,
inferredData,
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.shouldWriteClosedWorld && closedWorld != null) {
serializationTask.serializeClosedWorld(closedWorld, indices);
if (options.producesModifiedDill) {
serializationTask.serializeComponent(
component,
includeSourceBytes: false,
);
}
} 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.shouldWriteClosedWorld ||
stage.emitsDeferredLoadIds ||
stopAfterClosedWorldForTesting;
Future<GlobalTypeInferenceResults> produceGlobalTypeInferenceResults(
JClosedWorld closedWorld,
ir.Component component,
SerializationIndices indices,
) async {
GlobalTypeInferenceResults globalTypeInferenceResults;
if (!stage.shouldReadGlobalInference) {
globalTypeInferenceResults = performGlobalTypeInference(closedWorld);
if (stage.shouldWriteGlobalInference) {
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.shouldWriteGlobalInference ||
stopAfterGlobalTypeInferenceForTesting;
CodegenInputs initializeCodegen(
GlobalTypeInferenceResults globalTypeInferenceResults,
) {
backendStrategy.registerJClosedWorld(
globalTypeInferenceResults.closedWorld,
);
_resolutionStatus = _ResolutionStatus.compiling;
return backendStrategy.onCodegenStart(globalTypeInferenceResults);
}
Future<CodegenResults> produceCodegenResults(
GlobalTypeInferenceResults globalTypeInferenceResults,
SourceLookup sourceLookup,
SerializationIndices indices,
) async {
CodegenInputs codegenInputs = initializeCodegen(globalTypeInferenceResults);
CodegenResults codegenResults;
if (stage.shouldReadCodegenShards && options.codegenShards != null) {
codegenResults = await serializationTask.deserializeCodegen(
backendStrategy,
globalTypeInferenceResults.closedWorld,
codegenInputs,
useDeferredSourceReads,
sourceLookup,
indices,
);
} else {
codegenResults = OnDemandCodegenResults(
codegenInputs,
backendStrategy.functionCompiler,
);
if (stage.shouldWriteCodegen) {
serializationTask.serializeCodegen(
backendStrategy,
globalTypeInferenceResults.closedWorld.abstractValueDomain,
codegenResults,
indices,
);
}
}
return codegenResults;
}
bool get shouldStopAfterCodegen => stage.shouldWriteCodegen;
bool get useDeferredSourceReads => stage.shouldUseDeferredSourceReads;
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;
DataSinkWriter? sinkForDumpInfo;
if (stage.emitsDumpInfo) {
globalTypeInferenceResultsForDumpInfo = globalTypeInferenceResults;
abstractValueDomainForDumpInfo = closedWorld.abstractValueDomain;
outputUnitDataForDumpInfo = closedWorld.outputUnitData;
indicesForDumpInfo = indices;
} else if (stage.shouldWriteDumpInfoData) {
sinkForDumpInfo = serializationTask.dataSinkWriterForDumpInfo(
closedWorld.abstractValueDomain,
indices,
);
dumpInfoRegistry.registerDataSinkWriter(sinkForDumpInfo);
}
// Run codegen.
final sourceLookup = SourceLookup(output.component);
CodegenResults codegenResults = await produceCodegenResults(
globalTypeInferenceResults,
sourceLookup,
indices,
);
if (shouldStopAfterCodegen) return;
final inferredData = globalTypeInferenceResults.inferredData;
if (stage.shouldReadDumpInfoData) {
final dumpInfoData = await serializationTask
.deserializeDumpInfoProgramData(
backendStrategy,
abstractValueDomainForDumpInfo!,
outputUnitDataForDumpInfo!,
indicesForDumpInfo!,
);
await runDumpInfo(
codegenResults,
globalTypeInferenceResultsForDumpInfo!,
dumpInfoData,
);
} else {
// Link.
final programSize = runCodegenEnqueuer(
codegenResults,
inferredData,
sourceLookup,
closedWorld,
);
if (stage.emitsDumpInfo || stage.shouldWriteDumpInfoData) {
final dumpInfoData = DumpInfoProgramData.fromEmitterResults(
backendStrategy.emitterTask,
dumpInfoRegistry,
programSize,
);
dumpInfoRegistry.close();
if (stage.shouldWriteDumpInfoData) {
serializationTask.serializeDumpInfoProgramData(
sinkForDumpInfo!,
backendStrategy,
dumpInfoData,
dumpInfoRegistry,
);
} 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,
codegenResults,
backendStrategy,
);
} else {
dumpInfoState = await dumpInfoTask.dumpInfo(
closedWorld,
globalTypeInferenceResults,
codegenResults,
backendStrategy,
);
}
if (retainDataForTesting) {
dumpInfoStateForTesting = dumpInfoState;
}
}
/// Perform the steps needed to fully end the resolution phase.
JClosedWorld? closeResolution(
FunctionEntity mainFunction,
ResolutionWorldBuilder resolutionWorldBuilder,
) {
_resolutionStatus = _ResolutionStatus.doneResolving;
KClosedWorld kClosedWorld = resolutionWorldBuilder.closeWorld(reporter);
OutputUnitData result = deferredLoadTask.run(mainFunction, kClosedWorld);
if (stage.emitsDeferredLoadIds) 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 Function(Enqueuer enqueuer)? onProgress,
}) {
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 Function(Enqueuer enqueuer)? onProgress,
}) {
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).
void checkQueue(Enqueuer enqueuer) {
enqueuer.checkQueueIsEmpty();
}
void showResolutionProgress(Enqueuer enqueuer) {
assert(
_resolutionStatus == _ResolutionStatus.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<Uint8List>> 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,
Object exception,
StackTrace stackTrace,
) {
reporter.onCrashInUserCode(message, exception, stackTrace);
}
/// Messages for which compile-time errors are reported but compilation
/// continues regardless.
static const List<MessageKind> benignErrors = <MessageKind>[
MessageKind.invalidMetadata,
MessageKind.invalidMetadataGeneric,
];
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 !benignErrors.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 == _ResolutionStatus.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.verboseInfo,
);
}
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)
: _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();
}
}
}