Add a write barrier elimination pass

We can eliminate redundant write barriers as we don't need several
for the same receiver. For example:

```
MyObject o;
o.inner_obj = io;
o.inner_obj2 = io2;
o.inner_obj3 = io3;
```

We can keep the write barrier for `inner_obj` and remove the other
two.

Note that we cannot perform this optimization across
invokes, suspend check, or instructions that can throw.

Local improvements (pixel 5, speed compile):
 * System server: -280KB (-0.56%)
 * SystemUIGoogle: -330KB (-1.16%)
 * AGSA: -3876KB (-1.19%)

Bug: 260843353
Fixes: 260843353
Change-Id: Ibf98efbe891ee00e46125853c3e97ae30aa3ff30
diff --git a/compiler/Android.bp b/compiler/Android.bp
index 117e8dc..cab7fbe 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -91,6 +91,7 @@
         "optimizing/ssa_phi_elimination.cc",
         "optimizing/stack_map_stream.cc",
         "optimizing/superblock_cloner.cc",
+        "optimizing/write_barrier_elimination.cc",
         "trampolines/trampoline_compiler.cc",
         "utils/assembler.cc",
         "utils/jni_macro_assembler.cc",
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 7fb3b24..5a5d36d 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -43,6 +43,7 @@
 #include "mirror/var_handle.h"
 #include "offsets.h"
 #include "optimizing/common_arm64.h"
+#include "optimizing/nodes.h"
 #include "thread.h"
 #include "utils/arm64/assembler_arm64.h"
 #include "utils/assembler.h"
@@ -1426,12 +1427,12 @@
   }
 }
 
-void CodeGeneratorARM64::MarkGCCard(Register object, Register value, bool value_can_be_null) {
+void CodeGeneratorARM64::MarkGCCard(Register object, Register value, bool emit_null_check) {
   UseScratchRegisterScope temps(GetVIXLAssembler());
   Register card = temps.AcquireX();
   Register temp = temps.AcquireW();   // Index within the CardTable - 32bit.
   vixl::aarch64::Label done;
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Cbz(value, &done);
   }
   // Load the address of the card table into `card`.
@@ -1453,7 +1454,7 @@
   // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
   // (no need to explicitly load `kCardDirty` as an immediate value).
   __ Strb(card, MemOperand(card, temp.X()));
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Bind(&done);
   }
 }
@@ -2229,7 +2230,8 @@
 
 void InstructionCodeGeneratorARM64::HandleFieldSet(HInstruction* instruction,
                                                    const FieldInfo& field_info,
-                                                   bool value_can_be_null) {
+                                                   bool value_can_be_null,
+                                                   WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
   bool is_predicated =
       instruction->IsInstanceFieldSet() && instruction->AsInstanceFieldSet()->GetIsPredicatedSet();
@@ -2269,8 +2271,12 @@
     }
   }
 
-  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
-    codegen_->MarkGCCard(obj, Register(value), value_can_be_null);
+  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1)) &&
+      write_barrier_kind != WriteBarrierKind::kDontEmit) {
+    codegen_->MarkGCCard(
+        obj,
+        Register(value),
+        value_can_be_null && write_barrier_kind == WriteBarrierKind::kEmitWithNullCheck);
   }
 
   if (is_predicated) {
@@ -2935,7 +2941,11 @@
       }
     }
 
-    codegen_->MarkGCCard(array, value.W(), /* value_can_be_null= */ false);
+    if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+      DCHECK_EQ(instruction->GetWriteBarrierKind(), WriteBarrierKind::kEmitNoNullCheck)
+          << " Already null checked so we shouldn't do it again.";
+      codegen_->MarkGCCard(array, value.W(), /* emit_null_check= */ false);
+    }
 
     if (can_value_be_null) {
       DCHECK(do_store.IsLinked());
@@ -3957,7 +3967,10 @@
 }
 
 void InstructionCodeGeneratorARM64::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 // Temp is used for read barrier.
@@ -6220,7 +6233,10 @@
 }
 
 void InstructionCodeGeneratorARM64::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderARM64::VisitStringBuilderAppend(HStringBuilderAppend* instruction) {
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index 4342631..deba88b 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -329,7 +329,8 @@
 
   void HandleFieldSet(HInstruction* instruction,
                       const FieldInfo& field_info,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
   void HandleCondition(HCondition* instruction);
 
@@ -617,7 +618,7 @@
   // Emit a write barrier.
   void MarkGCCard(vixl::aarch64::Register object,
                   vixl::aarch64::Register value,
-                  bool value_can_be_null);
+                  bool emit_null_check);
 
   void GenerateMemoryBarrier(MemBarrierKind kind);
 
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 002ca79..51d6a46 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -5803,8 +5803,9 @@
   __ CompareAndBranchIfNonZero(temp1, &fail);
 }
 
-void LocationsBuilderARMVIXL::HandleFieldSet(
-    HInstruction* instruction, const FieldInfo& field_info) {
+void LocationsBuilderARMVIXL::HandleFieldSet(HInstruction* instruction,
+                                             const FieldInfo& field_info,
+                                             WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations =
@@ -5827,8 +5828,12 @@
   // Temporary registers for the write barrier.
   // TODO: consider renaming StoreNeedsWriteBarrier to StoreNeedsGCMark.
   if (needs_write_barrier) {
-    locations->AddTemp(Location::RequiresRegister());  // Possibly used for reference poisoning too.
-    locations->AddTemp(Location::RequiresRegister());
+    if (write_barrier_kind != WriteBarrierKind::kDontEmit) {
+      locations->AddTemp(Location::RequiresRegister());
+      locations->AddTemp(Location::RequiresRegister());
+    } else if (kPoisonHeapReferences) {
+      locations->AddTemp(Location::RequiresRegister());
+    }
   } else if (generate_volatile) {
     // ARM encoding have some additional constraints for ldrexd/strexd:
     // - registers need to be consecutive
@@ -5849,7 +5854,8 @@
 
 void InstructionCodeGeneratorARMVIXL::HandleFieldSet(HInstruction* instruction,
                                                      const FieldInfo& field_info,
-                                                     bool value_can_be_null) {
+                                                     bool value_can_be_null,
+                                                     WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations = instruction->GetLocations();
@@ -5965,10 +5971,16 @@
       UNREACHABLE();
   }
 
-  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
+  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1)) &&
+      write_barrier_kind != WriteBarrierKind::kDontEmit) {
     vixl32::Register temp = RegisterFrom(locations->GetTemp(0));
     vixl32::Register card = RegisterFrom(locations->GetTemp(1));
-    codegen_->MarkGCCard(temp, card, base, RegisterFrom(value), value_can_be_null);
+    codegen_->MarkGCCard(
+        temp,
+        card,
+        base,
+        RegisterFrom(value),
+        value_can_be_null && write_barrier_kind == WriteBarrierKind::kEmitWithNullCheck);
   }
 
   if (is_volatile) {
@@ -6241,11 +6253,14 @@
 }
 
 void LocationsBuilderARMVIXL::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo());
+  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetWriteBarrierKind());
 }
 
 void InstructionCodeGeneratorARMVIXL::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderARMVIXL::VisitInstanceFieldGet(HInstanceFieldGet* instruction) {
@@ -6278,11 +6293,14 @@
 }
 
 void LocationsBuilderARMVIXL::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo());
