blob: 73271f938eef7104269356ccdbfe0586c6d95e1e [file] [log] [blame]
// 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.7
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/deferred_load.dart';
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,
Map<Id, ActualData<Features>> actualMap,
{bool verbose: false}) {
JsClosedWorld 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,
Map<Id, ActualData<Features>> actualMap,
{bool verbose: false}) {
JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
JsToElementMap elementMap = closedWorld.elementMap;
ClassDefinition definition = elementMap.getClassDefinition(cls);
OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
closedWorld.outputUnitData, closedWorld.closureDataLookup)
.computeForClass(definition.node);
}
@override
void computeLibraryData(Compiler compiler, LibraryEntity library,
Map<Id, ActualData<Features>> actualMap,
{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(
DiagnosticReporter reporter,
Map<Id, ActualData<Features>> actualMap,
this._preDeferredFragments,
this._fragmentsToLoad,
this._omittedOutputUnits)
: super(reporter, actualMap);
@override
Features computeLibraryValue(Id id, ir.Library library) {
var name = '${library.importUri.pathSegments.last}';
Features features = new 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(
DiagnosticReporter reporter,
Map<Id, ActualData<Features>> actualMap,
this._elementMap,
this._data,
this._closureDataLookup)
: super(reporter, actualMap);
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(node, initializer);
if (!constant.isPrimitive) {
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(null, node);
if (!constant.isPrimitive) {
_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);
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,
Map<Id, ActualData<T>> actualMap, CompilerDiagnosticReporter 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);
}
}