[dart/vm] Replaces eager with lazy reading of constants table

Rationale:
Previously all constants were read from the constants table,
whether they were needed (at that point or at all) or not. This
CL replaces that eager reading with a lazy reading that makes
the raw bytes of the constants table available through
KernelProgramInfo and attaches a hashmap to KernelProgramInfo,
which maps offsets into the constant table to evaluated constants.
The maps starts empty and on a miss, the constant is evaluated,
but no sooner than when it it needed.

https://github.com/dart-lang/sdk/issues/36220

Change-Id: Ief4df3d70c950c4beb61d896632ce06cfd12d525
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/99088
Commit-Queue: Aart Bik <ajcbik@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/vm/class_finalizer.cc b/runtime/vm/class_finalizer.cc
index 688f192..42e1d22 100644
--- a/runtime/vm/class_finalizer.cc
+++ b/runtime/vm/class_finalizer.cc
@@ -1197,14 +1197,7 @@
       kernel::BytecodeReader::LoadClassDeclaration(cls);
     }
 #endif
-    // TODO(36584) : We expect is_type_finalized to be true for all classes
-    // here, but with eager reading of the constant table we get into
-    // situations where we see classes whose types have not been finalized yet,
-    // the real solution is to implement lazy evaluation of constants. This is
-    // a temporary workaround until lazy evaluation is implemented.
-    if (!cls.is_type_finalized()) {
-      FinalizeTypesInClass(cls);
-    }
+    ASSERT(cls.is_type_finalized());
     ClassFinalizer::FinalizeClass(cls);
     return Error::null();
   } else {
diff --git a/runtime/vm/compiler/frontend/constant_evaluator.cc b/runtime/vm/compiler/frontend/constant_evaluator.cc
index a69ee37..e5065e2 100644
--- a/runtime/vm/compiler/frontend/constant_evaluator.cc
+++ b/runtime/vm/compiler/frontend/constant_evaluator.cc
@@ -168,10 +168,12 @@
         EvaluateNullLiteral();
         break;
       case kConstantExpression:
-        EvaluateConstantExpression(tag);
+        helper_->ReadPosition();
+        helper_->SkipDartType();
+        result_ = EvaluateConstantExpression(helper_->ReadUInt());
         break;
       case kDeprecated_ConstantExpression:
-        EvaluateConstantExpression(tag);
+        result_ = EvaluateConstantExpression(helper_->ReadUInt());
         break;
       default:
         H.ReportError(
@@ -291,6 +293,248 @@
   return metadata_values.raw();
 }
 
+RawInstance* ConstantEvaluator::EvaluateConstantExpression(
+    intptr_t constant_offset) {
+  ASSERT(!H.constants().IsNull());
+  ASSERT(!H.constants_table().IsNull());  // raw bytes
+
+  // For kernel-level cache (in contrast with script-level caching),
+  // we need to access the raw constants array inside the shared
+  // KernelProgramInfo directly, so that all scripts will see the
+  // results after new insertions. These accesses at kernel-level
+  // must be locked since mutator and background compiler can
+  // access the array at the same time.
+  {
+    SafepointMutexLocker ml(H.thread()->isolate()->kernel_constants_mutex());
+    KernelConstantsMap constant_map(H.info().constants());
+    result_ ^= constant_map.GetOrNull(constant_offset);
+    ASSERT(constant_map.Release().raw() == H.info().constants());
+  }
+
+  // On miss, evaluate, and insert value.
+  if (result_.IsNull()) {
+    result_ = EvaluateConstant(constant_offset);
+    SafepointMutexLocker ml(H.thread()->isolate()->kernel_constants_mutex());
+    KernelConstantsMap constant_map(H.info().constants());
+    auto insert = constant_map.InsertNewOrGetValue(constant_offset, result_);
+    ASSERT(insert == result_.raw());
+    H.info().set_constants(constant_map.Release());  // update!
+  }
+  return result_.raw();
+}
+
+RawInstance* ConstantEvaluator::EvaluateConstant(intptr_t constant_offset) {
+  // Get reader directly into raw bytes of constant table.
+  KernelReaderHelper reader(Z, &H, script_, H.constants_table(), 0);
+  reader.ReadUInt();  // skip variable-sized int for adjusted constant offset
+  reader.SetOffset(reader.ReaderOffset() + constant_offset);
+  // Construct constant from raw bytes.
+  Instance& instance = Instance::Handle(Z);
+  const intptr_t constant_tag = reader.ReadByte();
+  switch (constant_tag) {
+    case kNullConstant:
+      instance = Instance::null();
+      break;
+    case kBoolConstant:
+      instance = reader.ReadByte() == 1 ? Object::bool_true().raw()
+                                        : Object::bool_false().raw();
+      break;
+    case kIntConstant: {
+      uint8_t payload = 0;
+      Tag integer_tag = reader.ReadTag(&payload);  // read tag.
+      switch (integer_tag) {
+        case kBigIntLiteral: {
+          const String& value = H.DartString(reader.ReadStringReference());
+          instance = Integer::New(value, Heap::kOld);
+          break;
+        }
+        case kSpecializedIntLiteral: {
+          const int64_t value =
+              static_cast<int32_t>(payload) - SpecializedIntLiteralBias;
+          instance = Integer::New(value, Heap::kOld);
+          break;
+        }
+        case kNegativeIntLiteral: {
+          const int64_t value = -static_cast<int64_t>(reader.ReadUInt());
+          instance = Integer::New(value, Heap::kOld);
+          break;
+        }
+        case kPositiveIntLiteral: {
+          const int64_t value = reader.ReadUInt();
+          instance = Integer::New(value, Heap::kOld);
+          break;
+        }
+        default:
+          H.ReportError(
+              script_, TokenPosition::kNoSource,
+              "Cannot lazily read integer: unexpected kernel tag %s (%d)",
+              Reader::TagName(integer_tag), integer_tag);
+      }
+      break;
+    }
+    case kDoubleConstant:
+      instance = Double::New(reader.ReadDouble(), Heap::kOld);
+      break;
+    case kStringConstant:
+      instance = H.DartSymbolPlain(reader.ReadStringReference()).raw();
+      break;
+    case kSymbolConstant: {
+      Library& library = Library::Handle(Z);
+      library = Library::InternalLibrary();
+      const Class& symbol_class =
+          Class::Handle(Z, library.LookupClass(Symbols::Symbol()));
+      const Field& symbol_name_field = Field::Handle(
+          Z, symbol_class.LookupInstanceFieldAllowPrivate(Symbols::_name()));
+      ASSERT(!symbol_name_field.IsNull());
+      const NameIndex index = reader.ReadCanonicalNameReference();
+      if (index == -1) {
+        library = Library::null();
+      } else {
+        library = H.LookupLibraryByKernelLibrary(index);
+      }
+      const String& symbol =
+          H.DartIdentifier(library, reader.ReadStringReference());
+      instance = Instance::New(symbol_class, Heap::kOld);
+      instance.SetField(symbol_name_field, symbol);
+      break;
+    }
+    case kListConstant: {
+      const Library& corelib = Library::Handle(Z, Library::CoreLibrary());
+      const Class& list_class =
+          Class::Handle(Z, corelib.LookupClassAllowPrivate(Symbols::_List()));
+      // Build type from the raw bytes (needs temporary translator).
+      TypeTranslator type_translator(&reader, active_class_, true);
+      TypeArguments& type_arguments =
+          TypeArguments::ZoneHandle(Z, TypeArguments::New(1, Heap::kOld));
+      AbstractType& type = type_translator.BuildType();
+      type_arguments.SetTypeAt(0, type);
+      // Instantiate class.
+      type = Type::New(list_class, type_arguments, TokenPosition::kNoSource);
+      type = ClassFinalizer::FinalizeType(*active_class_->klass, type,
+                                          ClassFinalizer::kCanonicalize);
+      type_arguments = type.arguments();
+      // Fill array with constant elements.
+      const intptr_t length = reader.ReadUInt();
+      const Array& array =
+          Array::Handle(Z, ImmutableArray::New(length, Heap::kOld));
+      array.SetTypeArguments(type_arguments);
+      Instance& constant = Instance::Handle(Z);
+      for (intptr_t j = 0; j < length; ++j) {
+        // Recurse into lazily evaluating all "sub" constants
+        // needed to evaluate the current constant.
+        const intptr_t entry_offset = reader.ReadUInt();
+        ASSERT(entry_offset < constant_offset);  // DAG!
+        constant = EvaluateConstantExpression(entry_offset);
+        array.SetAt(j, constant);
+      }
+      instance = array.raw();
+      break;
+    }
+    case kInstanceConstant: {
+      const NameIndex index = reader.ReadCanonicalNameReference();
+      const Class& klass = Class::Handle(Z, H.LookupClassByKernelClass(index));
+      const Object& obj =
+          Object::Handle(Z, klass.EnsureIsFinalized(H.thread()));
+      ASSERT(obj.IsNull());
+      instance = Instance::New(klass, Heap::kOld);
+      // Build type from the raw bytes (needs temporary translator).
+      TypeTranslator type_translator(&reader, active_class_, true);
+      const intptr_t number_of_type_arguments = reader.ReadUInt();
+      if (klass.NumTypeArguments() > 0) {
+        TypeArguments& type_arguments = TypeArguments::ZoneHandle(
+            Z, TypeArguments::New(number_of_type_arguments, Heap::kOld));
+        for (intptr_t j = 0; j < number_of_type_arguments; ++j) {
+          type_arguments.SetTypeAt(j, type_translator.BuildType());
+        }
+        // Instantiate class.
+        AbstractType& type = AbstractType::Handle(
+            Z, Type::New(klass, type_arguments, TokenPosition::kNoSource));
+        type = ClassFinalizer::FinalizeType(*active_class_->klass, type,
+                                            ClassFinalizer::kCanonicalize);
+        type_arguments = type.arguments();
+        instance.SetTypeArguments(type_arguments);
+      } else {
+        ASSERT(number_of_type_arguments == 0);
+      }
+      // Set the fields.
+      const intptr_t number_of_fields = reader.ReadUInt();
+      Field& field = Field::Handle(Z);
+      Instance& constant = Instance::Handle(Z);
+      for (intptr_t j = 0; j < number_of_fields; ++j) {
+        field = H.LookupFieldByKernelField(reader.ReadCanonicalNameReference());
+        // Recurse into lazily evaluating all "sub" constants
+        // needed to evaluate the current constant.
+        const intptr_t entry_offset = reader.ReadUInt();
+        ASSERT(entry_offset < constant_offset);  // DAG!
+        constant = EvaluateConstantExpression(entry_offset);
+        instance.SetField(field, constant);
+      }
+      break;
+    }
+    case kPartialInstantiationConstant: {
+      // Recurse into lazily evaluating the "sub" constant
+      // needed to evaluate the current constant.
+      const intptr_t entry_offset = reader.ReadUInt();
+      ASSERT(entry_offset < constant_offset);  // DAG!
+      Instance& constant =
+          Instance::Handle(Z, EvaluateConstantExpression(entry_offset));
+      // Happens if the tearoff was in the vmservice library and we have
+      // [skip_vm_service_library] enabled.
+      if (constant.IsNull()) {
+        instance = Instance::null();
+        break;
+      }
+      // Build type from the raw bytes (needs temporary translator).
+      TypeTranslator type_translator(&reader, active_class_, true);
+      const intptr_t number_of_type_arguments = reader.ReadUInt();
+      ASSERT(number_of_type_arguments > 0);
+      TypeArguments& type_arguments = TypeArguments::ZoneHandle(
+          Z, TypeArguments::New(number_of_type_arguments, Heap::kOld));
+      for (intptr_t j = 0; j < number_of_type_arguments; ++j) {
+        type_arguments.SetTypeAt(j, type_translator.BuildType());
+      }
+      // Make a copy of the old closure, and set delayed type arguments.
+      Closure& closure = Closure::Handle(Z, Closure::RawCast(constant.raw()));
+      Function& function = Function::Handle(Z, closure.function());
+      TypeArguments& type_arguments2 =
+          TypeArguments::ZoneHandle(Z, closure.instantiator_type_arguments());
+      TypeArguments& type_arguments3 =
+          TypeArguments::ZoneHandle(Z, closure.function_type_arguments());
+      Context& context = Context::Handle(Z, closure.context());
+      instance = Closure::New(type_arguments2, Object::null_type_arguments(),
+                              type_arguments3, function, context,
+                              Heap::kOld);  // was type_arguments?
+      break;
+    }
+    case kTearOffConstant: {
+      const NameIndex index = reader.ReadCanonicalNameReference();
+      Function& function =
+          Function::Handle(Z, H.LookupStaticMethodByKernelProcedure(index));
+      function = function.ImplicitClosureFunction();
+      instance = function.ImplicitStaticClosure();
+      break;
+    }
+    case kTypeLiteralConstant: {
+      // Build type from the raw bytes (needs temporary translator).
+      TypeTranslator type_translator(&reader, active_class_, true);
+      instance = type_translator.BuildType().raw();
+      break;
+    }
+    default:
+      // Set literals (kSetConstant) are currently desugared in the frontend
+      // and will not reach the VM. See http://dartbug.com/35124 for some
+      // discussion. Map constants (kMapConstant ) are already lowered to
+      // InstanceConstant or ListConstant. We should never see unevaluated
+      // constants (kUnevaluatedConstant) in the constant table, they should
+      // have been fully evaluated before we get them.
+      H.ReportError(script_, TokenPosition::kNoSource,
+                    "Cannot lazily read constant: unexpected kernel tag (%" Pd
+                    ")",
+                    constant_tag);
+  }
+  return H.Canonicalize(instance);
+}
+
 void ConstantEvaluator::BailoutIfBackgroundCompilation() {
   if (Compiler::IsBackgroundCompilation()) {
     Compiler::AbortBackgroundCompilation(
@@ -868,20 +1112,6 @@
   result_ = Instance::null();
 }
 
-void ConstantEvaluator::EvaluateConstantExpression(Tag tag) {
-  // Please note that this constants array is constructed exactly once, see
-  // ReadConstantTable() and is immutable from that point on, so there is no
-  // need to guard against concurrent access between mutator and background
-  // compiler.
-  KernelConstantsMap constant_map(H.constants().raw());
-  if (tag == kConstantExpression) {
-    helper_->ReadPosition();
-    helper_->SkipDartType();
-  }
-  result_ ^= constant_map.GetOrDie(helper_->ReadUInt());
-  ASSERT(constant_map.Release().raw() == H.constants().raw());
-}
-
 // This depends on being about to read the list of positionals on arguments.
 const Object& ConstantEvaluator::RunFunction(TokenPosition position,
                                              const Function& function,
@@ -1109,273 +1339,6 @@
   }
 }
 
-ConstantHelper::ConstantHelper(Zone* zone,
-                               KernelReaderHelper* helper,
-                               TypeTranslator* type_translator,
-                               ActiveClass* active_class,
-                               NameIndex skip_vmservice_library)
-    : zone_(zone),
-      helper_(*helper),
-      type_translator_(*type_translator),
-      active_class_(active_class),
-      const_evaluator_(helper, type_translator, active_class, nullptr),
-      translation_helper_(helper->translation_helper_),
-      skip_vmservice_library_(skip_vmservice_library),
-      symbol_class_(Class::Handle(zone)),
-      symbol_name_field_(Field::Handle(zone)),
-      temp_type_(AbstractType::Handle(zone)),
-      temp_type_arguments_(TypeArguments::Handle(zone)),
-      temp_type_arguments2_(TypeArguments::Handle(zone)),
-      temp_type_arguments3_(TypeArguments::Handle(zone)),
-      temp_object_(Object::Handle(zone)),
-      temp_string_(String::Handle(zone)),
-      temp_array_(Array::Handle(zone)),
-      temp_instance_(Instance::Handle(zone)),
-      temp_field_(Field::Handle(zone)),
-      temp_class_(Class::Handle(zone)),
-      temp_library_(Library::Handle(zone)),
-      temp_function_(Function::Handle(zone)),
-      temp_closure_(Closure::Handle(zone)),
-      temp_context_(Context::Handle(zone)),
-      temp_integer_(Integer::Handle(zone)) {
-  temp_library_ = Library::InternalLibrary();
-  ASSERT(!temp_library_.IsNull());
-
-  symbol_class_ = temp_library_.LookupClass(Symbols::Symbol());
-  ASSERT(!symbol_class_.IsNull());
-
-  symbol_name_field_ =
-      symbol_class_.LookupInstanceFieldAllowPrivate(Symbols::_name());
-  ASSERT(!symbol_name_field_.IsNull());
-}
-
-const Array& ConstantHelper::ReadConstantTable() {
-  const intptr_t number_of_constants = helper_.ReadUInt();
-  if (number_of_constants == 0) {
-    return Array::empty_array();
-  }
-
-  const Library& corelib = Library::Handle(Z, Library::CoreLibrary());
-  const Class& list_class =
-      Class::Handle(Z, corelib.LookupClassAllowPrivate(Symbols::_List()));
-
-  // Eagerly finalize _ImmutableList (instead of doing it on every list
-  // constant).
-  temp_class_ = I->class_table()->At(kImmutableArrayCid);
-  temp_object_ = temp_class_.EnsureIsFinalized(H.thread());
-  ASSERT(temp_object_.IsNull());
-
-  KernelConstantsMap constants(
-      HashTables::New<KernelConstantsMap>(number_of_constants, Heap::kOld));
-
-  const intptr_t start_offset = helper_.ReaderOffset();
-
-  for (intptr_t i = 0; i < number_of_constants; ++i) {
-    const intptr_t offset = helper_.ReaderOffset();
-    const intptr_t constant_tag = helper_.ReadByte();
-    switch (constant_tag) {
-      case kNullConstant:
-        temp_instance_ = Instance::null();
-        break;
-      case kBoolConstant:
-        temp_instance_ = helper_.ReadByte() == 1 ? Object::bool_true().raw()
-                                                 : Object::bool_false().raw();
-        break;
-      case kIntConstant: {
-        temp_instance_ = const_evaluator_.EvaluateExpression(
-            helper_.ReaderOffset(), false /* reset position */);
-        break;
-      }
-      case kDoubleConstant: {
-        temp_instance_ = Double::New(helper_.ReadDouble(), Heap::kOld);
-        temp_instance_ = H.Canonicalize(temp_instance_);
-        break;
-      }
-      case kStringConstant: {
-        temp_instance_ =
-            H.Canonicalize(H.DartString(helper_.ReadStringReference()));
-        break;
-      }
-      case kSymbolConstant: {
-        const NameIndex index = helper_.ReadCanonicalNameReference();
-        if (index == -1) {
-          temp_library_ = Library::null();
-        } else {
-          temp_library_ = H.LookupLibraryByKernelLibrary(index);
-        }
-        const String& symbol =
-            H.DartIdentifier(temp_library_, helper_.ReadStringReference());
-        temp_instance_ = Instance::New(symbol_class_, Heap::kOld);
-        temp_instance_.SetField(symbol_name_field_, symbol);
-        temp_instance_ = H.Canonicalize(temp_instance_);
-        break;
-      }
-      case kListConstant: {
-        temp_type_arguments_ = TypeArguments::New(1, Heap::kOld);
-        const AbstractType& type = type_translator_.BuildType();
-        temp_type_arguments_.SetTypeAt(0, type);
-        InstantiateTypeArguments(list_class, &temp_type_arguments_);
-
-        const intptr_t length = helper_.ReadUInt();
-        temp_array_ = ImmutableArray::New(length, Heap::kOld);
-        temp_array_.SetTypeArguments(temp_type_arguments_);
-        for (intptr_t j = 0; j < length; ++j) {
-          const intptr_t entry_offset = helper_.ReadUInt();
-          ASSERT(entry_offset < (offset - start_offset));  // We have a DAG!
-          temp_object_ = constants.GetOrDie(entry_offset);
-          temp_array_.SetAt(j, temp_object_);
-        }
-
-        temp_instance_ = H.Canonicalize(temp_array_);
-        break;
-      }
-      case kSetConstant:
-        // Set literals are currently desugared in the frontend and will not
-        // reach the VM. See http://dartbug.com/35124 for discussion.
-        H.ReportError(script(), TokenPosition::kNoSource,
-                      "Unexpected set constant, this constant"
-                      " is expected to be evaluated at this point (%" Pd ")",
-                      constant_tag);
-        break;
-      case kInstanceConstant: {
-        const NameIndex index = helper_.ReadCanonicalNameReference();
-        if (ShouldSkipConstant(index)) {
-          temp_instance_ = Instance::null();
-          break;
-        }
-
-        temp_class_ = H.LookupClassByKernelClass(index);
-        temp_object_ = temp_class_.EnsureIsFinalized(H.thread());
-        ASSERT(temp_object_.IsNull());
-
-        temp_instance_ = Instance::New(temp_class_, Heap::kOld);
-
-        const intptr_t number_of_type_arguments = helper_.ReadUInt();
-        if (temp_class_.NumTypeArguments() > 0) {
-          temp_type_arguments_ =
-              TypeArguments::New(number_of_type_arguments, Heap::kOld);
-          for (intptr_t j = 0; j < number_of_type_arguments; ++j) {
-            temp_type_arguments_.SetTypeAt(j, type_translator_.BuildType());
-          }
-          InstantiateTypeArguments(temp_class_, &temp_type_arguments_);
-          temp_instance_.SetTypeArguments(temp_type_arguments_);
-        } else {
-          ASSERT(number_of_type_arguments == 0);
-        }
-
-        const intptr_t number_of_fields = helper_.ReadUInt();
-        for (intptr_t j = 0; j < number_of_fields; ++j) {
-          temp_field_ =
-              H.LookupFieldByKernelField(helper_.ReadCanonicalNameReference());
-          const intptr_t entry_offset = helper_.ReadUInt();
-          ASSERT(entry_offset < (offset - start_offset));  // We have a DAG!
-          temp_object_ = constants.GetOrDie(entry_offset);
-          temp_instance_.SetField(temp_field_, temp_object_);
-        }
-
-        temp_instance_ = H.Canonicalize(temp_instance_);
-        break;
-      }
-      case kPartialInstantiationConstant: {
-        const intptr_t entry_offset = helper_.ReadUInt();
-        ASSERT(entry_offset < (offset - start_offset));  // We have a DAG!
-        temp_object_ = constants.GetOrDie(entry_offset);
-
-        // Happens if the tearoff was in the vmservice library and we have
-        // [skip_vm_service_library] enabled.
-        if (temp_object_.IsNull()) {
-          temp_instance_ = Instance::null();
-          break;
-        }
-
-        const intptr_t number_of_type_arguments = helper_.ReadUInt();
-        ASSERT(number_of_type_arguments > 0);
-        temp_type_arguments_ =
-            TypeArguments::New(number_of_type_arguments, Heap::kOld);
-        for (intptr_t j = 0; j < number_of_type_arguments; ++j) {
-          temp_type_arguments_.SetTypeAt(j, type_translator_.BuildType());
-        }
-        temp_type_arguments_ = temp_type_arguments_.Canonicalize();
-
-        // Make a copy of the old closure, with the delayed type arguments
-        // set to [temp_type_arguments_].
-        temp_closure_ = Closure::RawCast(temp_object_.raw());
-        temp_function_ = temp_closure_.function();
-        temp_type_arguments2_ = temp_closure_.instantiator_type_arguments();
-        temp_type_arguments3_ = temp_closure_.function_type_arguments();
-        temp_context_ = temp_closure_.context();
-        temp_closure_ = Closure::New(
-            temp_type_arguments2_, Object::null_type_arguments(),
-            temp_type_arguments_, temp_function_, temp_context_, Heap::kOld);
-        temp_instance_ = H.Canonicalize(temp_closure_);
-        break;
-      }
-      case kTearOffConstant: {
-        const NameIndex index = helper_.ReadCanonicalNameReference();
-        if (ShouldSkipConstant(index)) {
-          temp_instance_ = Instance::null();
-          break;
-        }
-
-        temp_function_ = H.LookupStaticMethodByKernelProcedure(index);
-        temp_function_ = temp_function_.ImplicitClosureFunction();
-        temp_instance_ = temp_function_.ImplicitStaticClosure();
-        temp_instance_ = H.Canonicalize(temp_instance_);
-        break;
-      }
-      case kTypeLiteralConstant: {
-        temp_instance_ = type_translator_.BuildType().raw();
-        break;
-      }
-      case kMapConstant:
-        // Note: This is already lowered to InstanceConstant/ListConstant.
-        H.ReportError(script(), TokenPosition::kNoSource,
-                      "Unexpected map constant, this constant"
-                      " is expected to be evaluated at this point (%" Pd ")",
-                      constant_tag);
-        break;
-      case kUnevaluatedConstant:
-        // We should not see unevaluated constants in the constant table, they
-        // should have been fully evaluated before we get them.
-        H.ReportError(
-            script(), TokenPosition::kNoSource,
-            "Unexpected unevaluated constant, All constant expressions"
-            " are expected to be evaluated at this point (%" Pd ")",
-            constant_tag);
-        break;
-      default:
-        UNREACHABLE();
-    }
-    constants.InsertNewOrGetValue(offset - start_offset, temp_instance_);
-  }
-  return Array::Handle(Z, constants.Release().raw());
-}
-
-void ConstantHelper::InstantiateTypeArguments(const Class& receiver_class,
-                                              TypeArguments* type_arguments) {
-  // We make a temporary [Type] object and use `ClassFinalizer::FinalizeType` to
-  // finalize the argument types.
-  // (This can for example make the [type_arguments] vector larger)
-  temp_type_ =
-      Type::New(receiver_class, *type_arguments, TokenPosition::kNoSource);
-  temp_type_ = ClassFinalizer::FinalizeType(*active_class_->klass, temp_type_,
-                                            ClassFinalizer::kCanonicalize);
-  *type_arguments = temp_type_.arguments();
-}
-
-// If [index] has `dart:vm_service` as a parent and we are skipping the VM
-// service library, this method returns `true`, otherwise `false`.
-bool ConstantHelper::ShouldSkipConstant(NameIndex index) {
-  if (index == NameIndex::kInvalidName) {
-    return false;
-  }
-  while (!H.IsLibrary(index)) {
-    index = H.CanonicalNameParent(index);
-  }
-  ASSERT(H.IsLibrary(index));
-  return index == skip_vmservice_library_;
-}
-
 }  // namespace kernel
 }  // namespace dart
 