+  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetWriteBarrierKind());
 }
 
 void InstructionCodeGeneratorARMVIXL::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderARMVIXL::VisitStringBuilderAppend(HStringBuilderAppend* instruction) {
@@ -6764,8 +6782,10 @@
     locations->SetInAt(2, Location::RequiresRegister());
   }
   if (needs_write_barrier) {
-    // Temporary registers for the write barrier.
-    locations->AddTemp(Location::RequiresRegister());  // Possibly used for ref. poisoning too.
+    // Temporary registers for the write barrier or register poisoning.
+    // TODO(solanes): We could reduce the temp usage but it requires some non-trivial refactoring of
+    // InstructionCodeGeneratorARMVIXL::VisitArraySet.
+    locations->AddTemp(Location::RequiresRegister());
     locations->AddTemp(Location::RequiresRegister());
   }
 }
@@ -6917,7 +6937,11 @@
         }
       }
 
-      codegen_->MarkGCCard(temp1, temp2, array, value, /* value_can_be_null= */ false);
+      if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+        DCHECK_EQ(instruction->GetWriteBarrierKind(), WriteBarrierKind::kEmitNoNullCheck)
+            << " Already null checked so we shouldn't do it again.";
+        codegen_->MarkGCCard(temp1, temp2, array, value, /* emit_null_check= */ false);
+      }
 
       if (can_value_be_null) {
         DCHECK(do_store.IsReferenced());
@@ -7148,9 +7172,9 @@
                                       vixl32::Register card,
                                       vixl32::Register object,
                                       vixl32::Register value,
-                                      bool value_can_be_null) {
+                                      bool emit_null_check) {
   vixl32::Label is_null;
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ CompareAndBranchIfZero(value, &is_null, /* is_far_target=*/ false);
   }
   // Load the address of the card table into `card`.
@@ -7173,7 +7197,7 @@
   // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
   // (no need to explicitly load `kCardDirty` as an immediate value).
   __ Strb(card, MemOperand(card, temp));
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Bind(&is_null);
   }
 }
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index 20fb770..872a17b 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -310,7 +310,9 @@
   void HandleIntegerRotate(LocationSummary* locations);
   void HandleLongRotate(LocationSummary* locations);
   void HandleShift(HBinaryOperation* operation);
-  void HandleFieldSet(HInstruction* instruction, const FieldInfo& field_info);
+  void HandleFieldSet(HInstruction* instruction,
+                      const FieldInfo& field_info,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
 
   Location ArithmeticZeroOrFpuRegister(HInstruction* input);
@@ -379,7 +381,8 @@
 
   void HandleFieldSet(HInstruction* instruction,
                       const FieldInfo& field_info,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
 
   void GenerateMinMaxInt(LocationSummary* locations, bool is_min);
@@ -543,7 +546,7 @@
                   vixl::aarch32::Register card,
                   vixl::aarch32::Register object,
                   vixl::aarch32::Register value,
-                  bool value_can_be_null);
+                  bool emit_null_check);
 
   void GenerateMemoryBarrier(MemBarrierKind kind);
 
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 5daa73e..58cb56d 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -35,6 +35,7 @@
 #include "mirror/array-inl.h"
 #include "mirror/class-inl.h"
 #include "mirror/var_handle.h"
+#include "optimizing/nodes.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
 #include "utils/assembler.h"
@@ -5741,13 +5742,10 @@
   DCHECK_EQ(size, linker_patches->size());
 }
 
-void CodeGeneratorX86::MarkGCCard(Register temp,
-                                  Register card,
-                                  Register object,
-                                  Register value,
-                                  bool value_can_be_null) {
+void CodeGeneratorX86::MarkGCCard(
+    Register temp, Register card, Register object, Register value, bool emit_null_check) {
   NearLabel is_null;
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ testl(value, value);
     __ j(kEqual, &is_null);
   }
@@ -5772,7 +5770,7 @@
   // (no need to explicitly load `kCardDirty` as an immediate value).
   __ movb(Address(temp, card, TIMES_1, 0),
           X86ManagedRegister::FromCpuRegister(card).AsByteRegister());
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Bind(&is_null);
   }
 }
@@ -5876,7 +5874,9 @@
   }
 }
 
-void LocationsBuilderX86::HandleFieldSet(HInstruction* instruction, const FieldInfo& field_info) {
+void LocationsBuilderX86::HandleFieldSet(HInstruction* instruction,
+                                         const FieldInfo& field_info,
+                                         WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations =
@@ -5913,10 +5913,13 @@
     locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
 
     if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
-      // Temporary registers for the write barrier.
-      locations->AddTemp(Location::RequiresRegister());  // May be used for reference poisoning too.
-      // Ensure the card is in a byte register.
-      locations->AddTemp(Location::RegisterLocation(ECX));
+      if (write_barrier_kind != WriteBarrierKind::kDontEmit) {
+        locations->AddTemp(Location::RequiresRegister());
+        // Ensure the card is in a byte register.
+        locations->AddTemp(Location::RegisterLocation(ECX));
+      } else if (kPoisonHeapReferences) {
+        locations->AddTemp(Location::RequiresRegister());
+      }
     }
   }
 }
@@ -5927,7 +5930,8 @@
                                                  Address field_addr,
                                                  Register base,
                                                  bool is_volatile,
-                                                 bool value_can_be_null) {
+                                                 bool value_can_be_null,
+                                                 WriteBarrierKind write_barrier_kind) {
   LocationSummary* locations = instruction->GetLocations();
   Location value = locations->InAt(value_index);
   bool needs_write_barrier =
@@ -6040,10 +6044,15 @@
     codegen_->MaybeRecordImplicitNullCheck(instruction);
   }
 
-  if (needs_write_barrier) {
+  if (needs_write_barrier && write_barrier_kind != WriteBarrierKind::kDontEmit) {
     Register temp = locations->GetTemp(0).AsRegister<Register>();
     Register card = locations->GetTemp(1).AsRegister<Register>();
-    codegen_->MarkGCCard(temp, card, base, value.AsRegister<Register>(), value_can_be_null);
+    codegen_->MarkGCCard(
+        temp,
+        card,
+        base,
+        value.AsRegister<Register>(),
+        value_can_be_null && write_barrier_kind == WriteBarrierKind::kEmitWithNullCheck);
   }
 
   if (is_volatile) {
@@ -6053,7 +6062,8 @@
 
 void InstructionCodeGeneratorX86::HandleFieldSet(HInstruction* instruction,
                                                  const FieldInfo& field_info,
-                                                 bool value_can_be_null) {
+                                                 bool value_can_be_null,
+                                                 WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations = instruction->GetLocations();
@@ -6078,7 +6088,8 @@
                  field_addr,
                  base,
                  is_volatile,
-                 value_can_be_null);
+                 value_can_be_null,
+                 write_barrier_kind);
 
   if (is_predicated) {
     __ Bind(&pred_is_null);
@@ -6094,19 +6105,25 @@
 }
 
 void LocationsBuilderX86::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo());
+  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetWriteBarrierKind());
 }
 
 void InstructionCodeGeneratorX86::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderX86::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo());
