Fix and rewrite local value numbering.

Fix memory versioning to take aliasing and method calls
into account. Use more instructions for the null check
elimination. Return the local value name of the register
defined by the instruction if applicable.

Change-Id: I4560bc680ae1ad553a7a00fa092c937e3da9fbbe
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 791e954..3295d86 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -17,6 +17,7 @@
 LOCAL_PATH := art
 
 TEST_COMMON_SRC_FILES := \
+	compiler/dex/local_value_numbering_test.cc \
 	compiler/driver/compiler_driver_test.cc \
 	compiler/elf_writer_test.cc \
 	compiler/image_test.cc \
diff --git a/compiler/dex/local_value_numbering.cc b/compiler/dex/local_value_numbering.cc
index 9e83210..1384643 100644
--- a/compiler/dex/local_value_numbering.cc
+++ b/compiler/dex/local_value_numbering.cc
@@ -16,8 +16,121 @@
 
 #include "local_value_numbering.h"
 
+#include "mir_annotations.h"
+#include "mir_graph.h"
+
 namespace art {
 
+uint16_t LocalValueNumbering::GetFieldId(const DexFile* dex_file, uint16_t field_idx) {
+  FieldReference key = { dex_file, field_idx };
+  auto it = field_index_map_.find(key);
+  if (it != field_index_map_.end()) {
+    return it->second;
+  }
+  uint16_t id = field_index_map_.size();
+  field_index_map_.Put(key, id);
+  return id;
+}
+
+void LocalValueNumbering::AdvanceGlobalMemory() {
+  // See AdvanceMemoryVersion() for explanation.
+  global_memory_version_ = next_memory_version_;
+  ++next_memory_version_;
+}
+
+uint16_t LocalValueNumbering::GetMemoryVersion(uint16_t base, uint16_t field, uint16_t type) {
+  // See AdvanceMemoryVersion() for explanation.
+  MemoryVersionKey key = { base, field, type };
+  MemoryVersionMap::iterator it = memory_version_map_.find(key);
+  uint16_t memory_version = (it != memory_version_map_.end()) ? it->second : 0u;
+  if (base != NO_VALUE && non_aliasing_refs_.find(base) == non_aliasing_refs_.end()) {
+    // Check modifications by potentially aliased access.
+    MemoryVersionKey aliased_access_key = { NO_VALUE, field, type };
+    auto aa_it = memory_version_map_.find(aliased_access_key);
+    if (aa_it != memory_version_map_.end() && aa_it->second > memory_version) {
+      memory_version = aa_it->second;
+    }
+    memory_version = std::max(memory_version, global_memory_version_);
+  } else if (base != NO_VALUE) {
+    // Ignore global_memory_version_ for access via unique references.
+  } else {
+    memory_version = std::max(memory_version, global_memory_version_);
+  }
+  return memory_version;
+};
+
+uint16_t LocalValueNumbering::AdvanceMemoryVersion(uint16_t base, uint16_t field, uint16_t type) {
+  // When we read the same value from memory, we want to assign the same value name to it.
+  // However, we need to be careful not to assign the same value name if the memory location
+  // may have been written to between the reads. To avoid that we do "memory versioning".
+  //
+  // For each write to a memory location (instance field, static field, array element) we assign
+  // a new memory version number to the location identified by the value name of the base register,
+  // the field id and type, or "{ base, field, type }". For static fields the "base" is NO_VALUE
+  // since they are not accessed via a reference. For arrays the "field" is NO_VALUE since they
+  // don't have a field id.
+  //
+  // To account for the possibility of aliased access to the same memory location via different
+  // "base", we also store the memory version number with the key "{ NO_VALUE, field, type }"
+  // if "base" is an aliasing reference and check it in GetMemoryVersion() on reads via
+  // aliasing references. A global memory version is set for method calls as a method can
+  // potentially write to any memory location accessed via an aliasing reference.
+
+  uint16_t result = next_memory_version_;
+  ++next_memory_version_;
+  MemoryVersionKey key = { base, field, type };
+  memory_version_map_.Overwrite(key, result);
+  if (base != NO_VALUE && non_aliasing_refs_.find(base) == non_aliasing_refs_.end()) {
+    // Advance memory version for aliased access.
+    MemoryVersionKey aliased_access_key = { NO_VALUE, field, type };
+    memory_version_map_.Overwrite(aliased_access_key, result);
+  }
+  return result;
+};
+
+uint16_t LocalValueNumbering::MarkNonAliasingNonNull(MIR* mir) {
+  uint16_t res = GetOperandValue(mir->ssa_rep->defs[0]);
+  SetOperandValue(mir->ssa_rep->defs[0], res);
+  DCHECK(null_checked_.find(res) == null_checked_.end());
+  null_checked_.insert(res);
+  non_aliasing_refs_.insert(res);
+  return res;
+}
+
+void LocalValueNumbering::MakeArgsAliasing(MIR* mir) {
+  for (size_t i = 0u, count = mir->ssa_rep->num_uses; i != count; ++i) {
+    uint16_t reg = GetOperandValue(mir->ssa_rep->uses[i]);
+    non_aliasing_refs_.erase(reg);
+  }
+}
+
+void LocalValueNumbering::HandleNullCheck(MIR* mir, uint16_t reg) {
+  if (null_checked_.find(reg) != null_checked_.end()) {
+    if (cu_->verbose) {
+      LOG(INFO) << "Removing null check for 0x" << std::hex << mir->offset;
+    }
+    mir->optimization_flags |= MIR_IGNORE_NULL_CHECK;
+  } else {
+    null_checked_.insert(reg);
+  }
+}
+
+void LocalValueNumbering::HandleRangeCheck(MIR* mir, uint16_t array, uint16_t index) {
+  if (ValueExists(ARRAY_REF, array, index, NO_VALUE)) {
+    if (cu_->verbose) {
+      LOG(INFO) << "Removing range check for 0x" << std::hex << mir->offset;
+    }
+    mir->optimization_flags |= MIR_IGNORE_RANGE_CHECK;
+  }
+  // Use side effect to note range check completed.
+  (void)LookupValue(ARRAY_REF, array, index, NO_VALUE);
+}
+
+void LocalValueNumbering::HandlePutObject(MIR* mir) {
+  // If we're storing a non-aliasing reference, stop tracking it as non-aliasing now.
+  uint16_t base = GetOperandValue(mir->ssa_rep->uses[0]);
+  non_aliasing_refs_.erase(base);
+}
 
 uint16_t LocalValueNumbering::GetValueNumber(MIR* mir) {
   uint16_t res = NO_VALUE;
@@ -36,8 +149,6 @@
     case Instruction::CHECK_CAST:
     case Instruction::THROW:
     case Instruction::FILL_ARRAY_DATA:
-    case Instruction::FILLED_NEW_ARRAY:
-    case Instruction::FILLED_NEW_ARRAY_RANGE:
     case Instruction::PACKED_SWITCH:
     case Instruction::SPARSE_SWITCH:
     case Instruction::IF_EQ:
@@ -52,16 +163,6 @@
     case Instruction::IF_GEZ:
     case Instruction::IF_GTZ:
     case Instruction::IF_LEZ:
-    case Instruction::INVOKE_STATIC_RANGE:
-    case Instruction::INVOKE_STATIC:
-    case Instruction::INVOKE_DIRECT:
-    case Instruction::INVOKE_DIRECT_RANGE:
-    case Instruction::INVOKE_VIRTUAL:
-    case Instruction::INVOKE_VIRTUAL_RANGE:
-    case Instruction::INVOKE_SUPER:
-    case Instruction::INVOKE_SUPER_RANGE:
-    case Instruction::INVOKE_INTERFACE:
-    case Instruction::INVOKE_INTERFACE_RANGE:
     case kMirOpFusedCmplFloat:
     case kMirOpFusedCmpgFloat:
     case kMirOpFusedCmplDouble:
@@ -70,25 +171,55 @@
       // Nothing defined - take no action.
       break;
 
-    case Instruction::MOVE_EXCEPTION:
+    case Instruction::FILLED_NEW_ARRAY:
+    case Instruction::FILLED_NEW_ARRAY_RANGE:
+      // Nothing defined but the result will be unique and non-null.
+      if (mir->next != nullptr && mir->next->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT) {
+        MarkNonAliasingNonNull(mir->next);
+        // The MOVE_RESULT_OBJECT will be processed next and we'll return the value name then.
+      }
+      MakeArgsAliasing(mir);
+      break;
+
+    case Instruction::INVOKE_DIRECT:
+    case Instruction::INVOKE_DIRECT_RANGE:
+    case Instruction::INVOKE_VIRTUAL:
+    case Instruction::INVOKE_VIRTUAL_RANGE:
+    case Instruction::INVOKE_SUPER:
+    case Instruction::INVOKE_SUPER_RANGE:
+    case Instruction::INVOKE_INTERFACE:
+    case Instruction::INVOKE_INTERFACE_RANGE: {
+        // Nothing defined but handle the null check.
+        uint16_t reg = GetOperandValue(mir->ssa_rep->uses[0]);
+        HandleNullCheck(mir, reg);
+      }
+      // Intentional fall-through.
+    case Instruction::INVOKE_STATIC:
+    case Instruction::INVOKE_STATIC_RANGE:
+      AdvanceGlobalMemory();
+      MakeArgsAliasing(mir);
+      break;
+
     case Instruction::MOVE_RESULT:
     case Instruction::MOVE_RESULT_OBJECT:
     case Instruction::INSTANCE_OF:
+      // 1 result, treat as unique each time, use result s_reg - will be unique.
+      res = GetOperandValue(mir->ssa_rep->defs[0]);
+      SetOperandValue(mir->ssa_rep->defs[0], res);
+      break;
+    case Instruction::MOVE_EXCEPTION:
     case Instruction::NEW_INSTANCE:
     case Instruction::CONST_STRING:
     case Instruction::CONST_STRING_JUMBO:
     case Instruction::CONST_CLASS:
-    case Instruction::NEW_ARRAY: {
-        // 1 result, treat as unique each time, use result s_reg - will be unique.
-        uint16_t res = GetOperandValue(mir->ssa_rep->defs[0]);
-        SetOperandValue(mir->ssa_rep->defs[0], res);
-      }
+    case Instruction::NEW_ARRAY:
+      // 1 result, treat as unique each time, use result s_reg - will be unique.
+      res = MarkNonAliasingNonNull(mir);
       break;
-    case Instruction::MOVE_RESULT_WIDE: {
-        // 1 wide result, treat as unique each time, use result s_reg - will be unique.
-        uint16_t res = GetOperandValueWide(mir->ssa_rep->defs[0]);
-        SetOperandValueWide(mir->ssa_rep->defs[0], res);
-      }
+    case Instruction::MOVE_RESULT_WIDE:
+      // 1 wide result, treat as unique each time, use result s_reg - will be unique.
+      res = GetOperandValueWide(mir->ssa_rep->defs[0]);
+      SetOperandValueWide(mir->ssa_rep->defs[0], res);
       break;
 
     case kMirOpPhi:
@@ -104,35 +235,31 @@
     case Instruction::MOVE_OBJECT_16:
     case Instruction::MOVE_FROM16:
     case Instruction::MOVE_OBJECT_FROM16:
-    case kMirOpCopy: {
-        // Just copy value number of source to value number of resulit.
-        uint16_t res = GetOperandValue(mir->ssa_rep->uses[0]);
-        SetOperandValue(mir->ssa_rep->defs[0], res);
-      }
+    case kMirOpCopy:
+      // Just copy value number of source to value number of result.
+      res = GetOperandValue(mir->ssa_rep->uses[0]);
+      SetOperandValue(mir->ssa_rep->defs[0], res);
       break;
 
     case Instruction::MOVE_WIDE:
     case Instruction::MOVE_WIDE_16:
-    case Instruction::MOVE_WIDE_FROM16: {
-        // Just copy value number of source to value number of result.
-        uint16_t res = GetOperandValueWide(mir->ssa_rep->uses[0]);
-        SetOperandValueWide(mir->ssa_rep->defs[0], res);
-      }
+    case Instruction::MOVE_WIDE_FROM16:
+      // Just copy value number of source to value number of result.
+      res = GetOperandValueWide(mir->ssa_rep->uses[0]);
+      SetOperandValueWide(mir->ssa_rep->defs[0], res);
       break;
 
     case Instruction::CONST:
     case Instruction::CONST_4:
-    case Instruction::CONST_16: {
-        uint16_t res = LookupValue(Instruction::CONST, Low16Bits(mir->dalvikInsn.vB),
-                                   High16Bits(mir->dalvikInsn.vB >> 16), 0);
-        SetOperandValue(mir->ssa_rep->defs[0], res);
-      }
+    case Instruction::CONST_16:
+      res = LookupValue(Instruction::CONST, Low16Bits(mir->dalvikInsn.vB),
+                        High16Bits(mir->dalvikInsn.vB >> 16), 0);
+      SetOperandValue(mir->ssa_rep->defs[0], res);
       break;
 
-    case Instruction::CONST_HIGH16: {
-        uint16_t res = LookupValue(Instruction::CONST, 0, mir->dalvikInsn.vB, 0);
-        SetOperandValue(mir->ssa_rep->defs[0], res);
-      }
+    case Instruction::CONST_HIGH16:
+      res = LookupValue(Instruction::CONST, 0, mir->dalvikInsn.vB, 0);
+      SetOperandValue(mir->ssa_rep->defs[0], res);
       break;
 
     case Instruction::CONST_WIDE_16:
@@ -145,8 +272,8 @@
         } else {
           high_res = LookupValue(Instruction::CONST, 0, 0, 2);
         }
-        uint16_t res = LookupValue(Instruction::CONST, low_res, high_res, 3);
-        SetOperandValue(mir->ssa_rep->defs[0], res);
+        res = LookupValue(Instruction::CONST, low_res, high_res, 3);
+        SetOperandValueWide(mir->ssa_rep->defs[0], res);
       }
       break;
 
