|  | // Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import 'package:front_end/src/api_unstable/dart2js.dart' as fe; | 
|  |  | 
|  | import '../common.dart'; | 
|  | import '../constants/values.dart' | 
|  | show ConstantValue, DeferredGlobalConstantValue; | 
|  | import '../elements/entities.dart'; | 
|  | import '../serialization/serialization.dart'; | 
|  | import '../options.dart'; | 
|  |  | 
|  | /// A "hunk" of the program that will be loaded whenever one of its [imports] | 
|  | /// are loaded. | 
|  | /// | 
|  | /// Elements that are only used in one deferred import, is in an OutputUnit with | 
|  | /// the deferred import as single element in the [imports] set. | 
|  | /// | 
|  | /// Whenever a deferred Element is shared between several deferred imports it is | 
|  | /// in an output unit with those imports in the [imports] Set. | 
|  | /// | 
|  | /// We never create two OutputUnits sharing the same set of [imports]. | 
|  | class OutputUnit implements Comparable<OutputUnit> { | 
|  | /// `true` if this output unit is for the main output file. | 
|  | final bool isMainOutput; | 
|  |  | 
|  | /// A unique name representing this [OutputUnit]. | 
|  | final String name; | 
|  |  | 
|  | /// The deferred imports that use the elements in this output unit. | 
|  | final Set<ImportEntity> imports; | 
|  |  | 
|  | OutputUnit(this.isMainOutput, this.name, this.imports); | 
|  |  | 
|  | @override | 
|  | int compareTo(OutputUnit other) { | 
|  | if (identical(this, other)) return 0; | 
|  | if (isMainOutput && !other.isMainOutput) return -1; | 
|  | if (!isMainOutput && other.isMainOutput) return 1; | 
|  | var size = imports.length; | 
|  | var otherSize = other.imports.length; | 
|  | if (size != otherSize) return size.compareTo(otherSize); | 
|  | var thisImports = imports.toList(); | 
|  | var otherImports = other.imports.toList(); | 
|  | for (var i = 0; i < size; i++) { | 
|  | var cmp = compareImportEntities(thisImports[i], otherImports[i]); | 
|  | if (cmp != 0) return cmp; | 
|  | } | 
|  | // TODO(sigmund): make compare stable.  If we hit this point, all imported | 
|  | // libraries are the same, however [this] and [other] use different deferred | 
|  | // imports in the program. We can make this stable if we sort based on the | 
|  | // deferred imports themselves (e.g. their declaration location). | 
|  | return name.compareTo(other.name); | 
|  | } | 
|  |  | 
|  | @override | 
|  | String toString() => "OutputUnit($name, $imports)"; | 
|  | } | 
|  |  | 
|  | int compareImportEntities(ImportEntity a, ImportEntity b) { | 
|  | if (a == b) { | 
|  | return 0; | 
|  | } else { | 
|  | return a.uri.path.compareTo(b.uri.path); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Interface for updating an [OutputUnitData] object with data for late | 
|  | /// members, that is, members created on demand during code generation. | 
|  | class LateOutputUnitDataBuilder { | 
|  | final OutputUnitData _outputUnitData; | 
|  |  | 
|  | LateOutputUnitDataBuilder(this._outputUnitData); | 
|  |  | 
|  | /// Registers [newEntity] to be emitted in the same output unit as | 
|  | /// [existingEntity]; | 
|  | void registerColocatedMembers( | 
|  | MemberEntity existingEntity, MemberEntity newEntity) { | 
|  | assert(_outputUnitData._memberToUnit[newEntity] == null); | 
|  | _outputUnitData._memberToUnit[newEntity] = | 
|  | _outputUnitData.outputUnitForMember(existingEntity); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Results of the deferred loading algorithm. | 
|  | /// | 
|  | /// Provides information about the output unit associated with entities and | 
|  | /// constants, as well as other helper methods. | 
|  | // TODO(sigmund): consider moving here every piece of data used as a result of | 
|  | // deferred loading (including hunksToLoad, etc). | 
|  | class OutputUnitData { | 
|  | /// Tag used for identifying serialized [OutputUnitData] objects in a | 
|  | /// debugging data stream. | 
|  | static const String tag = 'output-unit-data'; | 
|  |  | 
|  | final bool isProgramSplit; | 
|  | final OutputUnit mainOutputUnit; | 
|  | final Map<ClassEntity, OutputUnit> _classToUnit; | 
|  | final Map<ClassEntity, OutputUnit> _classTypeToUnit; | 
|  | final Map<MemberEntity, OutputUnit> _memberToUnit; | 
|  | final Map<Local, OutputUnit> _localFunctionToUnit; | 
|  | final Map<ConstantValue, OutputUnit> _constantToUnit; | 
|  | final List<OutputUnit> outputUnits; | 
|  | final Map<ImportEntity, String> importDeferName; | 
|  |  | 
|  | /// Because the token-stream is forgotten later in the program, we cache a | 
|  | /// description of each deferred import. | 
|  | final Map<ImportEntity, ImportDescription> deferredImportDescriptions; | 
|  |  | 
|  | OutputUnitData( | 
|  | this.isProgramSplit, | 
|  | this.mainOutputUnit, | 
|  | this._classToUnit, | 
|  | this._classTypeToUnit, | 
|  | this._memberToUnit, | 
|  | this._localFunctionToUnit, | 
|  | this._constantToUnit, | 
|  | this.outputUnits, | 
|  | this.importDeferName, | 
|  | this.deferredImportDescriptions); | 
|  |  | 
|  | // Creates J-world data from the K-world data. | 
|  | factory OutputUnitData.from( | 
|  | OutputUnitData other, | 
|  | LibraryEntity convertLibrary(LibraryEntity library), | 
|  | Map<ClassEntity, OutputUnit> Function( | 
|  | Map<ClassEntity, OutputUnit>, Map<Local, OutputUnit>) | 
|  | convertClassMap, | 
|  | Map<MemberEntity, OutputUnit> Function( | 
|  | Map<MemberEntity, OutputUnit>, Map<Local, OutputUnit>) | 
|  | convertMemberMap, | 
|  | Map<ConstantValue, OutputUnit> Function(Map<ConstantValue, OutputUnit>) | 
|  | convertConstantMap) { | 
|  | Map<ClassEntity, OutputUnit> classToUnit = | 
|  | convertClassMap(other._classToUnit, other._localFunctionToUnit); | 
|  | Map<ClassEntity, OutputUnit> classTypeToUnit = | 
|  | convertClassMap(other._classTypeToUnit, other._localFunctionToUnit); | 
|  | Map<MemberEntity, OutputUnit> memberToUnit = | 
|  | convertMemberMap(other._memberToUnit, other._localFunctionToUnit); | 
|  | Map<ConstantValue, OutputUnit> constantToUnit = | 
|  | convertConstantMap(other._constantToUnit); | 
|  | Map<ImportEntity, ImportDescription> deferredImportDescriptions = {}; | 
|  | other.deferredImportDescriptions | 
|  | .forEach((ImportEntity import, ImportDescription description) { | 
|  | deferredImportDescriptions[import] = ImportDescription.internal( | 
|  | description.importingUri, | 
|  | description.prefix, | 
|  | convertLibrary(description.importingLibrary)); | 
|  | }); | 
|  |  | 
|  | return OutputUnitData( | 
|  | other.isProgramSplit, | 
|  | other.mainOutputUnit, | 
|  | classToUnit, | 
|  | classTypeToUnit, | 
|  | memberToUnit, | 
|  | // Local functions only make sense in the K-world model. | 
|  | const {}, | 
|  | constantToUnit, | 
|  | other.outputUnits, | 
|  | other.importDeferName, | 
|  | deferredImportDescriptions); | 
|  | } | 
|  |  | 
|  | /// Deserializes an [OutputUnitData] object from [source]. | 
|  | factory OutputUnitData.readFromDataSource(DataSourceReader source) { | 
|  | source.begin(tag); | 
|  | bool isProgramSplit = source.readBool(); | 
|  | List<OutputUnit> outputUnits = source.readList(() { | 
|  | bool isMainOutput = source.readBool(); | 
|  | String name = source.readString(); | 
|  | Set<ImportEntity> importSet = source.readImports().toSet(); | 
|  | return OutputUnit(isMainOutput, name, importSet); | 
|  | }); | 
|  | OutputUnit mainOutputUnit = outputUnits[source.readInt()]; | 
|  |  | 
|  | Map<ClassEntity, OutputUnit> classToUnit = source.readClassMap(() { | 
|  | return outputUnits[source.readInt()]; | 
|  | }); | 
|  | Map<ClassEntity, OutputUnit> classTypeToUnit = source.readClassMap(() { | 
|  | return outputUnits[source.readInt()]; | 
|  | }); | 
|  | Map<MemberEntity, OutputUnit> memberToUnit = | 
|  | source.readMemberMap((MemberEntity member) { | 
|  | return outputUnits[source.readInt()]; | 
|  | }); | 
|  | Map<ConstantValue, OutputUnit> constantToUnit = source.readConstantMap(() { | 
|  | return outputUnits[source.readInt()]; | 
|  | }); | 
|  | Map<ImportEntity, String> importDeferName = | 
|  | source.readImportMap(source.readString); | 
|  | Map<ImportEntity, ImportDescription> deferredImportDescriptions = | 
|  | source.readImportMap(() { | 
|  | String importingUri = source.readString(); | 
|  | String prefix = source.readString(); | 
|  | LibraryEntity importingLibrary = source.readLibrary(); | 
|  | return ImportDescription.internal(importingUri, prefix, importingLibrary); | 
|  | }); | 
|  | source.end(tag); | 
|  | return OutputUnitData( | 
|  | isProgramSplit, | 
|  | mainOutputUnit, | 
|  | classToUnit, | 
|  | classTypeToUnit, | 
|  | memberToUnit, | 
|  | // Local functions only make sense in the K-world model. | 
|  | const {}, | 
|  | constantToUnit, | 
|  | outputUnits, | 
|  | importDeferName, | 
|  | deferredImportDescriptions); | 
|  | } | 
|  |  | 
|  | /// Serializes this [OutputUnitData] to [sink]. | 
|  | void writeToDataSink(DataSinkWriter sink) { | 
|  | sink.begin(tag); | 
|  | sink.writeBool(isProgramSplit); | 
|  | Map<OutputUnit, int> outputUnitIndices = {}; | 
|  | sink.writeList(outputUnits, (OutputUnit outputUnit) { | 
|  | outputUnitIndices[outputUnit] = outputUnitIndices.length; | 
|  | sink.writeBool(outputUnit.isMainOutput); | 
|  | sink.writeString(outputUnit.name); | 
|  | sink.writeImports(outputUnit.imports); | 
|  | }); | 
|  | sink.writeInt(outputUnitIndices[mainOutputUnit]!); | 
|  | sink.writeClassMap(_classToUnit, (OutputUnit outputUnit) { | 
|  | sink.writeInt(outputUnitIndices[outputUnit]!); | 
|  | }); | 
|  | sink.writeClassMap(_classTypeToUnit, (OutputUnit outputUnit) { | 
|  | sink.writeInt(outputUnitIndices[outputUnit]!); | 
|  | }); | 
|  | sink.writeMemberMap(_memberToUnit, | 
|  | (MemberEntity member, OutputUnit outputUnit) { | 
|  | sink.writeInt(outputUnitIndices[outputUnit]!); | 
|  | }); | 
|  | sink.writeConstantMap(_constantToUnit, (OutputUnit outputUnit) { | 
|  | sink.writeInt(outputUnitIndices[outputUnit]!); | 
|  | }); | 
|  | sink.writeImportMap(importDeferName, sink.writeString); | 
|  | sink.writeImportMap(deferredImportDescriptions, | 
|  | (ImportDescription importDescription) { | 
|  | sink.writeString(importDescription.importingUri); | 
|  | sink.writeString(importDescription.prefix); | 
|  | sink.writeLibrary(importDescription.importingLibrary); | 
|  | }); | 
|  | sink.end(tag); | 
|  | } | 
|  |  | 
|  | /// Returns the [OutputUnit] where [cls] belongs. | 
|  | // TODO(johnniwinther): Remove the need for [allowNull]. Dump-info currently | 
|  | // needs it. | 
|  | OutputUnit outputUnitForClass(ClassEntity cls, {bool allowNull = false}) { | 
|  | if (!isProgramSplit) return mainOutputUnit; | 
|  | OutputUnit? unit = _classToUnit[cls]; | 
|  | assert(allowNull || unit != null, 'No output unit for class $cls'); | 
|  | return unit ?? mainOutputUnit; | 
|  | } | 
|  |  | 
|  | OutputUnit? outputUnitForClassForTesting(ClassEntity cls) => | 
|  | _classToUnit[cls]; | 
|  |  | 
|  | /// Returns the [OutputUnit] where [cls]'s type belongs. | 
|  | // TODO(joshualitt): see above TODO regarding allowNull. | 
|  | OutputUnit outputUnitForClassType(ClassEntity cls, {bool allowNull = false}) { | 
|  | if (!isProgramSplit) return mainOutputUnit; | 
|  | OutputUnit? unit = _classTypeToUnit[cls]; | 
|  | assert(allowNull || unit != null, 'No output unit for type $cls'); | 
|  | return unit ?? mainOutputUnit; | 
|  | } | 
|  |  | 
|  | OutputUnit? outputUnitForClassTypeForTesting(ClassEntity cls) => | 
|  | _classTypeToUnit[cls]; | 
|  |  | 
|  | /// Returns the [OutputUnit] where [member] belongs. | 
|  | OutputUnit outputUnitForMember(MemberEntity member) { | 
|  | if (!isProgramSplit) return mainOutputUnit; | 
|  | OutputUnit? unit = _memberToUnit[member]; | 
|  | assert(unit != null, 'No output unit for member $member'); | 
|  | return unit ?? mainOutputUnit; | 
|  | } | 
|  |  | 
|  | OutputUnit? outputUnitForMemberForTesting(MemberEntity member) => | 
|  | _memberToUnit[member]; | 
|  |  | 
|  | /// Direct access to the output-unit to constants map used for testing. | 
|  | Iterable<ConstantValue> get constantsForTesting => _constantToUnit.keys; | 
|  |  | 
|  | /// Returns the [OutputUnit] where [constant] belongs. | 
|  | OutputUnit outputUnitForConstant(ConstantValue constant) { | 
|  | if (!isProgramSplit) return mainOutputUnit; | 
|  | OutputUnit? unit = _constantToUnit[constant]; | 
|  | // TODO(sigmund): enforce unit is not null: it is sometimes null on some | 
|  | // corner cases on internal apps. | 
|  | return unit ?? mainOutputUnit; | 
|  | } | 
|  |  | 
|  | OutputUnit? outputUnitForConstantForTesting(ConstantValue constant) => | 
|  | _constantToUnit[constant]; | 
|  |  | 
|  | /// Indicates whether [element] is deferred. | 
|  | bool isDeferredClass(ClassEntity element) { | 
|  | return outputUnitForClass(element) != mainOutputUnit; | 
|  | } | 
|  |  | 
|  | /// Returns `true` if element [to] is reachable from element [from] without | 
|  | /// crossing a deferred import. | 
|  | /// | 
|  | /// For example, if we have two deferred libraries `A` and `B` that both | 
|  | /// import a library `C`, then even though elements from `A` and `C` end up in | 
|  | /// different output units, there is a non-deferred path between `A` and `C`. | 
|  | bool hasOnlyNonDeferredImportPaths(MemberEntity from, MemberEntity to) { | 
|  | OutputUnit outputUnitFrom = outputUnitForMember(from); | 
|  | OutputUnit outputUnitTo = outputUnitForMember(to); | 
|  | if (outputUnitTo == mainOutputUnit) return true; | 
|  | if (outputUnitFrom == mainOutputUnit) return false; | 
|  | return outputUnitTo.imports.containsAll(outputUnitFrom.imports); | 
|  | } | 
|  |  | 
|  | /// Returns `true` if constant [to] is reachable from element [from] without | 
|  | /// crossing a deferred import. | 
|  | /// | 
|  | /// For example, if we have two deferred libraries `A` and `B` that both | 
|  | /// import a library `C`, then even though elements from `A` and `C` end up in | 
|  | /// different output units, there is a non-deferred path between `A` and `C`. | 
|  | bool hasOnlyNonDeferredImportPathsToConstant( | 
|  | MemberEntity from, ConstantValue to) { | 
|  | OutputUnit outputUnitFrom = outputUnitForMember(from); | 
|  | OutputUnit outputUnitTo = outputUnitForConstant(to); | 
|  | if (outputUnitTo == mainOutputUnit) return true; | 
|  | if (outputUnitFrom == mainOutputUnit) return false; | 
|  | return outputUnitTo.imports.containsAll(outputUnitFrom.imports); | 
|  | } | 
|  |  | 
|  | /// Returns `true` if class [to] is reachable from element [from] without | 
|  | /// crossing a deferred import. | 
|  | /// | 
|  | /// For example, if we have two deferred libraries `A` and `B` that both | 
|  | /// import a library `C`, then even though elements from `A` and `C` end up in | 
|  | /// different output units, there is a non-deferred path between `A` and `C`. | 
|  | bool hasOnlyNonDeferredImportPathsToClass(MemberEntity from, ClassEntity to) { | 
|  | OutputUnit outputUnitFrom = outputUnitForMember(from); | 
|  | OutputUnit outputUnitTo = outputUnitForClass(to); | 
|  | if (outputUnitTo == mainOutputUnit) return true; | 
|  | if (outputUnitFrom == mainOutputUnit) return false; | 
|  | return outputUnitTo.imports.containsAll(outputUnitFrom.imports); | 
|  | } | 
|  |  | 
|  | /// Registers that a constant is used in the same deferred output unit as | 
|  | /// [field]. | 
|  | void registerConstantDeferredUse(DeferredGlobalConstantValue constant) { | 
|  | if (!isProgramSplit) return; | 
|  | OutputUnit unit = constant.unit; | 
|  | assert( | 
|  | _constantToUnit[constant] == null || _constantToUnit[constant] == unit); | 
|  | _constantToUnit[constant] = unit; | 
|  | } | 
|  |  | 
|  | /// Returns the unique name for the given deferred [import]. | 
|  | String getImportDeferName(Spannable node, ImportEntity import) { | 
|  | String? name = importDeferName[import]; | 
|  | if (name == null) { | 
|  | throw SpannableAssertionFailure(node, "No deferred name for $import."); | 
|  | } | 
|  | return name; | 
|  | } | 
|  |  | 
|  | /// Returns the names associated with each deferred import in [unit]. | 
|  | Iterable<String> getImportNames(OutputUnit unit) { | 
|  | return unit.imports.map((i) => importDeferName[i]!); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ImportDescription { | 
|  | /// Relative uri to the importing library. | 
|  | final String importingUri; | 
|  |  | 
|  | /// The prefix this import is imported as. | 
|  | final String prefix; | 
|  |  | 
|  | final LibraryEntity importingLibrary; | 
|  |  | 
|  | ImportDescription.internal( | 
|  | this.importingUri, this.prefix, this.importingLibrary); | 
|  |  | 
|  | ImportDescription( | 
|  | ImportEntity import, LibraryEntity importingLibrary, Uri mainLibraryUri) | 
|  | : this.internal( | 
|  | fe.relativizeUri( | 
|  | mainLibraryUri, importingLibrary.canonicalUri, false), | 
|  | import.name!, | 
|  | importingLibrary); | 
|  | } | 
|  |  | 
|  | /// Returns the filename for the output-unit named [name]. | 
|  | /// | 
|  | /// The filename is of the form "<main output file>_<name>.part.js". | 
|  | /// If [addExtension] is false, the ".part.js" suffix is left out. | 
|  | String deferredPartFileName(CompilerOptions options, String name, | 
|  | {bool addExtension = true}) { | 
|  | assert(name != ""); | 
|  | String outPath = options.outputUri != null ? options.outputUri!.path : "out"; | 
|  | String outName = outPath.substring(outPath.lastIndexOf('/') + 1); | 
|  | String extension = addExtension ? ".part.js" : ""; | 
|  | return "${outName}_$name$extension"; | 
|  | } |