Merge "Fix x86_64 assembler LoadRef to use movl."
diff --git a/build/Android.common_build.mk b/build/Android.common_build.mk
index 0dcefea..e4be21b 100644
--- a/build/Android.common_build.mk
+++ b/build/Android.common_build.mk
@@ -122,7 +122,7 @@
 endif
 
 # Clang on the target. Target builds use GCC by default.
-ART_TARGET_CLANG :=
+ART_TARGET_CLANG := false
 ART_TARGET_CLANG_arm :=
 ART_TARGET_CLANG_arm64 :=
 ART_TARGET_CLANG_mips :=
@@ -158,6 +158,7 @@
   -Wno-unused-parameter \
   -Wstrict-aliasing \
   -fstrict-aliasing \
+  -Wunreachable-code \
   -fvisibility=protected
 
 ART_TARGET_CLANG_CFLAGS :=
@@ -167,7 +168,7 @@
 ART_TARGET_CLANG_CFLAGS_x86 :=
 ART_TARGET_CLANG_CFLAGS_x86_64 :=
 
-# these are necessary for Clang ARM64 ART builds
+# These are necessary for Clang ARM64 ART builds. TODO: remove.
 ART_TARGET_CLANG_CFLAGS_arm64  += \
   -Wno-implicit-exception-spec-mismatch \
   -DNVALGRIND \
@@ -235,6 +236,14 @@
 ART_TARGET_CFLAGS += -DART_BASE_ADDRESS_MIN_DELTA=$(LIBART_IMG_TARGET_MIN_BASE_ADDRESS_DELTA)
 ART_TARGET_CFLAGS += -DART_BASE_ADDRESS_MAX_DELTA=$(LIBART_IMG_TARGET_MAX_BASE_ADDRESS_DELTA)
 
+# Colorize clang compiler warnings.
+ifeq ($(ART_HOST_CLANG),true)
+  ART_HOST_CFLAGS += -fcolor-diagnostics
+endif
+ifeq ($(ART_TARGET_CLANG),true)
+  ART_TARGET_CFLAGS += -fcolor-diagnostics
+endif
+
 ART_TARGET_LDFLAGS :=
 ifeq ($(TARGET_CPU_SMP),true)
   ART_TARGET_CFLAGS += -DANDROID_SMP=1
