| // 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/strategies.dart'; | |
| import 'serialization/task.dart'; | |
| import 'source_file_provider.dart'; | |
| import 'universe/codegen_world_builder.dart'; | |
| import 'universe/resolution_world_builder.dart'; | |
| import 'universe/selector.dart' show Selector; | |
| 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(); | |
| } | |
| } | |
| } |