[vm] Avoid calling runtime when lazily initializing late static fields.

This CL changes the handling of late static fields to be similar to the
handling of late instance fields. That is, if the field does not need a
load guard and has an initializer, the new InitLateStaticField and
InitLateFinalStaticField stubs are used, which directly call the
initializer function instead of going to the runtime. Thus, the only
runtime call left in these cases is when a late final static field is
modified by the initializer function, in which case an appropiate
exception is thrown.

In the case where a late static field might call the initializer
function but no initializer exists, a LateInitializationErrorSlowPath is
generated to be called if the field value is the sentinel, also similar
to the late instance field case.

TEST=Refactoring, so existing tests.

Change-Id: I95887022d8ece1164a8f7c34b77d9de8c53aa898
Bug: https://github.com/dart-lang/sdk/issues/45138
Cq-Include-Trybots: luci.dart.try:vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-linux-release-ia32-try,vm-kernel-linux-release-x64-try,vm-kernel-nnbd-linux-release-ia32-try,vm-kernel-nnbd-linux-release-simarm-try,vm-kernel-nnbd-linux-release-simarm64-try,vm-kernel-nnbd-linux-release-x64-try,vm-kernel-precomp-linux-release-simarm-try,vm-kernel-precomp-linux-release-simarm64-try,vm-kernel-precomp-linux-release-simarm_x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-nnbd-linux-release-simarm64-try,vm-kernel-precomp-nnbd-linux-release-simarm_x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/220009
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/vm/compiler/assembler/assembler_arm.cc b/runtime/vm/compiler/assembler/assembler_arm.cc
index 79de554..eee0fb3 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm.cc
@@ -3747,6 +3747,18 @@
   }
 }
 
+void Assembler::LoadStaticFieldAddress(Register address,
+                                       Register field,
+                                       Register scratch) {
+  LoadCompressedFieldFromOffset(
+      scratch, field, target::Field::host_offset_or_field_id_offset());
+  const intptr_t field_table_offset =
+      compiler::target::Thread::field_table_values_offset();
+  LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
+  add(address, address,
+      Operand(scratch, LSL, target::kWordSizeLog2 - kSmiTagShift));
+}
+
 void Assembler::LoadFieldAddressForRegOffset(Register address,
                                              Register instance,
                                              Register offset_in_words_as_smi) {
diff --git a/runtime/vm/compiler/assembler/assembler_arm.h b/runtime/vm/compiler/assembler/assembler_arm.h
index e98eef8..457b1da 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.h
+++ b/runtime/vm/compiler/assembler/assembler_arm.h
@@ -1346,6 +1346,10 @@
                                      Register array,
                                      Register index);
 
+  void LoadStaticFieldAddress(Register address,
+                              Register field,
+                              Register scratch);
+
   void LoadCompressedFieldAddressForRegOffset(Register address,
                                               Register instance,
                                               Register offset_in_words_as_smi) {
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.cc b/runtime/vm/compiler/assembler/assembler_arm64.cc
index b844404..3bfb9a8 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64.cc
@@ -2263,6 +2263,18 @@
   }
 }
 
+void Assembler::LoadStaticFieldAddress(Register address,
+                                       Register field,
+                                       Register scratch) {
+  LoadCompressedSmiFieldFromOffset(
+      scratch, field, target::Field::host_offset_or_field_id_offset());
+  const intptr_t field_table_offset =
+      compiler::target::Thread::field_table_values_offset();
+  LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
+  add(address, address,
+      Operand(scratch, LSL, target::kWordSizeLog2 - kSmiTagShift));
+}
+
 void Assembler::LoadCompressedFieldAddressForRegOffset(
     Register address,
     Register instance,
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index d2432b5..886ab74 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -2212,6 +2212,10 @@
                                         Register array,
                                         Register index);
 
+  void LoadStaticFieldAddress(Register address,
+                              Register field,
+                              Register scratch);
+
   void LoadCompressedFieldAddressForRegOffset(Register address,
                                               Register instance,
                                               Register offset_in_words_as_smi);
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.h b/runtime/vm/compiler/assembler/assembler_ia32.h
index 6bd5f28..e5b2b1a 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.h
+++ b/runtime/vm/compiler/assembler/assembler_ia32.h
@@ -882,6 +882,18 @@
                                            Register index,
                                            intptr_t extra_disp = 0);
 