+  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetWriteBarrierKind());
 }
 
 void InstructionCodeGeneratorX86::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderX86::VisitPredicatedInstanceFieldGet(
@@ -6367,10 +6384,12 @@
     locations->SetInAt(2, Location::RegisterOrConstant(instruction->InputAt(2)));
   }
   if (needs_write_barrier) {
-    // Temporary registers for the write barrier.
-    locations->AddTemp(Location::RequiresRegister());  // Possibly used for ref. poisoning too.
-    // Ensure the card is in a byte register.
-    locations->AddTemp(Location::RegisterLocation(ECX));
+    // Used by reference poisoning or emitting write barrier.
+    locations->AddTemp(Location::RequiresRegister());
+    if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+      // Only used when emitting a write barrier. Ensure the card is in a byte register.
+      locations->AddTemp(Location::RegisterLocation(ECX));
+    }
   }
 }
 
@@ -6487,9 +6506,16 @@
         }
       }
 
-      Register card = locations->GetTemp(1).AsRegister<Register>();
-      codegen_->MarkGCCard(
-          temp, card, array, value.AsRegister<Register>(), /* value_can_be_null= */ false);
+      if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+        DCHECK_EQ(instruction->GetWriteBarrierKind(), WriteBarrierKind::kEmitNoNullCheck)
+            << " Already null checked so we shouldn't do it again.";
+        Register card = locations->GetTemp(1).AsRegister<Register>();
+        codegen_->MarkGCCard(temp,
+                             card,
+                             array,
+                             value.AsRegister<Register>(),
+                             /* emit_null_check= */ false);
+      }
 
       if (can_value_be_null) {
         DCHECK(do_store.IsLinked());
diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h
index 02b967f..9f09e17 100644
--- a/compiler/optimizing/code_generator_x86.h
+++ b/compiler/optimizing/code_generator_x86.h
@@ -197,7 +197,9 @@
   void HandleInvoke(HInvoke* invoke);
   void HandleCondition(HCondition* condition);
   void HandleShift(HBinaryOperation* instruction);
-  void HandleFieldSet(HInstruction* instruction, const FieldInfo& field_info);
+  void HandleFieldSet(HInstruction* instruction,
+                      const FieldInfo& field_info,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
   bool CpuHasAvxFeatureFlag();
   bool CpuHasAvx2FeatureFlag();
@@ -250,7 +252,8 @@
                       Address field_addr,
                       Register base,
                       bool is_volatile,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
 
  private:
   // Generate code for the given suspend check. If not null, `successor`
@@ -280,7 +283,8 @@
 
   void HandleFieldSet(HInstruction* instruction,
                       const FieldInfo& field_info,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
 
   // Generate a heap reference load using one register `out`:
@@ -520,11 +524,8 @@
   void EmitJitRootPatches(uint8_t* code, const uint8_t* roots_data) override;
 
   // Emit a write barrier.
-  void MarkGCCard(Register temp,
-                  Register card,
-                  Register object,
-                  Register value,
-                  bool value_can_be_null);
+  void MarkGCCard(
+      Register temp, Register card, Register object, Register value, bool emit_null_check);
 
   void GenerateMemoryBarrier(MemBarrierKind kind);
 
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index d786711..2d7dc44 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -36,6 +36,7 @@
 #include "mirror/class-inl.h"
 #include "mirror/object_reference.h"
 #include "mirror/var_handle.h"
+#include "optimizing/nodes.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
 #include "utils/assembler.h"
@@ -5173,6 +5174,9 @@
       locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
     }
   }
+
+  // TODO(solanes): We could reduce the temp usage but it requires some non-trivial refactoring of
+  // InstructionCodeGeneratorX86_64::HandleFieldSet.
   if (needs_write_barrier) {
     // Temporary registers for the write barrier.
     locations->AddTemp(Location::RequiresRegister());
@@ -5234,7 +5238,8 @@
                                                     bool is_volatile,
                                                     bool is_atomic,
                                                     bool value_can_be_null,
-                                                    bool byte_swap) {
+                                                    bool byte_swap,
+                                                    WriteBarrierKind write_barrier_kind) {
   LocationSummary* locations = instruction->GetLocations();
   Location value = locations->InAt(value_index);
 
@@ -5352,10 +5357,16 @@
     codegen_->MaybeRecordImplicitNullCheck(instruction);
   }
 
-  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(value_index))) {
+  if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(value_index)) &&
+      write_barrier_kind != WriteBarrierKind::kDontEmit) {
     CpuRegister temp = locations->GetTemp(0).AsRegister<CpuRegister>();
     CpuRegister card = locations->GetTemp(extra_temp_index).AsRegister<CpuRegister>();
-    codegen_->MarkGCCard(temp, card, base, value.AsRegister<CpuRegister>(), value_can_be_null);
+    codegen_->MarkGCCard(
+        temp,
+        card,
+        base,
+        value.AsRegister<CpuRegister>(),
+        value_can_be_null && write_barrier_kind == WriteBarrierKind::kEmitWithNullCheck);
   }
 
   if (is_volatile) {
@@ -5365,7 +5376,8 @@
 
 void InstructionCodeGeneratorX86_64::HandleFieldSet(HInstruction* instruction,
                                                     const FieldInfo& field_info,
-                                                    bool value_can_be_null) {
+                                                    bool value_can_be_null,
+                                                    WriteBarrierKind write_barrier_kind) {
   DCHECK(instruction->IsInstanceFieldSet() || instruction->IsStaticFieldSet());
 
   LocationSummary* locations = instruction->GetLocations();
@@ -5390,7 +5402,9 @@
                  base,
                  is_volatile,
                  /*is_atomic=*/ false,
-                 value_can_be_null);
+                 value_can_be_null,
+                 /*byte_swap=*/ false,
+                 write_barrier_kind);
 
   if (is_predicated) {
     __ Bind(&pred_is_null);
@@ -5402,7 +5416,10 @@
 }
 
 void InstructionCodeGeneratorX86_64::VisitInstanceFieldSet(HInstanceFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderX86_64::VisitPredicatedInstanceFieldGet(
@@ -5442,7 +5459,10 @@
 }
 
 void InstructionCodeGeneratorX86_64::VisitStaticFieldSet(HStaticFieldSet* instruction) {
-  HandleFieldSet(instruction, instruction->GetFieldInfo(), instruction->GetValueCanBeNull());
+  HandleFieldSet(instruction,
+                 instruction->GetFieldInfo(),
+                 instruction->GetValueCanBeNull(),
+                 instruction->GetWriteBarrierKind());
 }
 
 void LocationsBuilderX86_64::VisitStringBuilderAppend(HStringBuilderAppend* instruction) {
@@ -5673,9 +5693,12 @@
   }
 
   if (needs_write_barrier) {
-    // Temporary registers for the write barrier.
-    locations->AddTemp(Location::RequiresRegister());  // Possibly used for ref. poisoning too.
+    // Used by reference poisoning or emitting write barrier.
     locations->AddTemp(Location::RequiresRegister());
+    if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+      // Only used when emitting a write barrier.
+      locations->AddTemp(Location::RequiresRegister());
+    }
   }
 }
 
@@ -5793,9 +5816,16 @@
         }
       }
 