diff --git a/runtime/vm/compiler/frontend/constant_evaluator.h b/runtime/vm/compiler/frontend/constant_evaluator.h
index 0483baa..7981835 100644
--- a/runtime/vm/compiler/frontend/constant_evaluator.h
+++ b/runtime/vm/compiler/frontend/constant_evaluator.h
@@ -56,7 +56,13 @@
   RawObject* EvaluateExpressionSafe(intptr_t offset);
   RawObject* EvaluateAnnotations();
 
+  // Evaluates a constant at the given offset (possibly by recursing
+  // into sub-constants).
+  RawInstance* EvaluateConstantExpression(intptr_t constant_offset);
+
  private:
+  RawInstance* EvaluateConstant(intptr_t constant_offset);
+
   void BailoutIfBackgroundCompilation();
 
   bool IsBuildingFlowGraph() const;
@@ -88,7 +94,6 @@
   void EvaluateDoubleLiteral();
   void EvaluateBoolLiteral(bool value);
   void EvaluateNullLiteral();
-  void EvaluateConstantExpression(Tag tag);
 
   void EvaluateGetStringLength(intptr_t expression_offset,
                                TokenPosition position);
@@ -140,59 +145,6 @@
   DISALLOW_COPY_AND_ASSIGN(ConstantEvaluator);
 };
 