diff --git a/compiler/dex/local_value_numbering.cc b/compiler/dex/local_value_numbering.cc
index 4279955..eb0806b 100644
--- a/compiler/dex/local_value_numbering.cc
+++ b/compiler/dex/local_value_numbering.cc
@@ -1169,8 +1169,9 @@
   const MirFieldInfo& field_info = gvn_->GetMirGraph()->GetIFieldLoweringInfo(mir);
   uint16_t res;
   if (!field_info.IsResolved() || field_info.IsVolatile()) {
-    // Volatile fields always get a new memory version; field id is irrelevant.
     // Unresolved fields may be volatile, so handle them as such to be safe.
+    HandleInvokeOrClInitOrAcquireOp(mir);  // Volatile GETs have acquire semantics.
+    // Volatile fields always get a new memory version; field id is irrelevant.
     // Use result s_reg - will be unique.
     res = gvn_->LookupValue(kNoValue, mir->ssa_rep->defs[0], kNoValue, kNoValue);
   } else {
@@ -1269,14 +1270,16 @@
 
 uint16_t LocalValueNumbering::HandleSGet(MIR* mir, uint16_t opcode) {
   const MirSFieldLoweringInfo& field_info = gvn_->GetMirGraph()->GetSFieldLoweringInfo(mir);
-  if (!field_info.IsInitialized() && (mir->optimization_flags & MIR_IGNORE_CLINIT_CHECK) == 0) {
-    // Class initialization can call arbitrary functions, we need to wipe aliasing values.
-    HandleInvokeOrClInit(mir);
+  if (!field_info.IsResolved() || field_info.IsVolatile() ||
+      (!field_info.IsInitialized() && (mir->optimization_flags & MIR_IGNORE_CLINIT_CHECK) == 0)) {
+    // Volatile SGETs (and unresolved fields are potentially volatile) have acquire semantics
+    // and class initialization can call arbitrary functions, we need to wipe aliasing values.
+    HandleInvokeOrClInitOrAcquireOp(mir);
   }
   uint16_t res;
   if (!field_info.IsResolved() || field_info.IsVolatile()) {
-    // Volatile fields always get a new memory version; field id is irrelevant.
     // Unresolved fields may be volatile, so handle them as such to be safe.
+    // Volatile fields always get a new memory version; field id is irrelevant.
     // Use result s_reg - will be unique.
     res = gvn_->LookupValue(kNoValue, mir->ssa_rep->defs[0], kNoValue, kNoValue);
   } else {
@@ -1306,7 +1309,7 @@
   const MirSFieldLoweringInfo& field_info = gvn_->GetMirGraph()->GetSFieldLoweringInfo(mir);
   if (!field_info.IsInitialized() && (mir->optimization_flags & MIR_IGNORE_CLINIT_CHECK) == 0) {
     // Class initialization can call arbitrary functions, we need to wipe aliasing values.
-    HandleInvokeOrClInit(mir);
+    HandleInvokeOrClInitOrAcquireOp(mir);
   }
   uint16_t type = opcode - Instruction::SPUT;
   if (!field_info.IsResolved()) {
@@ -1351,7 +1354,7 @@
   }
 }
 
-void LocalValueNumbering::HandleInvokeOrClInit(MIR* mir) {
+void LocalValueNumbering::HandleInvokeOrClInitOrAcquireOp(MIR* mir) {
   // Use mir->offset as modifier; without elaborate inlining, it will be unique.
   global_memory_version_ =
       gvn_->LookupValue(kInvokeMemoryVersionBumpOp, 0u, 0u, mir->offset);
@@ -1404,9 +1407,7 @@
 
     case Instruction::MONITOR_ENTER:
       HandleNullCheck(mir, GetOperandValue(mir->ssa_rep->uses[0]));
-      // NOTE: Keeping all aliasing values intact. Programs that rely on loads/stores of the
-      // same non-volatile locations outside and inside a synchronized block being different
-      // contain races that we cannot fix.
+      HandleInvokeOrClInitOrAcquireOp(mir);  // Acquire operation.
       break;
 
     case Instruction::MONITOR_EXIT:
@@ -1468,7 +1469,7 @@
           uint16_t reg = GetOperandValue(mir->ssa_rep->uses[i]);
           non_aliasing_refs_.erase(reg);
         }
-        HandleInvokeOrClInit(mir);
+        HandleInvokeOrClInitOrAcquireOp(mir);
       }
       break;
 
diff --git a/compiler/dex/local_value_numbering.h b/compiler/dex/local_value_numbering.h
index e11c6e5..c60da32 100644
--- a/compiler/dex/local_value_numbering.h
+++ b/compiler/dex/local_value_numbering.h
@@ -308,7 +308,7 @@
   uint16_t HandleSGet(MIR* mir, uint16_t opcode);
   void HandleSPut(MIR* mir, uint16_t opcode);
   void RemoveSFieldsForType(uint16_t type);
-  void HandleInvokeOrClInit(MIR* mir);
+  void HandleInvokeOrClInitOrAcquireOp(MIR* mir);
 
   bool SameMemoryVersion(const LocalValueNumbering& other) const;
 
diff --git a/compiler/dex/local_value_numbering_test.cc b/compiler/dex/local_value_numbering_test.cc
index e53c640..067bea2 100644
--- a/compiler/dex/local_value_numbering_test.cc
+++ b/compiler/dex/local_value_numbering_test.cc
@@ -338,16 +338,19 @@
       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.
+      DEF_IGET(Instruction::IGET, 4u,  0u, 0u),  // Non-volatile.
   };
 
   PrepareIFields(ifields);
   PrepareMIRs(mirs);
   PerformLVN();
-  ASSERT_EQ(value_names_.size(), 4u);
+  ASSERT_EQ(value_names_.size(), 5u);
   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_NE(value_names_[1], value_names_[4]);  // Not guaranteed to be the same after "acquire".
+
   for (size_t i = 0; i != arraysize(mirs); ++i) {
-    EXPECT_EQ((i == 2u) ? MIR_IGNORE_NULL_CHECK : 0,
+    EXPECT_EQ((i == 2u || i == 4u) ? MIR_IGNORE_NULL_CHECK : 0,
               mirs_[i].optimization_flags) << i;
   }
 }
@@ -363,7 +366,7 @@
       DEF_IGET(Instruction::IGET, 1u, 20u, 0u),             // Resolved field #1, unique object.
       DEF_IGET(Instruction::IGET, 2u, 21u, 0u),             // Resolved field #1.
       DEF_IGET_WIDE(Instruction::IGET_WIDE, 3u, 21u, 1u),   // Resolved field #2.
-      DEF_IGET(Instruction::IGET, 4u, 22u, 2u),             // IGET doesn't clobber anything.
+      DEF_IGET(Instruction::IGET, 4u, 22u, 2u),             // Unresolved IGET can be "acquire".
       DEF_IGET(Instruction::IGET, 5u, 20u, 0u),             // Resolved field #1, unique object.
       DEF_IGET(Instruction::IGET, 6u, 21u, 0u),             // Resolved field #1.
       DEF_IGET_WIDE(Instruction::IGET_WIDE, 7u, 21u, 1u),   // Resolved field #2.
@@ -381,14 +384,15 @@
   PrepareMIRs(mirs);
   PerformLVN();
   ASSERT_EQ(value_names_.size(), 16u);
-  EXPECT_EQ(value_names_[1], value_names_[5]);
-  EXPECT_EQ(value_names_[2], value_names_[6]);
-  EXPECT_EQ(value_names_[3], value_names_[7]);
-  EXPECT_EQ(value_names_[1], value_names_[9]);
-  EXPECT_NE(value_names_[2], value_names_[10]);  // This aliased with unresolved IPUT.
-  EXPECT_EQ(value_names_[3], value_names_[11]);
-  EXPECT_EQ(value_names_[12], value_names_[15]);
-  EXPECT_NE(value_names_[1], value_names_[14]);  // This aliased with unresolved IPUT.
+  // Unresolved field is potentially volatile, so we need to adhere to the volatile semantics.
+  EXPECT_EQ(value_names_[1], value_names_[5]);    // Unique object.
+  EXPECT_NE(value_names_[2], value_names_[6]);    // Not guaranteed to be the same after "acquire".
+  EXPECT_NE(value_names_[3], value_names_[7]);    // Not guaranteed to be the same after "acquire".
+  EXPECT_EQ(value_names_[1], value_names_[9]);    // Unique object.
+  EXPECT_NE(value_names_[6], value_names_[10]);   // This aliased with unresolved IPUT.
+  EXPECT_EQ(value_names_[7], value_names_[11]);   // Still the same after "release".
+  EXPECT_EQ(value_names_[12], value_names_[15]);  // Still the same after "release".
+  EXPECT_NE(value_names_[1], value_names_[14]);   // This aliased with unresolved IPUT.
   EXPECT_EQ(mirs_[0].optimization_flags, 0u);
   EXPECT_EQ(mirs_[1].optimization_flags, MIR_IGNORE_NULL_CHECK);
   EXPECT_EQ(mirs_[2].optimization_flags, 0u);
@@ -409,7 +413,7 @@
   static const MIRDef mirs[] = {
       DEF_SGET(Instruction::SGET, 0u, 0u),            // Resolved field #1.
       DEF_SGET_WIDE(Instruction::SGET_WIDE, 1u, 1u),  // Resolved field #2.
-      DEF_SGET(Instruction::SGET, 2u, 2u),            // SGET doesn't clobber anything.
+      DEF_SGET(Instruction::SGET, 2u, 2u),            // Unresolved SGET can be "acquire".
       DEF_SGET(Instruction::SGET, 3u, 0u),            // Resolved field #1.
       DEF_SGET_WIDE(Instruction::SGET_WIDE, 4u, 1u),  // Resolved field #2.
       DEF_SPUT(Instruction::SPUT, 5u, 2u),            // SPUT clobbers field #1 (#2 is wide).
@@ -421,10 +425,11 @@
   PrepareMIRs(mirs);
   PerformLVN();
   ASSERT_EQ(value_names_.size(), 8u);
-  EXPECT_EQ(value_names_[0], value_names_[3]);
-  EXPECT_EQ(value_names_[1], value_names_[4]);
-  EXPECT_NE(value_names_[0], value_names_[6]);  // This aliased with unresolved IPUT.
-  EXPECT_EQ(value_names_[1], value_names_[7]);
+  // Unresolved field is potentially volatile, so we need to adhere to the volatile semantics.
+  EXPECT_NE(value_names_[0], value_names_[3]);  // Not guaranteed to be the same after "acquire".
+  EXPECT_NE(value_names_[1], value_names_[4]);  // Not guaranteed to be the same after "acquire".
+  EXPECT_NE(value_names_[3], value_names_[6]);  // This aliased with unresolved IPUT.
+  EXPECT_EQ(value_names_[4], value_names_[7]);  // Still the same after "release".
   for (size_t i = 0u; i != mir_count_; ++i) {
     EXPECT_EQ(0, mirs_[i].optimization_flags) << i;
   }
diff --git a/compiler/dex/mir_dataflow.cc b/compiler/dex/mir_dataflow.cc
index e71c806..246ae44 100644
--- a/compiler/dex/mir_dataflow.cc
+++ b/compiler/dex/mir_dataflow.cc
@@ -1272,6 +1272,22 @@
   return true;
 }
 
+void MIRGraph::InitializeBasicBlockDataFlow() {
+  /*
+   * Allocate the BasicBlockDataFlow structure for the entry and code blocks.
+   */
+  for (BasicBlock* bb : block_list_) {
+    if (bb->hidden == true) continue;
+    if (bb->block_type == kDalvikByteCode ||
+        bb->block_type == kEntryBlock ||
+        bb->block_type == kExitBlock) {
+      bb->data_flow_info =
+          static_cast<BasicBlockDataFlow*>(arena_->Alloc(sizeof(BasicBlockDataFlow),
+                                                         kArenaAllocDFInfo));
+      }
+  }
+}
+
 /* Setup the basic data structures for SSA conversion */
 void MIRGraph::CompilerInitializeSSAConversion() {
   size_t num_reg = GetNumOfCodeAndTempVRs();
@@ -1319,19 +1335,7 @@
   // The MIR graph keeps track of the sreg for method pointer specially, so record that now.
   method_sreg_ = method_temp->s_reg_low;
 
-  /*
-   * Allocate the BasicBlockDataFlow structure for the entry and code blocks
-   */
-  for (BasicBlock* bb : block_list_) {
-    if (bb->hidden == true) continue;
-    if (bb->block_type == kDalvikByteCode ||
-        bb->block_type == kEntryBlock ||
-        bb->block_type == kExitBlock) {
-      bb->data_flow_info =
-          static_cast<BasicBlockDataFlow*>(arena_->Alloc(sizeof(BasicBlockDataFlow),
-                                                         kArenaAllocDFInfo));
-      }
-  }
+  InitializeBasicBlockDataFlow();
 }
 
 /*
diff --git a/compiler/dex/mir_graph.cc b/compiler/dex/mir_graph.cc
index bd7e4f7..bcbfb5a 100644
--- a/compiler/dex/mir_graph.cc
+++ b/compiler/dex/mir_graph.cc
@@ -568,8 +568,6 @@
                                       const uint16_t* code_ptr, const uint16_t* code_end) {
   bool in_try_block = try_block_addr->IsBitSet(cur_offset);
   bool is_throw = (insn->dalvikInsn.opcode == Instruction::THROW);
-  bool build_all_edges =
-      (cu_->disable_opt & (1 << kSuppressExceptionEdges)) || is_throw || in_try_block;
 
   /* In try block */
   if (in_try_block) {
@@ -605,6 +603,8 @@
     }
     in_try_block = (cur_block->successor_block_list_type != kNotUsed);
   }
+  bool build_all_edges =
+      (cu_->disable_opt & (1 << kSuppressExceptionEdges)) || is_throw || in_try_block;
   if (!in_try_block && build_all_edges) {
     BasicBlock* eh_block = CreateNewBB(kExceptionHandling);
     cur_block->taken = eh_block->id;
@@ -1153,7 +1153,7 @@
   }
 
   // Remove the BB information and also find the after_list.
-  for (MIR* mir = first_list_mir; mir != last_list_mir; mir = mir->next) {
+  for (MIR* mir = first_list_mir; mir != last_list_mir->next; mir = mir->next) {
     mir->bb = NullBasicBlockId;
   }
 
@@ -1684,9 +1684,6 @@
   temp_bit_vector_size_ = GetNumOfCodeAndTempVRs();
   temp_bit_vector_ = new (temp_scoped_alloc_.get()) ArenaBitVector(
       temp_scoped_alloc_.get(), temp_bit_vector_size_, false, kBitMapRegisterV);
-
-  // Update the maximum number of reachable blocks.
-  max_num_reachable_blocks_ = num_reachable_blocks_;
 }
 
 void MIRGraph::SSATransformationEnd() {
@@ -1699,6 +1696,9 @@
   temp_bit_vector_ = nullptr;
   DCHECK(temp_scoped_alloc_.get() != nullptr);
   temp_scoped_alloc_.reset();
+
+  // Update the maximum number of reachable blocks.
+  max_num_reachable_blocks_ = num_reachable_blocks_;
 }
 
 size_t MIRGraph::GetNumDalvikInsns() const {
diff --git a/compiler/dex/mir_graph.h b/compiler/dex/mir_graph.h
index f53ec89..f14b187 100644
--- a/compiler/dex/mir_graph.h
+++ b/compiler/dex/mir_graph.h
@@ -525,7 +525,7 @@
 class MIRGraph {
  public:
   MIRGraph(CompilationUnit* cu, ArenaAllocator* arena);
-  ~MIRGraph();
+  virtual ~MIRGraph();
 
   /*
    * Examine the graph to determine whether it's worthwile to spend the time compiling
@@ -1147,6 +1147,7 @@
   void ComputeDefBlockMatrix();
   void ComputeDominators();
   void CompilerInitializeSSAConversion();
+  virtual void InitializeBasicBlockDataFlow();
   void InsertPhiNodes();
   void DoDFSPreOrderSSARename(BasicBlock* block);
 
diff --git a/compiler/dex/quick/x86/assemble_x86.cc b/compiler/dex/quick/x86/assemble_x86.cc
index ab1608b..dce2b73 100644
--- a/compiler/dex/quick/x86/assemble_x86.cc
+++ b/compiler/dex/quick/x86/assemble_x86.cc
@@ -580,7 +580,7 @@
         case kX86CallA: return true;
         default: return false;
       }
-    case kPcRel: return true;
+    case kPcRel:
        switch (entry->opcode) {
          case kX86PcRelLoadRA: return true;
          default: return false;
diff --git a/compiler/dex/quick/x86/target_x86.cc b/compiler/dex/quick/x86/target_x86.cc
index 760358e..9aeaf60 100755
--- a/compiler/dex/quick/x86/target_x86.cc
+++ b/compiler/dex/quick/x86/target_x86.cc
@@ -2236,8 +2236,6 @@
   OpSize opsize = static_cast<OpSize>(mir->dalvikInsn.vC >> 16);
   RegLocation rl_dest = mir_graph_->GetDest(mir);
   RegStorage vector_src = RegStorage::Solo128(mir->dalvikInsn.vB);
-  int extract_index = mir->dalvikInsn.arg[0];
-  int extr_opcode = 0;
   RegLocation rl_result;
   bool is_wide = false;
 
@@ -2273,12 +2271,7 @@
      * 2-2) In 32-bit case, use movd twice to move to 32-bit GP pair.
      * 3) Store the result to the final destination.
      */
-    RegStorage rs_tmp_vector = Get128BitRegister(AllocTempDouble());
-    NewLIR2(kX86MovdqaRR, rs_tmp_vector.GetReg(), vector_src.GetReg());
-    NewLIR2(kX86PsrldqRI, rs_tmp_vector.GetReg(), 8);
-    NewLIR2(kX86PaddqRR, vector_src.GetReg(), rs_tmp_vector.GetReg());
-    FreeTemp(rs_tmp_vector);
-
+    NewLIR2(kX86PsrldqRI, vector_src.GetReg(), 8);
     rl_result = EvalLocWide(rl_dest, kCoreReg, true);
     if (cu_->target64) {
       DCHECK(!rl_result.reg.IsPair());
@@ -2291,16 +2284,21 @@
 
     StoreValueWide(rl_dest, rl_result);
   } else {
+    int extract_index = mir->dalvikInsn.arg[0];
+    int extr_opcode = 0;
+    rl_result = UpdateLocTyped(rl_dest, kCoreReg);
+
     // Handle the rest of integral types now.
     switch (opsize) {
       case k32:
-        rl_result = UpdateLocTyped(rl_dest, kCoreReg);
-        extr_opcode = (rl_result.location == kLocPhysReg) ? kX86PextrdMRI : kX86PextrdRRI;
+        extr_opcode = (rl_result.location == kLocPhysReg) ? kX86PextrdRRI : kX86PextrdMRI;
         break;
       case kSignedHalf:
       case kUnsignedHalf:
-        rl_result= UpdateLocTyped(rl_dest, kCoreReg);
-        extr_opcode = (rl_result.location == kLocPhysReg) ? kX86PextrwMRI : kX86PextrwRRI;
+        extr_opcode = (rl_result.location == kLocPhysReg) ? kX86PextrwRRI : kX86PextrwMRI;
+        break;
+      case kSignedByte:
+        extr_opcode = (rl_result.location == kLocPhysReg) ? kX86PextrbRRI : kX86PextrbMRI;
         break;
       default:
         LOG(FATAL) << "Unsupported vector reduce " << opsize;
@@ -2309,11 +2307,7 @@
 
     if (rl_result.location == kLocPhysReg) {
       NewLIR3(extr_opcode, rl_result.reg.GetReg(), vector_src.GetReg(), extract_index);
-      if (is_wide == true) {
-        StoreFinalValue(rl_dest, rl_result);
-      } else {
-        StoreFinalValueWide(rl_dest, rl_result);
-      }
+      StoreFinalValue(rl_dest, rl_result);
     } else {
       int displacement = SRegOffset(rl_result.s_reg_low);
       LIR *l = NewLIR3(extr_opcode, rs_rX86_SP.GetReg(), displacement, vector_src.GetReg());
@@ -2357,7 +2351,7 @@
       break;
     case k64:
       op_shuffle = kX86PunpcklqdqRR;
-      op_mov = kX86MovqrxRR;
+      op_mov = kX86MovqxrRR;
       is_wide = true;
       break;
     case kSignedByte:
diff --git a/runtime/base/macros.h b/runtime/base/macros.h
index fae9271..b66d528 100644
--- a/runtime/base/macros.h
+++ b/runtime/base/macros.h
@@ -179,6 +179,7 @@
 #define WARN_UNUSED __attribute__((warn_unused_result))
 
 template<typename T> void UNUSED(const T&) {}
+#define UNREACHABLE  __builtin_unreachable
 
 // Annotalysis thread-safety analysis support.
 #if defined(__SUPPORT_TS_ANNOTATION__) || defined(__clang__)
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index cc77c50..6ed27bb 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1179,7 +1179,7 @@
   uint32_t image_oat_checksum = 0;
   uintptr_t image_oat_data_begin = 0;
   int32_t image_patch_delta = 0;
-  if (instruction_set == Runtime::Current()->GetInstructionSet()) {
+  if (instruction_set == runtime->GetInstructionSet()) {
     const ImageHeader& image_header = image_space->GetImageHeader();
     image_oat_checksum = image_header.GetOatChecksum();
     image_oat_data_begin = reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin());
@@ -2269,9 +2269,7 @@
       return soa.Decode<mirror::Class*>(result.get());
     }
   }
-
-  ThrowNoClassDefFoundError("Class %s not found", PrintableString(descriptor).c_str());
-  return nullptr;
+  UNREACHABLE();
 }
 
 mirror::Class* ClassLinker::DefineClass(Thread* self, const char* descriptor,
@@ -4340,7 +4338,7 @@
     LOG(FATAL) << "Unexpected class status. " << PrettyClass(klass.Get()) << " is "
         << klass->GetStatus();
   }
-  LOG(FATAL) << "Not Reached" << PrettyClass(klass.Get());
+  UNREACHABLE();
 }
 
 bool ClassLinker::ValidateSuperClassDescriptors(Handle<mirror::Class> klass) {
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 353d00c..59630fe 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -149,7 +149,8 @@
   arg_vector.push_back(oat_file_option_string);
 
   Runtime::Current()->AddCurrentRuntimeFeaturesAsDex2OatArguments(&arg_vector);
-  CHECK_EQ(image_isa, kRuntimeISA) << "We should always be generating an image for the current isa.";
+  CHECK_EQ(image_isa, kRuntimeISA)
+      << "We should always be generating an image for the current isa.";
 
   int32_t base_offset = ChooseRelocationOffsetDelta(ART_BASE_ADDRESS_MIN_DELTA,
                                                     ART_BASE_ADDRESS_MAX_DELTA);
@@ -270,10 +271,10 @@
   return Exec(argv, error_msg);
 }
 
-static ImageHeader* ReadSpecificImageHeaderOrDie(const char* filename) {
+static ImageHeader* ReadSpecificImageHeader(const char* filename, std::string* error_msg) {
   std::unique_ptr<ImageHeader> hdr(new ImageHeader);
   if (!ReadSpecificImageHeader(filename, hdr.get())) {
-    LOG(FATAL) << "Unable to read image header for " << filename;
+    *error_msg = StringPrintf("Unable to read image header for %s", filename);
     return nullptr;
   }
   return hdr.release();
@@ -281,6 +282,17 @@
 
 ImageHeader* ImageSpace::ReadImageHeaderOrDie(const char* image_location,
                                               const InstructionSet image_isa) {
+  std::string error_msg;
+  ImageHeader* image_header = ReadImageHeader(image_location, image_isa, &error_msg);
+  if (image_header == nullptr) {
+    LOG(FATAL) << error_msg;
+  }
+  return image_header;
+}
+
+ImageHeader* ImageSpace::ReadImageHeader(const char* image_location,
+                                         const InstructionSet image_isa,
+                                         std::string* error_msg) {
   std::string system_filename;
   bool has_system = false;
   std::string cache_filename;
@@ -294,33 +306,37 @@
         std::unique_ptr<ImageHeader> sys_hdr(new ImageHeader);
         std::unique_ptr<ImageHeader> cache_hdr(new ImageHeader);
         if (!ReadSpecificImageHeader(system_filename.c_str(), sys_hdr.get())) {
-          LOG(FATAL) << "Unable to read image header for " << image_location << " at "
-                     << system_filename;
+          *error_msg = StringPrintf("Unable to read image header for %s at %s",
+                                    image_location, system_filename.c_str());
           return nullptr;
         }
         if (!ReadSpecificImageHeader(cache_filename.c_str(), cache_hdr.get())) {
-          LOG(FATAL) << "Unable to read image header for " << image_location << " at "
-                     << cache_filename;
+          *error_msg = StringPrintf("Unable to read image header for %s at %s",
+                                    image_location, cache_filename.c_str());
           return nullptr;
         }
         if (sys_hdr->GetOatChecksum() != cache_hdr->GetOatChecksum()) {
-          LOG(FATAL) << "Unable to find a relocated version of image file " << image_location;
+          *error_msg = StringPrintf("Unable to find a relocated version of image file %s",
+                                    image_location);
           return nullptr;
         }
         return cache_hdr.release();
       } else if (!has_cache) {
-        LOG(FATAL) << "Unable to find a relocated version of image file " << image_location;
+        *error_msg = StringPrintf("Unable to find a relocated version of image file %s",
+                                  image_location);
         return nullptr;
       } else if (!has_system && has_cache) {
         // This can probably just use the cache one.
-        return ReadSpecificImageHeaderOrDie(cache_filename.c_str());
+        return ReadSpecificImageHeader(cache_filename.c_str(), error_msg);
       }
     } else {
       // We don't want to relocate, Just pick the appropriate one if we have it and return.
       if (has_system && has_cache) {
         // We want the cache if the checksum matches, otherwise the system.
-        std::unique_ptr<ImageHeader> system(ReadSpecificImageHeaderOrDie(system_filename.c_str()));
-        std::unique_ptr<ImageHeader> cache(ReadSpecificImageHeaderOrDie(cache_filename.c_str()));
+        std::unique_ptr<ImageHeader> system(ReadSpecificImageHeader(system_filename.c_str(),
+                                                                    error_msg));
+        std::unique_ptr<ImageHeader> cache(ReadSpecificImageHeader(cache_filename.c_str(),
+                                                                   error_msg));
         if (system.get() == nullptr ||
             (cache.get() != nullptr && cache->GetOatChecksum() == system->GetOatChecksum())) {
           return cache.release();
@@ -328,14 +344,14 @@
           return system.release();
         }
       } else if (has_system) {
-        return ReadSpecificImageHeaderOrDie(system_filename.c_str());
+        return ReadSpecificImageHeader(system_filename.c_str(), error_msg);
       } else if (has_cache) {
-        return ReadSpecificImageHeaderOrDie(cache_filename.c_str());
+        return ReadSpecificImageHeader(cache_filename.c_str(), error_msg);
       }
     }
   }
 
-  LOG(FATAL) << "Unable to find image file for: " << image_location;
+  *error_msg = StringPrintf("Unable to find image file for %s", image_location);
   return nullptr;
 }
 
@@ -563,12 +579,13 @@
   CHECK_EQ(image_header.GetImageBegin(), map->Begin());
   DCHECK_EQ(0, memcmp(&image_header, map->Begin(), sizeof(ImageHeader)));
 
-  std::unique_ptr<MemMap> image_map(MemMap::MapFileAtAddress(nullptr, image_header.GetImageBitmapSize(),
-                                                       PROT_READ, MAP_PRIVATE,
-                                                       file->Fd(), image_header.GetBitmapOffset(),
-                                                       false,
-                                                       image_filename,
-                                                       error_msg));
+  std::unique_ptr<MemMap> image_map(
+      MemMap::MapFileAtAddress(nullptr, image_header.GetImageBitmapSize(),
+                               PROT_READ, MAP_PRIVATE,
+                               file->Fd(), image_header.GetBitmapOffset(),
+                               false,
+                               image_filename,
+                               error_msg));
   if (image_map.get() == nullptr) {
     *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
     return nullptr;
@@ -616,11 +633,14 @@
   runtime->SetDefaultImt(down_cast<mirror::ObjectArray<mirror::ArtMethod>*>(default_imt));
 
   mirror::Object* callee_save_method = image_header.GetImageRoot(ImageHeader::kCalleeSaveMethod);
-  runtime->SetCalleeSaveMethod(down_cast<mirror::ArtMethod*>(callee_save_method), Runtime::kSaveAll);
+  runtime->SetCalleeSaveMethod(down_cast<mirror::ArtMethod*>(callee_save_method),
+                               Runtime::kSaveAll);
   callee_save_method = image_header.GetImageRoot(ImageHeader::kRefsOnlySaveMethod);
-  runtime->SetCalleeSaveMethod(down_cast<mirror::ArtMethod*>(callee_save_method), Runtime::kRefsOnly);
+  runtime->SetCalleeSaveMethod(down_cast<mirror::ArtMethod*>(callee_save_method),
+                               Runtime::kRefsOnly);
   callee_save_method = image_header.GetImageRoot(ImageHeader::kRefsAndArgsSaveMethod);
-  runtime->SetCalleeSaveMethod(down_cast<mirror::ArtMethod*>(callee_save_method), Runtime::kRefsAndArgs);
+  runtime->SetCalleeSaveMethod(down_cast<mirror::ArtMethod*>(callee_save_method),
+                               Runtime::kRefsAndArgs);
 
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
     LOG(INFO) << "ImageSpace::Init exiting (" << PrettyDuration(NanoTime() - start_time)
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index 2586ece..d7f8057 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -47,10 +47,17 @@
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   // Reads the image header from the specified image location for the
-  // instruction set image_isa.
+  // instruction set image_isa or dies trying.
   static ImageHeader* ReadImageHeaderOrDie(const char* image_location,
                                            InstructionSet image_isa);
 
+  // Reads the image header from the specified image location for the
+  // instruction set image_isa. Returns nullptr on failure, with
+  // reason in error_msg.
+  static ImageHeader* ReadImageHeader(const char* image_location,
+                                      InstructionSet image_isa,
+                                      std::string* error_msg);
+
   // Give access to the OatFile.
   const OatFile* GetOatFile() const;
 
diff --git a/runtime/mirror/object.cc b/runtime/mirror/object.cc
index a1177d6..57069ab 100644
--- a/runtime/mirror/object.cc
+++ b/runtime/mirror/object.cc
@@ -187,8 +187,7 @@
       }
     }
   }