-      CpuRegister card = locations->GetTemp(1).AsRegister<CpuRegister>();
-      codegen_->MarkGCCard(
-          temp, card, array, value.AsRegister<CpuRegister>(), /* value_can_be_null= */ false);
+      if (instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit) {
+        DCHECK_EQ(instruction->GetWriteBarrierKind(), WriteBarrierKind::kEmitNoNullCheck)
+            << " Already null checked so we shouldn't do it again.";
+        CpuRegister card = locations->GetTemp(1).AsRegister<CpuRegister>();
+        codegen_->MarkGCCard(temp,
+                             card,
+                             array,
+                             value.AsRegister<CpuRegister>(),
+                             /* emit_null_check= */ false);
+      }
 
       if (can_value_be_null) {
         DCHECK(do_store.IsLinked());
@@ -5994,9 +6024,9 @@
                                      CpuRegister card,
                                      CpuRegister object,
                                      CpuRegister value,
-                                     bool value_can_be_null) {
+                                     bool emit_null_check) {
   NearLabel is_null;
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ testl(value, value);
     __ j(kEqual, &is_null);
   }
@@ -6021,7 +6051,7 @@
   // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
   // (no need to explicitly load `kCardDirty` as an immediate value).
   __ movb(Address(temp, card, TIMES_1, 0), card);
-  if (value_can_be_null) {
+  if (emit_null_check) {
     __ Bind(&is_null);
   }
 }
diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h
index ffc2641..1fac62f 100644
--- a/compiler/optimizing/code_generator_x86_64.h
+++ b/compiler/optimizing/code_generator_x86_64.h
@@ -251,7 +251,8 @@
                       bool is_volatile,
                       bool is_atomic,
                       bool value_can_be_null,
-                      bool byte_swap = false);
+                      bool byte_swap,
+                      WriteBarrierKind write_barrier_kind);
 
   void Bswap(Location value, DataType::Type type, CpuRegister* temp = nullptr);
 
@@ -274,7 +275,8 @@
 
   void HandleFieldSet(HInstruction* instruction,
                       const FieldInfo& field_info,
-                      bool value_can_be_null);
+                      bool value_can_be_null,
+                      WriteBarrierKind write_barrier_kind);
   void HandleFieldGet(HInstruction* instruction, const FieldInfo& field_info);
 
   void GenerateMinMaxInt(LocationSummary* locations, bool is_min, DataType::Type type);
@@ -436,7 +438,7 @@
                   CpuRegister card,
                   CpuRegister object,
                   CpuRegister value,
-                  bool value_can_be_null);
+                  bool emit_null_check);
 
   void GenerateMemoryBarrier(MemBarrierKind kind);
 
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index f607652..96eaa61 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -483,6 +483,7 @@
     StartAttributeStream("can_trigger_gc")
         << std::boolalpha << array_set->GetSideEffects().Includes(SideEffects::CanTriggerGC())
         << std::noboolalpha;
+    StartAttributeStream("write_barrier_kind") << array_set->GetWriteBarrierKind();
   }
 
   void VisitCompare(HCompare* compare) override {
@@ -552,7 +553,9 @@
         iset->GetFieldInfo().GetDexFile().PrettyField(iset->GetFieldInfo().GetFieldIndex(),
                                                       /* with type */ false);
     StartAttributeStream("field_type") << iset->GetFieldType();
-    StartAttributeStream("predicated") << std::boolalpha << iset->GetIsPredicatedSet();
+    StartAttributeStream("predicated")
+        << std::boolalpha << iset->GetIsPredicatedSet() << std::noboolalpha;
+    StartAttributeStream("write_barrier_kind") << iset->GetWriteBarrierKind();
   }
 
   void VisitStaticFieldGet(HStaticFieldGet* sget) override {
@@ -567,6 +570,7 @@
         sset->GetFieldInfo().GetDexFile().PrettyField(sset->GetFieldInfo().GetFieldIndex(),
                                                       /* with type */ false);
     StartAttributeStream("field_type") << sset->GetFieldType();
+    StartAttributeStream("write_barrier_kind") << sset->GetWriteBarrierKind();
   }
 
   void VisitUnresolvedInstanceFieldGet(HUnresolvedInstanceFieldGet* field_access) override {
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index 88c125d..e319634 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -4041,13 +4041,16 @@
   InstructionCodeGeneratorX86* instr_codegen =
         down_cast<InstructionCodeGeneratorX86*>(codegen->GetInstructionVisitor());
   // Store the value to the field
-  instr_codegen->HandleFieldSet(invoke,
-                                value_index,
-                                value_type,
-                                Address(reference, offset, TIMES_1, 0),
-                                reference,
-                                is_volatile,
-                                /* value_can_be_null */ true);
+  instr_codegen->HandleFieldSet(
+      invoke,
+      value_index,
+      value_type,
+      Address(reference, offset, TIMES_1, 0),
+      reference,
+      is_volatile,
+      /* value_can_be_null */ true,
+      // Value can be null, and this write barrier is not being relied on for other sets.
+      WriteBarrierKind::kEmitWithNullCheck);
 
   __ Bind(slow_path->GetExitLabel());
 }
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index 98702e6..be15c07 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -3985,16 +3985,19 @@
   Address dst(CpuRegister(target.object), CpuRegister(target.offset), TIMES_1, 0);
 
   // Store the value to the field.
-  codegen->GetInstructionCodegen()->HandleFieldSet(invoke,
-                                                   value_index,
-                                                   last_temp_index,
-                                                   value_type,
-                                                   dst,
-                                                   CpuRegister(target.object),
-                                                   is_volatile,
-                                                   is_atomic,
-                                                   /*value_can_be_null=*/ true,
-                                                   byte_swap);
+  codegen->GetInstructionCodegen()->HandleFieldSet(
+      invoke,
+      value_index,
+      last_temp_index,
+      value_type,
+      dst,
+      CpuRegister(target.object),
+      is_volatile,
+      is_atomic,
+      /*value_can_be_null=*/true,
+      byte_swap,
+      // Value can be null, and this write barrier is not being relied on for other sets.
+      WriteBarrierKind::kEmitWithNullCheck);
 
   // setVolatile needs kAnyAny barrier, but HandleFieldSet takes care of that.
 
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index cbb5591..f33b0d8 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -6403,6 +6403,27 @@
   const FieldInfo field_info_;
 };
 
