| // 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:_fe_analyzer_shared/src/testing/features.dart'; | |
| import 'package:compiler/src/closure.dart'; | |
| import 'package:compiler/src/common.dart'; | |
| import 'package:compiler/src/compiler.dart'; | |
| import 'package:compiler/src/deferred_load/output_unit.dart' | |
| show OutputUnit, OutputUnitData; | |
| import 'package:compiler/src/elements/entities.dart'; | |
| import 'package:compiler/src/ir/util.dart'; | |
| import 'package:compiler/src/js_model/element_map.dart'; | |
| import 'package:compiler/src/js_model/js_world.dart'; | |
| import 'package:compiler/src/js_emitter/startup_emitter/fragment_merger.dart'; | |
| import 'package:compiler/src/kernel/kernel_strategy.dart'; | |
| import 'package:expect/expect.dart'; | |
| import '../equivalence/id_equivalence.dart'; | |
| import '../equivalence/id_equivalence_helper.dart'; | |
| import 'package:compiler/src/constants/values.dart'; | |
| import 'package:kernel/ast.dart' as ir; | |
| // For ease of testing and making our tests easier to read, we impose an | |
| // artificial constraint of requiring every deferred import use a different | |
| // named prefix per test. We enforce this constraint here by checking that no | |
| // prefix name responds to two different libraries. | |
| Map<String, Uri> importPrefixes = {}; | |
| String importPrefixString(OutputUnit unit) { | |
| List<String> importNames = []; | |
| for (ImportEntity import in unit.imports) { | |
| importNames.add(import.name!); | |
| Expect.isTrue(import.isDeferred); | |
| if (importPrefixes.containsKey(import.name)) { | |
| var existing = importPrefixes[import.name]; | |
| var current = import.enclosingLibraryUri; | |
| Expect.equals( | |
| existing, | |
| current, | |
| '\n Duplicate prefix \'${import.name}\' used in both:\n' | |
| ' - $existing and\n' | |
| ' - $current.\n' | |
| ' We require using unique prefixes on these tests to make ' | |
| 'the expectations more readable.', | |
| ); | |
| } | |
| importPrefixes[import.name!] = import.enclosingLibraryUri; | |
| } | |
| importNames.sort(); | |
| return importNames.join(', '); | |
| } | |
| /// Create a consistent string representation of [OutputUnit]s for both | |
| /// KImportEntities and ImportElements. | |
| String outputUnitString(OutputUnit? unit) { | |
| if (unit == null) return 'none'; | |
| String sb = importPrefixString(unit); | |
| return '${unit.name}{$sb}'; | |
| } | |
| Map<String, List<PreFragment>> buildPreFragmentMap( | |
| Map<String, List<FinalizedFragment>> fragmentsToLoad, | |
| List<PreFragment> preDeferredFragments, | |
| ) { | |
| Map<FinalizedFragment, PreFragment> fragmentMap = {}; | |
| for (var preFragment in preDeferredFragments) { | |
| fragmentMap[preFragment.finalizedFragment] = preFragment; | |
| } | |
| Map<String, List<PreFragment>> preFragmentMap = {}; | |
| fragmentsToLoad.forEach((loadId, fragments) { | |
| List<PreFragment> preFragments = []; | |
| for (var fragment in fragments) { | |
| preFragments.add(fragmentMap[fragment]!); | |
| } | |
| preFragmentMap[loadId] = preFragments.toList(); | |
| }); | |
| return preFragmentMap; | |
| } | |
| class Tags { | |
| static const String cls = 'class_unit'; | |
| static const String member = 'member_unit'; | |
| static const String closure = 'closure_unit'; | |
| static const String constants = 'constants'; | |
| static const String type = 'type_unit'; | |
| // The below tags appear in a single block comment in the main file. | |
| // To keep them appearing in sequential order we prefix characters. | |
| static const String preFragments = 'a_pre_fragments'; | |
| static const String finalizedFragments = 'b_finalized_fragments'; | |
| static const String steps = 'c_steps'; | |
| } | |
| class OutputUnitDataComputer extends DataComputer<Features> { | |
| const OutputUnitDataComputer(); | |
| /// OutputData for [member] as a kernel based element. | |
| /// | |
| /// At this point the compiler has already been run, so it is holding the | |
| /// relevant OutputUnits, we just need to extract that information from it. We | |
| /// fill [actualMap] with the data computed about what the resulting OutputUnit | |
| /// is. | |
| @override | |
| void computeMemberData( | |
| Compiler compiler, | |
| MemberEntity member, | |
| ActualDataMap<Features> actualMap, { | |
| bool verbose = false, | |
| }) { | |
| JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; | |
| JsToElementMap elementMap = closedWorld.elementMap; | |
| MemberDefinition definition = elementMap.getMemberDefinition(member); | |
| OutputUnitIrComputer( | |
| compiler.reporter, | |
| actualMap, | |
| elementMap, | |
| closedWorld.outputUnitData, | |
| closedWorld.closureDataLookup, | |
| ).run(definition.node); | |
| } | |
| @override | |
| void computeClassData( | |
| Compiler compiler, | |
| ClassEntity cls, | |
| ActualDataMap<Features> actualMap, { | |
| bool verbose = false, | |
| }) { | |
| JClosedWorld closedWorld = compiler.backendClosedWorldForTesting!; | |
| JsToElementMap elementMap = closedWorld.elementMap; | |
| ClassDefinition definition = elementMap.getClassDefinition(cls); | |
| OutputUnitIrComputer( | |
| compiler.reporter, | |
| actualMap, | |
| elementMap, | |
| closedWorld.outputUnitData, | |
| closedWorld.closureDataLookup, | |
| ).computeForClass(definition.node as ir.Class); | |
| } | |
| @override | |
| void computeLibraryData( | |
| Compiler compiler, | |
| LibraryEntity library, | |
| ActualDataMap<Features> actualMap, { | |
| required bool verbose, | |
| }) { | |
| KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy; | |
| ir.Library node = frontendStrategy.elementMap.getLibraryNode(library); | |
| List<PreFragment> preDeferredFragments = compiler | |
| .backendStrategy | |
| .emitterTask | |
| .emitter | |
| .preDeferredFragmentsForTesting!; | |
| Map<String, List<FinalizedFragment>> fragmentsToLoad = | |
| compiler.backendStrategy.emitterTask.emitter.finalizedFragmentsToLoad; | |
| Set<OutputUnit> omittedOutputUnits = | |
| compiler.backendStrategy.emitterTask.emitter.omittedOutputUnits; | |
| PreFragmentsIrComputer( | |
| compiler.reporter, | |
| actualMap, | |
| preDeferredFragments, | |
| fragmentsToLoad, | |
| omittedOutputUnits, | |
| ).computeForLibrary(node); | |
| } | |
| @override | |
| DataInterpreter<Features> get dataValidator => | |
| const FeaturesDataInterpreter(); | |
| } | |
| class PreFragmentsIrComputer extends IrDataExtractor<Features> { | |
| final List<PreFragment> _preDeferredFragments; | |
| final Map<String, List<FinalizedFragment>> _fragmentsToLoad; | |
| final Set<OutputUnit> _omittedOutputUnits; | |
| PreFragmentsIrComputer( | |
| super.reporter, | |
| super.actualMap, | |
| this._preDeferredFragments, | |
| this._fragmentsToLoad, | |
| this._omittedOutputUnits, | |
| ); | |
| @override | |
| Features computeLibraryValue(Id id, ir.Library library) { | |
| var name = '${library.importUri.pathSegments.last}'; | |
| Features features = Features(); | |
| if (!name.startsWith('main')) return features; | |
| // First build a list of pre fragments and their dependencies. | |
| int index = 1; | |
| Map<FinalizedFragment, int> finalizedFragmentIndices = {}; | |
| Map<PreFragment, int> preFragmentIndices = {}; | |
| Map<int, PreFragment> reversePreFragmentIndices = {}; | |
| Map<int, FinalizedFragment> reverseFinalizedFragmentIndices = {}; | |
| for (var preFragment in _preDeferredFragments) { | |
| if (!preFragmentIndices.containsKey(preFragment)) { | |
| var finalizedFragment = preFragment.finalizedFragment; | |
| preFragmentIndices[preFragment] = index; | |
| finalizedFragmentIndices[finalizedFragment] = index; | |
| reversePreFragmentIndices[index] = preFragment; | |
| reverseFinalizedFragmentIndices[index] = finalizedFragment; | |
| index++; | |
| } | |
| } | |
| for (int i = 1; i < index; i++) { | |
| var preFragment = reversePreFragmentIndices[i]!; | |
| List<String> needs = []; | |
| List<OutputUnit> supplied = []; | |
| List<String> usedBy = []; | |
| for (var dependent in preFragment.successors) { | |
| if (preFragmentIndices.containsKey(dependent)) { | |
| usedBy.add('p${preFragmentIndices[dependent]}'); | |
| } | |
| } | |
| for (var dependency in preFragment.predecessors) { | |
| if (preFragmentIndices.containsKey(dependency)) { | |
| needs.add('p${preFragmentIndices[dependency]}'); | |
| } | |
| } | |
| for (var emittedOutputUnit in preFragment.emittedOutputUnits) { | |
| supplied.add(emittedOutputUnit.outputUnit); | |
| } | |
| var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]'; | |
| features.addElement( | |
| Tags.preFragments, | |
| 'p$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}', | |
| ); | |
| } | |
| // Now dump finalized fragments and load ids. | |
| for (int i = 1; i < index; i++) { | |
| var finalizedFragment = reverseFinalizedFragmentIndices[i]!; | |
| List<String> supplied = []; | |
| for (var codeFragment in finalizedFragment.codeFragments) { | |
| List<String> outputUnitStrings = []; | |
| for (var outputUnit in codeFragment.outputUnits) { | |
| if (!_omittedOutputUnits.contains(outputUnit)) { | |
| outputUnitStrings.add(outputUnitString(outputUnit)); | |
| } | |
| } | |
| if (outputUnitStrings.isNotEmpty) { | |
| supplied.add(outputUnitStrings.join('+')); | |
| } | |
| } | |
| if (supplied.isNotEmpty) { | |
| var suppliedString = '[${supplied.join(', ')}]'; | |
| features.addElement(Tags.finalizedFragments, 'f$i: $suppliedString'); | |
| } | |
| } | |
| _fragmentsToLoad.forEach((loadId, finalizedFragments) { | |
| List<String> finalizedFragmentNeeds = []; | |
| for (var finalizedFragment in finalizedFragments) { | |
| assert(finalizedFragmentIndices.containsKey(finalizedFragment)); | |
| finalizedFragmentNeeds.add( | |
| 'f${finalizedFragmentIndices[finalizedFragment]}', | |
| ); | |
| } | |
| features.addElement( | |
| Tags.steps, | |
| '$loadId=(${finalizedFragmentNeeds.join(', ')})', | |
| ); | |
| }); | |
| return features; | |
| } | |
| } | |
| class OutputUnitIrComputer extends IrDataExtractor<Features> { | |
| final JsToElementMap _elementMap; | |
| final OutputUnitData _data; | |
| final ClosureData _closureDataLookup; | |
| Set<String> _constants = {}; | |
| OutputUnitIrComputer( | |
| super.reporter, | |
| super.actualMap, | |
| this._elementMap, | |
| this._data, | |
| this._closureDataLookup, | |
| ); | |
| Features getMemberValue( | |
| String tag, | |
| MemberEntity member, | |
| Set<String> constants, | |
| ) { | |
| Features features = Features(); | |
| features.add( | |
| tag, | |
| value: outputUnitString(_data.outputUnitForMemberForTesting(member)), | |
| ); | |
| for (var constant in constants) { | |
| features.addElement(Tags.constants, constant); | |
| } | |
| return features; | |
| } | |
| @override | |
| Features computeClassValue(Id id, ir.Class node) { | |
| var cls = _elementMap.getClass(node); | |
| Features features = Features(); | |
| features.add( | |
| Tags.cls, | |
| value: outputUnitString(_data.outputUnitForClassForTesting(cls)), | |
| ); | |
| features.add( | |
| Tags.type, | |
| value: outputUnitString(_data.outputUnitForClassTypeForTesting(cls)), | |
| ); | |
| return features; | |
| } | |
| @override | |
| Features computeMemberValue(Id id, ir.Member node) { | |
| if (node is ir.Field && node.isConst) { | |
| ir.Expression? initializer = node.initializer!; | |
| ConstantValue constant = _elementMap.getConstantValue(initializer)!; | |
| if (constant is! PrimitiveConstantValue) { | |
| SourceSpan span = computeSourceSpanFromTreeNode(initializer); | |
| if (initializer is ir.ConstructorInvocation) { | |
| // Adjust the source-span to match the AST-based location. The kernel FE | |
| // skips the "const" keyword for the expression offset and any prefix in | |
| // front of the constructor. The "-6" is an approximation assuming that | |
| // there is just a single space after "const" and no prefix. | |
| // TODO(sigmund): offsets should be fixed in the FE instead. | |
| span = SourceSpan(span.uri, span.begin - 6, span.end - 6); | |
| } | |
| _registerValue( | |
| NodeId(span.begin, IdKind.node), | |
| Features.fromMap({ | |
| Tags.member: outputUnitString( | |
| _data.outputUnitForConstantForTesting(constant), | |
| ), | |
| }), | |
| node, | |
| span, | |
| actualMap, | |
| reporter, | |
| ); | |
| } | |
| } | |
| Features features = getMemberValue( | |
| Tags.member, | |
| _elementMap.getMember(node), | |
| _constants, | |
| ); | |
| _constants = {}; | |
| return features; | |
| } | |
| @override | |
| visitConstantExpression(ir.ConstantExpression node) { | |
| ConstantValue constant = _elementMap.getConstantValue(node)!; | |
| if (constant is! PrimitiveConstantValue) { | |
| _constants.add( | |
| '${constant.toStructuredText(_elementMap.types)}=' | |
| '${outputUnitString(_data.outputUnitForConstant(constant))}', | |
| ); | |
| } | |
| return super.visitConstantExpression(node); | |
| } | |
| @override | |
| Features? computeNodeValue(Id id, ir.TreeNode node) { | |
| if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) { | |
| ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo( | |
| node as ir.LocalFunction, | |
| ); | |
| return getMemberValue(Tags.closure, info.callMethod!, const {}); | |
| } | |
| return null; | |
| } | |
| } | |
| /// Set [actualMap] to hold a key of [id] with the computed data [value] | |
| /// corresponding to [object] at location [sourceSpan]. We also perform error | |
| /// checking to ensure that the same [id] isn't added twice. | |
| void _registerValue<T>( | |
| Id id, | |
| T value, | |
| Object object, | |
| SourceSpan sourceSpan, | |
| ActualDataMap<T> actualMap, | |
| DiagnosticReporter reporter, | |
| ) { | |
| if (actualMap.containsKey(id)) { | |
| ActualData<T> existingData = actualMap[id]!; | |
| reportHere( | |
| reporter, | |
| sourceSpan, | |
| "Duplicate id ${id}, value=$value, object=$object", | |
| ); | |
| reportHere( | |
| reporter, | |
| sourceSpan, | |
| "Duplicate id ${id}, value=${existingData.value}, " | |
| "object=${existingData.object}", | |
| ); | |
| Expect.fail("Duplicate id $id."); | |
| } | |
| if (value != null) { | |
| actualMap[id] = ActualData<T>( | |
| id, | |
| value, | |
| sourceSpan.uri, | |
| sourceSpan.begin, | |
| object, | |
| ); | |
| } | |
| } |