-// Helper class that reads a kernel Constant from binary.
-class ConstantHelper {
- public:
-  ConstantHelper(Zone* zone,
-                 KernelReaderHelper* helper,
-                 TypeTranslator* type_translator,
-                 ActiveClass* active_class,
-                 NameIndex skip_vmservice_library);
-
-  // Reads the constant table from the binary.
-  //
-  // This method assumes the Reader is positioned already at the constant table
-  // and an active class scope is setup.
-  const Array& ReadConstantTable();
-
- private:
-  const Script& script() const { return helper_.script_; }
-
-  void InstantiateTypeArguments(const Class& receiver_class,
-                                TypeArguments* type_arguments);
-
-  // If [index] has `dart:vm_service` as a parent and we are skipping the VM
-  // service library, this method returns `true`, otherwise `false`.
-  bool ShouldSkipConstant(NameIndex index);
-
-  Zone* zone_;
-  KernelReaderHelper& helper_;
-  TypeTranslator& type_translator_;
-  ActiveClass* const active_class_;
-  ConstantEvaluator const_evaluator_;
-  TranslationHelper& translation_helper_;
-  NameIndex skip_vmservice_library_;
-  Class& symbol_class_;
-  Field& symbol_name_field_;
-  AbstractType& temp_type_;
-  TypeArguments& temp_type_arguments_;
-  TypeArguments& temp_type_arguments2_;
-  TypeArguments& temp_type_arguments3_;
-  Object& temp_object_;
-  String& temp_string_;
-  Array& temp_array_;
-  Instance& temp_instance_;
-  Field& temp_field_;
-  Class& temp_class_;
-  Library& temp_library_;
-  Function& temp_function_;
-  Closure& temp_closure_;
-  Context& temp_context_;
-  Integer& temp_integer_;
-
-  DISALLOW_COPY_AND_ASSIGN(ConstantHelper);
-};
-
 class KernelConstMapKeyEqualsTraits : public AllStatic {
  public:
   static const char* Name() { return "KernelConstMapKeyEqualsTraits"; }
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index d018655..89a647e 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -3776,10 +3776,8 @@
   }
   if (position != nullptr) *position = p;
   const intptr_t constant_offset = ReadUInt();