@@ -157,7 +284,7 @@
                                        High16Bits(low_word), 1);
         uint16_t high_res = LookupValue(Instruction::CONST, Low16Bits(high_word),
                                        High16Bits(high_word), 2);
-        uint16_t res = LookupValue(Instruction::CONST, low_res, high_res, 3);
+        res = LookupValue(Instruction::CONST, low_res, high_res, 3);
         SetOperandValueWide(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -165,7 +292,7 @@
     case Instruction::CONST_WIDE_HIGH16: {
         uint16_t low_res = LookupValue(Instruction::CONST, 0, 0, 1);
         uint16_t high_res = LookupValue(Instruction::CONST, 0, Low16Bits(mir->dalvikInsn.vB), 2);
-        uint16_t res = LookupValue(Instruction::CONST, low_res, high_res, 3);
+        res = LookupValue(Instruction::CONST, low_res, high_res, 3);
         SetOperandValueWide(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -181,7 +308,7 @@
     case Instruction::FLOAT_TO_INT: {
         // res = op + 1 operand
         uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]);
-        uint16_t res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE);
+        res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE);
         SetOperandValue(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -192,7 +319,7 @@
     case Instruction::DOUBLE_TO_INT: {
         // res = op + 1 wide operand
         uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]);
-        uint16_t res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE);
+        res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE);
         SetOperandValue(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -205,7 +332,7 @@
     case Instruction::NEG_DOUBLE: {
         // wide res = op + 1 wide operand
         uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]);