+enum class WriteBarrierKind {
+  // Emit the write barrier, with a runtime optimization which checks if the value that it is being
+  // set is null.
+  kEmitWithNullCheck,
+  // Emit the write barrier, without the runtime null check optimization. This could be set because:
+  //  A) It is a write barrier for an ArraySet (which does the optimization with the type check, so
+  //  it never does the optimization at the write barrier stage)
+  //  B) We know that the input can't be null
+  //  C) This write barrier is actually several write barriers coalesced into one. Potentially we
+  //  could ask if every value is null for a runtime optimization at the cost of compile time / code
+  //  size. At the time of writing it was deemed not worth the effort.
+  kEmitNoNullCheck,
+  // Skip emitting the write barrier. This could be set because:
+  //  A) The write barrier is not needed (e.g. it is not a reference, or the value is the null
+  //  constant)
+  //  B) This write barrier was coalesced into another one so there's no need to emit it.
+  kDontEmit,
+  kLast = kDontEmit
+};
+std::ostream& operator<<(std::ostream& os, WriteBarrierKind rhs);
+
 class HInstanceFieldSet final : public HExpression<2> {
  public:
   HInstanceFieldSet(HInstruction* object,
@@ -6427,6 +6448,7 @@
                     dex_file) {
     SetPackedFlag<kFlagValueCanBeNull>(true);
     SetPackedFlag<kFlagIsPredicatedSet>(false);
+    SetPackedField<WriteBarrierKindField>(WriteBarrierKind::kEmitWithNullCheck);
     SetRawInputAt(0, object);
     SetRawInputAt(1, value);
   }
@@ -6447,6 +6469,12 @@
   void ClearValueCanBeNull() { SetPackedFlag<kFlagValueCanBeNull>(false); }
   bool GetIsPredicatedSet() const { return GetPackedFlag<kFlagIsPredicatedSet>(); }
   void SetIsPredicatedSet(bool value = true) { SetPackedFlag<kFlagIsPredicatedSet>(value); }
+  WriteBarrierKind GetWriteBarrierKind() { return GetPackedField<WriteBarrierKindField>(); }
+  void SetWriteBarrierKind(WriteBarrierKind kind) {
+    DCHECK(kind != WriteBarrierKind::kEmitWithNullCheck)
+        << "We shouldn't go back to the original value.";
+    SetPackedField<WriteBarrierKindField>(kind);
+  }
 
   DECLARE_INSTRUCTION(InstanceFieldSet);
 
@@ -6456,11 +6484,17 @@
  private:
   static constexpr size_t kFlagValueCanBeNull = kNumberOfGenericPackedBits;
   static constexpr size_t kFlagIsPredicatedSet = kFlagValueCanBeNull + 1;
-  static constexpr size_t kNumberOfInstanceFieldSetPackedBits = kFlagIsPredicatedSet + 1;
+  static constexpr size_t kWriteBarrierKind = kFlagIsPredicatedSet + 1;
+  static constexpr size_t kWriteBarrierKindSize =
+      MinimumBitsToStore(static_cast<size_t>(WriteBarrierKind::kLast));
+  static constexpr size_t kNumberOfInstanceFieldSetPackedBits =
+      kWriteBarrierKind + kWriteBarrierKindSize;
   static_assert(kNumberOfInstanceFieldSetPackedBits <= kMaxNumberOfPackedBits,
                 "Too many packed fields.");
 
   const FieldInfo field_info_;
+  using WriteBarrierKindField =
+      BitField<WriteBarrierKind, kWriteBarrierKind, kWriteBarrierKindSize>;
 };
 
 class HArrayGet final : public HExpression<2> {
@@ -6581,6 +6615,8 @@
     SetPackedFlag<kFlagNeedsTypeCheck>(value->GetType() == DataType::Type::kReference);
     SetPackedFlag<kFlagValueCanBeNull>(true);
     SetPackedFlag<kFlagStaticTypeOfArrayIsObjectArray>(false);
+    // ArraySets never do the null check optimization at the write barrier stage.
+    SetPackedField<WriteBarrierKindField>(WriteBarrierKind::kEmitNoNullCheck);
     SetRawInputAt(0, array);
     SetRawInputAt(1, index);
     SetRawInputAt(2, value);
@@ -6653,6 +6689,16 @@
                                                       : SideEffects::None();
   }
 
+  WriteBarrierKind GetWriteBarrierKind() { return GetPackedField<WriteBarrierKindField>(); }
+
+  void SetWriteBarrierKind(WriteBarrierKind kind) {
+    DCHECK(kind != WriteBarrierKind::kEmitNoNullCheck)
+        << "We shouldn't go back to the original value.";
+    DCHECK(kind != WriteBarrierKind::kEmitWithNullCheck)
+        << "We never do the null check optimization for ArraySets.";
+    SetPackedField<WriteBarrierKindField>(kind);
+  }
+
   DECLARE_INSTRUCTION(ArraySet);
 
  protected:
@@ -6668,11 +6714,16 @@
   // Cached information for the reference_type_info_ so that codegen
   // does not need to inspect the static type.
   static constexpr size_t kFlagStaticTypeOfArrayIsObjectArray = kFlagValueCanBeNull + 1;
-  static constexpr size_t kNumberOfArraySetPackedBits =
-      kFlagStaticTypeOfArrayIsObjectArray + 1;
+  static constexpr size_t kWriteBarrierKind = kFlagStaticTypeOfArrayIsObjectArray + 1;
+  static constexpr size_t kWriteBarrierKindSize =
+      MinimumBitsToStore(static_cast<size_t>(WriteBarrierKind::kLast));
+  static constexpr size_t kNumberOfArraySetPackedBits = kWriteBarrierKind + kWriteBarrierKindSize;
   static_assert(kNumberOfArraySetPackedBits <= kMaxNumberOfPackedBits, "Too many packed fields.");
   using ExpectedComponentTypeField =
       BitField<DataType::Type, kFieldExpectedComponentType, kFieldExpectedComponentTypeSize>;
+
+  using WriteBarrierKindField =
+      BitField<WriteBarrierKind, kWriteBarrierKind, kWriteBarrierKindSize>;
 };
 
 class HArrayLength final : public HExpression<1> {
@@ -7470,6 +7521,7 @@
                     declaring_class_def_index,
                     dex_file) {
     SetPackedFlag<kFlagValueCanBeNull>(true);
+    SetPackedField<WriteBarrierKindField>(WriteBarrierKind::kEmitWithNullCheck);
     SetRawInputAt(0, cls);
     SetRawInputAt(1, value);
   }
@@ -7485,6 +7537,13 @@
   bool GetValueCanBeNull() const { return GetPackedFlag<kFlagValueCanBeNull>(); }
   void ClearValueCanBeNull() { SetPackedFlag<kFlagValueCanBeNull>(false); }
 
+  WriteBarrierKind GetWriteBarrierKind() { return GetPackedField<WriteBarrierKindField>(); }
+  void SetWriteBarrierKind(WriteBarrierKind kind) {
+    DCHECK(kind != WriteBarrierKind::kEmitWithNullCheck)
+        << "We shouldn't go back to the original value.";
+    SetPackedField<WriteBarrierKindField>(kind);
+  }
+
   DECLARE_INSTRUCTION(StaticFieldSet);
 
  protected:
@@ -7492,11 +7551,17 @@
 
  private:
   static constexpr size_t kFlagValueCanBeNull = kNumberOfGenericPackedBits;
-  static constexpr size_t kNumberOfStaticFieldSetPackedBits = kFlagValueCanBeNull + 1;
+  static constexpr size_t kWriteBarrierKind = kFlagValueCanBeNull + 1;
+  static constexpr size_t kWriteBarrierKindSize =
+      MinimumBitsToStore(static_cast<size_t>(WriteBarrierKind::kLast));
+  static constexpr size_t kNumberOfStaticFieldSetPackedBits =
+      kWriteBarrierKind + kWriteBarrierKindSize;
   static_assert(kNumberOfStaticFieldSetPackedBits <= kMaxNumberOfPackedBits,
                 "Too many packed fields.");
 
   const FieldInfo field_info_;