-  KernelConstantsMap constant_map(H.constants().raw());
-  Fragment result =
-      Constant(Object::ZoneHandle(Z, constant_map.GetOrDie(constant_offset)));
-  ASSERT(constant_map.Release().raw() == H.constants().raw());
+  Fragment result = Constant(Object::ZoneHandle(
+      Z, constant_evaluator_.EvaluateConstantExpression(constant_offset)));
   return result;
 }
 
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index d70628f..a3555db 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -32,6 +32,7 @@
       metadata_payloads_(ExternalTypedData::Handle(Z)),
       metadata_mappings_(ExternalTypedData::Handle(Z)),
       constants_(Array::Handle(Z)),
+      constants_table_(ExternalTypedData::Handle(Z)),
       info_(KernelProgramInfo::Handle(Z)),
       name_index_handle_(Smi::Handle(Z)) {}
 
@@ -46,6 +47,7 @@
       metadata_payloads_(ExternalTypedData::Handle(Z)),
       metadata_mappings_(ExternalTypedData::Handle(Z)),
       constants_(Array::Handle(Z)),
+      constants_table_(ExternalTypedData::Handle(Z)),
       info_(KernelProgramInfo::Handle(Z)),
       name_index_handle_(Smi::Handle(Z)) {}
 
@@ -79,6 +81,7 @@
   SetMetadataPayloads(ExternalTypedData::Handle(Z, info.metadata_payloads()));
   SetMetadataMappings(ExternalTypedData::Handle(Z, info.metadata_mappings()));
   SetConstants(Array::Handle(Z, info.constants()));