+  void LoadStaticFieldAddress(Register address,
+                              Register field,
+                              Register scratch) {
+    LoadCompressedFieldFromOffset(
+        scratch, field, target::Field::host_offset_or_field_id_offset());
+    const intptr_t field_table_offset =
+        compiler::target::Thread::field_table_values_offset();
+    LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
+    static_assert(kSmiTagShift == 1, "adjust scale factor");
+    leal(address, Address(address, scratch, TIMES_HALF_WORD_SIZE, 0));
+  }
+
   void LoadCompressedFieldAddressForRegOffset(Register address,
                                               Register instance,
                                               Register offset_in_words_as_smi) {
diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h
index 237bd61..5312859 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.h
+++ b/runtime/vm/compiler/assembler/assembler_x64.h
@@ -1228,6 +1228,19 @@
                                            Register array,
                                            Register index);
 
+  void LoadStaticFieldAddress(Register address,
+                              Register field,
+                              Register scratch) {
+    LoadCompressedSmi(
+        scratch, compiler::FieldAddress(
+                     field, target::Field::host_offset_or_field_id_offset()));
+    const intptr_t field_table_offset =
+        compiler::target::Thread::field_table_values_offset();
+    LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
+    static_assert(kSmiTagShift == 1, "adjust scale factor");
+    leaq(address, Address(address, scratch, TIMES_HALF_WORD_SIZE, 0));
+  }
+
   void LoadFieldAddressForRegOffset(Register address,
                                     Register instance,
                                     Register offset_in_words_as_smi) {
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index e31b958..9e56049 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -3381,9 +3381,7 @@
 
 void LateInitializationErrorSlowPath::PushArgumentsForRuntimeCall(
     FlowGraphCompiler* compiler) {
-  const Field& original_field = Field::ZoneHandle(
-      instruction()->AsLoadField()->slot().field().Original());
-  __ PushObject(original_field);
+  __ PushObject(Field::ZoneHandle(OriginalField()));
 }
 
 void LateInitializationErrorSlowPath::EmitSharedStubCall(
@@ -3394,9 +3392,8 @@
 #else
   ASSERT(instruction()->locs()->temp(0).reg() ==
          LateInitializationErrorABI::kFieldReg);
-  const Field& original_field = Field::ZoneHandle(
-      instruction()->AsLoadField()->slot().field().Original());
-  __ LoadObject(LateInitializationErrorABI::kFieldReg, original_field);
+  __ LoadObject(LateInitializationErrorABI::kFieldReg,
+                Field::ZoneHandle(OriginalField()));
   auto object_store = compiler->isolate_group()->object_store();
   const auto& stub = Code::ZoneHandle(
       compiler->zone(),
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index e195a18..7bf8100 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -388,9 +388,11 @@
 
 class LateInitializationErrorSlowPath : public ThrowErrorSlowPathCode {
  public:
-  explicit LateInitializationErrorSlowPath(LoadFieldInstr* instruction)
+  explicit LateInitializationErrorSlowPath(Instruction* instruction)
       : ThrowErrorSlowPathCode(instruction,
-                               kLateFieldNotInitializedErrorRuntimeEntry) {}
+                               kLateFieldNotInitializedErrorRuntimeEntry) {
+    ASSERT(instruction->IsLoadField() || instruction->IsLoadStaticField());
+  }
   virtual const char* name() { return "late initialization error"; }
 
   virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
@@ -401,6 +403,13 @@
 
   virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
                                   bool save_fpu_registers);
+
+ private:
+  FieldPtr OriginalField() const {
+    return instruction()->IsLoadField()
+               ? instruction()->AsLoadField()->slot().field().Original()
+               : instruction()->AsLoadStaticField()->field().Original();
+  }
 };
 
 class FlowGraphCompiler : public ValueObject {
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 4ca03ed..c3683d0 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -4140,10 +4140,25 @@
 LocationSummary* LoadStaticFieldInstr::MakeLocationSummary(Zone* zone,
                                                            bool opt) const {
   const intptr_t kNumInputs = 0;
-  const intptr_t kNumTemps = 0;
+  const bool use_shared_stub = UseSharedSlowPathStub(opt);
+  const intptr_t kNumTemps = calls_initializer() &&
+                                     throw_exception_on_initialization() &&
+                                     use_shared_stub
+                                 ? 1
+                                 : 0;
   LocationSummary* locs = new (zone) LocationSummary(
       zone, kNumInputs, kNumTemps,
-      calls_initializer() ? LocationSummary::kCall : LocationSummary::kNoCall);
+      calls_initializer()
+          ? (throw_exception_on_initialization()
+                 ? (use_shared_stub ? LocationSummary::kCallOnSharedSlowPath
+                                    : LocationSummary::kCallOnSlowPath)
+                 : LocationSummary::kCall)
+          : LocationSummary::kNoCall);
+  if (calls_initializer() && throw_exception_on_initialization() &&
+      use_shared_stub) {
+    locs->set_temp(
+        0, Location::RegisterLocation(LateInitializationErrorABI::kFieldReg));
+  }
   locs->set_out(0, calls_initializer() ? Location::RegisterLocation(
                                              InitStaticFieldABI::kResultReg)
                                        : Location::RequiresRegister());
@@ -4164,26 +4179,50 @@
   __ LoadMemoryValue(result, result, static_cast<int32_t>(field_offset));
 
   if (calls_initializer()) {
-    compiler::Label call_runtime, no_call;
-    __ CompareObject(result, Object::sentinel());
+    if (throw_exception_on_initialization()) {
+      ThrowErrorSlowPathCode* slow_path =
+          new LateInitializationErrorSlowPath(this);
+      compiler->AddSlowPathCode(slow_path);
 
+      __ CompareObject(result, Object::sentinel());
+      __ BranchIf(EQUAL, slow_path->entry_label());
+      return;
+    }
+    ASSERT(field().has_initializer());
+    auto object_store = compiler->isolate_group()->object_store();
+    const Field& original_field = Field::ZoneHandle(field().Original());
+
+    compiler::Label no_call, call_initializer;
+    __ CompareObject(result, Object::sentinel());
     if (!field().is_late()) {
-      __ BranchIf(EQUAL, &call_runtime);
+      __ BranchIf(EQUAL, &call_initializer);
       __ CompareObject(result, Object::transition_sentinel());
     }
-
     __ BranchIf(NOT_EQUAL, &no_call);
 
-    __ Bind(&call_runtime);
-    __ LoadObject(InitStaticFieldABI::kFieldReg,
-                  Field::ZoneHandle(field().Original()));
+    auto& stub = Code::ZoneHandle(compiler->zone());
+    __ Bind(&call_initializer);
+    if (field().needs_load_guard()) {
+      stub = object_store->init_static_field_stub();
+    } else if (field().is_late()) {
+      // The stubs below call the initializer function directly, so make sure
+      // one is created.
+      original_field.EnsureInitializerFunction();
+      stub = field().is_final()
+                 ? object_store->init_late_final_static_field_stub()
+                 : object_store->init_late_static_field_stub();
+    } else {
+      // We call to runtime for non-late fields because the stub would need to
+      // catch any exception generated by the initialization function to change
+      // the value of the static field from the transition sentinel to null.
+      stub = object_store->init_static_field_stub();
+    }
 
-    auto object_store = compiler->isolate_group()->object_store();
-    const auto& init_static_field_stub = Code::ZoneHandle(
-        compiler->zone(), object_store->init_static_field_stub());
-    compiler->GenerateStubCall(source(), init_static_field_stub,
+    __ LoadObject(InitStaticFieldABI::kFieldReg, original_field);
+    compiler->GenerateStubCall(source(), stub,
                                /*kind=*/UntaggedPcDescriptors::kOther, locs(),
                                deopt_id(), env());
+
     __ Bind(&no_call);
   }
 }
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 3c8bb1f..3ef43cc 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -5585,18 +5585,76 @@
   DISALLOW_COPY_AND_ASSIGN(GuardFieldTypeInstr);
 };
 
-class LoadStaticFieldInstr : public TemplateDefinition<0, Throws> {
+template <intptr_t N>
+class TemplateLoadField : public TemplateDefinition<N, Throws> {
+  using Base = TemplateDefinition<N, Throws>;
+
+ public:
+  TemplateLoadField(const InstructionSource& source,
+                    bool calls_initializer = false,
+                    intptr_t deopt_id = DeoptId::kNone,
+                    const Field* field = nullptr)
+      : Base(source, deopt_id),
+        token_pos_(source.token_pos),
+        calls_initializer_(calls_initializer),
+        throw_exception_on_initialization_(false) {
+    ASSERT(!calls_initializer || (deopt_id != DeoptId::kNone));
+    if (calls_initializer_) {
+      ASSERT(field != nullptr);
+      throw_exception_on_initialization_ = !field->needs_load_guard() &&
+                                           field->is_late() &&
+                                           !field->has_initializer();
+    }
+  }
+
+  virtual TokenPosition token_pos() const { return token_pos_; }
+  bool calls_initializer() const { return calls_initializer_; }
+  void set_calls_initializer(bool value) { calls_initializer_ = value; }
+
+  bool throw_exception_on_initialization() const {
+    return throw_exception_on_initialization_;
+  }
+
+  // Slow path is used if load throws exception on initialization.
+  virtual bool UseSharedSlowPathStub(bool is_optimizing) const {
+    return Base::SlowPathSharingSupported(is_optimizing);
+  }
+
+  virtual intptr_t DeoptimizationTarget() const { return Base::GetDeoptId(); }
+  virtual bool ComputeCanDeoptimize() const { return false; }
+  virtual bool ComputeCanDeoptimizeAfterCall() const {
+    return calls_initializer() && !CompilerState::Current().is_aot();
+  }
+  virtual intptr_t NumberOfInputsConsumedBeforeCall() const {
+    return Base::InputCount();
+  }
+
+  virtual bool HasUnknownSideEffects() const {
+    return calls_initializer() && !throw_exception_on_initialization();
+  }
+
+  virtual bool CanCallDart() const {
+    return calls_initializer() && !throw_exception_on_initialization();
+  }
+  virtual bool CanTriggerGC() const { return calls_initializer(); }
+  virtual bool MayThrow() const { return calls_initializer(); }
+
+ private:
+  const TokenPosition token_pos_;
+  bool calls_initializer_;
+  bool throw_exception_on_initialization_;
+
+  DISALLOW_COPY_AND_ASSIGN(TemplateLoadField);
+};
+
+class LoadStaticFieldInstr : public TemplateLoadField<0> {
  public:
   LoadStaticFieldInstr(const Field& field,
                        const InstructionSource& source,
                        bool calls_initializer = false,
                        intptr_t deopt_id = DeoptId::kNone)
-      : TemplateDefinition(source, deopt_id),
-        field_(field),
-        token_pos_(source.token_pos),
-        calls_initializer_(calls_initializer) {
-    ASSERT(!calls_initializer || (deopt_id != DeoptId::kNone));
-  }
+      : TemplateLoadField<0>(source, calls_initializer, deopt_id, &field),
+        field_(field) {}
 
   DECLARE_INSTRUCTION(LoadStaticField)
 
@@ -5604,9 +5662,6 @@
 
   const Field& field() const { return field_; }
 
-  bool calls_initializer() const { return calls_initializer_; }
-  void set_calls_initializer(bool value) { calls_initializer_ = value; }
-
   virtual bool AllowsCSE() const {
     // If two loads of a static-final-late field call the initializer and one
     // dominates another, we can remove the dominated load with the result of
@@ -5619,23 +5674,12 @@
            (!field().is_late() || field().has_initializer());
   }
 
-  virtual bool ComputeCanDeoptimize() const {
-    return calls_initializer() && !CompilerState::Current().is_aot();
-  }
-  virtual bool HasUnknownSideEffects() const { return calls_initializer(); }
-  virtual bool CanTriggerGC() const { return calls_initializer(); }
-  virtual bool MayThrow() const { return calls_initializer(); }
-
   virtual bool AttributesEqual(const Instruction& other) const;
 
-  virtual TokenPosition token_pos() const { return token_pos_; }
-
   PRINT_OPERANDS_TO_SUPPORT
 
  private:
   const Field& field_;
-  const TokenPosition token_pos_;
-  bool calls_initializer_;
 
   DISALLOW_COPY_AND_ASSIGN(LoadStaticFieldInstr);
 };
@@ -6648,46 +6692,24 @@
 // Note: if slot was a subject of the field unboxing optimization then this load
 // would both load the box stored in the field and then load the content of
 // the box.
-class LoadFieldInstr : public TemplateDefinition<1, Throws> {
+class LoadFieldInstr : public TemplateLoadField<1> {
  public:
   LoadFieldInstr(Value* instance,
                  const Slot& slot,
                  const InstructionSource& source,
                  bool calls_initializer = false,
                  intptr_t deopt_id = DeoptId::kNone)
-      : TemplateDefinition(source, deopt_id),
-        slot_(slot),
-        token_pos_(source.token_pos),
-        calls_initializer_(calls_initializer),
-        throw_exception_on_initialization_(false) {
-    ASSERT(!calls_initializer || (deopt_id != DeoptId::kNone));
-    ASSERT(!calls_initializer || slot.IsDartField());
+      : TemplateLoadField(source,
+                          calls_initializer,
+                          deopt_id,
+                          slot.IsDartField() ? &slot.field() : nullptr),
+        slot_(slot) {
     SetInputAt(0, instance);
-    if (calls_initializer_) {
-      const Field& field = slot.field();
-      throw_exception_on_initialization_ = !field.needs_load_guard() &&
-                                           field.is_late() &&
-                                           !field.has_initializer();
-    }
   }
 
   Value* instance() const { return inputs_[0]; }
   const Slot& slot() const { return slot_; }
 
-  virtual TokenPosition token_pos() const { return token_pos_; }
-
-  bool calls_initializer() const { return calls_initializer_; }
-  void set_calls_initializer(bool value) { calls_initializer_ = value; }
-
-  bool throw_exception_on_initialization() const {
-    return throw_exception_on_initialization_;
-  }
-
-  // Slow path is used if load throws exception on initialization.
-  virtual bool UseSharedSlowPathStub(bool is_optimizing) const {
-    return SlowPathSharingSupported(is_optimizing);
-  }
-
   virtual Representation representation() const;
 
   // Returns whether this instruction is an unboxed load from a _boxed_ Dart
@@ -6704,25 +6726,6 @@
 
   virtual CompileType ComputeType() const;
 
-  virtual intptr_t DeoptimizationTarget() const { return GetDeoptId(); }
-  virtual bool ComputeCanDeoptimize() const { return false; }
-  virtual bool ComputeCanDeoptimizeAfterCall() const {
-    return calls_initializer() && !CompilerState::Current().is_aot();
-  }
-  virtual intptr_t NumberOfInputsConsumedBeforeCall() const {
-    return InputCount();
-  }
-
-  virtual bool HasUnknownSideEffects() const {
-    return calls_initializer() && !throw_exception_on_initialization();
-  }
-
-  virtual bool CanCallDart() const {
-    return calls_initializer() && !throw_exception_on_initialization();
-  }
-  virtual bool CanTriggerGC() const { return calls_initializer(); }
-  virtual bool MayThrow() const { return calls_initializer(); }
-
   virtual void InferRange(RangeAnalysis* analysis, Range* range);
 
   bool IsImmutableLengthLoad() const { return slot().IsImmutableLengthSlot(); }
@@ -6761,9 +6764,6 @@
   void EmitNativeCodeForInitializerCall(FlowGraphCompiler* compiler);
 
   const Slot& slot_;
-  const TokenPosition token_pos_;
-  bool calls_initializer_;
-  bool throw_exception_on_initialization_;
 
   DISALLOW_COPY_AND_ASSIGN(LoadFieldInstr);
 };
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index 672ee76..a4c8a57 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -48,6 +48,68 @@
   __ Ret();
 }
 
+void StubCodeCompiler::GenerateInitLateStaticFieldStub(Assembler* assembler,
+                                                       bool is_final) {
+  const Register kResultReg = InitStaticFieldABI::kResultReg;
+  const Register kFunctionReg = InitLateStaticFieldInternalRegs::kFunctionReg;
+  const Register kFieldReg = InitStaticFieldABI::kFieldReg;
+  const Register kAddressReg = InitLateStaticFieldInternalRegs::kAddressReg;
+  const Register kScratchReg = InitLateStaticFieldInternalRegs::kScratchReg;
+
+  __ EnterStubFrame();
+
+  __ Comment("Calling initializer function");
+  __ PushRegister(kFieldReg);
+  __ LoadCompressedFieldFromOffset(
+      kFunctionReg, InitInstanceFieldABI::kFieldReg,
+      target::Field::initializer_function_offset());
+  if (!FLAG_precompiled_mode) {
+    __ LoadCompressedFieldFromOffset(CODE_REG, kFunctionReg,
+                                     target::Function::code_offset());
+    // Load a GC-safe value for the arguments descriptor (unused but tagged).
+    __ LoadImmediate(ARGS_DESC_REG, 0);
+  }
+  __ Call(FieldAddress(kFunctionReg, target::Function::entry_point_offset()));
+  __ MoveRegister(kResultReg, CallingConventions::kReturnReg);
+  __ PopRegister(kFieldReg);
+  __ LoadStaticFieldAddress(kAddressReg, kFieldReg, kScratchReg);
+
+  Label throw_exception;
+  if (is_final) {
+    __ Comment("Checking that initializer did not set late final field");
+    __ LoadFromOffset(kScratchReg, kAddressReg, 0);
+    __ CompareObject(kScratchReg, SentinelObject());
+    __ BranchIf(NOT_EQUAL, &throw_exception);
+  }
+
+  __ StoreToOffset(kResultReg, kAddressReg, 0);
+  __ LeaveStubFrame();
+  __ Ret();
+
+  if (is_final) {
+#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+    // We are jumping over LeaveStubFrame so restore LR state to match one
+    // at the jump point.
+    __ set_lr_state(compiler::LRState::OnEntry().EnterFrame());
+#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
+    __ Bind(&throw_exception);
+    __ PushObject(NullObject());  // Make room for (unused) result.
+    __ PushRegister(kFieldReg);
+    __ CallRuntime(kLateFieldAssignedDuringInitializationErrorRuntimeEntry,
+                   /*argument_count=*/1);
+    __ Breakpoint();
+  }
+}
+
+void StubCodeCompiler::GenerateInitLateStaticFieldStub(Assembler* assembler) {
+  GenerateInitLateStaticFieldStub(assembler, /*is_final=*/false);
+}
+
+void StubCodeCompiler::GenerateInitLateFinalStaticFieldStub(
+    Assembler* assembler) {
+  GenerateInitLateStaticFieldStub(assembler, /*is_final=*/true);
+}
+
 void StubCodeCompiler::GenerateInitInstanceFieldStub(Assembler* assembler) {
   __ EnterStubFrame();
   __ PushObject(NullObject());  // Make room for result.
diff --git a/runtime/vm/compiler/stub_code_compiler.h b/runtime/vm/compiler/stub_code_compiler.h
index 58f52ce..fc28901 100644
--- a/runtime/vm/compiler/stub_code_compiler.h
+++ b/runtime/vm/compiler/stub_code_compiler.h
@@ -151,6 +151,11 @@
   static intptr_t WordOffsetFromFpToCpuRegister(Register cpu_register);
 
  private:
+  // Common function for generating InitLateStaticField and
+  // InitLateFinalStaticField stubs.
+  static void GenerateInitLateStaticFieldStub(Assembler* assembler,
+                                              bool is_final);
+
   // Common function for generating InitLateInstanceField and
   // InitLateFinalInstanceField stubs.
   static void GenerateInitLateInstanceFieldStub(Assembler* assembler,
diff --git a/runtime/vm/constants_arm.h b/runtime/vm/constants_arm.h
index 6adbe81..c774203 100644
--- a/runtime/vm/constants_arm.h
+++ b/runtime/vm/constants_arm.h
@@ -412,10 +412,17 @@
 
 // ABI for InitStaticFieldStub.
 struct InitStaticFieldABI {
-  static const Register kFieldReg = R0;
+  static const Register kFieldReg = R2;
   static const Register kResultReg = R0;
 };
 
+// Registers used inside the implementation of InitLateStaticFieldStub.
+struct InitLateStaticFieldInternalRegs {
+  static const Register kFunctionReg = R0;
+  static const Register kAddressReg = R3;
+  static const Register kScratchReg = R4;
+};
+
 // ABI for InitInstanceFieldStub.
 struct InitInstanceFieldABI {
   static const Register kInstanceReg = R1;
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 28ac726..7947a60 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -251,10 +251,17 @@
 
 // ABI for InitStaticFieldStub.
 struct InitStaticFieldABI {
-  static const Register kFieldReg = R0;
+  static const Register kFieldReg = R2;
   static const Register kResultReg = R0;
 };
 
+// Registers used inside the implementation of InitLateStaticFieldStub.
+struct InitLateStaticFieldInternalRegs {
+  static const Register kFunctionReg = R0;
+  static const Register kAddressReg = R3;
+  static const Register kScratchReg = R4;
+};
+
 // ABI for InitInstanceFieldStub.
 struct InitInstanceFieldABI {
   static const Register kInstanceReg = R1;
diff --git a/runtime/vm/constants_ia32.h b/runtime/vm/constants_ia32.h
index 5f9c331..4a497730 100644
--- a/runtime/vm/constants_ia32.h
+++ b/runtime/vm/constants_ia32.h
@@ -148,10 +148,17 @@
 
 // ABI for InitStaticFieldStub.
 struct InitStaticFieldABI {
-  static const Register kFieldReg = EAX;
+  static const Register kFieldReg = EDX;
   static const Register kResultReg = EAX;
 };
 
+// Registers used inside the implementation of InitLateStaticFieldStub.
+struct InitLateStaticFieldInternalRegs {
+  static const Register kFunctionReg = EAX;
+  static const Register kAddressReg = ECX;
+  static const Register kScratchReg = EDI;
+};
+
 // ABI for InitInstanceFieldStub.
 struct InitInstanceFieldABI {
   static const Register kInstanceReg = EBX;
diff --git a/runtime/vm/constants_x64.h b/runtime/vm/constants_x64.h
index eb24c81..3e7f3c5 100644
--- a/runtime/vm/constants_x64.h
+++ b/runtime/vm/constants_x64.h
@@ -223,10 +223,17 @@
 
 // ABI for InitStaticFieldStub.
 struct InitStaticFieldABI {
-  static const Register kFieldReg = RAX;
+  static const Register kFieldReg = RDX;
   static const Register kResultReg = RAX;
 };
 
+// Registers used inside the implementation of InitLateStaticFieldStub.
+struct InitLateStaticFieldInternalRegs {
+  static const Register kFunctionReg = RAX;
+  static const Register kAddressReg = RCX;
+  static const Register kScratchReg = RSI;
+};
+
 // ABI for InitInstanceFieldStub.
 struct InitInstanceFieldABI {
   static const Register kInstanceReg = RBX;
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 3b308c6..03125b2 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -215,6 +215,8 @@
   RW(Code, assert_boolean_stub)                                                \
   RW(Code, instance_of_stub)                                                   \
   RW(Code, init_static_field_stub)                                             \
+  RW(Code, init_late_static_field_stub)                                        \
+  RW(Code, init_late_final_static_field_stub)                                  \
   RW(Code, init_instance_field_stub)                                           \
   RW(Code, init_late_instance_field_stub)                                      \
   RW(Code, init_late_final_instance_field_stub)                                \
@@ -296,6 +298,8 @@
   DO(re_throw_stub, ReThrow)                                                   \
   DO(assert_boolean_stub, AssertBoolean)                                       \
   DO(init_static_field_stub, InitStaticField)                                  \
+  DO(init_late_static_field_stub, InitLateStaticField)                         \
+  DO(init_late_final_static_field_stub, InitLateFinalStaticField)              \
   DO(init_instance_field_stub, InitInstanceField)                              \
   DO(init_late_instance_field_stub, InitLateInstanceField)                     \
   DO(init_late_final_instance_field_stub, InitLateFinalInstanceField)          \
diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h
index 64b36a9..fa7b06b 100644
--- a/runtime/vm/stub_code_list.h
+++ b/runtime/vm/stub_code_list.h
@@ -127,6 +127,8 @@
   V(ExitSafepoint)                                                             \
   V(CallNativeThroughSafepoint)                                                \
   V(InitStaticField)                                                           \
+  V(InitLateStaticField)                                                       \
+  V(InitLateFinalStaticField)                                                  \
   V(InitInstanceField)                                                         \
   V(InitLateInstanceField)                                                     \
   V(InitLateFinalInstanceField)                                                \