+  using WriteBarrierKindField =
+      BitField<WriteBarrierKind, kWriteBarrierKind, kWriteBarrierKindSize>;
 };
 
 class HStringBuilderAppend final : public HVariableInputSizeInstruction {
diff --git a/compiler/optimizing/optimization.cc b/compiler/optimizing/optimization.cc
index e0c5933..73a4751 100644
--- a/compiler/optimizing/optimization.cc
+++ b/compiler/optimizing/optimization.cc
@@ -55,6 +55,7 @@
 #include "select_generator.h"
 #include "sharpening.h"
 #include "side_effects_analysis.h"
+#include "write_barrier_elimination.h"
 
 // Decide between default or alternative pass name.
 
@@ -95,6 +96,8 @@
       return ConstructorFenceRedundancyElimination::kCFREPassName;
     case OptimizationPass::kScheduling:
       return HInstructionScheduling::kInstructionSchedulingPassName;
+    case OptimizationPass::kWriteBarrierElimination:
+      return WriteBarrierElimination::kWBEPassName;
 #ifdef ART_ENABLE_CODEGEN_arm
     case OptimizationPass::kInstructionSimplifierArm:
       return arm::InstructionSimplifierArm::kInstructionSimplifierArmPassName;
@@ -268,6 +271,9 @@
       case OptimizationPass::kLoadStoreElimination:
         opt = new (allocator) LoadStoreElimination(graph, stats, pass_name);
         break;
+      case OptimizationPass::kWriteBarrierElimination:
+        opt = new (allocator) WriteBarrierElimination(graph, stats, pass_name);
+        break;
       case OptimizationPass::kScheduling:
         opt = new (allocator) HInstructionScheduling(
             graph, codegen->GetCompilerOptions().GetInstructionSet(), codegen, pass_name);
diff --git a/compiler/optimizing/optimization.h b/compiler/optimizing/optimization.h
index 76960bf..c3ba175 100644
--- a/compiler/optimizing/optimization.h
+++ b/compiler/optimizing/optimization.h
@@ -84,6 +84,7 @@
   kScheduling,
   kSelectGenerator,
   kSideEffectsAnalysis,
+  kWriteBarrierElimination,
 #ifdef ART_ENABLE_CODEGEN_arm
   kInstructionSimplifierArm,
   kCriticalNativeAbiFixupArm,
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 807c78e..dbf247c 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -51,6 +51,7 @@
 #include "linker/linker_patch.h"
 #include "nodes.h"
 #include "oat_quick_method_header.h"
+#include "optimizing/write_barrier_elimination.h"
 #include "prepare_for_register_allocation.h"
 #include "reference_type_propagation.h"
 #include "register_allocator_linear_scan.h"
@@ -899,6 +900,8 @@
     RunBaselineOptimizations(graph, codegen.get(), dex_compilation_unit, &pass_observer);
   } else {
     RunOptimizations(graph, codegen.get(), dex_compilation_unit, &pass_observer);
+    PassScope scope(WriteBarrierElimination::kWBEPassName, &pass_observer);
+    WriteBarrierElimination(graph, compilation_stats_.get()).Run();
   }
 
   RegisterAllocator::Strategy regalloc_strategy =
@@ -992,6 +995,10 @@
                    optimizations);
 
   RunArchOptimizations(graph, codegen.get(), dex_compilation_unit, &pass_observer);