+  SetConstantsTable(ExternalTypedData::Handle(Z, info.constants_table()));
   SetKernelProgramInfo(info);
 }
 
@@ -126,6 +129,12 @@
   constants_ = constants.raw();
 }
 
+void TranslationHelper::SetConstantsTable(
+    const ExternalTypedData& constants_table) {
+  ASSERT(constants_table_.IsNull());
+  constants_table_ = constants_table.raw();
+}
+
 void TranslationHelper::SetKernelProgramInfo(const KernelProgramInfo& info) {
   info_ = info.raw();
 }
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index 1dc3f3c4..43efd07 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -41,24 +41,35 @@
   Heap::Space allocation_space() { return allocation_space_; }
 
   // Access to strings.
-  const TypedData& string_offsets() { return string_offsets_; }
+  const TypedData& string_offsets() const { return string_offsets_; }
   void SetStringOffsets(const TypedData& string_offsets);
 
-  const ExternalTypedData& string_data() { return string_data_; }
+  const ExternalTypedData& string_data() const { return string_data_; }
   void SetStringData(const ExternalTypedData& string_data);
 
-  const TypedData& canonical_names() { return canonical_names_; }
+  const TypedData& canonical_names() const { return canonical_names_; }
   void SetCanonicalNames(const TypedData& canonical_names);
 
-  const ExternalTypedData& metadata_payloads() { return metadata_payloads_; }
+  const ExternalTypedData& metadata_payloads() const {
+    return metadata_payloads_;
+  }
   void SetMetadataPayloads(const ExternalTypedData& metadata_payloads);
 
-  const ExternalTypedData& metadata_mappings() { return metadata_mappings_; }
+  const ExternalTypedData& metadata_mappings() const {
+    return metadata_mappings_;
+  }
   void SetMetadataMappings(const ExternalTypedData& metadata_mappings);
 
+  // Access to previously evaluated constants from the constants table.
   const Array& constants() { return constants_; }
   void SetConstants(const Array& constants);
 
+  // Access to the raw bytes of the constants table.
+  const ExternalTypedData& constants_table() const { return constants_table_; }
+  void SetConstantsTable(const ExternalTypedData& constants_table);
+
+  KernelProgramInfo& info() { return info_; }
+
   RawGrowableObjectArray* EnsurePotentialPragmaFunctions();
 
   void SetKernelProgramInfo(const KernelProgramInfo& info);
@@ -206,6 +217,7 @@
   ExternalTypedData& metadata_payloads_;
   ExternalTypedData& metadata_mappings_;
   Array& constants_;
+  ExternalTypedData& constants_table_;
   KernelProgramInfo& info_;
   Smi& name_index_handle_;
 
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index d37c67d..f7ce8e0 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -190,6 +190,7 @@
       zone_(thread_->zone()),
       isolate_(thread_->isolate()),
       patch_classes_(Array::ZoneHandle(zone_)),
+      active_class_(),
       library_kernel_offset_(-1),  // Set to the correct value in LoadLibrary
       correction_offset_(-1),      // Set to the correct value in LoadLibrary
       loading_native_wrappers_library_(false),
@@ -206,6 +207,7 @@
       bytecode_metadata_helper_(&helper_, &active_class_),
       external_name_class_(Class::Handle(Z)),
       external_name_field_(Field::Handle(Z)),
+      evaluating_(GrowableObjectArray::Handle(Z)),
       potential_natives_(GrowableObjectArray::Handle(Z)),
       potential_pragma_functions_(GrowableObjectArray::Handle(Z)),
       potential_extension_libraries_(GrowableObjectArray::Handle(Z)),
@@ -455,6 +457,7 @@
       bytecode_metadata_helper_(&helper_, &active_class_),
       external_name_class_(Class::Handle(Z)),
       external_name_field_(Field::Handle(Z)),
+      evaluating_(GrowableObjectArray::Handle(Z)),
       potential_natives_(GrowableObjectArray::Handle(Z)),
       potential_pragma_functions_(GrowableObjectArray::Handle(Z)),
       potential_extension_libraries_(GrowableObjectArray::Handle(Z)),
@@ -472,32 +475,6 @@
   H.InitFromKernelProgramInfo(kernel_program_info_);
 }
 
