| // 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"; |
| } |