+  {
+    PassScope scope(WriteBarrierElimination::kWBEPassName, &pass_observer);
+    WriteBarrierElimination(graph, compilation_stats_.get()).Run();
+  }
 
   AllocateRegisters(graph,
                     codegen.get(),
diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h
index 100441a..698a147 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -118,6 +118,8 @@
   kConstructorFenceRemovedLSE,
   kConstructorFenceRemovedPFRA,
   kConstructorFenceRemovedCFRE,
+  kPossibleWriteBarrier,
+  kRemovedWriteBarrier,
   kBitstringTypeCheck,
   kJitOutOfMemoryForCommit,
   kFullLSEAllocationRemoved,
diff --git a/compiler/optimizing/write_barrier_elimination.cc b/compiler/optimizing/write_barrier_elimination.cc
new file mode 100644
index 0000000..9023cc2
--- /dev/null
+++ b/compiler/optimizing/write_barrier_elimination.cc
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "write_barrier_elimination.h"
+
+#include "base/arena_allocator.h"
+#include "base/scoped_arena_allocator.h"
+#include "base/scoped_arena_containers.h"
+#include "optimizing/nodes.h"
+
+namespace art HIDDEN {
+
+class WBEVisitor : public HGraphVisitor {
+ public:
+  WBEVisitor(HGraph* graph, OptimizingCompilerStats* stats)
+      : HGraphVisitor(graph),
+        scoped_allocator_(graph->GetArenaStack()),
+        current_write_barriers_(scoped_allocator_.Adapter(kArenaAllocWBE)),
+        stats_(stats) {}
+
+  void VisitBasicBlock(HBasicBlock* block) override {
+    // We clear the map to perform this optimization only in the same block. Doing it across blocks
+    // would entail non-trivial merging of states.
+    current_write_barriers_.clear();
+    HGraphVisitor::VisitBasicBlock(block);
+  }
+
+  void VisitInstanceFieldSet(HInstanceFieldSet* instruction) override {
+    DCHECK(!instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()));
+
+    if (instruction->GetFieldType() != DataType::Type::kReference ||
+        instruction->GetValue()->IsNullConstant()) {
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      return;
+    }
+
+    MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
+    HInstruction* obj = HuntForOriginalReference(instruction->InputAt(0));
+    auto it = current_write_barriers_.find(obj);
+    if (it != current_write_barriers_.end()) {
+      DCHECK(it->second->IsInstanceFieldSet());
+      DCHECK(it->second->AsInstanceFieldSet()->GetWriteBarrierKind() !=
+             WriteBarrierKind::kDontEmit);
+      DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
+      it->second->AsInstanceFieldSet()->SetWriteBarrierKind(WriteBarrierKind::kEmitNoNullCheck);
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
+    } else {
+      const bool inserted = current_write_barriers_.insert({obj, instruction}).second;
+      DCHECK(inserted);
+      DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+    }
+  }
+
+  void VisitStaticFieldSet(HStaticFieldSet* instruction) override {
+    DCHECK(!instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()));
+
+    if (instruction->GetFieldType() != DataType::Type::kReference ||
+        instruction->GetValue()->IsNullConstant()) {
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      return;
+    }
+
+    MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
+    HInstruction* cls = HuntForOriginalReference(instruction->InputAt(0));
+    auto it = current_write_barriers_.find(cls);
+    if (it != current_write_barriers_.end()) {
+      DCHECK(it->second->IsStaticFieldSet());
+      DCHECK(it->second->AsStaticFieldSet()->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+      DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
+      it->second->AsStaticFieldSet()->SetWriteBarrierKind(WriteBarrierKind::kEmitNoNullCheck);
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
+    } else {
+      const bool inserted = current_write_barriers_.insert({cls, instruction}).second;
+      DCHECK(inserted);
+      DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+    }
+  }
+
+  void VisitArraySet(HArraySet* instruction) override {
+    if (instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC())) {
+      ClearCurrentValues();
+    }
+
+    if (instruction->GetComponentType() != DataType::Type::kReference ||
+        instruction->GetValue()->IsNullConstant()) {
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      return;
+    }
+
+    HInstruction* arr = HuntForOriginalReference(instruction->InputAt(0));
+    MaybeRecordStat(stats_, MethodCompilationStat::kPossibleWriteBarrier);
+    auto it = current_write_barriers_.find(arr);
+    if (it != current_write_barriers_.end()) {
+      DCHECK(it->second->IsArraySet());
+      DCHECK(it->second->AsArraySet()->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+      DCHECK_EQ(it->second->GetBlock(), instruction->GetBlock());
+      // We never skip the null check in ArraySets so that value is already set.
+      DCHECK(it->second->AsArraySet()->GetWriteBarrierKind() == WriteBarrierKind::kEmitNoNullCheck);
+      instruction->SetWriteBarrierKind(WriteBarrierKind::kDontEmit);
+      MaybeRecordStat(stats_, MethodCompilationStat::kRemovedWriteBarrier);
+    } else {
+      const bool inserted = current_write_barriers_.insert({arr, instruction}).second;
+      DCHECK(inserted);
+      DCHECK(instruction->GetWriteBarrierKind() != WriteBarrierKind::kDontEmit);
+    }
+  }
+
+  void VisitInstruction(HInstruction* instruction) override {
+    if (instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC())) {
+      ClearCurrentValues();
+    }
+  }
+
+ private:
+  void ClearCurrentValues() { current_write_barriers_.clear(); }
+
+  HInstruction* HuntForOriginalReference(HInstruction* ref) const {
+    // An original reference can be transformed by instructions like:
+    //   i0 NewArray
+    //   i1 HInstruction(i0)  <-- NullCheck, BoundType, IntermediateAddress.
+    //   i2 ArraySet(i1, index, value)
+    DCHECK(ref != nullptr);
+    while (ref->IsNullCheck() || ref->IsBoundType() || ref->IsIntermediateAddress()) {
+      ref = ref->InputAt(0);
+    }
+    return ref;
+  }
+
+  ScopedArenaAllocator scoped_allocator_;
+
+  // Stores a map of <Receiver, InstructionWhereTheWriteBarrierIs>.
+  // `InstructionWhereTheWriteBarrierIs` is used for DCHECKs only.
+  ScopedArenaHashMap<HInstruction*, HInstruction*> current_write_barriers_;
+
+  OptimizingCompilerStats* const stats_;
+
+  DISALLOW_COPY_AND_ASSIGN(WBEVisitor);
+};
+
+bool WriteBarrierElimination::Run() {
+  WBEVisitor wbe_visitor(graph_, stats_);
+  wbe_visitor.VisitReversePostOrder();
+  return true;
+}
+
+}  // namespace art
diff --git a/compiler/optimizing/write_barrier_elimination.h b/compiler/optimizing/write_barrier_elimination.h
new file mode 100644
index 0000000..a3769e7
--- /dev/null
+++ b/compiler/optimizing/write_barrier_elimination.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_COMPILER_OPTIMIZING_WRITE_BARRIER_ELIMINATION_H_
+#define ART_COMPILER_OPTIMIZING_WRITE_BARRIER_ELIMINATION_H_
+
+#include "base/macros.h"
+#include "optimization.h"
+
+namespace art HIDDEN {
+
+// Eliminates unnecessary write barriers from InstanceFieldSet, StaticFieldSet, and ArraySet.
+//
+// We can eliminate redundant write barriers as we don't need several for the same receiver. For
+// example:
+//   MyObject o;
+//   o.inner_obj = io;
+//   o.inner_obj2 = io2;
+//   o.inner_obj3 = io3;
+// We can keep the write barrier for `inner_obj` and remove the other two.
+//
+// In order to do this, we set the WriteBarrierKind of the instruction. The instruction's kind are
+// set to kEmitNoNullCheck (if this write barrier coalesced other write barriers, we don't want to
+// perform the null check optimization), or to kDontEmit (if the write barrier as a whole is not
+// needed).
+class WriteBarrierElimination : public HOptimization {
+ public:
+  WriteBarrierElimination(HGraph* graph,
+                          OptimizingCompilerStats* stats,
+                          const char* name = kWBEPassName)
+      : HOptimization(graph, name, stats) {}
+
+  bool Run() override;
+
+  static constexpr const char* kWBEPassName = "write_barrier_elimination";
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(WriteBarrierElimination);
+};
+
+}  // namespace art
+
+#endif  // ART_COMPILER_OPTIMIZING_WRITE_BARRIER_ELIMINATION_H_
diff --git a/libartbase/base/arena_allocator.cc b/libartbase/base/arena_allocator.cc
index 69c8d0b..d38a64e 100644
--- a/libartbase/base/arena_allocator.cc
+++ b/libartbase/base/arena_allocator.cc
@@ -73,6 +73,7 @@
   "LSE          ",
   "CFRE         ",
   "LICM         ",
+  "WBE          ",
   "LoopOpt      ",
   "SsaLiveness  ",
   "SsaPhiElim   ",
diff --git a/libartbase/base/arena_allocator.h b/libartbase/base/arena_allocator.h
index 3dfeebe..c4f713a 100644
--- a/libartbase/base/arena_allocator.h
+++ b/libartbase/base/arena_allocator.h
@@ -84,6 +84,7 @@
   kArenaAllocLSE,
   kArenaAllocCFRE,
   kArenaAllocLICM,
+  kArenaAllocWBE,
   kArenaAllocLoopOptimization,
   kArenaAllocSsaLiveness,
   kArenaAllocSsaPhiElimination,