-const Array& KernelLoader::ReadConstantTable() {
-  if (program_->library_count() == 0) {
-    return Array::empty_array();
-  }
-  // We use the very first library's toplevel class as an owner for an
-  // [ActiveClassScope]
-  //
-  // Though since constants cannot refer to types containing type parameter
-  // references, the only purpose of the class is to serve as an owner for
-  // signature functions (which get created for function types).
-  const dart::Library& owner_library =
-      Library::Handle(Z, LookupLibrary(library_canonical_name(0)));
-  const dart::Class& toplevel_class =
-      Class::Handle(Z, owner_library.toplevel_class());
-  ActiveClassScope active_class_scope(&active_class_, &toplevel_class);
-
-  helper_.SetOffset(program_->constant_table_offset());
-  TypeTranslator type_translator_(&helper_, &active_class_,
-                                  true /* finalize */);
-  ASSERT(type_translator_.active_class_ == &active_class_);
-
-  ConstantHelper helper(Z, &helper_, &type_translator_, &active_class_,
-                        skip_vmservice_library_);
-  return helper.ReadConstantTable();
-}
-
 void KernelLoader::EvaluateDelayedPragmas() {
   potential_pragma_functions_ =
       kernel_program_info_.potential_pragma_functions();
@@ -522,66 +499,68 @@
       GrowableObjectArray::Handle(Z));
 }
 
-void KernelLoader::AnnotateNativeProcedures(const Array& constant_table_array) {
-  KernelConstantsMap constant_table(constant_table_array.raw());
+void KernelLoader::AnnotateNativeProcedures() {
   potential_natives_ = kernel_program_info_.potential_natives();
   const intptr_t length =
       !potential_natives_.IsNull() ? potential_natives_.Length() : 0;
-  if (length > 0) {
-    // Obtain `dart:_internal::ExternalName.name`.
-    EnsureExternalClassIsLookedUp();
-    Instance& constant = Instance::Handle(Z);
-    String& native_name = String::Handle(Z);
+  if (length == 0) return;
 
-    // Start scanning all candidates in [potential_natives] for the annotation
-    // constant.  If the annotation is found, flag the [Function] as native and
-    // attach the native name to it.
-    Function& function = Function::Handle(Z);
-    for (intptr_t i = 0; i < length; ++i) {
-      function ^= potential_natives_.At(i);
-      helper_.SetOffset(function.KernelDataProgramOffset() +
-                        function.kernel_offset());
-      {
-        ProcedureHelper procedure_helper(&helper_);
-        procedure_helper.ReadUntilExcluding(ProcedureHelper::kAnnotations);
-      }
+  // Prepare lazy constant reading.
+  ConstantEvaluator constant_evaluator(&helper_, &type_translator_,
+                                       &active_class_);
 
-      const intptr_t annotation_count = helper_.ReadListLength();
-      for (intptr_t j = 0; j < annotation_count; ++j) {
-        const intptr_t tag = helper_.PeekTag();
-        if (tag == kConstantExpression ||
-            tag == kDeprecated_ConstantExpression) {
-          helper_.ReadByte();  // Skip the tag.
+  // Obtain `dart:_internal::ExternalName.name`.
+  EnsureExternalClassIsLookedUp();
+  Instance& constant = Instance::Handle(Z);
+  String& native_name = String::Handle(Z);
 
-          // We have a candiate.  Let's look if it's an instance of the
-          // ExternalName class.
-          if (tag == kConstantExpression) {
-            helper_.ReadPosition();  // Skip fileOffset.
-            helper_.SkipDartType();  // Skip type.
-          }
-          const intptr_t constant_table_offset = helper_.ReadUInt();
-          constant ^= constant_table.GetOrDie(constant_table_offset);
-          if (constant.clazz() == external_name_class_.raw()) {
-            // We found the annotation, let's flag the function as native and
-            // set the native name!
-            native_name ^= constant.GetField(external_name_field_);
-            function.set_is_native(true);
-            function.set_native_name(native_name);
-            function.set_is_external(false);
-            break;
-          }
-        } else {
-          helper_.SkipExpression();
-        }
-      }
+  // Start scanning all candidates in [potential_natives] for the annotation
+  // constant.  If the annotation is found, flag the [Function] as native and
+  // attach the native name to it.
+  Function& function = Function::Handle(Z);
+  for (intptr_t i = 0; i < length; ++i) {
+    function ^= potential_natives_.At(i);
+    helper_.SetOffset(function.KernelDataProgramOffset() +
+                      function.kernel_offset());
+    {
+      ProcedureHelper procedure_helper(&helper_);
+      procedure_helper.ReadUntilExcluding(ProcedureHelper::kAnnotations);
     }
 
-    // Clear out the list of [Function] objects which might need their native
-    // name to be set after reading the constant table from the kernel blob.
-    potential_natives_ = GrowableObjectArray::null();
-    kernel_program_info_.set_potential_natives(potential_natives_);
+    const intptr_t annotation_count = helper_.ReadListLength();
+    for (intptr_t j = 0; j < annotation_count; ++j) {
+      const intptr_t tag = helper_.PeekTag();
+      if (tag == kConstantExpression || tag == kDeprecated_ConstantExpression) {
+        helper_.ReadByte();  // Skip the tag.
+
+        // We have a candidate.  Let's look if it's an instance of the
+        // ExternalName class.
+        if (tag == kConstantExpression) {
+          helper_.ReadPosition();  // Skip fileOffset.
+          helper_.SkipDartType();  // Skip type.
+        }
+        const intptr_t constant_table_offset = helper_.ReadUInt();
+        constant = constant_evaluator.EvaluateConstantExpression(
+            constant_table_offset);
+        if (constant.clazz() == external_name_class_.raw()) {
+          // We found the annotation, let's flag the function as native and
+          // set the native name!
+          native_name ^= constant.GetField(external_name_field_);
+          function.set_is_native(true);
+          function.set_native_name(native_name);
+          function.set_is_external(false);
+          break;
+        }
+      } else {
+        helper_.SkipExpression();
+      }
+    }
   }
-  ASSERT(constant_table.Release().raw() == constant_table_array.raw());
+
+  // Clear out the list of [Function] objects which might need their native
+  // name to be set after reading the constant table from the kernel blob.
+  potential_natives_ = GrowableObjectArray::null();
+  kernel_program_info_.set_potential_natives(potential_natives_);
 }
 
 RawString* KernelLoader::DetectExternalNameCtor() {
@@ -638,14 +617,15 @@
   return IsClassName(annotation_class, Symbols::DartCore(), Symbols::Pragma());
 }
 
-void KernelLoader::LoadNativeExtensionLibraries(
-    const Array& constant_table_array) {
+void KernelLoader::LoadNativeExtensionLibraries() {
   const intptr_t length = !potential_extension_libraries_.IsNull()
                               ? potential_extension_libraries_.Length()
                               : 0;
   if (length == 0) return;
 
-  KernelConstantsMap constant_table(constant_table_array.raw());
+  // Prepare lazy constant reading.
+  ConstantEvaluator constant_evaluator(&helper_, &type_translator_,
+                                       &active_class_);
 
   // Obtain `dart:_internal::ExternalName.name`.
   EnsureExternalClassIsLookedUp();
@@ -677,8 +657,9 @@
           helper_.ReadPosition();  // Skip fileOffset.
           helper_.SkipDartType();  // Skip type.
         }
-        const intptr_t constant_table_index = helper_.ReadUInt();
-        constant ^= constant_table.GetOrDie(constant_table_index);
+        const intptr_t constant_table_offset = helper_.ReadUInt();
+        constant = constant_evaluator.EvaluateConstantExpression(
+            constant_table_offset);
         if (constant.clazz() == external_name_class_.raw()) {
           uri_path ^= constant.GetField(external_name_field_);
         }
@@ -716,7 +697,6 @@
     }
   }
   potential_extension_libraries_ = GrowableObjectArray::null();
-  ASSERT(constant_table.Release().raw() == constant_table_array.raw());
 }
 
 RawObject* KernelLoader::LoadProgram(bool process_pending_classes) {
@@ -743,6 +723,7 @@
       }
     }
 
