Support eagerly initialized static fields.
Change-Id: I4265fe9bcfccc6a99627e57705868a7ee1da828d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/94749
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/ir/scope_visitor.dart b/pkg/compiler/lib/src/ir/scope_visitor.dart
index cd51bbb..016d4dc 100644
--- a/pkg/compiler/lib/src/ir/scope_visitor.dart
+++ b/pkg/compiler/lib/src/ir/scope_visitor.dart
@@ -715,30 +715,40 @@
@override
InitializerComplexity visitListLiteral(ir.ListLiteral node) {
- visitInContext(node.typeArgument, VariableUse.listLiteral);
- visitNodes(node.expressions);
- return node.isConst
- ? const InitializerComplexity.constant()
- : const InitializerComplexity.lazy();
+ InitializerComplexity complexity =
+ visitInContext(node.typeArgument, VariableUse.listLiteral);
+ complexity = complexity.combine(visitNodes(node.expressions));
+ if (node.isConst) {
+ return const InitializerComplexity.constant();
+ } else {
+ return complexity.makeEager();
+ }
}
@override
InitializerComplexity visitSetLiteral(ir.SetLiteral node) {
- visitInContext(node.typeArgument, VariableUse.setLiteral);
- visitNodes(node.expressions);
- return node.isConst
- ? const InitializerComplexity.constant()
- : const InitializerComplexity.lazy();
+ InitializerComplexity complexity =
+ visitInContext(node.typeArgument, VariableUse.setLiteral);
+ complexity = complexity.combine(visitNodes(node.expressions));
+ if (node.isConst) {
+ return const InitializerComplexity.constant();
+ } else {
+ return complexity.makeEager();
+ }
}
@override
InitializerComplexity visitMapLiteral(ir.MapLiteral node) {
- visitInContext(node.keyType, VariableUse.mapLiteral);
- visitInContext(node.valueType, VariableUse.mapLiteral);
- visitNodes(node.entries);
- return node.isConst
- ? const InitializerComplexity.constant()
- : const InitializerComplexity.lazy();
+ InitializerComplexity complexity =
+ visitInContext(node.keyType, VariableUse.mapLiteral);
+ complexity = complexity
+ .combine(visitInContext(node.valueType, VariableUse.mapLiteral));
+ complexity = complexity.combine(visitNodes(node.entries));
+ if (node.isConst) {
+ return const InitializerComplexity.constant();
+ } else {
+ return complexity.makeEager();
+ }
}
@override
@@ -779,9 +789,16 @@
@override
InitializerComplexity visitStaticGet(ir.StaticGet node) {
- return node.target.isConst
- ? const InitializerComplexity.constant()
- : const InitializerComplexity.lazy();
+ ir.Member target = node.target;
+ if (target is ir.Field) {
+ return target.isConst
+ ? const InitializerComplexity.constant()
+ : new InitializerComplexity.eager(fields: <ir.Field>{target});
+ } else if (target is ir.Procedure &&
+ target.kind == ir.ProcedureKind.Method) {
+ return const InitializerComplexity.constant();
+ }
+ return const InitializerComplexity.lazy();
}
@override
@@ -1097,43 +1114,89 @@
class InitializerComplexity {
final ComplexityLevel level;
+ final Set<ir.Field> fields;
// TODO(johnniwinther): This should hold the constant literal from CFE when
// provided.
- const InitializerComplexity.constant() : level = ComplexityLevel.constant;
+ const InitializerComplexity.constant()
+ : level = ComplexityLevel.constant,
+ fields = null;
// TODO(johnniwinther): Use this to collect data on the size of the
// initializer.
- const InitializerComplexity.eager()
+ InitializerComplexity.eager({this.fields})
: level = ComplexityLevel.potentiallyEager;
- const InitializerComplexity.lazy() : level = ComplexityLevel.definitelyLazy;
+ const InitializerComplexity.lazy()
+ : level = ComplexityLevel.definitelyLazy,
+ fields = null;
InitializerComplexity combine(InitializerComplexity other) {
- if (level == other.level) {
- // TODO(johnniwinther): Special case 'eager' when it contains data.
+ if (identical(this, other)) {
return this;
- } else if (level == ComplexityLevel.definitelyLazy ||
- other.level == ComplexityLevel.definitelyLazy) {
+ } else if (isLazy || other.isLazy) {
return const InitializerComplexity.lazy();
- } else if (level == ComplexityLevel.potentiallyEager) {
+ } else if (isEager || other.isEager) {
+ if (fields != null && other.fields != null) {
+ fields.addAll(other.fields);
+ return this;
+ } else if (fields != null) {
+ return this;
+ } else {
+ return other;
+ }
+ } else if (isConstant && other.isConstant) {
+ // TODO(johnniwinther): This is case doesn't work if InitializerComplexity
+ // objects of constant complexity hold the constant literal.
+ return this;
+ } else if (isEager) {
+ assert(other.isConstant);
return this;
} else {
- assert(other.level == ComplexityLevel.potentiallyEager);
+ assert(isConstant);
+ assert(other.isEager);
return other;
}
}
+ InitializerComplexity makeEager() {
+ if (isLazy || isEager) {
+ return this;
+ } else {
+ return new InitializerComplexity.eager();
+ }
+ }
+
+ bool get isConstant => level == ComplexityLevel.constant;
+
+ bool get isEager => level == ComplexityLevel.potentiallyEager;
+
+ bool get isLazy => level == ComplexityLevel.definitelyLazy;
+
/// Returns a short textual representation used for testing.
String get shortText {
+ StringBuffer sb = new StringBuffer();
switch (level) {
case ComplexityLevel.constant:
- return 'constant';
+ sb.write('constant');
+ break;
case ComplexityLevel.potentiallyEager:
- return 'eager';
+ sb.write('eager');
+ if (fields != null) {
+ sb.write('&fields=[');
+ List<String> names = fields.map((f) => f.name.name).toList()..sort();
+ sb.write(names.join(','));
+ sb.write(']');
+ }
+ break;
case ComplexityLevel.definitelyLazy:
- return 'lazy';
+ sb.write('lazy');
+ break;
+ default:
+ throw new UnsupportedError("Unexpected complexity level $level");
}
- throw new UnsupportedError("Unexpected complexity level $level");
+ return sb.toString();
}
+
+ String toString() => 'InitializerComplexity($shortText)';
}
diff --git a/pkg/compiler/lib/src/js_backend/field_analysis.dart b/pkg/compiler/lib/src/js_backend/field_analysis.dart
index 41477f0..5217327 100644
--- a/pkg/compiler/lib/src/js_backend/field_analysis.dart
+++ b/pkg/compiler/lib/src/js_backend/field_analysis.dart
@@ -4,20 +4,20 @@
import 'package:kernel/ast.dart' as ir;
+import '../common.dart';
import '../constants/values.dart';
import '../elements/entities.dart';
+import '../elements/entity_utils.dart';
import '../ir/scope_visitor.dart';
import '../js_model/elements.dart' show JField;
import '../js_model/js_world_builder.dart';
import '../kernel/element_map.dart';
import '../kernel/kernel_strategy.dart';
import '../kernel/kelements.dart' show KClass, KField, KConstructor;
+import '../kernel/kernel_world.dart';
import '../options.dart';
import '../serialization/serialization.dart';
import '../universe/member_usage.dart';
-import '../world.dart';
-
-abstract class FieldAnalysis {}
/// AllocatorAnalysis
///
@@ -35,7 +35,7 @@
//
// this.x = this.z = null;
//
-class KFieldAnalysis implements FieldAnalysis {
+class KFieldAnalysis {
final KernelToElementMap _elementMap;
final Map<KClass, ClassData> _classData = {};
@@ -154,6 +154,8 @@
final InitializerComplexity complexity;
StaticFieldData(this.initialValue, this.complexity);
+
+ bool get hasDependencies => complexity != null && complexity.fields != null;
}
class AllocatorData {
@@ -212,10 +214,10 @@
String toString() => shortText;
}
-class JFieldAnalysis implements FieldAnalysis {
+class JFieldAnalysis {
/// Tag used for identifying serialized [JFieldAnalysis] objects in a
/// debugging data stream.
- static const String tag = 'allocator-analysis';
+ static const String tag = 'field-analysis';
// --csp and --fast-startup have different constraints to the generated code.
@@ -241,8 +243,8 @@
sink.end(tag);
}
- factory JFieldAnalysis.from(
- KClosedWorld closedWorld, JsToFrontendMap map, CompilerOptions options) {
+ factory JFieldAnalysis.from(KClosedWorldImpl closedWorld, JsToFrontendMap map,
+ CompilerOptions options) {
Map<FieldEntity, FieldAnalysisData> fieldData = {};
bool canBeElided(FieldEntity field) {
@@ -355,33 +357,178 @@
});
});
+ List<KField> independentFields = [];
+ List<KField> dependentFields = [];
+
closedWorld.liveMemberUsage
.forEach((MemberEntity member, MemberUsage memberUsage) {
if (member.isField && !member.isInstanceMember) {
- JField jField = map.toBackendMember(member);
- if (jField == null) return;
-
- if (!memberUsage.hasRead && canBeElided(member)) {
- fieldData[jField] = const FieldAnalysisData(isElided: true);
+ StaticFieldData staticFieldData =
+ closedWorld.fieldAnalysis._staticFieldData[member];
+ if (staticFieldData.hasDependencies) {
+ dependentFields.add(member);
} else {
- bool isEffectivelyFinal = !memberUsage.hasWrite;
- StaticFieldData staticFieldData =
- closedWorld.fieldAnalysis._staticFieldData[member];
- ConstantValue value = map
- .toBackendConstant(staticFieldData.initialValue, allowNull: true);
- bool isElided =
- isEffectivelyFinal && value != null && canBeElided(member);
- // TODO(johnniwinther): Compute effective initializer complexity.
- if (value != null || isEffectivelyFinal) {
- fieldData[jField] = new FieldAnalysisData(
- initialValue: value,
- isEffectivelyFinal: isEffectivelyFinal,
- isElided: isElided);
- }
+ independentFields.add(member);
}
}
});
+ // Fields already processed.
+ Set<KField> processedFields = {};
+
+ // Fields currently being processed. Use for detecting cyclic dependencies.
+ Set<KField> currentFields = {};
+
+ // Index ascribed to eager fields that depend on other fields. This is
+ // used to sort the field in emission to ensure that used fields have been
+ // initialized when read.
+ int eagerCreationIndex = 0;
+
+ /// Computes the [FieldAnalysisData] for the JField corresponding to
+ /// [kField].
+ ///
+ /// If the data is currently been computed, that is, [kField] has a
+ /// cyclic dependency, `null` is returned.
+ FieldAnalysisData processField(KField kField) {
+ JField jField = map.toBackendMember(kField);
+ // TODO(johnniwinther): Can we assert that [jField] exists?
+ if (jField == null) return null;
+
+ FieldAnalysisData data = fieldData[jField];
+ if (processedFields.contains(kField)) {
+ // We only store data for non-trivial [FieldAnalysisData].
+ return data ?? const FieldAnalysisData();
+ }
+ if (currentFields.contains(kField)) {
+ // Cyclic dependency.
+ return null;
+ }
+ currentFields.add(kField);
+ MemberUsage memberUsage = closedWorld.liveMemberUsage[kField];
+ if (!memberUsage.hasRead && canBeElided(kField)) {
+ data = fieldData[jField] = const FieldAnalysisData(isElided: true);
+ } else {
+ bool isEffectivelyFinal = !memberUsage.hasWrite;
+ StaticFieldData staticFieldData =
+ closedWorld.fieldAnalysis._staticFieldData[kField];
+ ConstantValue value = map
+ .toBackendConstant(staticFieldData.initialValue, allowNull: true);
+
+ // If the field is effectively final with a constant initializer we
+ // elide the field, if allowed, because it is effectively constant.
+ bool isElided =
+ isEffectivelyFinal && value != null && canBeElided(kField);
+
+ bool isEager;
+
+ // If the field is eager and dependent on other eager fields,
+ // [eagerFieldDependencies] holds these fields and [creationIndex] is
+ // given the creation order index used to ensure that all dependencies
+ // have been assigned their values before this field is initialized.
+ //
+ // Since we only need the values of [eagerFieldDependencies] for testing
+ // and only the non-emptiness for determining the need for creation
+ // order indices, [eagerFieldDependencies] is non-null if the field has
+ // dependencies but only hold these when [retainDataForTesting] is
+ // `true`.
+ List<FieldEntity> eagerFieldDependencies;
+ int creationIndex = null;
+
+ if (isElided) {
+ // If the field is elided it needs no initializer and is therefore
+ // not eager.
+ isEager = false;
+ } else {
+ // If the field has a constant initializer we know it can be
+ // initialized eagerly.
+ //
+ // Ideally this should be the same as
+ // `staticFieldData.complexity.isConstant` but currently the constant
+ // evaluator handles cases that the analysis doesn't, so we use the
+ // better result.
+ isEager = value != null;
+ if (!isEager) {
+ // The field might be eager depending on the initializer complexity
+ // and its dependencies.
+ InitializerComplexity complexity = staticFieldData.complexity;
+ isEager = complexity?.isEager ?? false;
+ if (isEager && complexity.fields != null) {
+ for (ir.Field node in complexity.fields) {
+ KField otherField = closedWorld.elementMap.getField(node);
+ FieldAnalysisData otherData = processField(otherField);
+ if (otherData == null) {
+ // Cyclic dependency on [otherField].
+ isEager = false;
+ break;
+ }
+ if (otherData.isLazy) {
+ // [otherField] needs lazy initialization.
+ isEager = false;
+ break;
+ }
+ if (!otherData.isEffectivelyFinal) {
+ // [otherField] might not hold its initial value when this field
+ // is accessed the first time, so we need to initialize this
+ // field lazily.
+ isEager = false;
+ break;
+ }
+ if (!otherData.isEffectivelyConstant) {
+ eagerFieldDependencies ??= [];
+ if (retainDataForTesting) {
+ eagerFieldDependencies.add(map.toBackendMember(otherField));
+ }
+ }
+ }
+ }
+ }
+
+ if (isEager && eagerFieldDependencies != null) {
+ creationIndex = eagerCreationIndex++;
+ if (!retainDataForTesting) {
+ eagerFieldDependencies = null;
+ }
+ } else {
+ eagerFieldDependencies = null;
+ }
+ }
+
+ data = fieldData[jField] = new FieldAnalysisData(
+ initialValue: value,
+ isEffectivelyFinal: isEffectivelyFinal,
+ isElided: isElided,
+ isEager: isEager,
+ eagerCreationIndex: creationIndex,
+ eagerFieldDependenciesForTesting: eagerFieldDependencies);
+ }
+
+ currentFields.remove(kField);
+ processedFields.add(kField);
+ return data;
+ }
+
+ // Process independent fields in no particular order. The emitter sorts
+ // these later.
+ independentFields.forEach(processField);
+
+ // Process dependent fields in declaration order to make ascribed creation
+ // indices stable. The emitter uses the creation indices for sorting
+ // dependent fields.
+ dependentFields.sort((KField a, KField b) {
+ int result =
+ compareLibrariesUris(a.library.canonicalUri, b.library.canonicalUri);
+ if (result != 0) return result;
+ ir.Location aLocation = closedWorld.elementMap.getMemberNode(a).location;
+ ir.Location bLocation = closedWorld.elementMap.getMemberNode(b).location;
+ result = compareSourceUris(aLocation.file, bLocation.file);
+ if (result != 0) return result;
+ result = aLocation.line.compareTo(bLocation.line);
+ if (result != 0) return result;
+ return aLocation.column.compareTo(bLocation.column);
+ });
+
+ dependentFields.forEach(processField);
+
return new JFieldAnalysis._(fieldData);
}
@@ -402,11 +549,25 @@
final bool isEffectivelyFinal;
final bool isElided;
+ /// If `true` the field is not effectively constant but the initializer can be
+ /// generated eagerly without the need for lazy initialization wrapper.
+ final bool isEager;
+
+ /// Index ascribed to eager fields that depend on other fields. This is
+ /// used to sort the field in emission to ensure that used fields have been
+ /// initialized when read.
+ final int eagerCreationIndex;
+
+ final List<FieldEntity> eagerFieldDependenciesForTesting;
+
const FieldAnalysisData(
{this.initialValue,
this.isInitializedInAllocator: false,
this.isEffectivelyFinal: false,
- this.isElided: false});
+ this.isElided: false,
+ this.isEager: false,
+ this.eagerCreationIndex: null,
+ this.eagerFieldDependenciesForTesting: null});
factory FieldAnalysisData.fromDataSource(DataSource source) {
source.begin(tag);
@@ -415,12 +576,19 @@
bool isInitializedInAllocator = source.readBool();
bool isEffectivelyFinal = source.readBool();
bool isElided = source.readBool();
+ bool isEager = source.readBool();
+ int eagerCreationIndex = source.readIntOrNull();
+ List<FieldEntity> eagerFieldDependencies =
+ source.readMembers<FieldEntity>(emptyAsNull: true);
source.end(tag);
return new FieldAnalysisData(
initialValue: initialValue,
isInitializedInAllocator: isInitializedInAllocator,
isEffectivelyFinal: isEffectivelyFinal,
- isElided: isElided);
+ isElided: isElided,
+ isEager: isEager,
+ eagerCreationIndex: eagerCreationIndex,
+ eagerFieldDependenciesForTesting: eagerFieldDependencies);
}
void writeToDataSink(DataSink sink) {
@@ -429,9 +597,16 @@
sink.writeBool(isInitializedInAllocator);
sink.writeBool(isEffectivelyFinal);
sink.writeBool(isElided);
+ sink.writeBool(isEager);
+ sink.writeIntOrNull(eagerCreationIndex);
+ sink.writeMembers(eagerFieldDependenciesForTesting, allowNull: true);
sink.end(tag);
}
+ /// If `true` the initializer for this field requires a lazy initialization
+ /// wrapper.
+ bool get isLazy => initialValue == null && !isEager;
+
bool get isEffectivelyConstant =>
isEffectivelyFinal && isElided && initialValue != null;
@@ -440,5 +615,7 @@
String toString() =>
'FieldAnalysisData(initialValue=${initialValue?.toStructuredText()},'
'isInitializedInAllocator=$isInitializedInAllocator,'
- 'isEffectivelyFinal=$isEffectivelyFinal,isElided=$isElided)';
+ 'isEffectivelyFinal=$isEffectivelyFinal,isElided=$isElided,'
+ 'isEager=$isEager,eagerCreationIndex=$eagerCreationIndex,'
+ 'eagerFieldDependencies=$eagerFieldDependenciesForTesting)';
}
diff --git a/pkg/compiler/lib/src/js_emitter/model.dart b/pkg/compiler/lib/src/js_emitter/model.dart
index 6f7b580..e12153b 100644
--- a/pkg/compiler/lib/src/js_emitter/model.dart
+++ b/pkg/compiler/lib/src/js_emitter/model.dart
@@ -213,9 +213,10 @@
final js.Expression code;
final bool isFinal;
final bool isLazy;
+ final bool isInitializedByConstant;
StaticField(this.element, this.name, this.getterName, this.holder, this.code,
- this.isFinal, this.isLazy);
+ {this.isFinal, this.isLazy, this.isInitializedByConstant: false});
String toString() {
return 'StaticField(name=${name.key},element=${element})';
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/collector.dart b/pkg/compiler/lib/src/js_emitter/program_builder/collector.dart
index d74d9c7..d0e09c8 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/collector.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/collector.dart
@@ -263,22 +263,36 @@
void computeNeededStaticNonFinalFields() {
addToOutputUnit(FieldEntity element) {
List<FieldEntity> list = outputStaticNonFinalFieldLists.putIfAbsent(
- // ignore: UNNECESSARY_CAST
- _outputUnitData.outputUnitForMember(element as MemberEntity),
+ _outputUnitData.outputUnitForMember(element),
() => new List<FieldEntity>());
list.add(element);
}
- Iterable<FieldEntity> fields =
+ List<FieldEntity> fields =
// TODO(johnniwinther): This should be accessed from a codegen closed
// world.
_worldBuilder.allReferencedStaticFields.where((FieldEntity field) {
- FieldAnalysisData fieldData =
- _closedWorld.fieldAnalysis.getFieldData(field);
- return !fieldData.isEffectivelyFinal && fieldData.initialValue != null;
- });
+ return _closedWorld.fieldAnalysis.getFieldData(field).isEager;
+ }).toList();
- _sorter.sortMembers(fields).forEach((MemberEntity e) => addToOutputUnit(e));
+ fields.sort((FieldEntity a, FieldEntity b) {
+ FieldAnalysisData aFieldData = _closedWorld.fieldAnalysis.getFieldData(a);
+ FieldAnalysisData bFieldData = _closedWorld.fieldAnalysis.getFieldData(b);
+ int aIndex = aFieldData.eagerCreationIndex;
+ int bIndex = bFieldData.eagerCreationIndex;
+ if (aIndex != null && bIndex != null) {
+ return aIndex.compareTo(bIndex);
+ } else if (aIndex != null) {
+ // Sort [b] before [a].
+ return 1;
+ } else if (bIndex != null) {
+ // Sort [a] before [b].
+ return -1;
+ } else {
+ return _sorter.compareMembersByLocation(a, b);
+ }
+ });
+ fields.forEach(addToOutputUnit);
}
void computeNeededLibraries() {
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
index efd77cd..bbf43e4 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
@@ -409,21 +409,28 @@
StaticField _buildStaticField(FieldEntity element) {
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(element);
ConstantValue initialValue = fieldData.initialValue;
- // TODO(zarah): The holder should not be registered during building of
- // a static field.
- _registry.registerHolder(_namer.globalObjectForConstant(initialValue),
- isConstantsHolder: true);
- js.Expression code = _task.emitter.constantReference(initialValue);
+ js.Expression code;
+ if (initialValue != null) {
+ // TODO(zarah): The holder should not be registered during building of
+ // a static field.
+ _registry.registerHolder(_namer.globalObjectForConstant(initialValue),
+ isConstantsHolder: true);
+ code = _task.emitter.constantReference(initialValue);
+ } else {
+ assert(fieldData.isEager);
+ code = _generatedCode[element];
+ }
js.Name name = _namer.globalPropertyNameForMember(element);
- bool isFinal = false;
- bool isLazy = false;
// TODO(floitsch): we shouldn't update the registry in the middle of
// building a static field. (Note that the static-state holder was
// already registered earlier, and that we just call the register to get
// the holder-instance.
- return new StaticField(element, name, null, _registerStaticStateHolder(),
- code, isFinal, isLazy);
+ return new StaticField(
+ element, name, null, _registerStaticStateHolder(), code,
+ isFinal: false,
+ isLazy: false,
+ isInitializedByConstant: initialValue != null);
}
List<StaticField> _buildStaticLazilyInitializedFields(
@@ -449,14 +456,13 @@
js.Name name = _namer.globalPropertyNameForMember(element);
js.Name getterName = _namer.lazyInitializerName(element);
- bool isFinal = !element.isAssignable;
- bool isLazy = true;
// TODO(floitsch): we shouldn't update the registry in the middle of
// building a static field. (Note that the static-state holder was
// already registered earlier, and that we just call the register to get
// the holder-instance.
- return new StaticField(element, name, getterName,
- _registerStaticStateHolder(), code, isFinal, isLazy);
+ return new StaticField(
+ element, name, getterName, _registerStaticStateHolder(), code,
+ isFinal: !element.isAssignable, isLazy: true);
}
List<Library> _buildLibraries(LibrariesMap librariesMap) {
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index e47c77a..29c8051 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -434,14 +434,16 @@
// Instantiates all constants.
#constants;
+
+// Emits the embedded globals. Due to type checks in eager initializers this is
+// needed before static non-final fields initializers.
+#embeddedGlobals;
+
// Initializes the static non-final fields (with their constant values).
#staticNonFinalFields;
// Creates lazy getters for statics that must run initializers on first access.
#lazyStatics;
-// Emits the embedded globals.
-#embeddedGlobals;
-
// Sets up the native support.
// Native-support uses setOrUpdateInterceptorsByTag and setOrUpdateLeafTags.
#nativeSupport;
@@ -1605,8 +1607,43 @@
//
Iterable<js.Statement> statements = fields.map((StaticField field) {
assert(field.holder.isStaticStateHolder);
- js.Statement statement = js.js
- .statement("#.# = #;", [field.holder.name, field.name, field.code]);
+ js.Statement statement;
+ if (field.isInitializedByConstant) {
+ statement = js.js
+ .statement("#.# = #;", [field.holder.name, field.name, field.code]);
+ } else {
+ // This is a bit of a hack. Field initializers are generated as a
+ // function ending with a return statement. We replace the function
+ // with the body block and replace the return statement with an
+ // assignment to the field.
+ //
+ // Since unneeded blocks are not generated in the output,
+ // the statement(s) of the initializes are inlined in the emitted code.
+ //
+ // This is a cheap way of supporting eager fields (as opposed to
+ // generating one SSA graph for all eager fields) though it does not
+ // avoid redundant declaration of local variable, for instance for
+ // type arguments.
+ js.Fun code = field.code;
+ if (code.params.isEmpty &&
+ code.body.statements.length == 1 &&
+ code.body.statements.last is js.Return) {
+ // For now we only support initializers of the form
+ //
+ // function() { return e; }
+ //
+ // To avoid unforeseen consequences of having parameters and locals
+ // in the initializer code.
+ js.Return last = code.body.statements.last;
+ statement = js.js.statement(
+ "#.# = #;", [field.holder.name, field.name, last.value]);
+ } else {
+ // Safe fallback in the event of a field initializer with no return
+ // statement as the last statement.
+ statement = js.js
+ .statement("#.# = #();", [field.holder.name, field.name, code]);
+ }
+ }
registerEntityAst(field.element, statement,
library: field.element.library);
return statement;
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 242f060..e1fe0fb 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -233,7 +233,11 @@
// the constant value.
return null;
} else if (targetElement.isStatic || targetElement.isTopLevel) {
- backend.constants.registerLazyStatic(targetElement);
+ if (_fieldAnalysis.getFieldData(targetElement).isLazy) {
+ // TODO(johnniwinther): Lazy fields should be collected like
+ // eager and non-final fields.
+ backend.constants.registerLazyStatic(targetElement);
+ }
}
buildField(target);
} else if (target is ir.FunctionExpression) {
@@ -3078,26 +3082,26 @@
} else if (staticTarget is ir.Field) {
FieldEntity field = _elementMap.getField(staticTarget);
FieldAnalysisData fieldData = _fieldAnalysis.getFieldData(field);
- if (fieldData.initialValue != null) {
- if (fieldData.isEffectivelyFinal) {
- var unit = closedWorld.outputUnitData.outputUnitForMember(field);
- // TODO(sigmund): this is not equivalent to what the old FE does: if
- // there is no prefix the old FE wouldn't treat this in any special
- // way. Also, if the prefix points to a constant in the main output
- // unit, the old FE would still generate a deferred wrapper here.
- if (!closedWorld.outputUnitData
- .hasOnlyNonDeferredImportPaths(targetElement, field)) {
- stack.add(graph.addDeferredConstant(fieldData.initialValue, unit,
- sourceInformation, compiler, closedWorld));
- } else {
- stack.add(graph.addConstant(fieldData.initialValue, closedWorld,
- sourceInformation: sourceInformation));
- }
+ if (fieldData.isEager) {
+ push(new HStatic(field, _typeInferenceMap.getInferredTypeOf(field),
+ sourceInformation));
+ } else if (fieldData.isEffectivelyConstant) {
+ var unit = closedWorld.outputUnitData.outputUnitForMember(field);
+ // TODO(sigmund): this is not equivalent to what the old FE does: if
+ // there is no prefix the old FE wouldn't treat this in any special
+ // way. Also, if the prefix points to a constant in the main output
+ // unit, the old FE would still generate a deferred wrapper here.
+ if (!closedWorld.outputUnitData
+ .hasOnlyNonDeferredImportPaths(targetElement, field)) {
+ stack.add(graph.addDeferredConstant(fieldData.initialValue, unit,
+ sourceInformation, compiler, closedWorld));
} else {
- push(new HStatic(field, _typeInferenceMap.getInferredTypeOf(field),
- sourceInformation));
+ stack.add(graph.addConstant(fieldData.initialValue, closedWorld,
+ sourceInformation: sourceInformation));
}
} else {
+ assert(
+ fieldData.isLazy, "Unexpected field data for $field: $fieldData");
push(new HLazyStatic(field, _typeInferenceMap.getInferredTypeOf(field),
sourceInformation));
}
diff --git a/pkg/compiler/lib/src/ssa/ssa.dart b/pkg/compiler/lib/src/ssa/ssa.dart
index 41360b05..ca0a323 100644
--- a/pkg/compiler/lib/src/ssa/ssa.dart
+++ b/pkg/compiler/lib/src/ssa/ssa.dart
@@ -123,11 +123,9 @@
/// constant value.
return true;
}
- } else {
- // If the constant-handler was not able to produce a result we have to
- // go through the builder (below) to generate the lazy initializer for
- // the static variable.
- // We also need to register the use of the cyclic-error helper.
+ } else if (fieldData.isLazy) {
+ // The generated initializer needs be wrapped in the cyclic-error
+ // helper.
registry.worldImpact.registerStaticUse(new StaticUse.staticInvoke(
closedWorld.commonElements.cyclicThrowHelper,
CallStructure.ONE_ARG));
diff --git a/tests/compiler/dart2js/field_analysis/jdata/static_initializers.dart b/tests/compiler/dart2js/field_analysis/jdata/static_initializers.dart
index 967f63c..4e0c97f 100644
--- a/tests/compiler/dart2js/field_analysis/jdata/static_initializers.dart
+++ b/tests/compiler/dart2js/field_analysis/jdata/static_initializers.dart
@@ -17,6 +17,56 @@
print(field3b);
field3c = null;
print(field3c);
+ print(field3d);
+ print(field3e);
+ print(field3f);
+ print(field3g);
+ print(field3h);
+
+ print(field4a);
+ print(field4b);
+ print(field4c);
+
+ print(field5a);
+ print(field5b);
+ print(field5c);
+
+ print(field6a);
+ print(field6b);
+ print(field6c);
+
+ print(field7a);
+ print(field7b);
+ print(field7c);
+ print(field7d);
+ print(field7e);
+
+ print(field8a);
+ print(field8b);
+ print(field8c);
+ print(field8d);
+
+ print(field9a);
+ print(field9b);
+ print(field9c);
+ print(field9d);
+ field9e = null;
+ print(field9e);
+ print(field9f);
+ print(field9g);
+ print(field9h);
+ print(field9i);
+
+ print(field10a);
+ print(field10b);
+}
+
+method() {}
+
+class Class {
+ const Class.generative();
+
+ const factory Class.fact() = Class.generative;
}
/*element: field1a:constant=IntConstant(0)*/
@@ -37,11 +87,133 @@
/*element: field2c:initial=ListConstant([])*/
var field2c = const [];
-/*element: field3a:*/
+/*element: field3a:eager,final*/
final field3a = [];
-/*element: field3b:*/
+/*element: field3b:eager,final*/
var field3b = [];
-/*element: field3c:*/
+/*element: field3c:eager*/
var field3c = [];
+
+/*element: field3d:eager,final*/
+var field3d = [1, 2, 3];
+
+/*element: field3e:eager,final*/
+var field3e = [
+ 1,
+ 2,
+ [
+ 3,
+ 4,
+ [5, 6, method]
+ ]
+];
+
+/*element: field3f:final,lazy*/
+var field3f = [
+ 1,
+ 2,
+ [
+ 3,
+ 4,
+ [5, 6, method()]
+ ]
+];
+
+/*element: field3g:final,lazy*/
+var field3g = [method()];
+
+// TODO(johnniwinther): Recognize this as of eager complexity.
+/*element: field3h:final,lazy*/
+var field3h = [1 + 3];
+
+/*element: field4a:constant=IntConstant(5)*/
+final field4a = 2 + 3;
+
+/*element: field4b:constant=IntConstant(5)*/
+var field4b = 2 + 3;
+
+/*element: field4c:constant=IntConstant(5)*/
+const field4c = 2 + 3;
+
+/*element: field5a:constant=FunctionConstant(method)*/
+final field5a = method;
+
+/*element: field5b:constant=FunctionConstant(method)*/
+var field5b = method;
+
+/*element: field5c:constant=FunctionConstant(method)*/
+const field5c = method;
+
+/*element: field6a:constant=ConstructedConstant(Class())*/
+var field6a = const Class.generative();
+
+/*element: field6b:constant=ConstructedConstant(Class())*/
+var field6b = const Class.fact();
+
+/*element: field6c:final,lazy*/
+var field6c = method();
+
+/*element: field7a:eager,final*/
+var field7a = {};
+
+/*element: field7b:eager,final*/
+var field7b = {0: 1};
+
+/*element: field7c:eager,final*/
+var field7c = {0: method};
+
+/*element: field7d:final,lazy*/
+var field7d = {0: method()};
+
+/*element: field7e:final,lazy*/
+var field7e = {method(): 0};
+
+/*element: field8a:eager,final*/
+var field8a = {};
+
+/*element: field8b:eager,final*/
+var field8b = {0};
+
+/*element: field8c:eager,final*/
+var field8c = {method};
+
+/*element: field8d:final,lazy*/
+var field8d = {method()};
+
+/*element: field9g:eager=[field9d],final,index=1*/
+var field9g = field9d;
+
+/*element: field9a:eager,final*/
+var field9a = [];
+
+/*element: field9c:eager=[field9b],final,index=3*/
+var field9c = [field9b];
+
+/*element: field9b:eager=[field9a],final,index=2*/
+var field9b = field9a;
+
+// Because [field9g] is declared first and it depends upon [field9d], [field9d]
+// must be created before [field9g] and thus has a lower index than, say,
+// [field9b].
+/*element: field9d:eager=[field9a],final,index=0*/
+var field9d = [field9a];
+
+/*element: field9e:eager*/
+var field9e = [];
+
+/*element: field9f:final,lazy*/
+var field9f = field9e;
+
+/*element: field9h:constant=ListConstant([])*/
+var field9h = const [];
+
+/*element: field9i:eager,final*/
+var field9i = [field9h];
+
+/*element: field10a:final,lazy*/
+int field10a = field10b;
+
+/*element: field10b:final,lazy*/
+int field10b = field10a;
diff --git a/tests/compiler/dart2js/field_analysis/jfield_analysis_test.dart b/tests/compiler/dart2js/field_analysis/jfield_analysis_test.dart
index 1e9dfb7..09870a6 100644
--- a/tests/compiler/dart2js/field_analysis/jfield_analysis_test.dart
+++ b/tests/compiler/dart2js/field_analysis/jfield_analysis_test.dart
@@ -26,6 +26,10 @@
static const String isInitializedInAllocator = 'allocator';
static const String initialValue = 'initial';
static const String constantValue = 'constant';
+ static const String isEager = 'eager';
+ static const String eagerCreationIndex = 'index';
+ static const String isLazy = 'lazy';
+ static const String isEffectivelyFinal = 'final';
}
class JAllocatorAnalysisDataComputer extends DataComputer<Features> {
@@ -41,14 +45,33 @@
ir.Member node = closedWorld.elementMap.getMemberDefinition(member).node;
Features features = new Features();
FieldAnalysisData fieldData = fieldAnalysis.getFieldData(member);
+ if (fieldData.isInitializedInAllocator) {
+ features.add(Tags.isInitializedInAllocator);
+ }
if (fieldData.isEffectivelyConstant) {
features[Tags.constantValue] =
fieldData.constantValue.toStructuredText();
} else if (fieldData.initialValue != null) {
features[Tags.initialValue] = fieldData.initialValue.toStructuredText();
+ } else if (fieldData.isEager) {
+ if (fieldData.eagerCreationIndex != null) {
+ features[Tags.eagerCreationIndex] =
+ fieldData.eagerCreationIndex.toString();
+ }
+ if (fieldData.eagerFieldDependenciesForTesting != null) {
+ for (FieldEntity field
+ in fieldData.eagerFieldDependenciesForTesting) {
+ features.addElement(Tags.isEager, field.name);
+ }
+ } else {
+ features.add(Tags.isEager);
+ }
}
- if (fieldData.isInitializedInAllocator) {
- features.add(Tags.isInitializedInAllocator);
+ if (!member.isInstanceMember && fieldData.isLazy) {
+ features.add(Tags.isLazy);
+ }
+ if (fieldData.isEffectivelyFinal && !fieldData.isEffectivelyConstant) {
+ features.add(Tags.isEffectivelyFinal);
}
Id id = computeEntityId(node);
actualMap[id] = new ActualData<Features>(
diff --git a/tests/compiler/dart2js/field_analysis/kdata/static_initializers.dart b/tests/compiler/dart2js/field_analysis/kdata/static_initializers.dart
index fc87293..6f393ef 100644
--- a/tests/compiler/dart2js/field_analysis/kdata/static_initializers.dart
+++ b/tests/compiler/dart2js/field_analysis/kdata/static_initializers.dart
@@ -14,10 +14,49 @@
print(field3a);
print(field3b);
print(field3c);
+ print(field3d);
+ print(field3e);
+ print(field3f);
+ print(field3g);
+ print(field3h);
print(field4a);
print(field4b);
print(field4c);
+
+ print(field5a);
+ print(field5b);
+ print(field5c);
+
+ print(field6a);
+ print(field6b);
+ print(field6c);
+
+ print(field7a);
+ print(field7b);
+ print(field7c);
+ print(field7d);
+ print(field7e);
+
+ print(field8a);
+ print(field8b);
+ print(field8c);
+ print(field8d);
+
+ print(field9a);
+ print(field9b);
+ print(field9c);
+
+ print(field10a);
+ print(field10b);
+}
+
+method() {}
+
+class Class {
+ const Class.generative();
+
+ const factory Class.fact() = Class.generative;
}
/*element: field1a:complexity=constant,initial=IntConstant(0)*/
@@ -38,16 +77,48 @@
/*element: field2c:complexity=constant,initial=ListConstant([])*/
const field2c = const [];
-/*element: field3a:complexity=lazy*/
+/*element: field3a:complexity=eager*/
final field3a = [];
-/*element: field3b:complexity=lazy*/
+/*element: field3b:complexity=eager*/
var field3b = [];
-/*element: field3c:complexity=lazy*/
+/*element: field3c:complexity=eager*/
var field3c = [];
-// TODO(johnniwinther): Recognize these as of constant complexity.
+/*element: field3d:complexity=eager*/
+var field3d = [1, 2, 3];
+
+/*element: field3e:complexity=eager*/
+var field3e = [
+ 1,
+ 2,
+ [
+ 3,
+ 4,
+ [5, 6, method]
+ ]
+];
+
+/*element: field3f:complexity=lazy*/
+var field3f = [
+ 1,
+ 2,
+ [
+ 3,
+ 4,
+ [5, 6, method()]
+ ]
+];
+
+/*element: field3g:complexity=lazy*/
+var field3g = [method()];
+
+// TODO(johnniwinther): Recognize this as of eager complexity.
+/*element: field3h:complexity=lazy*/
+var field3h = [1 + 3];
+
+// TODO(johnniwinther): Recognize `field4*` as of constant complexity.
/*element: field4a:complexity=lazy,initial=IntConstant(5)*/
final field4a = 2 + 3;
@@ -56,3 +127,63 @@
/*element: field4c:complexity=lazy,initial=IntConstant(5)*/
const field4c = 2 + 3;
+
+/*element: field5a:complexity=constant,initial=FunctionConstant(method)*/
+final field5a = method;
+
+/*element: field5b:complexity=constant,initial=FunctionConstant(method)*/
+var field5b = method;
+
+/*element: field5c:complexity=constant,initial=FunctionConstant(method)*/
+const field5c = method;
+
+/*element: field6a:complexity=constant,initial=ConstructedConstant(Class())*/
+var field6a = const Class.generative();
+
+/*element: field6b:complexity=constant,initial=ConstructedConstant(Class())*/
+var field6b = const Class.fact();
+
+/*element: field6c:complexity=lazy*/
+var field6c = method();
+
+/*element: field7a:complexity=eager*/
+var field7a = {};
+
+/*element: field7b:complexity=eager*/
+var field7b = {0: 1};
+
+/*element: field7c:complexity=eager*/
+var field7c = {0: method};
+
+/*element: field7d:complexity=lazy*/
+var field7d = {0: method()};
+
+/*element: field7e:complexity=lazy*/
+var field7e = {method(): 0};
+
+/*element: field8a:complexity=eager*/
+var field8a = {};
+
+/*element: field8b:complexity=eager*/
+var field8b = {0};
+
+/*element: field8c:complexity=eager*/
+var field8c = {method};
+
+/*element: field8d:complexity=lazy*/
+var field8d = {method()};
+
+/*element: field9a:complexity=eager*/
+var field9a = [];
+
+/*element: field9b:complexity=eager&fields=[field9a]*/
+var field9b = field9a;
+
+/*element: field9c:complexity=eager&fields=[field9b]*/
+var field9c = [field9b];
+
+/*element: field10a:complexity=eager&fields=[field10b]*/
+int field10a = field10b;
+
+/*element: field10b:complexity=eager&fields=[field10a]*/
+int field10b = field10a;