diff --git a/test/2247-checker-write-barrier-elimination/expected-stderr.txt b/test/2247-checker-write-barrier-elimination/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2247-checker-write-barrier-elimination/expected-stderr.txt
diff --git a/test/2247-checker-write-barrier-elimination/expected-stdout.txt b/test/2247-checker-write-barrier-elimination/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2247-checker-write-barrier-elimination/expected-stdout.txt
diff --git a/test/2247-checker-write-barrier-elimination/info.txt b/test/2247-checker-write-barrier-elimination/info.txt
new file mode 100644
index 0000000..2515317
--- /dev/null
+++ b/test/2247-checker-write-barrier-elimination/info.txt
@@ -0,0 +1 @@
+Tests that we eliminate unneeded write barriers.
diff --git a/test/2247-checker-write-barrier-elimination/src/Main.java b/test/2247-checker-write-barrier-elimination/src/Main.java
new file mode 100644
index 0000000..76fb05a
--- /dev/null
+++ b/test/2247-checker-write-barrier-elimination/src/Main.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class MultipleObject {
+    Object inner;
+    Object inner2;
+
+    static Object inner_static;
+}
+
+public class Main {
+    public static void main(String[] args) throws Error {
+        // Several sets, same receiver.
+        $noinline$testInstanceFieldSets(new Main(), new Object(), new Object(), new Object());
+        $noinline$testStaticFieldSets(new Object(), new Object(), new Object());
+        // Object ArraySets can throw since they need a type check so we cannot perform the
+        // optimization.
+        $noinline$testArraySets(new Object[3], new Object(), new Object(), new Object());
+        // If we are swapping elements in the array, no need for a type check.
+        $noinline$testSwapArray(new Object[3]);
+        // If the array and the values have the same RTI, no need for a type check.
+        $noinline$testArraySetsSameRTI();
+
+        // We cannot rely on `null` sets to perform the optimization.
+        $noinline$testNullInstanceFieldSets(new Main(), new Object());
+        $noinline$testNullStaticFieldSets(new Object());
+        $noinline$testNullArraySets(new Object[3], new Object());
+
+        // Several sets, multiple receivers. (set obj1, obj2, obj1 and see that the card of obj1
+        // gets eliminated)
+        $noinline$testInstanceFieldSetsMultipleReceivers(
+                new Main(), new Object(), new Object(), new Object());
+        $noinline$testStaticFieldSetsMultipleReceivers(new Object(), new Object(), new Object());
+        $noinline$testArraySetsMultipleReceiversSameRTI();
+
+        // The write barrier elimination optimization is blocked by invokes, suspend checks, and
+        // instructions that can throw.
+        $noinline$testInstanceFieldSetsBlocked(
+                new Main(), new Object(), new Object(), new Object());
+        $noinline$testStaticFieldSetsBlocked(new Object(), new Object(), new Object());
+        $noinline$testArraySetsSameRTIBlocked();
+    }
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSets(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:EmitNoNullCheck
+    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:DontEmit
+    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSets(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Main $noinline$testInstanceFieldSets(Main m, Object o, Object o2, Object o3) {
+        m.inner = o;
+        m.inner2 = o2;
+        m.inner3 = o3;
+        return m;
+    }
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSets(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:EmitNoNullCheck
+    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:DontEmit
+    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSets(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static void $noinline$testStaticFieldSets(Object o, Object o2, Object o3) {
+        inner_static = o;
+        inner_static2 = o2;
+        inner_static3 = o3;
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySets(java.lang.Object[], java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySets(java.lang.Object[], java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[] $noinline$testArraySets(
+            Object[] arr, Object o, Object o2, Object o3) {
+        arr[0] = o;
+        arr[1] = o2;
+        arr[2] = o3;
+        return arr;
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testSwapArray(java.lang.Object[]) disassembly (after)
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testSwapArray(java.lang.Object[]) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[] $noinline$testSwapArray(Object[] arr) {
+        arr[0] = arr[1];
+        arr[1] = arr[2];
+        arr[2] = arr[0];
+        return arr;
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTI() disassembly (after)
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTI() disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[] $noinline$testArraySetsSameRTI() {
+        Object[] arr = new Object[3];
+        arr[0] = inner_static;
+        arr[1] = inner_static2;
+        arr[2] = inner_static3;
+        return arr;
+    }
+
+    /// CHECK-START: Main Main.$noinline$testNullInstanceFieldSets(Main, java.lang.Object) disassembly (after)
+    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:DontEmit
+    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: Main Main.$noinline$testNullInstanceFieldSets(Main, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Main $noinline$testNullInstanceFieldSets(Main m, Object o) {
+        m.inner = null;
+        m.inner2 = o;
+        m.inner3 = null;
+        return m;
+    }
+
+    /// CHECK-START: void Main.$noinline$testNullStaticFieldSets(java.lang.Object) disassembly (after)
+    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:DontEmit
+    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: void Main.$noinline$testNullStaticFieldSets(java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static void $noinline$testNullStaticFieldSets(Object o) {
+        inner_static = null;
+        inner_static2 = o;
+        inner_static3 = null;
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testNullArraySets(java.lang.Object[], java.lang.Object) disassembly (after)
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testNullArraySets(java.lang.Object[], java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Object[] $noinline$testNullArraySets(Object[] arr, Object o) {
+        arr[0] = null;
+        arr[1] = o;
+        arr[2] = null;
+        return arr;
+    }
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsMultipleReceivers(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    // There are two extra card_tables for the initialization of the MultipleObject.
+    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner field_type:Reference write_barrier_kind:EmitNoNullCheck
+    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner2 field_type:Reference write_barrier_kind:DontEmit
+
+    // Each one of the two NewInstance instructions have their own `card_table` reference.
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsMultipleReceivers(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Main $noinline$testInstanceFieldSetsMultipleReceivers(
+            Main m, Object o, Object o2, Object o3) throws Error {
+        m.mo = new MultipleObject();
+        m.mo2 = new MultipleObject();
+
+        m.mo.inner = o;
+        // This card table for `m.mo2` can't me removed. Note that in `m.mo2 = new
+        // MultipleObject();` above the receiver is `m`, not `m.mo2.
+        m.mo2.inner = o2;
+        // This card table for `m.mo` can me removed.
+        m.mo.inner2 = o3;
+        return m;
+    }
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSetsMultipleReceivers(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: StaticFieldSet field_name:MultipleObject.inner_static field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitNoNullCheck
+    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSetsMultipleReceivers(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static void $noinline$testStaticFieldSetsMultipleReceivers(
+            Object o, Object o2, Object o3) {
+        MultipleObject.inner_static = o;
+        inner_static2 = o2;
+        inner_static3 = o3;
+    }
+
+    /// CHECK-START: java.lang.Object[][] Main.$noinline$testArraySetsMultipleReceiversSameRTI() disassembly (after)
+    // Initializing the values
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+    // Setting the `array_of_arrays`.
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
+
+    /// CHECK-START: java.lang.Object[][] Main.$noinline$testArraySetsMultipleReceiversSameRTI() disassembly (after)
+    // Two array sets can't eliminate the write barrier
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    // One write barrier for the array of arrays' sets
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[][] $noinline$testArraySetsMultipleReceiversSameRTI() {
+        Object[] arr = new Object[3];
+        Object[] other_arr = new Object[3];
+
+        arr[0] = inner_static;
+        other_arr[1] = inner_static2;
+        arr[2] = inner_static3;
+
+        // Return them so that LSE doesn't delete them
+        Object[][] array_of_arrays = {arr, other_arr};
+        return array_of_arrays;
+    }
+
+    private static void $noinline$emptyMethod() {}
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsBlocked(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
+    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: MonitorOperation kind:enter
+    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:EmitWithNullCheck
+
+    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsBlocked(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static Main $noinline$testInstanceFieldSetsBlocked(
+            Main m, Object o, Object o2, Object o3) {
+        m.inner = o;
+        $noinline$emptyMethod();
+        m.inner2 = o2;
+        synchronized (m) {
+            m.inner3 = o3;
+        }
+        return m;
+    }
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSetsBlocked(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
+    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitWithNullCheck
+    /// CHECK: MonitorOperation kind:enter
+    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:EmitWithNullCheck
+
+    /// CHECK-START: void Main.$noinline$testStaticFieldSetsBlocked(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static void $noinline$testStaticFieldSetsBlocked(Object o, Object o2, Object o3) {
+        inner_static = o;
+        $noinline$emptyMethod();
+        inner_static2 = o2;
+        Main m = new Main();
+        synchronized (m) {
+            inner_static3 = o3;
+        }
+    }
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTIBlocked() disassembly (after)
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+    /// CHECK: MonitorOperation kind:enter
+    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
+
+    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTIBlocked() disassembly (after)
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK: ; card_table
+    /// CHECK-NOT: ; card_table
+    private static java.lang.Object[] $noinline$testArraySetsSameRTIBlocked() {
+        Object[] arr = new Object[3];
+        arr[0] = inner_static;
+        $noinline$emptyMethod();
+        arr[1] = inner_static2;
+        Main m = new Main();
+        synchronized (m) {
+            arr[2] = inner_static3;
+        }
+        return arr;
+    }
+
+    Object inner;
+    Object inner2;
+    Object inner3;
+
+    MultipleObject mo;
+    MultipleObject mo2;
+
+    static Object inner_static;
+    static Object inner_static2;
+    static Object inner_static3;
+}