MIPS32: Refactor implicit null checks in array/field get/set.

Rationale: on MIPS32 64-bit loads and stores may be performed
as pairs of 32-bit loads/stores. Implicit null checks must be
associated with the first 32-bit load/store in a pair and not
the last. This change ensures proper association of said checks
(a few were done after the last 32-bit load/store in a pair)
and lays ground for further improvements in array/field get/set.

Test: booted MIPS32 in QEMU
Test: test-art-host-gtest
Test: test-art-target-run-test-optimizing in QEMU

Change-Id: I3674947c00bb17930790a7a47c9b7aadc0c030b8
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index a7fbc84..8a2f90d 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -1833,11 +1833,19 @@
   }
 }
 
+auto InstructionCodeGeneratorMIPS::GetImplicitNullChecker(HInstruction* instruction) {
+  auto null_checker = [this, instruction]() {
+    this->codegen_->MaybeRecordImplicitNullCheck(instruction);
+  };
+  return null_checker;
+}
+
 void InstructionCodeGeneratorMIPS::VisitArrayGet(HArrayGet* instruction) {
   LocationSummary* locations = instruction->GetLocations();
   Register obj = locations->InAt(0).AsRegister<Register>();
   Location index = locations->InAt(1);
   uint32_t data_offset = CodeGenerator::GetArrayDataOffset(instruction);
+  auto null_checker = GetImplicitNullChecker(instruction);
 
   Primitive::Type type = instruction->GetType();
   switch (type) {
@@ -1846,10 +1854,10 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_1) + data_offset;
-        __ LoadFromOffset(kLoadUnsignedByte, out, obj, offset);
+        __ LoadFromOffset(kLoadUnsignedByte, out, obj, offset, null_checker);
       } else {
         __ Addu(TMP, obj, index.AsRegister<Register>());
-        __ LoadFromOffset(kLoadUnsignedByte, out, TMP, data_offset);
+        __ LoadFromOffset(kLoadUnsignedByte, out, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -1859,10 +1867,10 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_1) + data_offset;
-        __ LoadFromOffset(kLoadSignedByte, out, obj, offset);
+        __ LoadFromOffset(kLoadSignedByte, out, obj, offset, null_checker);
       } else {
         __ Addu(TMP, obj, index.AsRegister<Register>());
-        __ LoadFromOffset(kLoadSignedByte, out, TMP, data_offset);
+        __ LoadFromOffset(kLoadSignedByte, out, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -1872,11 +1880,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_2) + data_offset;
-        __ LoadFromOffset(kLoadSignedHalfword, out, obj, offset);
+        __ LoadFromOffset(kLoadSignedHalfword, out, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_2);
         __ Addu(TMP, obj, TMP);
-        __ LoadFromOffset(kLoadSignedHalfword, out, TMP, data_offset);
+        __ LoadFromOffset(kLoadSignedHalfword, out, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -1886,11 +1894,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_2) + data_offset;
-        __ LoadFromOffset(kLoadUnsignedHalfword, out, obj, offset);
+        __ LoadFromOffset(kLoadUnsignedHalfword, out, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_2);
         __ Addu(TMP, obj, TMP);
-        __ LoadFromOffset(kLoadUnsignedHalfword, out, TMP, data_offset);
+        __ LoadFromOffset(kLoadUnsignedHalfword, out, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -1902,11 +1910,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
-        __ LoadFromOffset(kLoadWord, out, obj, offset);
+        __ LoadFromOffset(kLoadWord, out, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_4);
         __ Addu(TMP, obj, TMP);
-        __ LoadFromOffset(kLoadWord, out, TMP, data_offset);
+        __ LoadFromOffset(kLoadWord, out, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -1916,11 +1924,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + data_offset;
-        __ LoadFromOffset(kLoadDoubleword, out, obj, offset);
+        __ LoadFromOffset(kLoadDoubleword, out, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_8);
         __ Addu(TMP, obj, TMP);
-        __ LoadFromOffset(kLoadDoubleword, out, TMP, data_offset);
+        __ LoadFromOffset(kLoadDoubleword, out, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -1930,11 +1938,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
-        __ LoadSFromOffset(out, obj, offset);
+        __ LoadSFromOffset(out, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_4);
         __ Addu(TMP, obj, TMP);
-        __ LoadSFromOffset(out, TMP, data_offset);
+        __ LoadSFromOffset(out, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -1944,11 +1952,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + data_offset;
-        __ LoadDFromOffset(out, obj, offset);
+        __ LoadDFromOffset(out, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_8);
         __ Addu(TMP, obj, TMP);
-        __ LoadDFromOffset(out, TMP, data_offset);
+        __ LoadDFromOffset(out, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -1957,7 +1965,6 @@
       LOG(FATAL) << "Unreachable type " << instruction->GetType();
       UNREACHABLE();
   }
-  codegen_->MaybeRecordImplicitNullCheck(instruction);
 }
 
 void LocationsBuilderMIPS::VisitArrayLength(HArrayLength* instruction) {
@@ -2004,6 +2011,7 @@
   bool needs_runtime_call = locations->WillCall();
   bool needs_write_barrier =
       CodeGenerator::StoreNeedsWriteBarrier(value_type, instruction->GetValue());
+  auto null_checker = GetImplicitNullChecker(instruction);
 
   switch (value_type) {
     case Primitive::kPrimBoolean:
@@ -2013,10 +2021,10 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_1) + data_offset;
-        __ StoreToOffset(kStoreByte, value, obj, offset);
+        __ StoreToOffset(kStoreByte, value, obj, offset, null_checker);
       } else {
         __ Addu(TMP, obj, index.AsRegister<Register>());
-        __ StoreToOffset(kStoreByte, value, TMP, data_offset);
+        __ StoreToOffset(kStoreByte, value, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -2028,11 +2036,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_2) + data_offset;
-        __ StoreToOffset(kStoreHalfword, value, obj, offset);
+        __ StoreToOffset(kStoreHalfword, value, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_2);
         __ Addu(TMP, obj, TMP);
-        __ StoreToOffset(kStoreHalfword, value, TMP, data_offset);
+        __ StoreToOffset(kStoreHalfword, value, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -2045,14 +2053,13 @@
         if (index.IsConstant()) {
           size_t offset =
               (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
-          __ StoreToOffset(kStoreWord, value, obj, offset);
+          __ StoreToOffset(kStoreWord, value, obj, offset, null_checker);
         } else {
           DCHECK(index.IsRegister()) << index;
           __ Sll(TMP, index.AsRegister<Register>(), TIMES_4);
           __ Addu(TMP, obj, TMP);
-          __ StoreToOffset(kStoreWord, value, TMP, data_offset);
+          __ StoreToOffset(kStoreWord, value, TMP, data_offset, null_checker);
         }
-        codegen_->MaybeRecordImplicitNullCheck(instruction);
         if (needs_write_barrier) {
           DCHECK_EQ(value_type, Primitive::kPrimNot);
           codegen_->MarkGCCard(obj, value);
@@ -2075,11 +2082,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + data_offset;
-        __ StoreToOffset(kStoreDoubleword, value, obj, offset);
+        __ StoreToOffset(kStoreDoubleword, value, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_8);
         __ Addu(TMP, obj, TMP);
-        __ StoreToOffset(kStoreDoubleword, value, TMP, data_offset);
+        __ StoreToOffset(kStoreDoubleword, value, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -2091,11 +2098,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + data_offset;
-        __ StoreSToOffset(value, obj, offset);
+        __ StoreSToOffset(value, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_4);
         __ Addu(TMP, obj, TMP);
-        __ StoreSToOffset(value, TMP, data_offset);
+        __ StoreSToOffset(value, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -2107,11 +2114,11 @@
       if (index.IsConstant()) {
         size_t offset =
             (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + data_offset;
-        __ StoreDToOffset(value, obj, offset);
+        __ StoreDToOffset(value, obj, offset, null_checker);
       } else {
         __ Sll(TMP, index.AsRegister<Register>(), TIMES_8);
         __ Addu(TMP, obj, TMP);
-        __ StoreDToOffset(value, TMP, data_offset);
+        __ StoreDToOffset(value, TMP, data_offset, null_checker);
       }
       break;
     }
@@ -2120,11 +2127,6 @@
       LOG(FATAL) << "Unreachable type " << instruction->GetType();
       UNREACHABLE();
   }
-
-  // Ints and objects are handled in the switch.
-  if (value_type != Primitive::kPrimInt && value_type != Primitive::kPrimNot) {
-    codegen_->MaybeRecordImplicitNullCheck(instruction);
-  }
 }
 
 void LocationsBuilderMIPS::VisitBoundsCheck(HBoundsCheck* instruction) {
@@ -3589,6 +3591,7 @@
   LoadOperandType load_type = kLoadUnsignedByte;
   bool is_volatile = field_info.IsVolatile();
   uint32_t offset = field_info.GetFieldOffset().Uint32Value();
+  auto null_checker = GetImplicitNullChecker(instruction);
 
   switch (type) {
     case Primitive::kPrimBoolean:
@@ -3654,34 +3657,20 @@
       if (type == Primitive::kPrimLong) {
         DCHECK(locations->Out().IsRegisterPair());
         dst = locations->Out().AsRegisterPairLow<Register>();
-        Register dst_high = locations->Out().AsRegisterPairHigh<Register>();
-        if (obj == dst) {
-          __ LoadFromOffset(kLoadWord, dst_high, obj, offset + kMipsWordSize);
-          codegen_->MaybeRecordImplicitNullCheck(instruction);
-          __ LoadFromOffset(kLoadWord, dst, obj, offset);
-        } else {
-          __ LoadFromOffset(kLoadWord, dst, obj, offset);
-          codegen_->MaybeRecordImplicitNullCheck(instruction);
-          __ LoadFromOffset(kLoadWord, dst_high, obj, offset + kMipsWordSize);
-        }
       } else {
         DCHECK(locations->Out().IsRegister());
         dst = locations->Out().AsRegister<Register>();
-        __ LoadFromOffset(load_type, dst, obj, offset);
       }
+      __ LoadFromOffset(load_type, dst, obj, offset, null_checker);
     } else {
       DCHECK(locations->Out().IsFpuRegister());
       FRegister dst = locations->Out().AsFpuRegister<FRegister>();
       if (type == Primitive::kPrimFloat) {
-        __ LoadSFromOffset(dst, obj, offset);
+        __ LoadSFromOffset(dst, obj, offset, null_checker);
       } else {
-        __ LoadDFromOffset(dst, obj, offset);
+        __ LoadDFromOffset(dst, obj, offset, null_checker);
       }
     }
-    // Longs are handled earlier.
-    if (type != Primitive::kPrimLong) {
-      codegen_->MaybeRecordImplicitNullCheck(instruction);
-    }
   }
 
   if (is_volatile) {
@@ -3729,6 +3718,7 @@
   StoreOperandType store_type = kStoreByte;
   bool is_volatile = field_info.IsVolatile();
   uint32_t offset = field_info.GetFieldOffset().Uint32Value();
+  auto null_checker = GetImplicitNullChecker(instruction);
 
   switch (type) {
     case Primitive::kPrimBoolean:
@@ -3800,28 +3790,20 @@
       if (type == Primitive::kPrimLong) {
         DCHECK(locations->InAt(1).IsRegisterPair());
         src = locations->InAt(1).AsRegisterPairLow<Register>();
-        Register src_high = locations->InAt(1).AsRegisterPairHigh<Register>();
-        __ StoreToOffset(kStoreWord, src, obj, offset);
-        codegen_->MaybeRecordImplicitNullCheck(instruction);
-        __ StoreToOffset(kStoreWord, src_high, obj, offset + kMipsWordSize);
       } else {
         DCHECK(locations->InAt(1).IsRegister());
         src = locations->InAt(1).AsRegister<Register>();
-        __ StoreToOffset(store_type, src, obj, offset);
       }
+      __ StoreToOffset(store_type, src, obj, offset, null_checker);
     } else {
       DCHECK(locations->InAt(1).IsFpuRegister());
       FRegister src = locations->InAt(1).AsFpuRegister<FRegister>();
       if (type == Primitive::kPrimFloat) {
-        __ StoreSToOffset(src, obj, offset);
+        __ StoreSToOffset(src, obj, offset, null_checker);
       } else {
-        __ StoreDToOffset(src, obj, offset);
+        __ StoreDToOffset(src, obj, offset, null_checker);
       }
     }
-    // Longs are handled earlier.
-    if (type != Primitive::kPrimLong) {
-      codegen_->MaybeRecordImplicitNullCheck(instruction);
-    }
   }
 
   // TODO: memory barriers?
diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h
index 63a0345..46810d6 100644
--- a/compiler/optimizing/code_generator_mips.h
+++ b/compiler/optimizing/code_generator_mips.h
@@ -257,6 +257,7 @@
   void GenerateDivRemWithAnyConstant(HBinaryOperation* instruction);
   void GenerateDivRemIntegral(HBinaryOperation* instruction);
   void HandleGoto(HInstruction* got, HBasicBlock* successor);
+  auto GetImplicitNullChecker(HInstruction* instruction);
 
   MipsAssembler* const assembler_;
   CodeGeneratorMIPS* const codegen_;
diff --git a/compiler/utils/mips/assembler_mips.cc b/compiler/utils/mips/assembler_mips.cc
index 8b7da3f..bfc63d1 100644
--- a/compiler/utils/mips/assembler_mips.cc
+++ b/compiler/utils/mips/assembler_mips.cc
@@ -1407,44 +1407,6 @@
   }
 }
 
-void MipsAssembler::StoreConst32ToOffset(int32_t value,
-                                         Register base,
-                                         int32_t offset,
-                                         Register temp) {
-  CHECK_NE(temp, AT);  // Must not use AT as temp, so as not to overwrite the adjusted base.
-  AdjustBaseAndOffset(base, offset, /* is_doubleword */ false);
-  if (value == 0) {
-    temp = ZERO;
-  } else {
-    LoadConst32(temp, value);
-  }
-  Sw(temp, base, offset);
-}
-
-void MipsAssembler::StoreConst64ToOffset(int64_t value,
-                                         Register base,
-                                         int32_t offset,
-                                         Register temp) {
-  CHECK_NE(temp, AT);  // Must not use AT as temp, so as not to overwrite the adjusted base.
-  AdjustBaseAndOffset(base, offset, /* is_doubleword */ true);
-  uint32_t low = Low32Bits(value);
-  uint32_t high = High32Bits(value);
-  if (low == 0) {
-    Sw(ZERO, base, offset);
-  } else {
-    LoadConst32(temp, low);
-    Sw(temp, base, offset);
-  }
-  if (high == 0) {
-    Sw(ZERO, base, offset + kMipsWordSize);
-  } else {
-    if (high != low) {
-      LoadConst32(temp, high);
-    }
-    Sw(temp, base, offset + kMipsWordSize);
-  }
-}
-
 void MipsAssembler::LoadSConst32(FRegister r, int32_t value, Register temp) {
   if (value == 0) {
     temp = ZERO;
@@ -2533,61 +2495,19 @@
   CHECK_EQ(misalignment, offset & (kMipsDoublewordSize - 1));
 }
 
-void MipsAssembler::LoadFromOffset(LoadOperandType type, Register reg, Register base,
+void MipsAssembler::LoadFromOffset(LoadOperandType type,
+                                   Register reg,
+                                   Register base,
                                    int32_t offset) {
-  AdjustBaseAndOffset(base, offset, /* is_doubleword */ (type == kLoadDoubleword));
-  switch (type) {
-    case kLoadSignedByte:
-      Lb(reg, base, offset);
-      break;
-    case kLoadUnsignedByte:
-      Lbu(reg, base, offset);
-      break;
-    case kLoadSignedHalfword:
-      Lh(reg, base, offset);
-      break;
-    case kLoadUnsignedHalfword:
-      Lhu(reg, base, offset);
-      break;
-    case kLoadWord:
-      Lw(reg, base, offset);
-      break;
-    case kLoadDoubleword:
-      if (reg == base) {
-        // This will clobber the base when loading the lower register. Since we have to load the
-        // higher register as well, this will fail. Solution: reverse the order.
-        Lw(static_cast<Register>(reg + 1), base, offset + kMipsWordSize);
-        Lw(reg, base, offset);
-      } else {
-        Lw(reg, base, offset);
-        Lw(static_cast<Register>(reg + 1), base, offset + kMipsWordSize);
-      }
-      break;
-    default:
-      LOG(FATAL) << "UNREACHABLE";
-  }
+  LoadFromOffset<>(type, reg, base, offset);
 }
 
 void MipsAssembler::LoadSFromOffset(FRegister reg, Register base, int32_t offset) {
-  AdjustBaseAndOffset(base, offset, /* is_doubleword */ false, /* is_float */ true);
-  Lwc1(reg, base, offset);
+  LoadSFromOffset<>(reg, base, offset);
 }
 
 void MipsAssembler::LoadDFromOffset(FRegister reg, Register base, int32_t offset) {
-  AdjustBaseAndOffset(base, offset, /* is_doubleword */ true, /* is_float */ true);
-  if (offset & 0x7) {
-    if (Is32BitFPU()) {
-      Lwc1(reg, base, offset);
-      Lwc1(static_cast<FRegister>(reg + 1), base, offset + kMipsWordSize);
-    } else {
-      // 64-bit FPU.
-      Lwc1(reg, base, offset);
-      Lw(T8, base, offset + kMipsWordSize);
-      Mthc1(T8, reg);
-    }
-  } else {
-    Ldc1(reg, base, offset);
-  }
+  LoadDFromOffset<>(reg, base, offset);
 }
 
 void MipsAssembler::EmitLoad(ManagedRegister m_dst, Register src_register, int32_t src_offset,
@@ -2611,53 +2531,19 @@
   }
 }
 
-void MipsAssembler::StoreToOffset(StoreOperandType type, Register reg, Register base,
+void MipsAssembler::StoreToOffset(StoreOperandType type,
+                                  Register reg,
+                                  Register base,
                                   int32_t offset) {
-  // Must not use AT as `reg`, so as not to overwrite the value being stored
-  // with the adjusted `base`.
-  CHECK_NE(reg, AT);
-  AdjustBaseAndOffset(base, offset, /* is_doubleword */ (type == kStoreDoubleword));
-  switch (type) {
-    case kStoreByte:
-      Sb(reg, base, offset);
-      break;
-    case kStoreHalfword:
-      Sh(reg, base, offset);
-      break;
-    case kStoreWord:
-      Sw(reg, base, offset);
-      break;
-    case kStoreDoubleword:
-      CHECK_NE(reg, base);
-      CHECK_NE(static_cast<Register>(reg + 1), base);
-      Sw(reg, base, offset);
-      Sw(static_cast<Register>(reg + 1), base, offset + kMipsWordSize);
-      break;
-    default:
-      LOG(FATAL) << "UNREACHABLE";
-  }
+  StoreToOffset<>(type, reg, base, offset);
 }
 
 void MipsAssembler::StoreSToOffset(FRegister reg, Register base, int32_t offset) {
-  AdjustBaseAndOffset(base, offset, /* is_doubleword */ false, /* is_float */ true);
-  Swc1(reg, base, offset);
+  StoreSToOffset<>(reg, base, offset);
 }
 
 void MipsAssembler::StoreDToOffset(FRegister reg, Register base, int32_t offset) {
-  AdjustBaseAndOffset(base, offset, /* is_doubleword */ true, /* is_float */ true);
-  if (offset & 0x7) {
-    if (Is32BitFPU()) {
-      Swc1(reg, base, offset);
-      Swc1(static_cast<FRegister>(reg + 1), base, offset + kMipsWordSize);
-    } else {
-      // 64-bit FPU.
-      Mfhc1(T8, reg);
-      Swc1(reg, base, offset);
-      Sw(T8, base, offset + kMipsWordSize);
-    }
-  } else {
-    Sdc1(reg, base, offset);
-  }
+  StoreDToOffset<>(reg, base, offset);
 }
 
 static dwarf::Reg DWARFReg(Register reg) {
diff --git a/compiler/utils/mips/assembler_mips.h b/compiler/utils/mips/assembler_mips.h
index 41b6c6b..434ca67 100644
--- a/compiler/utils/mips/assembler_mips.h
+++ b/compiler/utils/mips/assembler_mips.h
@@ -412,8 +412,6 @@
   void LoadConst64(Register reg_hi, Register reg_lo, int64_t value);
   void LoadDConst64(FRegister rd, int64_t value, Register temp);
   void LoadSConst32(FRegister r, int32_t value, Register temp);
-  void StoreConst32ToOffset(int32_t value, Register base, int32_t offset, Register temp);
-  void StoreConst64ToOffset(int64_t value, Register base, int32_t offset, Register temp);
   void Addiu32(Register rt, Register rs, int32_t value, Register rtmp = AT);
 
   // These will generate R2 branches or R6 branches as appropriate.
@@ -444,6 +442,204 @@
                            int32_t& offset,
                            bool is_doubleword,
                            bool is_float = false);
+
+ private:
+  struct NoImplicitNullChecker {
+    void operator()() {}
+  };
+
+ public:
+  template <typename ImplicitNullChecker = NoImplicitNullChecker>
+  void StoreConst32ToOffset(int32_t value,
+                            Register base,
+                            int32_t offset,
+                            Register temp,
+                            ImplicitNullChecker null_checker = NoImplicitNullChecker()) {
+    CHECK_NE(temp, AT);  // Must not use AT as temp, so as not to overwrite the adjusted base.
+    AdjustBaseAndOffset(base, offset, /* is_doubleword */ false);
+    if (value == 0) {
+      temp = ZERO;
+    } else {
+      LoadConst32(temp, value);
+    }
+    Sw(temp, base, offset);
+    null_checker();
+  }
+
+  template <typename ImplicitNullChecker = NoImplicitNullChecker>
+  void StoreConst64ToOffset(int64_t value,
+                            Register base,
+                            int32_t offset,
+                            Register temp,
+                            ImplicitNullChecker null_checker = NoImplicitNullChecker()) {
+    CHECK_NE(temp, AT);  // Must not use AT as temp, so as not to overwrite the adjusted base.
+    AdjustBaseAndOffset(base, offset, /* is_doubleword */ true);
+    uint32_t low = Low32Bits(value);
+    uint32_t high = High32Bits(value);
+    if (low == 0) {
+      Sw(ZERO, base, offset);
+    } else {
+      LoadConst32(temp, low);
+      Sw(temp, base, offset);
+    }
+    null_checker();
+    if (high == 0) {
+      Sw(ZERO, base, offset + kMipsWordSize);
+    } else {
+      if (high != low) {
+        LoadConst32(temp, high);
+      }
+      Sw(temp, base, offset + kMipsWordSize);
+    }
+  }
+
+  template <typename ImplicitNullChecker = NoImplicitNullChecker>
+  void LoadFromOffset(LoadOperandType type,
+                      Register reg,
+                      Register base,
+                      int32_t offset,
+                      ImplicitNullChecker null_checker = NoImplicitNullChecker()) {
+    AdjustBaseAndOffset(base, offset, /* is_doubleword */ (type == kLoadDoubleword));
+    switch (type) {
+      case kLoadSignedByte:
+        Lb(reg, base, offset);
+        break;
+      case kLoadUnsignedByte:
+        Lbu(reg, base, offset);
+        break;
+      case kLoadSignedHalfword:
+        Lh(reg, base, offset);
+        break;
+      case kLoadUnsignedHalfword:
+        Lhu(reg, base, offset);
+        break;
+      case kLoadWord:
+        Lw(reg, base, offset);
+        break;
+      case kLoadDoubleword:
+        if (reg == base) {
+          // This will clobber the base when loading the lower register. Since we have to load the
+          // higher register as well, this will fail. Solution: reverse the order.
+          Lw(static_cast<Register>(reg + 1), base, offset + kMipsWordSize);
+          null_checker();
+          Lw(reg, base, offset);
+        } else {
+          Lw(reg, base, offset);
+          null_checker();
+          Lw(static_cast<Register>(reg + 1), base, offset + kMipsWordSize);
+        }
+        break;
+      default:
+        LOG(FATAL) << "UNREACHABLE";
+    }
+    if (type != kLoadDoubleword) {
+      null_checker();
+    }
+  }
+
+  template <typename ImplicitNullChecker = NoImplicitNullChecker>
+  void LoadSFromOffset(FRegister reg,
+                       Register base,
+                       int32_t offset,
+                       ImplicitNullChecker null_checker = NoImplicitNullChecker()) {
+    AdjustBaseAndOffset(base, offset, /* is_doubleword */ false, /* is_float */ true);
+    Lwc1(reg, base, offset);
+    null_checker();
+  }
+
+  template <typename ImplicitNullChecker = NoImplicitNullChecker>
+  void LoadDFromOffset(FRegister reg,
+                       Register base,
+                       int32_t offset,
+                       ImplicitNullChecker null_checker = NoImplicitNullChecker()) {
+    AdjustBaseAndOffset(base, offset, /* is_doubleword */ true, /* is_float */ true);
+    if (IsAligned<kMipsDoublewordSize>(offset)) {
+      Ldc1(reg, base, offset);
+      null_checker();
+    } else {
+      if (Is32BitFPU()) {
+        Lwc1(reg, base, offset);
+        null_checker();
+        Lwc1(static_cast<FRegister>(reg + 1), base, offset + kMipsWordSize);
+      } else {
+        // 64-bit FPU.
+        Lwc1(reg, base, offset);
+        null_checker();
+        Lw(T8, base, offset + kMipsWordSize);
+        Mthc1(T8, reg);
+      }
+    }
+  }
+
+  template <typename ImplicitNullChecker = NoImplicitNullChecker>
+  void StoreToOffset(StoreOperandType type,
+                     Register reg,
+                     Register base,
+                     int32_t offset,
+                     ImplicitNullChecker null_checker = NoImplicitNullChecker()) {
+    // Must not use AT as `reg`, so as not to overwrite the value being stored
+    // with the adjusted `base`.
+    CHECK_NE(reg, AT);
+    AdjustBaseAndOffset(base, offset, /* is_doubleword */ (type == kStoreDoubleword));
+    switch (type) {
+      case kStoreByte:
+        Sb(reg, base, offset);
+        break;
+      case kStoreHalfword:
+        Sh(reg, base, offset);
+        break;
+      case kStoreWord:
+        Sw(reg, base, offset);
+        break;
+      case kStoreDoubleword:
+        CHECK_NE(reg, base);
+        CHECK_NE(static_cast<Register>(reg + 1), base);
+        Sw(reg, base, offset);
+        null_checker();
+        Sw(static_cast<Register>(reg + 1), base, offset + kMipsWordSize);
+        break;
+      default:
+        LOG(FATAL) << "UNREACHABLE";
+    }
+    if (type != kStoreDoubleword) {
+      null_checker();
+    }
+  }
+
+  template <typename ImplicitNullChecker = NoImplicitNullChecker>
+  void StoreSToOffset(FRegister reg,
+                      Register base,
+                      int32_t offset,
+                      ImplicitNullChecker null_checker = NoImplicitNullChecker()) {
+    AdjustBaseAndOffset(base, offset, /* is_doubleword */ false, /* is_float */ true);
+    Swc1(reg, base, offset);
+    null_checker();
+  }
+
+  template <typename ImplicitNullChecker = NoImplicitNullChecker>
+  void StoreDToOffset(FRegister reg,
+                      Register base,
+                      int32_t offset,
+                      ImplicitNullChecker null_checker = NoImplicitNullChecker()) {
+    AdjustBaseAndOffset(base, offset, /* is_doubleword */ true, /* is_float */ true);
+    if (IsAligned<kMipsDoublewordSize>(offset)) {
+      Sdc1(reg, base, offset);
+      null_checker();
+    } else {
+      if (Is32BitFPU()) {
+        Swc1(reg, base, offset);
+        null_checker();
+        Swc1(static_cast<FRegister>(reg + 1), base, offset + kMipsWordSize);
+      } else {
+        // 64-bit FPU.
+        Mfhc1(T8, reg);
+        Swc1(reg, base, offset);
+        null_checker();
+        Sw(T8, base, offset + kMipsWordSize);
+      }
+    }
+  }
+
   void LoadFromOffset(LoadOperandType type, Register reg, Register base, int32_t offset);
   void LoadSFromOffset(FRegister reg, Register base, int32_t offset);
   void LoadDFromOffset(FRegister reg, Register base, int32_t offset);
diff --git a/test/551-implicit-null-checks/expected.txt b/test/551-implicit-null-checks/expected.txt
index e69de29..49b3771 100644
--- a/test/551-implicit-null-checks/expected.txt
+++ b/test/551-implicit-null-checks/expected.txt
@@ -0,0 +1,4 @@
+NPE from GetLong
+NPE from PutLong
+NPE from GetDouble
+NPE from PutDouble
diff --git a/test/551-implicit-null-checks/info.txt b/test/551-implicit-null-checks/info.txt
index bdd066b..bd3ecfd 100644
--- a/test/551-implicit-null-checks/info.txt
+++ b/test/551-implicit-null-checks/info.txt
@@ -1 +1 @@
-Test that implicit null checks are recorded correctly for longs.
\ No newline at end of file
+Test that implicit null checks are recorded correctly for longs and doubles.
diff --git a/test/551-implicit-null-checks/src/Main.java b/test/551-implicit-null-checks/src/Main.java
index 677e8d3..3586a29 100644
--- a/test/551-implicit-null-checks/src/Main.java
+++ b/test/551-implicit-null-checks/src/Main.java
@@ -18,6 +18,7 @@
 
   private class Inner {
     private long i1;
+    private double i2;
   }
   private Inner inst;
 
@@ -26,12 +27,22 @@
     try {
       m.$opt$noinline$testGetLong();
     } catch (NullPointerException ex) {
-      // good
+      System.out.println("NPE from GetLong");
     }
     try {
       m.$opt$noinline$testPutLong(778899112233L);
     } catch (NullPointerException ex) {
-      // good
+      System.out.println("NPE from PutLong");
+    }
+    try {
+      m.$opt$noinline$testGetDouble();
+    } catch (NullPointerException ex) {
+      System.out.println("NPE from GetDouble");
+    }
+    try {
+      m.$opt$noinline$testPutDouble(1.0);
+    } catch (NullPointerException ex) {
+      System.out.println("NPE from PutDouble");
     }
   }
 
@@ -44,4 +55,14 @@
     inst.i1 = a;
     throw new Exception();  // prevent inline
   }
+
+  public void $opt$noinline$testGetDouble() throws Exception {
+    double result = inst.i2;
+    throw new Exception();  // prevent inline
+  }
+
+  public void $opt$noinline$testPutDouble(double a) throws Exception {
+    inst.i2 = a;
+    throw new Exception();  // prevent inline
+  }
 }