+    // Finalize still pending classes if requested.
     if (process_pending_classes) {
       if (!ClassFinalizer::ProcessPendingClasses()) {
         // Class finalization failed -> sticky error would be set.
@@ -750,44 +731,17 @@
       }
     }
 
-    // Set pending fields array to flag constant table loading.
-    ASSERT(I->object_store()->pending_unevaluated_const_fields() ==
-           GrowableObjectArray::null());
-    GrowableObjectArray& pending_unevaluated_const_fields =
-        GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
-    I->object_store()->set_pending_unevaluated_const_fields(
-        pending_unevaluated_const_fields);
-
-    // All classes were successfully loaded, so let's:
-    //     a) load & canonicalize the constant table
-    const Array& constants = ReadConstantTable();
-
-    //     b) set the native names for native functions which have been created
-    //        so far (the rest will be directly set during LoadProcedure)
-    AnnotateNativeProcedures(constants);
-    LoadNativeExtensionLibraries(constants);
-
-    //     c) update all scripts with the constants array
-    ASSERT(kernel_program_info_.constants() == Array::null());
-    kernel_program_info_.set_constants(constants);
-    kernel_program_info_.set_constants_table(ExternalTypedData::Handle(Z));
-
-    //     d) evaluate pending field initializers
-    Error& error = Error::Handle(Z);
-    Field& field = Field::Handle(Z);
-    for (intptr_t i = 0, n = pending_unevaluated_const_fields.Length(); i < n;
-         i++) {
-      field ^= pending_unevaluated_const_fields.At(i);
-      error = field.Initialize();
-      if (!error.IsNull()) {
-        H.ReportError(error, "postponed field initializer");
-      }
-    }
-    pending_unevaluated_const_fields = GrowableObjectArray::null();
-    I->object_store()->set_pending_unevaluated_const_fields(
-        pending_unevaluated_const_fields);
-
-    //     e) evaluate pragmas that were delayed
+    // Sets the constants array to an empty hash and leaves the constant
+    // table's raw bytes in place for lazy reading. We can fix up all
+    // "pending" processing now, and must ensure we don't create new
+    // ones from this point on.
+    ASSERT(kernel_program_info_.constants_table() != ExternalTypedData::null());
+    const Array& array =
+        Array::Handle(Z, HashTables::New<KernelConstantsMap>(16, Heap::kOld));
+    kernel_program_info_.set_constants(array);
+    H.SetConstants(array);  // for caching
+    AnnotateNativeProcedures();
+    LoadNativeExtensionLibraries();
     EvaluateDelayedPragmas();
 
     NameIndex main = program_->main_method();
@@ -1067,6 +1021,8 @@
       helper_.ReaderOffset() - correction_offset_;
   intptr_t annotation_count = helper_.ReadListLength();  // read list length.
   if (annotation_count > 0) {
+    // This must wait until we can evaluate constants.
+    // So put on the "pending" list.
     EnsurePotentialExtensionLibraries();
     potential_extension_libraries_.Add(library);
   }
@@ -1148,7 +1104,6 @@
   if (toplevel_class.is_loaded()) {
     return;
   }
-
   TIMELINE_DURATION(Thread::Current(), Isolate, "FinishTopLevelClassLoading");
 
   ActiveClassScope active_class_scope(&active_class_, &toplevel_class);