-        uint16_t res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE);
+        res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE);
         SetOperandValueWide(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -216,7 +343,7 @@
     case Instruction::INT_TO_LONG: {
         // wide res = op + 1 operand
         uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]);
-        uint16_t res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE);
+        res = LookupValue(opcode, operand1, NO_VALUE, NO_VALUE);
         SetOperandValueWide(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -227,7 +354,7 @@
         // res = op + 2 wide operands
         uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]);
         uint16_t operand2 = GetOperandValueWide(mir->ssa_rep->uses[2]);
-        uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE);
+        res = LookupValue(opcode, operand1, operand2, NO_VALUE);
         SetOperandValue(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -259,7 +386,7 @@
         // res = op + 2 operands
         uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]);
         uint16_t operand2 = GetOperandValue(mir->ssa_rep->uses[1]);
-        uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE);
+        res = LookupValue(opcode, operand1, operand2, NO_VALUE);
         SetOperandValue(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -293,7 +420,7 @@
         // wide res = op + 2 wide operands
         uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]);
         uint16_t operand2 = GetOperandValueWide(mir->ssa_rep->uses[2]);
-        uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE);
+        res = LookupValue(opcode, operand1, operand2, NO_VALUE);
         SetOperandValueWide(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -307,7 +434,7 @@
         // wide res = op + 1 wide operand + 1 operand
         uint16_t operand1 = GetOperandValueWide(mir->ssa_rep->uses[0]);
         uint16_t operand2 = GetOperandValueWide(mir->ssa_rep->uses[2]);
-        uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE);
+        res = LookupValue(opcode, operand1, operand2, NO_VALUE);
         SetOperandValueWide(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -325,7 +452,7 @@
         // res = op + 2 operands
         uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]);
         uint16_t operand2 = GetOperandValue(mir->ssa_rep->uses[1]);
-        uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE);
+        res = LookupValue(opcode, operand1, operand2, NO_VALUE);
         SetOperandValue(mir->ssa_rep->defs[0], res);
       }
       break;
@@ -352,38 +479,25 @@
         // Same as res = op + 2 operands, except use vB as operand 2
         uint16_t operand1 = GetOperandValue(mir->ssa_rep->uses[0]);
         uint16_t operand2 = LookupValue(Instruction::CONST, mir->dalvikInsn.vB, 0, 0);
-        uint16_t res = LookupValue(opcode, operand1, operand2, NO_VALUE);
+        res = LookupValue(opcode, operand1, operand2, NO_VALUE);
         SetOperandValue(mir->ssa_rep->defs[0], res);
       }
       break;
 
-    case Instruction::AGET_WIDE:
-    case Instruction::AGET:
     case Instruction::AGET_OBJECT:
+    case Instruction::AGET:
+    case Instruction::AGET_WIDE:
     case Instruction::AGET_BOOLEAN:
     case Instruction::AGET_BYTE:
     case Instruction::AGET_CHAR:
     case Instruction::AGET_SHORT: {
+        uint16_t type = opcode - Instruction::AGET;
         uint16_t array = GetOperandValue(mir->ssa_rep->uses[0]);
-        if (null_checked_.find(array) != null_checked_.end()) {
-          if (cu_->verbose) {
-            LOG(INFO) << "Removing null check for 0x" << std::hex << mir->offset;
-          }
-          mir->optimization_flags |= MIR_IGNORE_NULL_CHECK;
-        } else {
-          null_checked_.insert(array);
-        }
+        HandleNullCheck(mir, array);
         uint16_t index = GetOperandValue(mir->ssa_rep->uses[1]);
-        if (ValueExists(ARRAY_REF, array, index, NO_VALUE)) {
-          if (cu_->verbose) {
-            LOG(INFO) << "Removing range check for 0x" << std::hex << mir->offset;
-          }
-          mir->optimization_flags |= MIR_IGNORE_RANGE_CHECK;
-        }
-        // Use side effect to note range check completed.
-        (void)LookupValue(ARRAY_REF, array, index, NO_VALUE);
+        HandleRangeCheck(mir, array, index);
         // Establish value number for loaded register. Note use of memory version.
-        uint16_t memory_version = GetMemoryVersion(array, NO_VALUE);
+        uint16_t memory_version = GetMemoryVersion(array, NO_VALUE, type);
         uint16_t res = LookupValue(ARRAY_REF, array, index, memory_version);
         if (opcode == Instruction::AGET_WIDE) {
           SetOperandValueWide(mir->ssa_rep->defs[0], res);
@@ -393,116 +507,145 @@
       }
       break;
 
-    case Instruction::APUT_WIDE:
-    case Instruction::APUT:
     case Instruction::APUT_OBJECT:
-    case Instruction::APUT_SHORT:
-    case Instruction::APUT_CHAR:
+      HandlePutObject(mir);
+      // Intentional fall-through.
+    case Instruction::APUT:
+    case Instruction::APUT_WIDE:
     case Instruction::APUT_BYTE:
-    case Instruction::APUT_BOOLEAN: {
+    case Instruction::APUT_BOOLEAN:
+    case Instruction::APUT_SHORT:
+    case Instruction::APUT_CHAR: {
+        uint16_t type = opcode - Instruction::APUT;
         int array_idx = (opcode == Instruction::APUT_WIDE) ? 2 : 1;
         int index_idx = array_idx + 1;
         uint16_t array = GetOperandValue(mir->ssa_rep->uses[array_idx]);
-        if (null_checked_.find(array) != null_checked_.end()) {
-          if (cu_->verbose) {
-            LOG(INFO) << "Removing range check for 0x" << std::hex << mir->offset;
-          }
-          mir->optimization_flags |= MIR_IGNORE_NULL_CHECK;
-        } else {
-          null_checked_.insert(array);
-        }
+        HandleNullCheck(mir, array);
         uint16_t index = GetOperandValue(mir->ssa_rep->uses[index_idx]);
-        if (ValueExists(ARRAY_REF, array, index, NO_VALUE)) {
-          if (cu_->verbose) {
-            LOG(INFO) << "Removing range check for 0x" << std::hex << mir->offset;
-          }
-          mir->optimization_flags |= MIR_IGNORE_RANGE_CHECK;
-        }
-        // Use side effect to note range check completed.
-        (void)LookupValue(ARRAY_REF, array, index, NO_VALUE);
+        HandleRangeCheck(mir, array, index);
         // Rev the memory version
-        AdvanceMemoryVersion(array, NO_VALUE);
+        AdvanceMemoryVersion(array, NO_VALUE, type);
       }
       break;
 
     case Instruction::IGET_OBJECT:
-    case Instruction::IGET_WIDE:
     case Instruction::IGET:
-    case Instruction::IGET_CHAR:
-    case Instruction::IGET_SHORT:
+    case Instruction::IGET_WIDE:
     case Instruction::IGET_BOOLEAN:
-    case Instruction::IGET_BYTE: {
+    case Instruction::IGET_BYTE:
+    case Instruction::IGET_CHAR:
+    case Instruction::IGET_SHORT: {
+        uint16_t type = opcode - Instruction::IGET;
         uint16_t base = GetOperandValue(mir->ssa_rep->uses[0]);
-        if (null_checked_.find(base) != null_checked_.end()) {
-          if (cu_->verbose) {
-            LOG(INFO) << "Removing null check for 0x" << std::hex << mir->offset;
-          }
-          mir->optimization_flags |= MIR_IGNORE_NULL_CHECK;
+        HandleNullCheck(mir, base);
+        const IFieldAnnotation& annotation = cu_->mir_graph->GetIFieldAnnotation(mir);
+        uint16_t memory_version;
+        uint16_t field_id;
+        if (annotation.IsVolatile()) {
+          // Volatile fields always get a new memory version; field id is irrelevant.
+          // Unresolved fields are always marked as volatile and handled the same way here.
+          field_id = 0u;
+          memory_version = next_memory_version_;
+          ++next_memory_version_;
         } else {
-          null_checked_.insert(base);
+          DCHECK(annotation.IsResolved());
+          field_id = GetFieldId(annotation.DeclaringDexFile(), annotation.DeclaringFieldIndex());
+          memory_version = std::max(unresolved_ifield_version_[type],
+                                    GetMemoryVersion(base, field_id, type));
         }
-        uint16_t field_ref = mir->dalvikInsn.vC;
-        uint16_t memory_version = GetMemoryVersion(base, field_ref);
         if (opcode == Instruction::IGET_WIDE) {
-          uint16_t res = LookupValue(Instruction::IGET_WIDE, base, field_ref, memory_version);
+          res = LookupValue(Instruction::IGET_WIDE, base, field_id, memory_version);
           SetOperandValueWide(mir->ssa_rep->defs[0], res);
         } else {
-          uint16_t res = LookupValue(Instruction::IGET, base, field_ref, memory_version);
+          res = LookupValue(Instruction::IGET, base, field_id, memory_version);
           SetOperandValue(mir->ssa_rep->defs[0], res);
         }
       }
       break;
 
-    case Instruction::IPUT_WIDE:
     case Instruction::IPUT_OBJECT:
+      HandlePutObject(mir);
+      // Intentional fall-through.
     case Instruction::IPUT:
+    case Instruction::IPUT_WIDE:
     case Instruction::IPUT_BOOLEAN:
     case Instruction::IPUT_BYTE:
     case Instruction::IPUT_CHAR:
     case Instruction::IPUT_SHORT: {
+        uint16_t type = opcode - Instruction::IPUT;
         int base_reg = (opcode == Instruction::IPUT_WIDE) ? 2 : 1;
         uint16_t base = GetOperandValue(mir->ssa_rep->uses[base_reg]);
-        if (null_checked_.find(base) != null_checked_.end()) {
-          if (cu_->verbose) {
-            LOG(INFO) << "Removing null check for 0x" << std::hex << mir->offset;
-          }
-          mir->optimization_flags |= MIR_IGNORE_NULL_CHECK;
+        HandleNullCheck(mir, base);
+        const IFieldAnnotation& annotation = cu_->mir_graph->GetIFieldAnnotation(mir);
+        if (!annotation.IsResolved()) {
+          // Unresolved fields always alias with everything of the same type.
+          unresolved_ifield_version_[type] = next_memory_version_;
+          ++next_memory_version_;
+        } else if (annotation.IsVolatile()) {
+          // Nothing to do, resolved volatile fields always get a new memory version anyway and
+          // can't alias with resolved non-volatile fields.
         } else {
-          null_checked_.insert(base);
+          AdvanceMemoryVersion(base, GetFieldId(annotation.DeclaringDexFile(),
+                                                annotation.DeclaringFieldIndex()), type);
         }
-        uint16_t field_ref = mir->dalvikInsn.vC;
-        AdvanceMemoryVersion(base, field_ref);
       }
       break;
 
     case Instruction::SGET_OBJECT:
     case Instruction::SGET:
+    case Instruction::SGET_WIDE:
     case Instruction::SGET_BOOLEAN:
     case Instruction::SGET_BYTE:
     case Instruction::SGET_CHAR:
-    case Instruction::SGET_SHORT:
-    case Instruction::SGET_WIDE: {
-        uint16_t field_ref = mir->dalvikInsn.vB;
-        uint16_t memory_version = GetMemoryVersion(NO_VALUE, field_ref);
+    case Instruction::SGET_SHORT: {
+        uint16_t type = opcode - Instruction::SGET;
+        const SFieldAnnotation& annotation = cu_->mir_graph->GetSFieldAnnotation(mir);
+        uint16_t memory_version;
+        uint16_t field_id;
+        if (annotation.IsVolatile()) {
+          // Volatile fields always get a new memory version; field id is irrelevant.
+          // Unresolved fields are always marked as volatile and handled the same way here.
+          field_id = 0u;
+          memory_version = next_memory_version_;
+          ++next_memory_version_;
+        } else {
+          DCHECK(annotation.IsResolved());
+          field_id = GetFieldId(annotation.DeclaringDexFile(), annotation.DeclaringFieldIndex());
+          memory_version = std::max(unresolved_sfield_version_[type],
+                                    GetMemoryVersion(NO_VALUE, field_id, type));
+        }
         if (opcode == Instruction::SGET_WIDE) {
-          uint16_t res = LookupValue(Instruction::SGET_WIDE, NO_VALUE, field_ref, memory_version);
+          res = LookupValue(Instruction::SGET_WIDE, NO_VALUE, field_id, memory_version);
           SetOperandValueWide(mir->ssa_rep->defs[0], res);
         } else {
-          uint16_t res = LookupValue(Instruction::SGET, NO_VALUE, field_ref, memory_version);
+          res = LookupValue(Instruction::SGET, NO_VALUE, field_id, memory_version);
           SetOperandValue(mir->ssa_rep->defs[0], res);
         }
       }
       break;
 
     case Instruction::SPUT_OBJECT:
+      HandlePutObject(mir);
+      // Intentional fall-through.
     case Instruction::SPUT:
+    case Instruction::SPUT_WIDE:
     case Instruction::SPUT_BOOLEAN:
     case Instruction::SPUT_BYTE:
     case Instruction::SPUT_CHAR:
-    case Instruction::SPUT_SHORT:
-    case Instruction::SPUT_WIDE: {
-        uint16_t field_ref = mir->dalvikInsn.vB;
-        AdvanceMemoryVersion(NO_VALUE, field_ref);
+    case Instruction::SPUT_SHORT: {
+        uint16_t type = opcode - Instruction::SPUT;
+        const SFieldAnnotation& annotation = cu_->mir_graph->GetSFieldAnnotation(mir);
+        if (!annotation.IsResolved()) {
+          // Unresolved fields always alias with everything of the same type.
+          unresolved_sfield_version_[type] = next_memory_version_;
+          ++next_memory_version_;
+        } else if (annotation.IsVolatile()) {
+          // Nothing to do, resolved volatile fields always get a new memory version anyway and
+          // can't alias with resolved non-volatile fields.
+        } else {
+          AdvanceMemoryVersion(NO_VALUE, GetFieldId(annotation.DeclaringDexFile(),
+                                                    annotation.DeclaringFieldIndex()), type);
+        }
       }
       break;
   }
diff --git a/compiler/dex/local_value_numbering.h b/compiler/dex/local_value_numbering.h
index 33ca8f1..348bedc 100644
--- a/compiler/dex/local_value_numbering.h
+++ b/compiler/dex/local_value_numbering.h
@@ -24,16 +24,78 @@
 
 namespace art {
 
-// Key is s_reg, value is value name.
-typedef SafeMap<uint16_t, uint16_t> SregValueMap;
-// Key is concatenation of quad, value is value name.
-typedef SafeMap<uint64_t, uint16_t> ValueMap;
-// Key represents a memory address, value is generation.
-typedef SafeMap<uint32_t, uint16_t> MemoryVersionMap;
+class DexFile;
 
 class LocalValueNumbering {
+ private:
+  // Field types correspond to the ordering of GET/PUT instructions; this order is the same
+  // for IGET, IPUT, SGET, SPUT, AGET and APUT:
+  // op         0
+  // op_WIDE    1
+  // op_OBJECT  2
+  // op_BOOLEAN 3
+  // op_BYTE    4
+  // op_CHAR    5
+  // op_SHORT   6
+  static constexpr size_t kFieldTypeCount = 7;
+
+  // FieldReference represents either a unique resolved field or all unresolved fields together.
+  struct FieldReference {
+    const DexFile* dex_file;
+    uint16_t field_idx;
+  };
+
+  struct FieldReferenceComparator {
+    bool operator()(const FieldReference& lhs, const FieldReference& rhs) const {
+      if (lhs.field_idx != rhs.field_idx) {
+        return lhs.field_idx < rhs.field_idx;
+      }
+      return lhs.dex_file < rhs.dex_file;
+    }
+  };
+
+  struct MemoryVersionKey {
+    uint16_t base;
+    uint16_t field_id;
+    uint16_t type;
+  };
+
+  struct MemoryVersionKeyComparator {
+    bool operator()(const MemoryVersionKey& lhs, const MemoryVersionKey& rhs) const {
+      if (lhs.base != rhs.base) {
+        return lhs.base < rhs.base;
+      }
+      if (lhs.field_id != rhs.field_id) {
+        return lhs.field_id < rhs.field_id;
+      }
+      return lhs.type < rhs.type;
+    }
+  };
+
+  // Key is s_reg, value is value name.
+  typedef SafeMap<uint16_t, uint16_t> SregValueMap;
+  // Key is concatenation of opcode, operand1, operand2 and modifier, value is value name.
+  typedef SafeMap<uint64_t, uint16_t> ValueMap;
+  // Key represents a memory address, value is generation.
+  typedef SafeMap<MemoryVersionKey, uint16_t, MemoryVersionKeyComparator> MemoryVersionMap;
+  // Maps field key to field id for resolved fields.
+  typedef SafeMap<FieldReference, uint32_t, FieldReferenceComparator> FieldIndexMap;
+
  public:
-  explicit LocalValueNumbering(CompilationUnit* cu) : cu_(cu) {}
+  explicit LocalValueNumbering(CompilationUnit* cu)
+      : cu_(cu),
+        sreg_value_map_(),
+        sreg_wide_value_map_(),
+        value_map_(),
+        next_memory_version_(1u),
+        global_memory_version_(0u),
+        memory_version_map_(),
+        field_index_map_(),
+        non_aliasing_refs_(),
+        null_checked_() {
+    std::fill_n(unresolved_sfield_version_, kFieldTypeCount, 0u);
+    std::fill_n(unresolved_ifield_version_, kFieldTypeCount, 0u);
+  }
 
   static uint64_t BuildKey(uint16_t op, uint16_t operand1, uint16_t operand2, uint16_t modifier) {
     return (static_cast<uint64_t>(op) << 48 | static_cast<uint64_t>(operand1) << 32 |
@@ -59,29 +121,6 @@
     return (it != value_map_.end());
   };
 
-  uint16_t GetMemoryVersion(uint16_t base, uint16_t field) {
-    uint32_t key = (base << 16) | field;
-    uint16_t res;
-    MemoryVersionMap::iterator it = memory_version_map_.find(key);
-    if (it == memory_version_map_.end()) {
-      res = 0;
-      memory_version_map_.Put(key, res);
-    } else {
-      res = it->second;
-    }
-    return res;
-  };
-
-  void AdvanceMemoryVersion(uint16_t base, uint16_t field) {
-    uint32_t key = (base << 16) | field;
-    MemoryVersionMap::iterator it = memory_version_map_.find(key);
-    if (it == memory_version_map_.end()) {
-      memory_version_map_.Put(key, 0);
-    } else {
-      it->second++;
-    }
-  };
-
   void SetOperandValue(uint16_t s_reg, uint16_t value) {
     SregValueMap::iterator it = sreg_value_map_.find(s_reg);
     if (it != sreg_value_map_.end()) {
@@ -129,11 +168,28 @@
   uint16_t GetValueNumber(MIR* mir);
 
  private:
+  uint16_t GetFieldId(const DexFile* dex_file, uint16_t field_idx);
+  void AdvanceGlobalMemory();
+  uint16_t GetMemoryVersion(uint16_t base, uint16_t field, uint16_t type);
+  uint16_t AdvanceMemoryVersion(uint16_t base, uint16_t field, uint16_t type);
+  uint16_t MarkNonAliasingNonNull(MIR* mir);
+  void MakeArgsAliasing(MIR* mir);
+  void HandleNullCheck(MIR* mir, uint16_t reg);
+  void HandleRangeCheck(MIR* mir, uint16_t array, uint16_t index);
+  void HandlePutObject(MIR* mir);
+
   CompilationUnit* const cu_;
   SregValueMap sreg_value_map_;
   SregValueMap sreg_wide_value_map_;
   ValueMap value_map_;
+  uint16_t next_memory_version_;
+  uint16_t global_memory_version_;
+  uint16_t unresolved_sfield_version_[kFieldTypeCount];
+  uint16_t unresolved_ifield_version_[kFieldTypeCount];
   MemoryVersionMap memory_version_map_;
+  FieldIndexMap field_index_map_;
+  // Value names of references to objects that cannot be reached through a different value name.
+  std::set<uint16_t> non_aliasing_refs_;
   std::set<uint16_t> null_checked_;
 };
 
diff --git a/compiler/dex/local_value_numbering_test.cc b/compiler/dex/local_value_numbering_test.cc
new file mode 100644
index 0000000..230c012
--- /dev/null
+++ b/compiler/dex/local_value_numbering_test.cc
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2014 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 <vector>
+
+#include "local_value_numbering.h"
+#include "compiler_internals.h"
+#include "gtest/gtest.h"
+
+namespace art {
+
+class LocalValueNumberingTest : public testing::Test {
+ protected:
+  struct IFieldDef {
+    uint16_t field_idx;
+    uintptr_t declaring_dex_file;
+    uint16_t declaring_field_idx;
+    bool is_volatile;
+  };
+
+  struct SFieldDef {
+    uint16_t field_idx;
+    uintptr_t declaring_dex_file;
+    uint16_t declaring_field_idx;
+    bool is_volatile;
+  };
+
+  struct MIRDef {
+    static constexpr size_t kMaxSsaDefs = 2;
+    static constexpr size_t kMaxSsaUses = 3;
+
+    Instruction::Code opcode;
+    int64_t value;
+    uint32_t field_annotation;
+    size_t num_uses;
+    int32_t uses[kMaxSsaUses];
+    size_t num_defs;
+    int32_t defs[kMaxSsaDefs];
+  };
+
+#define DEF_CONST(opcode, reg, value) \
+    { opcode, value, 0u, 0, { }, 1, { reg } }
+#define DEF_CONST_WIDE(opcode, reg, value) \
+    { opcode, value, 0u, 0, { }, 2, { reg, reg + 1 } }
+#define DEF_IGET(opcode, reg, obj, field_annotation) \
+    { opcode, 0u, field_annotation, 1, { obj }, 1, { reg } }
+#define DEF_IGET_WIDE(opcode, reg, obj, field_annotation) \
+    { opcode, 0u, field_annotation, 1, { obj }, 2, { reg, reg + 1 } }
+#define DEF_IPUT(opcode, reg, obj, field_annotation) \
+    { opcode, 0u, field_annotation, 2, { reg, obj }, 0, { } }
+#define DEF_IPUT_WIDE(opcode, reg, obj, field_annotation) \
+    { opcode, 0u, field_annotation, 3, { reg, reg + 1, obj }, 0, { } }
+#define DEF_SGET(opcode, reg, field_annotation) \
+    { opcode, 0u, field_annotation, 0, { }, 1, { reg } }
+#define DEF_SGET_WIDE(opcode, reg, field_annotation) \
+    { opcode, 0u, field_annotation, 0, { }, 2, { reg, reg + 1 } }
+#define DEF_SPUT(opcode, reg, field_annotation) \
+    { opcode, 0u, field_annotation, 1, { reg }, 0, { } }
+#define DEF_SPUT_WIDE(opcode, reg, field_annotation) \
+    { opcode, 0u, field_annotation, 2, { reg, reg + 1 }, 0, { } }
+#define DEF_INVOKE1(opcode, reg) \
+    { opcode, 0u, 0u, 1, { reg }, 0, { } }
+#define DEF_UNIQUE_REF(opcode, reg) \
+    { opcode, 0u, 0u, 0, { }, 1, { reg } }  // CONST_CLASS, CONST_STRING, NEW_ARRAY, ...
+
+  void DoPrepareIFields(const IFieldDef* defs, size_t count) {
+    cu_.mir_graph->ifield_annotations_.Reset();
+    cu_.mir_graph->ifield_annotations_.Resize(count);
+    for (size_t i = 0u; i != count; ++i) {
+      const IFieldDef* def = &defs[i];
+      IFieldAnnotation annotation(def->field_idx);
+      if (def->declaring_dex_file != 0u) {
+        annotation.declaring_dex_file_ = reinterpret_cast<const DexFile*>(def->declaring_dex_file);
+        annotation.declaring_field_idx_ = def->declaring_field_idx;
+        annotation.is_volatile_ = def->is_volatile ? 1u : 0u;
+      }
+      cu_.mir_graph->ifield_annotations_.Insert(annotation);
+    }
+  }
+
+  template <size_t count>
+  void PrepareIFields(const IFieldDef (&defs)[count]) {
+    DoPrepareIFields(defs, count);
+  }
+
+  void DoPrepareSFields(const SFieldDef* defs, size_t count) {
+    cu_.mir_graph->sfield_annotations_.Reset();
+    cu_.mir_graph->sfield_annotations_.Resize(count);
+    for (size_t i = 0u; i != count; ++i) {
+      const SFieldDef* def = &defs[i];
+      SFieldAnnotation annotation(def->field_idx);
+      if (def->declaring_dex_file != 0u) {
+        annotation.declaring_dex_file_ = reinterpret_cast<const DexFile*>(def->declaring_dex_file);
+        annotation.declaring_field_idx_ = def->declaring_field_idx;
+        annotation.is_volatile_ = def->is_volatile ? 1u : 0u;
+      }
+      cu_.mir_graph->sfield_annotations_.Insert(annotation);
+    }
+  }
+
+  template <size_t count>
+  void PrepareSFields(const SFieldDef (&defs)[count]) {
+    DoPrepareSFields(defs, count);
+  }
+
+  void DoPrepareMIRs(const MIRDef* defs, size_t count) {
+    mir_count_ = count;
+    mirs_ = reinterpret_cast<MIR*>(cu_.arena.Alloc(sizeof(MIR) * count, ArenaAllocator::kAllocMIR));
+    ssa_reps_.resize(count);
+    for (size_t i = 0u; i != count; ++i) {
+      const MIRDef* def = &defs[i];
+      MIR* mir = &mirs_[i];
+      mir->dalvikInsn.opcode = def->opcode;
+      mir->dalvikInsn.vB = static_cast<int32_t>(def->value);
+      mir->dalvikInsn.vB_wide = def->value;
+      if (def->opcode >= Instruction::IGET && def->opcode <= Instruction::IPUT_SHORT) {
+        ASSERT_LT(def->field_annotation, cu_.mir_graph->ifield_annotations_.Size());
+        mir->meta.ifield_annotation = def->field_annotation;
+      } else if (def->opcode >= Instruction::SGET && def->opcode <= Instruction::SPUT_SHORT) {
+        ASSERT_LT(def->field_annotation, cu_.mir_graph->sfield_annotations_.Size());
+        mir->meta.sfield_annotation = def->field_annotation;
+      }
+      mir->ssa_rep = &ssa_reps_[i];
+      mir->ssa_rep->num_uses = def->num_uses;
+      mir->ssa_rep->uses = const_cast<int32_t*>(def->uses);  // Not modified by LVN.
+      mir->ssa_rep->fp_use = nullptr;  // Not used by LVN.
+      mir->ssa_rep->num_defs = def->num_defs;
+      mir->ssa_rep->defs = const_cast<int32_t*>(def->defs);  // Not modified by LVN.
+      mir->ssa_rep->fp_def = nullptr;  // Not used by LVN.
+      mir->dalvikInsn.opcode = def->opcode;
+      mir->offset = i;  // LVN uses offset only for debug output
+      mir->width = 1u;  // Not used by LVN.
+      mir->optimization_flags = 0u;
+
+      if (i != 0u) {
+        mirs_[i - 1u].next = mir;
+      }
+    }
+    mirs_[count - 1u].next = nullptr;
+  }
+
+  template <size_t count>
+  void PrepareMIRs(const MIRDef (&defs)[count]) {
+    DoPrepareMIRs(defs, count);
+  }
+
+  void PerformLVN() {
+    value_names_.resize(mir_count_);
+    for (size_t i = 0; i != mir_count_; ++i) {
+      value_names_[i] =  lvn_.GetValueNumber(&mirs_[i]);
+    }
+  }
+
+  LocalValueNumberingTest() : pool_(), cu_(&pool_), mir_count_(0u), mirs_(nullptr), lvn_(&cu_) {
+    cu_.mir_graph.reset(new MIRGraph(&cu_, &cu_.arena));
+  }
+
+  ArenaPool pool_;
+  CompilationUnit cu_;
+  size_t mir_count_;
+  MIR* mirs_;
+  std::vector<SSARepresentation> ssa_reps_;
+  std::vector<uint16_t> value_names_;
+  LocalValueNumbering lvn_;
+};
+
+TEST_F(LocalValueNumberingTest, TestIGetIGetInvokeIGet) {
+  static const IFieldDef ifields[] = {
+      { 1u, 1u, 1u, false }
+  };
+  static const MIRDef mirs[] = {
+      DEF_IGET(Instruction::IGET, 0u, 10u, 0u),
+      DEF_IGET(Instruction::IGET, 1u, 10u, 0u),
+      DEF_INVOKE1(Instruction::INVOKE_VIRTUAL, 11u),
+      DEF_IGET(Instruction::IGET, 2u, 10u, 0u),
+  };
+
+  PrepareIFields(ifields);
+  PrepareMIRs(mirs);
+  PerformLVN();
+  ASSERT_EQ(value_names_.size(), 4u);
+  EXPECT_EQ(value_names_[0], value_names_[1]);
+  EXPECT_NE(value_names_[0], value_names_[3]);
+  EXPECT_EQ(mirs_[0].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[1].optimization_flags, MIR_IGNORE_NULL_CHECK);
+  EXPECT_EQ(mirs_[2].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[3].optimization_flags, MIR_IGNORE_NULL_CHECK);
+}
+
+TEST_F(LocalValueNumberingTest, TestIGetIPutIGetIGetIGet) {
+  static const IFieldDef ifields[] = {
+      { 1u, 1u, 1u, false },
+      { 2u, 1u, 2u, false },
+  };
+  static const MIRDef mirs[] = {
+      DEF_IGET(Instruction::IGET, 0u, 10u, 0u),
+      DEF_IPUT(Instruction::IPUT, 1u, 11u, 0u),  // May alias.
+      DEF_IGET(Instruction::IGET, 2u, 10u, 0u),
+      DEF_IGET(Instruction::IGET, 3u,  0u, 1u),
+      DEF_IGET(Instruction::IGET, 4u,  2u, 1u),
+  };
+
+  PrepareIFields(ifields);
+  PrepareMIRs(mirs);
+  PerformLVN();
+  ASSERT_EQ(value_names_.size(), 5u);
+  EXPECT_NE(value_names_[0], value_names_[2]);
+  EXPECT_NE(value_names_[3], value_names_[4]);
+  EXPECT_EQ(mirs_[0].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[1].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[2].optimization_flags, MIR_IGNORE_NULL_CHECK);
+  EXPECT_EQ(mirs_[3].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[4].optimization_flags, 0u);
+}
+
+TEST_F(LocalValueNumberingTest, TestUniquePreserve1) {
+  static const IFieldDef ifields[] = {
+      { 1u, 1u, 1u, false },
+  };
+  static const MIRDef mirs[] = {
+      DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 10u),
+      DEF_IGET(Instruction::IGET, 0u, 10u, 0u),
+      DEF_IPUT(Instruction::IPUT, 1u, 11u, 0u),  // No aliasing since 10u is unique.
+      DEF_IGET(Instruction::IGET, 2u, 10u, 0u),
+  };
+
+  PrepareIFields(ifields);
+  PrepareMIRs(mirs);
+  PerformLVN();
+  ASSERT_EQ(value_names_.size(), 4u);
+  EXPECT_EQ(value_names_[1], value_names_[3]);
+  EXPECT_EQ(mirs_[1].optimization_flags, MIR_IGNORE_NULL_CHECK);
+  EXPECT_EQ(mirs_[2].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[3].optimization_flags, MIR_IGNORE_NULL_CHECK);
+}
+
+TEST_F(LocalValueNumberingTest, TestUniquePreserve2) {
+  static const IFieldDef ifields[] = {
+      { 1u, 1u, 1u, false },
+  };
+  static const MIRDef mirs[] = {
+      DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 11u),
+      DEF_IGET(Instruction::IGET, 0u, 10u, 0u),
+      DEF_IPUT(Instruction::IPUT, 1u, 11u, 0u),  // No aliasing since 11u is unique.
+      DEF_IGET(Instruction::IGET, 2u, 10u, 0u),
+  };
+
+  PrepareIFields(ifields);
+  PrepareMIRs(mirs);
+  PerformLVN();
+  ASSERT_EQ(value_names_.size(), 4u);
+  EXPECT_EQ(value_names_[1], value_names_[3]);
+  EXPECT_EQ(mirs_[1].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[2].optimization_flags, MIR_IGNORE_NULL_CHECK);
+  EXPECT_EQ(mirs_[3].optimization_flags, MIR_IGNORE_NULL_CHECK);
+}
+
+TEST_F(LocalValueNumberingTest, TestUniquePreserveAndEscape) {
+  static const IFieldDef ifields[] = {
+      { 1u, 1u, 1u, false },
+  };
+  static const MIRDef mirs[] = {
+      DEF_UNIQUE_REF(Instruction::NEW_INSTANCE, 10u),
+      DEF_IGET(Instruction::IGET, 0u, 10u, 0u),
+      DEF_INVOKE1(Instruction::INVOKE_VIRTUAL, 11u),  // 10u still unique.
+      DEF_IGET(Instruction::IGET, 2u, 10u, 0u),
+      DEF_INVOKE1(Instruction::INVOKE_VIRTUAL, 10u),  // 10u not unique anymore.
+      DEF_IGET(Instruction::IGET, 3u, 10u, 0u),
+  };
+
+  PrepareIFields(ifields);
+  PrepareMIRs(mirs);
+  PerformLVN();
+  ASSERT_EQ(value_names_.size(), 6u);
+  EXPECT_EQ(value_names_[1], value_names_[3]);
+  EXPECT_NE(value_names_[1], value_names_[5]);
+  EXPECT_EQ(mirs_[1].optimization_flags, MIR_IGNORE_NULL_CHECK);
+  EXPECT_EQ(mirs_[3].optimization_flags, MIR_IGNORE_NULL_CHECK);
+  EXPECT_EQ(mirs_[5].optimization_flags, MIR_IGNORE_NULL_CHECK);
+}
+
+TEST_F(LocalValueNumberingTest, TestVolatile) {
+  static const IFieldDef ifields[] = {
+      { 1u, 1u, 1u, false },
+      { 2u, 1u, 2u, true },
+  };
+  static const MIRDef mirs[] = {
+      DEF_IGET(Instruction::IGET, 0u, 10u, 1u),  // Volatile.
+      DEF_IGET(Instruction::IGET, 1u,  0u, 0u),  // Non-volatile.
+      DEF_IGET(Instruction::IGET, 2u, 10u, 1u),  // Volatile.
+      DEF_IGET(Instruction::IGET, 3u,  2u, 1u),  // Non-volatile.
+  };
+
+  PrepareIFields(ifields);
+  PrepareMIRs(mirs);
+  PerformLVN();
+  ASSERT_EQ(value_names_.size(), 4u);
+  EXPECT_NE(value_names_[0], value_names_[2]);  // Volatile has always different value name.
+  EXPECT_NE(value_names_[1], value_names_[3]);  // Used different base because of volatile.
+  EXPECT_EQ(mirs_[0].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[1].optimization_flags, 0u);
+  EXPECT_EQ(mirs_[2].optimization_flags, MIR_IGNORE_NULL_CHECK);
+  EXPECT_EQ(mirs_[3].optimization_flags, 0u);
+}
+
+}  // namespace art
diff --git a/compiler/dex/mir_annotations.h b/compiler/dex/mir_annotations.h
index 85761de..cb7b589 100644
--- a/compiler/dex/mir_annotations.h
+++ b/compiler/dex/mir_annotations.h
@@ -108,6 +108,8 @@
   uint16_t declaring_class_idx_;
   // The field index in the dex file that defines field, 0 if unresolved.
   uint16_t declaring_field_idx_;
+
+  friend class LocalValueNumberingTest;
 };
 
 class SFieldAnnotation {
@@ -208,6 +210,8 @@
   uint16_t declaring_class_idx_;
   // The field index in the dex file that defines field, 0 if unresolved.
   uint16_t declaring_field_idx_;
+
+  friend class LocalValueNumberingTest;
 };
 
 }  // namespace art
diff --git a/compiler/dex/mir_graph.h b/compiler/dex/mir_graph.h
index ea0289b..df48f7e 100644
--- a/compiler/dex/mir_graph.h
+++ b/compiler/dex/mir_graph.h
@@ -939,6 +939,8 @@
   size_t max_available_special_compiler_temps_;
   GrowableArray<IFieldAnnotation> ifield_annotations_;
   GrowableArray<SFieldAnnotation> sfield_annotations_;
+
+  friend class LocalValueNumberingTest;
 };
 
 }  // namespace art