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;