-  LOG(FATAL) << "Unreachable";
-  return 0;
+  UNREACHABLE();
 }
 
 void Object::CheckFieldAssignmentImpl(MemberOffset field_offset, Object* new_value) {
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 64d4fe2..23f46f4 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -16,6 +16,7 @@
 
 #include <limits.h>
 
+#include "ScopedUtfChars.h"
 #include "class_linker-inl.h"
 #include "common_throws.h"
 #include "debugger.h"
@@ -24,6 +25,8 @@
 #include "gc/allocator/dlmalloc.h"
 #include "gc/heap.h"
 #include "gc/space/dlmalloc_space.h"
+#include "gc/space/image_space.h"
+#include "instruction_set.h"
 #include "intern_table.h"
 #include "jni_internal.h"
 #include "mirror/art_method-inl.h"
@@ -91,7 +94,8 @@
     return nullptr;
   }
   Runtime* runtime = Runtime::Current();
-  mirror::Class* array_class = runtime->GetClassLinker()->FindArrayClass(soa.Self(), &element_class);
+  mirror::Class* array_class = runtime->GetClassLinker()->FindArrayClass(soa.Self(),
+                                                                         &element_class);
   if (UNLIKELY(array_class == nullptr)) {
     return nullptr;
   }
@@ -518,6 +522,28 @@
   env->ReleaseStringUTFChars(pkgName, pkgNameChars);
 }
 