@@ -1190,7 +1145,7 @@
     {
       String& native_name_unused = String::Handle();
       bool is_potential_native_unused;
-      ReadVMAnnotations(annotation_count, &native_name_unused,
+      ReadVMAnnotations(library, annotation_count, &native_name_unused,
                         &is_potential_native_unused, &has_pragma_annotation);
     }
     field_helper.SetJustRead(FieldHelper::kAnnotations);
@@ -1451,7 +1406,7 @@
   {
     String& native_name_unused = String::Handle(Z);
     bool is_potential_native_unused = false;
-    ReadVMAnnotations(annotation_count, &native_name_unused,
+    ReadVMAnnotations(library, annotation_count, &native_name_unused,
                       &is_potential_native_unused, &has_pragma_annotation);
   }
   if (has_pragma_annotation) {
@@ -1545,7 +1500,7 @@
       {
         String& native_name_unused = String::Handle();
         bool is_potential_native_unused;
-        ReadVMAnnotations(annotation_count, &native_name_unused,
+        ReadVMAnnotations(library, annotation_count, &native_name_unused,
                           &is_potential_native_unused, &has_pragma_annotation);
       }
       field_helper.SetJustRead(FieldHelper::kAnnotations);
@@ -1618,7 +1573,7 @@
     {
       String& native_name_unused = String::Handle();
       bool is_potential_native_unused;
-      ReadVMAnnotations(annotation_count, &native_name_unused,
+      ReadVMAnnotations(library, annotation_count, &native_name_unused,
                         &is_potential_native_unused, &has_pragma_annotation);
     }
     constructor_helper.SetJustRead(ConstructorHelper::kAnnotations);
@@ -1760,12 +1715,14 @@
 //   `has_pragma_annotation`: non-null if @pragma(...) was found (no information
 //   is given on the kind of pragma directive).
 //
-void KernelLoader::ReadVMAnnotations(intptr_t annotation_count,
+void KernelLoader::ReadVMAnnotations(const Library& library,
+                                     intptr_t annotation_count,
                                      String* native_name,
                                      bool* is_potential_native,
                                      bool* has_pragma_annotation) {
   *is_potential_native = false;
   *has_pragma_annotation = false;
+  Instance& constant = Instance::Handle(Z);
   String& detected_name = String::Handle(Z);
   for (intptr_t i = 0; i < annotation_count; ++i) {
     const intptr_t tag = helper_.PeekTag();
@@ -1828,7 +1785,13 @@
                           Symbols::DartCore(), Symbols::Pragma());
         }
       } else {
-        KernelConstantsMap constant_table(constant_table_array.raw());
+        // Prepare lazy constant reading.
+        const dart::Class& toplevel_class =
+            Class::Handle(Z, library.toplevel_class());
+        ActiveClassScope active_class_scope(&active_class_, &toplevel_class);
+        ConstantEvaluator constant_evaluator(&helper_, &type_translator_,
+                                             &active_class_);
+
         helper_.ReadByte();  // Skip the tag.
 
         // Obtain `dart:_internal::ExternalName.name`.
@@ -1841,18 +1804,27 @@
           helper_.ReadPosition();  // Skip fileOffset.
           helper_.SkipDartType();  // Skip type.
         }
-        const intptr_t constant_table_index = helper_.ReadUInt();
-        const Object& constant =
-            Object::Handle(constant_table.GetOrDie(constant_table_index));
-        if (constant.clazz() == external_name_class_.raw()) {
-          const Instance& instance =
-              Instance::Handle(Instance::RawCast(constant.raw()));
-          *native_name =
-              String::RawCast(instance.GetField(external_name_field_));
-        } else if (constant.clazz() == pragma_class_.raw()) {
-          *has_pragma_annotation = true;
+        const intptr_t constant_table_offset = helper_.ReadUInt();
+        // A cycle in evaluating the same library instance occurs when we are
+        // trying to finalize a class while evaluation the constant. We break
+        // this cycle by ignoring the second evaluation, since the first
+        // evaluation will take care of inspecting the result.
+        // TODO(ajcbik): avoid cycle detection completely by peeking
+        //               into the constants and proceed only for @pragma
+        //               or @ExternalName
+        if (EnqueueLibraryForEvaluation(library)) {
+          constant = constant_evaluator.EvaluateConstantExpression(
+              constant_table_offset);
+          DequeueLibraryForEvaluation(library);
+          if (constant.clazz() == external_name_class_.raw()) {
+            const Instance& instance =
+                Instance::Handle(Instance::RawCast(constant.raw()));
+            *native_name =
+                String::RawCast(instance.GetField(external_name_field_));
+          } else if (constant.clazz() == pragma_class_.raw()) {
+            *has_pragma_annotation = true;
+          }
         }
-        ASSERT(constant_table.Release().raw() == constant_table_array.raw());
       }
     } else {
       helper_.SkipExpression();
@@ -1881,8 +1853,8 @@
   bool is_potential_native;
   bool has_pragma_annotation;
   const intptr_t annotation_count = helper_.ReadListLength();
-  ReadVMAnnotations(annotation_count, &native_name, &is_potential_native,
-                    &has_pragma_annotation);
+  ReadVMAnnotations(library, annotation_count, &native_name,
+                    &is_potential_native, &has_pragma_annotation);
   // If this is a potential native, we'll unset is_external in
   // AnnotateNativeProcedures instead.
   is_external = is_external && native_name.IsNull();
@@ -1959,6 +1931,7 @@
     function.set_native_name(native_name);
   }
   if (is_potential_native) {
+    // Cannot be processed right now, so put on "pending" list.
     EnsurePotentialNatives();
     potential_natives_.Add(function);
   }
@@ -1979,6 +1952,8 @@
 
   if (has_pragma_annotation) {
     if (kernel_program_info_.constants() == Array::null()) {
+      // Any potential pragma function before point at which
+      // constant table could be loaded goes to "pending".
       EnsurePotentialPragmaFunctions();
       potential_pragma_functions_.Add(function);
     } else {
diff --git a/runtime/vm/kernel_loader.h b/runtime/vm/kernel_loader.h
index f6d7665..5eb7442 100644
--- a/runtime/vm/kernel_loader.h
+++ b/runtime/vm/kernel_loader.h
@@ -230,8 +230,6 @@
 
   void ReadObfuscationProhibitions();
 
-  const Array& ReadConstantTable();
-
   // Check for the presence of a (possibly const) constructor for the
   // 'ExternalName' class. If found, returns the name parameter to the
   // constructor.
@@ -243,11 +241,12 @@
 
   bool IsClassName(NameIndex name, const String& library, const String& klass);
 
-  void AnnotateNativeProcedures(const Array& constant_table);
-  void LoadNativeExtensionLibraries(const Array& constant_table);
+  void AnnotateNativeProcedures();
+  void LoadNativeExtensionLibraries();
   void EvaluateDelayedPragmas();
 
-  void ReadVMAnnotations(intptr_t annotation_count,
+  void ReadVMAnnotations(const Library& library,
+                         intptr_t annotation_count,
                          String* native_name,
                          bool* is_potential_native,
                          bool* has_pragma_annotation);
@@ -396,6 +395,37 @@
     }
   }
 
+  // Returns `true` if the [library] was newly enqueued or `false`
+  // if it was already enqueued. Allocates storage on first enqueue.
+  bool EnqueueLibraryForEvaluation(const Library& library) {
+    evaluating_ = kernel_program_info_.evaluating();
+    if (evaluating_.IsNull()) {
+      evaluating_ = GrowableObjectArray::New();
+      kernel_program_info_.set_evaluating(evaluating_);
+      ASSERT(!evaluating_.IsNull());
+    } else {
+      for (intptr_t i = 0, n = evaluating_.Length(); i < n; i++) {
+        if (library.raw() == evaluating_.At(i)) {
+          return false;
+        }
+      }
+    }
+    evaluating_.Add(library);
+    return true;
+  }
+
+  // Dequeues most recent libary. Releases storage when empty.
+  void DequeueLibraryForEvaluation(const Library& library) {
+    ASSERT(!evaluating_.IsNull());
+    RawObject* object = evaluating_.RemoveLast();
+    ASSERT(library.raw() == object);
+    if (evaluating_.Length() == 0) {
+      evaluating_ = GrowableObjectArray::null();
+      kernel_program_info_.set_evaluating(evaluating_);
+      ASSERT(evaluating_.IsNull());
+    }
+  }
+
   Program* program_;
 
   Thread* thread_;
@@ -423,6 +453,7 @@
 
   Class& external_name_class_;
   Field& external_name_field_;
+  GrowableObjectArray& evaluating_;
   GrowableObjectArray& potential_natives_;
   GrowableObjectArray& potential_pragma_functions_;
   GrowableObjectArray& potential_extension_libraries_;
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index e98d24c..adba2b0 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -12149,6 +12149,11 @@
   StorePointer(&raw_ptr()->constants_table_, value.raw());
 }
 
+void KernelProgramInfo::set_evaluating(
+    const GrowableObjectArray& evaluating) const {
+  StorePointer(&raw_ptr()->evaluating_, evaluating.raw());
+}
+
 void KernelProgramInfo::set_potential_natives(
     const GrowableObjectArray& candidates) const {
   StorePointer(&raw_ptr()->potential_natives_, candidates.raw());
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index bbfa0f7..58f52b3 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -4402,6 +4402,10 @@
   RawArray* constants() const { return raw_ptr()->constants_; }
   void set_constants(const Array& constants) const;
 
+  // Records libraries under evaluation to break evaluation cycles.
+  RawGrowableObjectArray* evaluating() const { return raw_ptr()->evaluating_; }
+  void set_evaluating(const GrowableObjectArray& evaluating) const;
+
   // If we load a kernel blob with evaluated constants, then we delay setting
   // the native names of [Function] objects until we've read the constant table
   // (since native names are encoded as constants).
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 88dfaf9..a13c5ba6 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -1258,6 +1258,7 @@
   RawArray* bytecode_component_;
   RawGrowableObjectArray* potential_natives_;
   RawGrowableObjectArray* potential_pragma_functions_;
+  RawGrowableObjectArray* evaluating_;  // detects cycles
   RawExternalTypedData* constants_table_;
   RawArray* libraries_cache_;
   RawArray* classes_cache_;
diff --git a/runtime/vm/raw_object_fields.cc b/runtime/vm/raw_object_fields.cc
index 35d4fdc..c3a3410 100644
--- a/runtime/vm/raw_object_fields.cc
+++ b/runtime/vm/raw_object_fields.cc
@@ -93,6 +93,7 @@
   F(KernelProgramInfo, bytecode_component_)                                    \
   F(KernelProgramInfo, potential_natives_)                                     \
   F(KernelProgramInfo, potential_pragma_functions_)                            \
+  F(KernelProgramInfo, evaluating_)                                            \
   F(KernelProgramInfo, constants_table_)                                       \
   F(KernelProgramInfo, libraries_cache_)                                       \
   F(KernelProgramInfo, classes_cache_)                                         \