| // 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. |
| |
| // @dart = 2.10 |
| |
| 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'; |
| |
| // TODO(48820): Move OutputUnit and compareImportEntities back here. |
| import 'output_unit_migrated.dart'; |
| export 'output_unit_migrated.dart'; |
| |
| /// 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"; |
| } |