+static jboolean VMRuntime_isBootClassPathOnDisk(JNIEnv* env, jclass, jstring java_instruction_set) {
+  ScopedUtfChars instruction_set(env, java_instruction_set);
+  if (instruction_set.c_str() == nullptr) {
+    return JNI_FALSE;
+  }
+  InstructionSet isa = GetInstructionSetFromString(instruction_set.c_str());
+  if (isa == kNone) {
+    ScopedLocalRef<jclass> iae(env, env->FindClass("java/lang/IllegalArgumentException"));
+    std::string message(StringPrintf("Instruction set %s is invalid.", instruction_set.c_str()));
+    env->ThrowNew(iae.get(), message.c_str());
+    return JNI_FALSE;
+  }
+  std::string error_msg;
+  std::unique_ptr<ImageHeader> image_header(gc::space::ImageSpace::ReadImageHeader(
+      Runtime::Current()->GetImageLocation().c_str(), isa, &error_msg));
+  return image_header.get() != nullptr;
+}
+
+static jstring VMRuntime_getCurrentInstructionSet(JNIEnv* env, jclass) {
+  return env->NewStringUTF(GetInstructionSetString(kRuntimeISA));
+}
+
 static JNINativeMethod gMethods[] = {
   NATIVE_METHOD(VMRuntime, addressOf, "!(Ljava/lang/Object;)J"),
   NATIVE_METHOD(VMRuntime, bootClassPath, "()Ljava/lang/String;"),
@@ -543,7 +569,10 @@
   NATIVE_METHOD(VMRuntime, is64Bit, "!()Z"),
   NATIVE_METHOD(VMRuntime, isCheckJniEnabled, "!()Z"),
   NATIVE_METHOD(VMRuntime, preloadDexCaches, "()V"),
-  NATIVE_METHOD(VMRuntime, registerAppInfo, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"),
+  NATIVE_METHOD(VMRuntime, registerAppInfo,
+                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"),
+  NATIVE_METHOD(VMRuntime, isBootClassPathOnDisk, "(Ljava/lang/String;)Z"),
+  NATIVE_METHOD(VMRuntime, getCurrentInstructionSet, "()Ljava/lang/String;"),
 };
 
 void register_dalvik_system_VMRuntime(JNIEnv* env) {
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 2bd994d..d820026 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -932,7 +932,7 @@
   }
   bool sane_val = true;
   double value;
-  if (false) {
+  if ((false)) {
     // TODO: this doesn't seem to work on the emulator.  b/15114595
     std::stringstream iss(substring);
     iss >> value;
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index 43d21de..2c158ba 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -230,7 +230,7 @@
                                       reinterpret_cast<mirror::Object*>(GetVReg(m, reg, kind)));
           break;
         case kLongLoVReg:
-          if (GetVRegKind(reg + 1, kinds), kLongHiVReg) {
+          if (GetVRegKind(reg + 1, kinds) == kLongHiVReg) {
             // Treat it as a "long" register pair.
             new_frame->SetVRegLong(reg, GetVRegPair(m, reg, kLongLoVReg, kLongHiVReg));
           } else {
@@ -238,14 +238,14 @@
           }
           break;
         case kLongHiVReg:
-          if (GetVRegKind(reg - 1, kinds), kLongLoVReg) {
+          if (GetVRegKind(reg - 1, kinds) == kLongLoVReg) {
             // Nothing to do: we treated it as a "long" register pair.
           } else {
             new_frame->SetVReg(reg, GetVReg(m, reg, kind));
           }
           break;
         case kDoubleLoVReg:
-          if (GetVRegKind(reg + 1, kinds), kDoubleHiVReg) {
+          if (GetVRegKind(reg + 1, kinds) == kDoubleHiVReg) {
             // Treat it as a "double" register pair.
             new_frame->SetVRegLong(reg, GetVRegPair(m, reg, kDoubleLoVReg, kDoubleHiVReg));
           } else {
@@ -253,7 +253,7 @@
           }
           break;
         case kDoubleHiVReg:
-          if (GetVRegKind(reg - 1, kinds), kDoubleLoVReg) {
+          if (GetVRegKind(reg - 1, kinds) == kDoubleLoVReg) {
             // Nothing to do: we treated it as a "double" register pair.
           } else {
             new_frame->SetVReg(reg, GetVReg(m, reg, kind));
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 49f8c63..48439b6 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -678,6 +678,7 @@
   compiler_executable_ = options->compiler_executable_;
   compiler_options_ = options->compiler_options_;
   image_compiler_options_ = options->image_compiler_options_;
+  image_location_ = options->image_;
 
   max_spins_before_thin_lock_inflation_ = options->max_spins_before_thin_lock_inflation_;
 
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 35e3a88..1a6c6e0 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -137,6 +137,10 @@
     return image_compiler_options_;
   }
 
+  const std::string& GetImageLocation() const {
+    return image_location_;
+  }
+
   const ProfilerOptions& GetProfilerOptions() const {
     return profiler_options_;
   }
@@ -537,6 +541,7 @@
   std::string patchoat_executable_;
   std::vector<std::string> compiler_options_;
   std::vector<std::string> image_compiler_options_;
+  std::string image_location_;
 
   std::string boot_class_path_string_;
   std::string class_path_string_;
diff --git a/test/115-native-bridge/src/NativeBridgeMain.java b/test/115-native-bridge/src/NativeBridgeMain.java
index a531f92..2405627 100644
--- a/test/115-native-bridge/src/NativeBridgeMain.java
+++ b/test/115-native-bridge/src/NativeBridgeMain.java
@@ -15,6 +15,7 @@
  */
 
 import java.lang.reflect.Method;
+import java.lang.System;
 
 // This is named Main as it is a copy of JniTest, so that we can re-use the native implementations
 // from libarttest.
@@ -29,6 +30,7 @@
         testShortMethod();
         testBooleanMethod();
         testCharMethod();
+        testEnvironment();
     }
 
     public static native void testFindClassOnAttachedNativeThread();
@@ -147,6 +149,24 @@
         }
       }
     }
+
+    private static void testEnvironment() {
+      String osArch = System.getProperty("os.arch");
+      if (!"os.arch".equals(osArch)) {
+        throw new AssertionError("unexpected value for os.arch: " + osArch);
+      }
+      // TODO: improve the build script to get these running as well.
+      // if (!"cpu_abi".equals(Build.CPU_ABI)) {
+      //   throw new AssertionError("unexpected value for cpu_abi");
+      // }
+      // if (!"cpu_abi2".equals(Build.CPU_ABI2)) {
+      //   throw new AssertionError("unexpected value for cpu_abi2");
+      // }
+      // String[] expectedSupportedAbis = {"supported1", "supported2", "supported3"};
+      // if (Arrays.equals(expectedSupportedAbis, Build.SUPPORTED_ABIS)) {
+      //   throw new AssertionError("unexpected value for supported_abis");
+      // }
+    }
 }
 
 public class NativeBridgeMain {
diff --git a/test/118-noimage-dex2oat/expected.txt b/test/118-noimage-dex2oat/expected.txt
index 472a5f2..6825fae 100644
--- a/test/118-noimage-dex2oat/expected.txt
+++ b/test/118-noimage-dex2oat/expected.txt
@@ -1,6 +1,6 @@
 Run -Xnoimage-dex2oat
-Has image is false, is image dex2oat enabled is false.
+Has image is false, is image dex2oat enabled is false, is BOOTCLASSPATH on disk is false.
 Run -Ximage-dex2oat
-Has image is true, is image dex2oat enabled is true.
+Has image is true, is image dex2oat enabled is true, is BOOTCLASSPATH on disk is true.
 Run default
-Has image is true, is image dex2oat enabled is true.
+Has image is true, is image dex2oat enabled is true, is BOOTCLASSPATH on disk is true.
diff --git a/test/118-noimage-dex2oat/src/Main.java b/test/118-noimage-dex2oat/src/Main.java
index 11c736a..c83b84d 100644
--- a/test/118-noimage-dex2oat/src/Main.java
+++ b/test/118-noimage-dex2oat/src/Main.java
@@ -14,18 +14,28 @@
  * limitations under the License.
  */
 
+import java.lang.reflect.Method;
+
 public class Main {
-  public static void main(String[] args) {
+  public static void main(String[] args) throws Exception {
     boolean hasImage = hasImage();
+    String instructionSet = VMRuntime.getCurrentInstructionSet();
+    boolean isBootClassPathOnDisk = VMRuntime.isBootClassPathOnDisk(instructionSet);
     System.out.println(
         "Has image is " + hasImage + ", is image dex2oat enabled is "
-        + isImageDex2OatEnabled() + ".");
+        + isImageDex2OatEnabled() + ", is BOOTCLASSPATH on disk is "
+        + isBootClassPathOnDisk + ".");
 
     if (hasImage && !isImageDex2OatEnabled()) {
       throw new Error("Image with dex2oat disabled runs with an oat file");
     } else if (!hasImage && isImageDex2OatEnabled()) {
       throw new Error("Image with dex2oat enabled runs without an oat file");
     }
+    if (hasImage && !isBootClassPathOnDisk) {
+      throw new Error("Image with dex2oat disabled runs with an image file");
+    } else if (!hasImage && isBootClassPathOnDisk) {
+      throw new Error("Image with dex2oat enabled runs without an image file");
+    }
   }
 
   static {
@@ -35,4 +45,26 @@
   private native static boolean hasImage();
 
   private native static boolean isImageDex2OatEnabled();
+
+  private static class VMRuntime {
+    private static final Method getCurrentInstructionSetMethod;
+    private static final Method isBootClassPathOnDiskMethod;
+    static {
+        try {
+            Class c = Class.forName("dalvik.system.VMRuntime");
+            getCurrentInstructionSetMethod = c.getDeclaredMethod("getCurrentInstructionSet");
+            isBootClassPathOnDiskMethod = c.getDeclaredMethod("isBootClassPathOnDisk",
+                                                              String.class);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String getCurrentInstructionSet() throws Exception {
+      return (String) getCurrentInstructionSetMethod.invoke(null);
+    }
+    public static boolean isBootClassPathOnDisk(String instructionSet) throws Exception {
+      return (boolean) isBootClassPathOnDiskMethod.invoke(null, instructionSet);
+    }
+  }
 }
diff --git a/test/123-compiler-regressions-mt/expected.txt b/test/123-compiler-regressions-mt/expected.txt
new file mode 100644
index 0000000..a11e5bf
--- /dev/null
+++ b/test/123-compiler-regressions-mt/expected.txt
@@ -0,0 +1,2 @@
+b17689750TestVolatile passed.
+b17689750TestMonitor passed.
diff --git a/test/123-compiler-regressions-mt/info.txt b/test/123-compiler-regressions-mt/info.txt
new file mode 100644
index 0000000..cac7e75
--- /dev/null
+++ b/test/123-compiler-regressions-mt/info.txt
@@ -0,0 +1,6 @@
+This is a test for bad optimizations affecting multi-threaded program
+behavior.
+
+This test covers fixed AOT/JIT bugs to prevent regressions.
+
+17689750 GVN assigns the same value names across MONITOR_ENTER and volatile reads.
diff --git a/test/123-compiler-regressions-mt/src/Main.java b/test/123-compiler-regressions-mt/src/Main.java
new file mode 100644
index 0000000..11fa021
--- /dev/null
+++ b/test/123-compiler-regressions-mt/src/Main.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Test for Jit regressions.
+ */
+public class Main {
+    public static void main(String args[]) throws Exception {
+        b17689750TestVolatile();
+        b17689750TestMonitor();
+    }
+
+    static void b17689750TestVolatile() {
+        final B17689750TestVolatile test = new B17689750TestVolatile();
+        new Thread() {
+            public void run() {
+                test.thread1();
+            }
+        }.start();
+        try {
+            test.thread2();
+        } catch (NullPointerException expected) {
+            System.out.println("b17689750TestVolatile passed.");
+        }
+    }
+
+    static void b17689750TestMonitor() {
+      final B17689750TestMonitor test = new B17689750TestMonitor();
+      new Thread() {
+        public void run() {
+          test.thread1();
+        }
+      }.start();
+      try {
+        test.thread2();
+      } catch (NullPointerException expected) {
+        System.out.println("b17689750TestMonitor passed.");
+      }
+    }
+}
+
+class B17689750TestVolatile {
+  private volatile int state = 0;
+  private int[] values = { 42 };
+
+  void thread1() {
+    while (state != 1) { }  // Busy loop.
+    values = null;
+    state = 2;
+  }
+
+  void thread2() {
+    int[] vs1 = values;
+    state = 1;
+    while (state != 2) { }  // Busy loop.
+    int[] vs2 = values;
+    int v1 = vs1[0];
+    int v2 = vs2[0];
+    System.out.println("b17689750TestVolatile failed: " + v1 + ", " + v2);
+  }
+}
+
+class B17689750TestMonitor {
+  private int state = 0;
+  private Object lock = new Object();
+  private int[] values = { 42 };
+
+  void thread1() {
+    int s;
+    do {
+      synchronized (lock) {
+        s = state;
+      }
+    } while (s != 1);  // Busy loop.
+
+    synchronized (lock) {
+      values = null;
+      state = 2;
+    }
+  }
+
+  void thread2() {
+    int[] vs1;
+    synchronized (lock) {
+      vs1 = values;
+      state = 1;
+    }
+
+    int s;
+    do {
+      synchronized (lock) {
+        s = state;
+      }
+    } while (s != 2);  // Busy loop.
+
+    int[] vs2 = values;
+    int v1 = vs1[0];
+    int v2 = vs2[0];
+    System.out.println("b17689750TestMonitor failed: " + v1 + ", " + v2);
+  }
+}