Merge "Test control flow instruction with optimizing compiler."
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 5b83056..ef5819d 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -177,7 +177,7 @@
     LOCAL_CLANG := $(ART_TARGET_CLANG)
     LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) $(ART_TARGET_DEBUG_CFLAGS)
     LOCAL_CFLAGS_x86 := $(ART_TARGET_CFLAGS_x86)
-    LOCAL_SHARED_LIBRARIES += libdl libicuuc libicui18n libnativehelper libz libcutils
+    LOCAL_SHARED_LIBRARIES += libdl libicuuc libicui18n libnativehelper libz libcutils libvixl
     LOCAL_STATIC_LIBRARIES += libgtest
     LOCAL_MODULE_PATH_32 := $(ART_BASE_NATIVETEST_OUT)
     LOCAL_MODULE_PATH_64 := $(ART_BASE_NATIVETEST_OUT)64
@@ -200,7 +200,7 @@
     LOCAL_CLANG := $(ART_HOST_CLANG)
     LOCAL_CFLAGS += $(ART_HOST_CFLAGS) $(ART_HOST_DEBUG_CFLAGS)
     LOCAL_SHARED_LIBRARIES += libicuuc-host libicui18n-host libnativehelper libz-host
-    LOCAL_STATIC_LIBRARIES += libcutils
+    LOCAL_STATIC_LIBRARIES += libcutils libvixl
     ifneq ($(WITHOUT_HOST_CLANG),true)
         # GCC host compiled tests fail with this linked, presumably due to destructors that run.
         LOCAL_STATIC_LIBRARIES += libgtest_host
diff --git a/compiler/dex/compiler_enums.h b/compiler/dex/compiler_enums.h
index 6c8c85d..38d37b0 100644
--- a/compiler/dex/compiler_enums.h
+++ b/compiler/dex/compiler_enums.h
@@ -324,7 +324,6 @@
 
 enum ThrowKind {
   kThrowNullPointer,
-  kThrowDivZero,
   kThrowArrayBounds,
   kThrowConstantArrayBounds,
   kThrowNoSuchMethod,
diff --git a/compiler/dex/quick/arm/int_arm.cc b/compiler/dex/quick/arm/int_arm.cc
index 1abb91d..8b4171d 100644
--- a/compiler/dex/quick/arm/int_arm.cc
+++ b/compiler/dex/quick/arm/int_arm.cc
@@ -90,8 +90,7 @@
  *     neg   rX
  * done:
  */
-void ArmMir2Lir::GenCmpLong(RegLocation rl_dest, RegLocation rl_src1,
-                            RegLocation rl_src2) {
+void ArmMir2Lir::GenCmpLong(RegLocation rl_dest, RegLocation rl_src1, RegLocation rl_src2) {
   LIR* target1;
   LIR* target2;
   rl_src1 = LoadValueWide(rl_src1, kCoreReg);
@@ -101,7 +100,7 @@
   OpRegReg(kOpCmp, rl_src1.reg.GetHigh(), rl_src2.reg.GetHigh());
   LIR* branch1 = OpCondBranch(kCondLt, NULL);
   LIR* branch2 = OpCondBranch(kCondGt, NULL);
-  OpRegRegReg(kOpSub, t_reg, rl_src1.reg, rl_src2.reg);
+  OpRegRegReg(kOpSub, t_reg, rl_src1.reg.GetLow(), rl_src2.reg.GetLow());
   LIR* branch3 = OpCondBranch(kCondEq, NULL);
 
   LIR* it = OpIT(kCondHi, "E");
@@ -917,7 +916,7 @@
   RegStorage t_reg = AllocTemp();
   NewLIR4(kThumb2OrrRRRs, t_reg.GetReg(), reg.GetLowReg(), reg.GetHighReg(), 0);
   FreeTemp(t_reg);
-  GenCheck(kCondEq, kThrowDivZero);
+  AddDivZeroSlowPath(kCondEq);
 }
 
 // Test suspend flag, return target of taken suspend branch
diff --git a/compiler/dex/quick/dex_file_method_inliner.cc b/compiler/dex/quick/dex_file_method_inliner.cc
index 80a88b8..8806e68 100644
--- a/compiler/dex/quick/dex_file_method_inliner.cc
+++ b/compiler/dex/quick/dex_file_method_inliner.cc
@@ -412,7 +412,8 @@
       result = GenInlineIGet(mir_graph, bb, invoke, move_result, method, method_idx);
       break;
     case kInlineOpIPut:
-      result = GenInlineIPut(mir_graph, bb, invoke, method, method_idx);
+      move_result = mir_graph->FindMoveResult(bb, invoke);
+      result = GenInlineIPut(mir_graph, bb, invoke, move_result, method, method_idx);
       break;
     default:
       LOG(FATAL) << "Unexpected inline op: " << method.opcode;
@@ -646,7 +647,11 @@
   bool object_is_this = (data.method_is_static == 0u && data.object_arg == 0u);
   if (!object_is_this) {
     // TODO: Implement inlining of IGET on non-"this" registers (needs correct stack trace for NPE).
-    return false;
+    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
+    if (!InlineMethodAnalyser::IsSyntheticAccessor(
+        mir_graph->GetMethodLoweringInfo(invoke).GetTargetMethod())) {
+      return false;
+    }
   }
 
   if (object_is_this) {
@@ -672,7 +677,8 @@
 }
 
 bool DexFileMethodInliner::GenInlineIPut(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
-                                         const InlineMethod& method, uint32_t method_idx) {
+                                         MIR* move_result, const InlineMethod& method,
+                                         uint32_t method_idx) {
   CompilationUnit* cu = mir_graph->GetCurrentDexCompilationUnit()->GetCompilationUnit();
   if (cu->enable_debug & (1 << kDebugSlowFieldPath)) {
     return false;
@@ -683,19 +689,32 @@
   DCHECK_EQ(InlineMethodAnalyser::IPutVariant(opcode), data.op_variant);
   uint32_t object_reg = GetInvokeReg(invoke, data.object_arg);
   uint32_t src_reg = GetInvokeReg(invoke, data.src_arg);
+  uint32_t return_reg =
+      data.return_arg_plus1 != 0u ? GetInvokeReg(invoke, data.return_arg_plus1 - 1u) : 0u;
 
   if (opcode == Instruction::IPUT_WIDE && !WideArgIsInConsecutiveDalvikRegs(invoke, data.src_arg)) {
     // The two halfs of the source value are not in consecutive dalvik registers in INVOKE.
     return false;
   }
 
+  DCHECK(move_result == nullptr || data.return_arg_plus1 != 0u);
+  if (move_result != nullptr && move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT_WIDE &&
+      !WideArgIsInConsecutiveDalvikRegs(invoke, data.return_arg_plus1 - 1u)) {
+    // The two halfs of the return value are not in consecutive dalvik registers in INVOKE.
+    return false;
+  }
+
   DCHECK_EQ(data.method_is_static != 0u,
             invoke->dalvikInsn.opcode == Instruction::INVOKE_STATIC ||
             invoke->dalvikInsn.opcode == Instruction::INVOKE_STATIC_RANGE);
   bool object_is_this = (data.method_is_static == 0u && data.object_arg == 0u);
   if (!object_is_this) {
     // TODO: Implement inlining of IPUT on non-"this" registers (needs correct stack trace for NPE).
-    return false;
+    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
+    if (!InlineMethodAnalyser::IsSyntheticAccessor(
+        mir_graph->GetMethodLoweringInfo(invoke).GetTargetMethod())) {
+      return false;
+    }
   }
 
   if (object_is_this) {
@@ -703,7 +722,7 @@
     invoke->dalvikInsn.opcode = static_cast<Instruction::Code>(kMirOpNop);
   }
 
-  MIR* insn = AllocReplacementMIR(mir_graph, invoke, nullptr);
+  MIR* insn = AllocReplacementMIR(mir_graph, invoke, move_result);
   insn->dalvikInsn.opcode = opcode;
   insn->dalvikInsn.vA = src_reg;
   insn->dalvikInsn.vB = object_reg;
@@ -715,6 +734,24 @@
   DCHECK_EQ(data.is_volatile, mir_graph->GetIFieldLoweringInfo(insn).IsVolatile() ? 1u : 0u);
 
   bb->InsertMIRAfter(invoke, insn);
+
+  if (move_result != nullptr) {
+    MIR* move = AllocReplacementMIR(mir_graph, invoke, move_result);
+    insn->width = invoke->width;
+    move->offset = move_result->offset;
+    move->width = move_result->width;
+    if (move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT) {
+      move->dalvikInsn.opcode = Instruction::MOVE_FROM16;
+    } else if (move_result->dalvikInsn.opcode == Instruction::MOVE_RESULT_OBJECT) {
+      move->dalvikInsn.opcode = Instruction::MOVE_OBJECT_FROM16;
+    } else {
+      DCHECK_EQ(move_result->dalvikInsn.opcode, Instruction::MOVE_RESULT_WIDE);
+      move->dalvikInsn.opcode = Instruction::MOVE_WIDE_FROM16;
+    }
+    move->dalvikInsn.vA = move_result->dalvikInsn.vA;
+    move->dalvikInsn.vB = return_reg;
+    bb->InsertMIRAfter(insn, move);
+  }
   return true;
 }
 
diff --git a/compiler/dex/quick/dex_file_method_inliner.h b/compiler/dex/quick/dex_file_method_inliner.h
index b4e190a..c03f89c 100644
--- a/compiler/dex/quick/dex_file_method_inliner.h
+++ b/compiler/dex/quick/dex_file_method_inliner.h
@@ -302,7 +302,7 @@
     static bool GenInlineIGet(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
                               MIR* move_result, const InlineMethod& method, uint32_t method_idx);
     static bool GenInlineIPut(MIRGraph* mir_graph, BasicBlock* bb, MIR* invoke,
-                              const InlineMethod& method, uint32_t method_idx);
+                              MIR* move_result, const InlineMethod& method, uint32_t method_idx);
 
     ReaderWriterMutex lock_;
     /*
diff --git a/compiler/dex/quick/gen_common.cc b/compiler/dex/quick/gen_common.cc
index a3fb420..4522379 100644
--- a/compiler/dex/quick/gen_common.cc
+++ b/compiler/dex/quick/gen_common.cc
@@ -42,17 +42,6 @@
   barrier->u.m.def_mask = ENCODE_ALL;
 }
 
-// TODO: need to do some work to split out targets with
-// condition codes and those without
-LIR* Mir2Lir::GenCheck(ConditionCode c_code, ThrowKind kind) {
-  DCHECK_NE(cu_->instruction_set, kMips);
-  LIR* tgt = RawLIR(0, kPseudoThrowTarget, kind, current_dalvik_offset_);
-  LIR* branch = OpCondBranch(c_code, tgt);
-  // Remember branch target - will process later
-  throw_launchpads_.Insert(tgt);
-  return branch;
-}
-
 LIR* Mir2Lir::GenImmedCheck(ConditionCode c_code, RegStorage reg, int imm_val, ThrowKind kind) {
   LIR* tgt;
   LIR* branch;
@@ -69,6 +58,38 @@
   return branch;
 }
 
+void Mir2Lir::AddDivZeroSlowPath(ConditionCode c_code) {
+  LIR* branch = OpCondBranch(c_code, nullptr);
+  AddDivZeroCheckSlowPath(branch);
+}
+
+void Mir2Lir::AddDivZeroSlowPath(ConditionCode c_code, RegStorage reg, int imm_val) {
+  LIR* branch;
+  if (c_code == kCondAl) {
+    branch = OpUnconditionalBranch(nullptr);
+  } else {
+    branch = OpCmpImmBranch(c_code, reg, imm_val, nullptr);
+  }
+  AddDivZeroCheckSlowPath(branch);
+}
+
+void Mir2Lir::AddDivZeroCheckSlowPath(LIR* branch) {
+  class DivZeroCheckSlowPath : public Mir2Lir::LIRSlowPath {
+   public:
+    DivZeroCheckSlowPath(Mir2Lir* m2l, LIR* branch)
+        : LIRSlowPath(m2l, m2l->GetCurrentDexPc(), branch) {
+    }
+
+    void Compile() {
+      m2l_->ResetRegPool();
+      m2l_->ResetDefTracking();
+      GenerateTargetLabel();
+      m2l_->CallRuntimeHelper(QUICK_ENTRYPOINT_OFFSET(4, pThrowDivZero), true);
+    }
+  };
+
+  AddSlowPath(new (arena_) DivZeroCheckSlowPath(this, branch));
+}
 
 /* Perform null-check on a register.  */
 LIR* Mir2Lir::GenNullCheck(RegStorage m_reg, int opt_flags) {
@@ -689,9 +710,6 @@
         }
         func_offset = QUICK_ENTRYPOINT_OFFSET(4, pThrowArrayBounds);
         break;
-      case kThrowDivZero:
-        func_offset = QUICK_ENTRYPOINT_OFFSET(4, pThrowDivZero);
-        break;
       case kThrowNoSuchMethod:
         OpRegCopy(TargetReg(kArg0), RegStorage::Solo32(v1));
         func_offset =
@@ -1533,7 +1551,7 @@
       rl_src1 = LoadValue(rl_src1, kCoreReg);
       rl_src2 = LoadValue(rl_src2, kCoreReg);
       if (check_zero) {
-          GenImmedCheck(kCondEq, rl_src2.reg, 0, kThrowDivZero);
+          AddDivZeroSlowPath(kCondEq, rl_src2.reg, 0);
       }
       rl_result = GenDivRem(rl_dest, rl_src1.reg, rl_src2.reg, op == kOpDiv);
       done = true;
@@ -1544,7 +1562,7 @@
         rl_src1 = LoadValue(rl_src1, kCoreReg);
         rl_src2 = LoadValue(rl_src2, kCoreReg);
         if (check_zero) {
-            GenImmedCheck(kCondEq, rl_src2.reg, 0, kThrowDivZero);
+            AddDivZeroSlowPath(kCondEq, rl_src2.reg, 0);
         }
         rl_result = GenDivRem(rl_dest, rl_src1.reg, rl_src2.reg, op == kOpDiv);
         done = true;
@@ -1559,7 +1577,7 @@
       RegStorage r_tgt = CallHelperSetup(func_offset);
       LoadValueDirectFixed(rl_src1, TargetReg(kArg0));
       if (check_zero) {
-        GenImmedCheck(kCondEq, TargetReg(kArg1), 0, kThrowDivZero);
+        AddDivZeroSlowPath(kCondEq, TargetReg(kArg1), 0);
       }
       // NOTE: callout here is not a safepoint.
       CallHelper(r_tgt, func_offset, false /* not a safepoint */);
@@ -1784,7 +1802,7 @@
     case Instruction::REM_INT_LIT8:
     case Instruction::REM_INT_LIT16: {
       if (lit == 0) {
-        GenImmedCheck(kCondAl, RegStorage::InvalidReg(), 0, kThrowDivZero);
+        AddDivZeroSlowPath(kCondAl, RegStorage::InvalidReg(), 0);
         return;
       }
       if ((opcode == Instruction::DIV_INT) ||
diff --git a/compiler/dex/quick/gen_invoke.cc b/compiler/dex/quick/gen_invoke.cc
index 396a709..d827568 100644
--- a/compiler/dex/quick/gen_invoke.cc
+++ b/compiler/dex/quick/gen_invoke.cc
@@ -87,6 +87,12 @@
   return call_inst;
 }
 
+void Mir2Lir::CallRuntimeHelper(ThreadOffset<4> helper_offset, bool safepoint_pc) {
+  RegStorage r_tgt = CallHelperSetup(helper_offset);
+  ClobberCallerSave();
+  CallHelper(r_tgt, helper_offset, safepoint_pc);
+}
+
 void Mir2Lir::CallRuntimeHelperImm(ThreadOffset<4> helper_offset, int arg0, bool safepoint_pc) {
   RegStorage r_tgt = CallHelperSetup(helper_offset);
   LoadConstant(TargetReg(kArg0), arg0);
diff --git a/compiler/dex/quick/mips/int_mips.cc b/compiler/dex/quick/mips/int_mips.cc
index 5fe96d2..0492fdb 100644
--- a/compiler/dex/quick/mips/int_mips.cc
+++ b/compiler/dex/quick/mips/int_mips.cc
@@ -346,7 +346,7 @@
   DCHECK(reg.IsPair());   // TODO: support k64BitSolo.
   RegStorage t_reg = AllocTemp();
   OpRegRegReg(kOpOr, t_reg, reg.GetLow(), reg.GetHigh());
-  GenImmedCheck(kCondEq, t_reg, 0, kThrowDivZero);
+  AddDivZeroSlowPath(kCondEq, t_reg, 0);
   FreeTemp(t_reg);
 }
 
diff --git a/compiler/dex/quick/mir_to_lir.cc b/compiler/dex/quick/mir_to_lir.cc
index 73fdc82..6fcdf70 100644
--- a/compiler/dex/quick/mir_to_lir.cc
+++ b/compiler/dex/quick/mir_to_lir.cc
@@ -120,7 +120,7 @@
 bool Mir2Lir::GenSpecialIGet(MIR* mir, const InlineMethod& special) {
   // FastInstance() already checked by DexFileMethodInliner.
   const InlineIGetIPutData& data = special.d.ifield_data;
-  if (data.method_is_static || data.object_arg != 0) {
+  if (data.method_is_static != 0u || data.object_arg != 0u) {
     // The object is not "this" and has to be null-checked.
     return false;
   }
@@ -151,10 +151,14 @@
 bool Mir2Lir::GenSpecialIPut(MIR* mir, const InlineMethod& special) {
   // FastInstance() already checked by DexFileMethodInliner.
   const InlineIGetIPutData& data = special.d.ifield_data;
-  if (data.method_is_static || data.object_arg != 0) {
+  if (data.method_is_static != 0u || data.object_arg != 0u) {
     // The object is not "this" and has to be null-checked.
     return false;
   }
+  if (data.return_arg_plus1 != 0u) {
+    // The setter returns a method argument which we don't support here.
+    return false;
+  }
 
   bool wide = (data.op_variant == InlineMethodAnalyser::IPutVariant(Instruction::IPUT_WIDE));
 
diff --git a/compiler/dex/quick/mir_to_lir.h b/compiler/dex/quick/mir_to_lir.h
index 35f948e..6dbeb34 100644
--- a/compiler/dex/quick/mir_to_lir.h
+++ b/compiler/dex/quick/mir_to_lir.h
@@ -562,7 +562,8 @@
     void HandleThrowLaunchPads();
     void HandleSlowPaths();
     void GenBarrier();
-    LIR* GenCheck(ConditionCode c_code, ThrowKind kind);
+    void AddDivZeroSlowPath(ConditionCode c_code);
+    void AddDivZeroSlowPath(ConditionCode c_code, RegStorage reg, int imm_val);
     void MarkPossibleNullPointerException(int opt_flags);
     void MarkPossibleStackOverflowException();
     void ForceImplicitNullCheck(RegStorage reg, int opt_flags);
@@ -619,6 +620,7 @@
     LIR* CallHelper(RegStorage r_tgt, ThreadOffset<4> helper_offset, bool safepoint_pc,
                     bool use_link = true);
     RegStorage CallHelperSetup(ThreadOffset<4> helper_offset);
+    void CallRuntimeHelper(ThreadOffset<4> helper_offset, bool safepoint_pc);
     void CallRuntimeHelperImm(ThreadOffset<4> helper_offset, int arg0, bool safepoint_pc);
     void CallRuntimeHelperReg(ThreadOffset<4> helper_offset, RegStorage arg0, bool safepoint_pc);
     void CallRuntimeHelperRegLocation(ThreadOffset<4> helper_offset, RegLocation arg0,
@@ -1220,6 +1222,7 @@
      */
     bool GenSpecialIdentity(MIR* mir, const InlineMethod& special);
 
+    void AddDivZeroCheckSlowPath(LIR* branch);
 
   public:
     // TODO: add accessors for these.
diff --git a/compiler/dex/quick/x86/int_x86.cc b/compiler/dex/quick/x86/int_x86.cc
index c1d1e01..a5f3b61 100644
--- a/compiler/dex/quick/x86/int_x86.cc
+++ b/compiler/dex/quick/x86/int_x86.cc
@@ -629,7 +629,7 @@
 
   if (check_zero) {
     // Handle division by zero case.
-    GenImmedCheck(kCondEq, rs_r1, 0, kThrowDivZero);
+    AddDivZeroSlowPath(kCondEq, rs_r1, 0);
   }
 
   // Have to catch 0x80000000/-1 case, or we will get an exception!
@@ -885,7 +885,7 @@
   OpRegRegReg(kOpOr, t_reg, reg.GetLow(), reg.GetHigh());
 
   // In case of zero, throw ArithmeticException.
-  GenCheck(kCondEq, kThrowDivZero);
+  AddDivZeroSlowPath(kCondEq);
 
   // The temp is no longer needed so free it at this time.
   FreeTemp(t_reg);
diff --git a/compiler/utils/arm64/assembler_arm64.h b/compiler/utils/arm64/assembler_arm64.h
index 0220724..8acd1f9 100644
--- a/compiler/utils/arm64/assembler_arm64.h
+++ b/compiler/utils/arm64/assembler_arm64.h
@@ -197,6 +197,8 @@
     CHECK(code < kNumberOfCoreRegisters) << code;
     if (code == SP) {
       return vixl::sp;
+    } else if (code == XZR) {
+      return vixl::xzr;
     }
     return vixl::Register::XRegFromCode(code);
   }
@@ -243,6 +245,9 @@
 
   // List of exception blocks to generate at the end of the code cache.
   std::vector<Arm64Exception*> exception_blocks_;
+
+  // Used for testing.
+  friend class Arm64ManagedRegister_VixlRegisters_Test;
 };
 
 class Arm64Exception {
diff --git a/compiler/utils/arm64/managed_register_arm64.cc b/compiler/utils/arm64/managed_register_arm64.cc
index de5cb8c..8977313 100644
--- a/compiler/utils/arm64/managed_register_arm64.cc
+++ b/compiler/utils/arm64/managed_register_arm64.cc
@@ -53,7 +53,7 @@
   CHECK(!IsNoRegister());
   int no;
   if (IsCoreRegister()) {
-    if (IsStackPointer()) {
+    if (IsZeroRegister()) {
       no = static_cast<int>(X31);
     } else {
       no = static_cast<int>(AsCoreRegister());
diff --git a/compiler/utils/arm64/managed_register_arm64.h b/compiler/utils/arm64/managed_register_arm64.h
index 80f17f5..a0f520f 100644
--- a/compiler/utils/arm64/managed_register_arm64.h
+++ b/compiler/utils/arm64/managed_register_arm64.h
@@ -24,7 +24,7 @@
 namespace art {
 namespace arm64 {
 
-const int kNumberOfCoreRegIds = 32;
+const int kNumberOfCoreRegIds = kNumberOfCoreRegisters;
 const int kNumberOfWRegIds = kNumberOfWRegisters;
 const int kNumberOfDRegIds = kNumberOfDRegisters;
 const int kNumberOfSRegIds = kNumberOfSRegisters;
@@ -78,7 +78,7 @@
 
   WRegister AsOverlappingCoreRegisterLow() const {
     CHECK(IsValidManagedRegister());
-    if (IsStackPointer()) return W31;
+    if (IsZeroRegister()) return W31;
     return static_cast<WRegister>(AsCoreRegister());
   }
 
@@ -189,6 +189,10 @@
     return IsCoreRegister() && (id_ == SP);
   }
 
+  bool IsZeroRegister() const {
+    return IsCoreRegister() && (id_ == XZR);
+  }
+
   int RegId() const {
     CHECK(!IsNoRegister());
     return id_;
diff --git a/compiler/utils/arm64/managed_register_arm64_test.cc b/compiler/utils/arm64/managed_register_arm64_test.cc
index 88c01ee..f149f1b 100644
--- a/compiler/utils/arm64/managed_register_arm64_test.cc
+++ b/compiler/utils/arm64/managed_register_arm64_test.cc
@@ -15,6 +15,7 @@
  */
 
 #include "globals.h"
+#include "assembler_arm64.h"
 #include "managed_register_arm64.h"
 #include "gtest/gtest.h"
 
@@ -295,9 +296,8 @@
 
   Arm64ManagedRegister reg_X31 = Arm64ManagedRegister::FromCoreRegister(X31);
   EXPECT_TRUE(!reg_X31.Equals(Arm64ManagedRegister::NoRegister()));
-  // TODO: Fix the infrastructure, then re-enable.
-  // EXPECT_TRUE(!reg_X31.Equals(Arm64ManagedRegister::FromCoreRegister(SP)));
-  // EXPECT_TRUE(reg_X31.Equals(Arm64ManagedRegister::FromCoreRegister(XZR)));
+  EXPECT_TRUE(reg_X31.Equals(Arm64ManagedRegister::FromCoreRegister(SP)));
+  EXPECT_TRUE(!reg_X31.Equals(Arm64ManagedRegister::FromCoreRegister(XZR)));
   EXPECT_TRUE(!reg_X31.Equals(Arm64ManagedRegister::FromWRegister(W31)));
   EXPECT_TRUE(!reg_X31.Equals(Arm64ManagedRegister::FromWRegister(WZR)));
   EXPECT_TRUE(!reg_X31.Equals(Arm64ManagedRegister::FromSRegister(S0)));
@@ -305,8 +305,7 @@
 
   Arm64ManagedRegister reg_SP = Arm64ManagedRegister::FromCoreRegister(SP);
   EXPECT_TRUE(!reg_SP.Equals(Arm64ManagedRegister::NoRegister()));
-  // TODO: We expect these to pass - SP has a different semantic than X31/XZR.
-  // EXPECT_TRUE(!reg_SP.Equals(Arm64ManagedRegister::FromCoreRegister(X31)));
+  EXPECT_TRUE(reg_SP.Equals(Arm64ManagedRegister::FromCoreRegister(X31)));
   EXPECT_TRUE(!reg_SP.Equals(Arm64ManagedRegister::FromCoreRegister(XZR)));
   EXPECT_TRUE(!reg_SP.Equals(Arm64ManagedRegister::FromWRegister(W31)));
   EXPECT_TRUE(!reg_SP.Equals(Arm64ManagedRegister::FromSRegister(S0)));
@@ -453,17 +452,17 @@
 
   reg = Arm64ManagedRegister::FromCoreRegister(XZR);
   reg_o = Arm64ManagedRegister::FromWRegister(WZR);
-  // TODO: Overlap not implemented, yet
-  // EXPECT_TRUE(reg.Overlaps(Arm64ManagedRegister::FromCoreRegister(X31)));
+  EXPECT_TRUE(reg.Overlaps(Arm64ManagedRegister::FromCoreRegister(X31)));
   EXPECT_TRUE(!reg.Overlaps(Arm64ManagedRegister::FromCoreRegister(X1)));
-  // EXPECT_TRUE(reg.Overlaps(Arm64ManagedRegister::FromCoreRegister(SP)));
-  // EXPECT_TRUE(reg.Overlaps(Arm64ManagedRegister::FromWRegister(W31)));
+  EXPECT_TRUE(reg.Overlaps(Arm64ManagedRegister::FromCoreRegister(SP)));
+  EXPECT_TRUE(reg.Overlaps(Arm64ManagedRegister::FromWRegister(W31)));
   EXPECT_TRUE(!reg.Overlaps(Arm64ManagedRegister::FromWRegister(W1)));
   EXPECT_TRUE(!reg.Overlaps(Arm64ManagedRegister::FromWRegister(W12)));
   EXPECT_TRUE(!reg.Overlaps(Arm64ManagedRegister::FromWRegister(W19)));
   EXPECT_EQ(X31, reg_o.AsOverlappingWRegisterCore());
-  // TODO: XZR is not a core register right now.
-  // EXPECT_EQ(W31, reg.AsOverlappingCoreRegisterLow());
+  EXPECT_EQ(SP, reg_o.AsOverlappingWRegisterCore());
+  EXPECT_NE(XZR, reg_o.AsOverlappingWRegisterCore());
+  EXPECT_EQ(W31, reg.AsOverlappingCoreRegisterLow());
   EXPECT_TRUE(!reg.Overlaps(Arm64ManagedRegister::FromSRegister(S0)));
   EXPECT_TRUE(!reg.Overlaps(Arm64ManagedRegister::FromSRegister(S1)));
   EXPECT_TRUE(!reg.Overlaps(Arm64ManagedRegister::FromSRegister(S2)));
@@ -610,5 +609,154 @@
   EXPECT_TRUE(!reg.Overlaps(Arm64ManagedRegister::FromDRegister(D20)));
 }
 
+TEST(Arm64ManagedRegister, VixlRegisters) {
+  // X Registers.
+  EXPECT_TRUE(vixl::x0.Is(Arm64Assembler::reg_x(X0)));
+  EXPECT_TRUE(vixl::x1.Is(Arm64Assembler::reg_x(X1)));
+  EXPECT_TRUE(vixl::x2.Is(Arm64Assembler::reg_x(X2)));
+  EXPECT_TRUE(vixl::x3.Is(Arm64Assembler::reg_x(X3)));
+  EXPECT_TRUE(vixl::x4.Is(Arm64Assembler::reg_x(X4)));
+  EXPECT_TRUE(vixl::x5.Is(Arm64Assembler::reg_x(X5)));
+  EXPECT_TRUE(vixl::x6.Is(Arm64Assembler::reg_x(X6)));
+  EXPECT_TRUE(vixl::x7.Is(Arm64Assembler::reg_x(X7)));
+  EXPECT_TRUE(vixl::x8.Is(Arm64Assembler::reg_x(X8)));
+  EXPECT_TRUE(vixl::x9.Is(Arm64Assembler::reg_x(X9)));
+  EXPECT_TRUE(vixl::x10.Is(Arm64Assembler::reg_x(X10)));
+  EXPECT_TRUE(vixl::x11.Is(Arm64Assembler::reg_x(X11)));
+  EXPECT_TRUE(vixl::x12.Is(Arm64Assembler::reg_x(X12)));
+  EXPECT_TRUE(vixl::x13.Is(Arm64Assembler::reg_x(X13)));
+  EXPECT_TRUE(vixl::x14.Is(Arm64Assembler::reg_x(X14)));
+  EXPECT_TRUE(vixl::x15.Is(Arm64Assembler::reg_x(X15)));
+  EXPECT_TRUE(vixl::x16.Is(Arm64Assembler::reg_x(X16)));
+  EXPECT_TRUE(vixl::x17.Is(Arm64Assembler::reg_x(X17)));
+  EXPECT_TRUE(vixl::x18.Is(Arm64Assembler::reg_x(X18)));
+  EXPECT_TRUE(vixl::x19.Is(Arm64Assembler::reg_x(X19)));
+  EXPECT_TRUE(vixl::x20.Is(Arm64Assembler::reg_x(X20)));
+  EXPECT_TRUE(vixl::x21.Is(Arm64Assembler::reg_x(X21)));
+  EXPECT_TRUE(vixl::x22.Is(Arm64Assembler::reg_x(X22)));
+  EXPECT_TRUE(vixl::x23.Is(Arm64Assembler::reg_x(X23)));
+  EXPECT_TRUE(vixl::x24.Is(Arm64Assembler::reg_x(X24)));
+  EXPECT_TRUE(vixl::x25.Is(Arm64Assembler::reg_x(X25)));
+  EXPECT_TRUE(vixl::x26.Is(Arm64Assembler::reg_x(X26)));
+  EXPECT_TRUE(vixl::x27.Is(Arm64Assembler::reg_x(X27)));
+  EXPECT_TRUE(vixl::x28.Is(Arm64Assembler::reg_x(X28)));
+  EXPECT_TRUE(vixl::x29.Is(Arm64Assembler::reg_x(X29)));
+  EXPECT_TRUE(vixl::x30.Is(Arm64Assembler::reg_x(X30)));
+  // FIXME: Take a look here.
+  EXPECT_TRUE(vixl::sp.Is(Arm64Assembler::reg_x(X31)));
+  EXPECT_TRUE(!vixl::x31.Is(Arm64Assembler::reg_x(X31)));
+
+  EXPECT_TRUE(vixl::x18.Is(Arm64Assembler::reg_x(TR)));
+  EXPECT_TRUE(vixl::ip0.Is(Arm64Assembler::reg_x(IP0)));
+  EXPECT_TRUE(vixl::ip1.Is(Arm64Assembler::reg_x(IP1)));
+  EXPECT_TRUE(vixl::x29.Is(Arm64Assembler::reg_x(FP)));
+  EXPECT_TRUE(vixl::lr.Is(Arm64Assembler::reg_x(LR)));
+  EXPECT_TRUE(vixl::sp.Is(Arm64Assembler::reg_x(SP)));
+  EXPECT_TRUE(vixl::xzr.Is(Arm64Assembler::reg_x(XZR)));
+
+  // W Registers.
+  EXPECT_TRUE(vixl::w0.Is(Arm64Assembler::reg_w(W0)));
+  EXPECT_TRUE(vixl::w1.Is(Arm64Assembler::reg_w(W1)));
+  EXPECT_TRUE(vixl::w2.Is(Arm64Assembler::reg_w(W2)));
+  EXPECT_TRUE(vixl::w3.Is(Arm64Assembler::reg_w(W3)));
+  EXPECT_TRUE(vixl::w4.Is(Arm64Assembler::reg_w(W4)));
+  EXPECT_TRUE(vixl::w5.Is(Arm64Assembler::reg_w(W5)));
+  EXPECT_TRUE(vixl::w6.Is(Arm64Assembler::reg_w(W6)));
+  EXPECT_TRUE(vixl::w7.Is(Arm64Assembler::reg_w(W7)));
+  EXPECT_TRUE(vixl::w8.Is(Arm64Assembler::reg_w(W8)));
+  EXPECT_TRUE(vixl::w9.Is(Arm64Assembler::reg_w(W9)));
+  EXPECT_TRUE(vixl::w10.Is(Arm64Assembler::reg_w(W10)));
+  EXPECT_TRUE(vixl::w11.Is(Arm64Assembler::reg_w(W11)));
+  EXPECT_TRUE(vixl::w12.Is(Arm64Assembler::reg_w(W12)));
+  EXPECT_TRUE(vixl::w13.Is(Arm64Assembler::reg_w(W13)));
+  EXPECT_TRUE(vixl::w14.Is(Arm64Assembler::reg_w(W14)));
+  EXPECT_TRUE(vixl::w15.Is(Arm64Assembler::reg_w(W15)));
+  EXPECT_TRUE(vixl::w16.Is(Arm64Assembler::reg_w(W16)));
+  EXPECT_TRUE(vixl::w17.Is(Arm64Assembler::reg_w(W17)));
+  EXPECT_TRUE(vixl::w18.Is(Arm64Assembler::reg_w(W18)));
+  EXPECT_TRUE(vixl::w19.Is(Arm64Assembler::reg_w(W19)));
+  EXPECT_TRUE(vixl::w20.Is(Arm64Assembler::reg_w(W20)));
+  EXPECT_TRUE(vixl::w21.Is(Arm64Assembler::reg_w(W21)));
+  EXPECT_TRUE(vixl::w22.Is(Arm64Assembler::reg_w(W22)));
+  EXPECT_TRUE(vixl::w23.Is(Arm64Assembler::reg_w(W23)));
+  EXPECT_TRUE(vixl::w24.Is(Arm64Assembler::reg_w(W24)));
+  EXPECT_TRUE(vixl::w25.Is(Arm64Assembler::reg_w(W25)));
+  EXPECT_TRUE(vixl::w26.Is(Arm64Assembler::reg_w(W26)));
+  EXPECT_TRUE(vixl::w27.Is(Arm64Assembler::reg_w(W27)));
+  EXPECT_TRUE(vixl::w28.Is(Arm64Assembler::reg_w(W28)));
+  EXPECT_TRUE(vixl::w29.Is(Arm64Assembler::reg_w(W29)));
+  EXPECT_TRUE(vixl::w30.Is(Arm64Assembler::reg_w(W30)));
+  EXPECT_TRUE(vixl::w31.Is(Arm64Assembler::reg_w(W31)));
+  EXPECT_TRUE(vixl::wzr.Is(Arm64Assembler::reg_w(WZR)));
+
+  // D Registers.
+  EXPECT_TRUE(vixl::d0.Is(Arm64Assembler::reg_d(D0)));
+  EXPECT_TRUE(vixl::d1.Is(Arm64Assembler::reg_d(D1)));
+  EXPECT_TRUE(vixl::d2.Is(Arm64Assembler::reg_d(D2)));
+  EXPECT_TRUE(vixl::d3.Is(Arm64Assembler::reg_d(D3)));
+  EXPECT_TRUE(vixl::d4.Is(Arm64Assembler::reg_d(D4)));
+  EXPECT_TRUE(vixl::d5.Is(Arm64Assembler::reg_d(D5)));
+  EXPECT_TRUE(vixl::d6.Is(Arm64Assembler::reg_d(D6)));
+  EXPECT_TRUE(vixl::d7.Is(Arm64Assembler::reg_d(D7)));
+  EXPECT_TRUE(vixl::d8.Is(Arm64Assembler::reg_d(D8)));
+  EXPECT_TRUE(vixl::d9.Is(Arm64Assembler::reg_d(D9)));
+  EXPECT_TRUE(vixl::d10.Is(Arm64Assembler::reg_d(D10)));
+  EXPECT_TRUE(vixl::d11.Is(Arm64Assembler::reg_d(D11)));
+  EXPECT_TRUE(vixl::d12.Is(Arm64Assembler::reg_d(D12)));
+  EXPECT_TRUE(vixl::d13.Is(Arm64Assembler::reg_d(D13)));
+  EXPECT_TRUE(vixl::d14.Is(Arm64Assembler::reg_d(D14)));
+  EXPECT_TRUE(vixl::d15.Is(Arm64Assembler::reg_d(D15)));
+  EXPECT_TRUE(vixl::d16.Is(Arm64Assembler::reg_d(D16)));
+  EXPECT_TRUE(vixl::d17.Is(Arm64Assembler::reg_d(D17)));
+  EXPECT_TRUE(vixl::d18.Is(Arm64Assembler::reg_d(D18)));
+  EXPECT_TRUE(vixl::d19.Is(Arm64Assembler::reg_d(D19)));
+  EXPECT_TRUE(vixl::d20.Is(Arm64Assembler::reg_d(D20)));
+  EXPECT_TRUE(vixl::d21.Is(Arm64Assembler::reg_d(D21)));
+  EXPECT_TRUE(vixl::d22.Is(Arm64Assembler::reg_d(D22)));
+  EXPECT_TRUE(vixl::d23.Is(Arm64Assembler::reg_d(D23)));
+  EXPECT_TRUE(vixl::d24.Is(Arm64Assembler::reg_d(D24)));
+  EXPECT_TRUE(vixl::d25.Is(Arm64Assembler::reg_d(D25)));
+  EXPECT_TRUE(vixl::d26.Is(Arm64Assembler::reg_d(D26)));
+  EXPECT_TRUE(vixl::d27.Is(Arm64Assembler::reg_d(D27)));
+  EXPECT_TRUE(vixl::d28.Is(Arm64Assembler::reg_d(D28)));
+  EXPECT_TRUE(vixl::d29.Is(Arm64Assembler::reg_d(D29)));
+  EXPECT_TRUE(vixl::d30.Is(Arm64Assembler::reg_d(D30)));
+  EXPECT_TRUE(vixl::d31.Is(Arm64Assembler::reg_d(D31)));
+
+  // S Registers.
+  EXPECT_TRUE(vixl::s0.Is(Arm64Assembler::reg_s(S0)));
+  EXPECT_TRUE(vixl::s1.Is(Arm64Assembler::reg_s(S1)));
+  EXPECT_TRUE(vixl::s2.Is(Arm64Assembler::reg_s(S2)));
+  EXPECT_TRUE(vixl::s3.Is(Arm64Assembler::reg_s(S3)));
+  EXPECT_TRUE(vixl::s4.Is(Arm64Assembler::reg_s(S4)));
+  EXPECT_TRUE(vixl::s5.Is(Arm64Assembler::reg_s(S5)));
+  EXPECT_TRUE(vixl::s6.Is(Arm64Assembler::reg_s(S6)));
+  EXPECT_TRUE(vixl::s7.Is(Arm64Assembler::reg_s(S7)));
+  EXPECT_TRUE(vixl::s8.Is(Arm64Assembler::reg_s(S8)));
+  EXPECT_TRUE(vixl::s9.Is(Arm64Assembler::reg_s(S9)));
+  EXPECT_TRUE(vixl::s10.Is(Arm64Assembler::reg_s(S10)));
+  EXPECT_TRUE(vixl::s11.Is(Arm64Assembler::reg_s(S11)));
+  EXPECT_TRUE(vixl::s12.Is(Arm64Assembler::reg_s(S12)));
+  EXPECT_TRUE(vixl::s13.Is(Arm64Assembler::reg_s(S13)));
+  EXPECT_TRUE(vixl::s14.Is(Arm64Assembler::reg_s(S14)));
+  EXPECT_TRUE(vixl::s15.Is(Arm64Assembler::reg_s(S15)));
+  EXPECT_TRUE(vixl::s16.Is(Arm64Assembler::reg_s(S16)));
+  EXPECT_TRUE(vixl::s17.Is(Arm64Assembler::reg_s(S17)));
+  EXPECT_TRUE(vixl::s18.Is(Arm64Assembler::reg_s(S18)));
+  EXPECT_TRUE(vixl::s19.Is(Arm64Assembler::reg_s(S19)));
+  EXPECT_TRUE(vixl::s20.Is(Arm64Assembler::reg_s(S20)));
+  EXPECT_TRUE(vixl::s21.Is(Arm64Assembler::reg_s(S21)));
+  EXPECT_TRUE(vixl::s22.Is(Arm64Assembler::reg_s(S22)));
+  EXPECT_TRUE(vixl::s23.Is(Arm64Assembler::reg_s(S23)));
+  EXPECT_TRUE(vixl::s24.Is(Arm64Assembler::reg_s(S24)));
+  EXPECT_TRUE(vixl::s25.Is(Arm64Assembler::reg_s(S25)));
+  EXPECT_TRUE(vixl::s26.Is(Arm64Assembler::reg_s(S26)));
+  EXPECT_TRUE(vixl::s27.Is(Arm64Assembler::reg_s(S27)));
+  EXPECT_TRUE(vixl::s28.Is(Arm64Assembler::reg_s(S28)));
+  EXPECT_TRUE(vixl::s29.Is(Arm64Assembler::reg_s(S29)));
+  EXPECT_TRUE(vixl::s30.Is(Arm64Assembler::reg_s(S30)));
+  EXPECT_TRUE(vixl::s31.Is(Arm64Assembler::reg_s(S31)));
+}
+
 }  // namespace arm64
 }  // namespace art
diff --git a/runtime/arch/arm64/registers_arm64.h b/runtime/arch/arm64/registers_arm64.h
index ca904bc..43c0ad6 100644
--- a/runtime/arch/arm64/registers_arm64.h
+++ b/runtime/arch/arm64/registers_arm64.h
@@ -63,8 +63,8 @@
   LR  = 30,
   SP  = 31,     // SP is X31 and overlaps with XRZ but we encode it as a
                 // special register, due to the different instruction semantics.
-  XZR = 32,     // FIXME This needs to be reconciled with the JNI assembler.
-  kNumberOfCoreRegisters = 32,
+  XZR = 32,
+  kNumberOfCoreRegisters = 33,
   kNoRegister = -1,
 };
 std::ostream& operator<<(std::ostream& os, const Register& rhs);
@@ -103,7 +103,6 @@
   W29 = 29,
   W30 = 30,
   W31 = 31,
-  WSP = 31,
   WZR = 31,
   kNumberOfWRegisters = 32,
   kNoWRegister = -1,
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 4b881f6..b50c098 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -76,6 +76,7 @@
   kClassLinkerClassesLock,
   kBreakpointLock,
   kMonitorLock,
+  kMonitorListLock,
   kThreadListLock,
   kBreakpointInvokeLock,
   kDeoptimizationLock,
diff --git a/runtime/gc/allocator/rosalloc.cc b/runtime/gc/allocator/rosalloc.cc
index 920741f..cbefa6a 100644
--- a/runtime/gc/allocator/rosalloc.cc
+++ b/runtime/gc/allocator/rosalloc.cc
@@ -2005,6 +2005,61 @@
   }
 }
 
+size_t RosAlloc::ReleasePages() {
+  VLOG(heap) << "RosAlloc::ReleasePages()";
+  DCHECK(!DoesReleaseAllPages());
+  Thread* self = Thread::Current();
+  size_t reclaimed_bytes = 0;
+  size_t i = 0;
+  while (true) {
+    MutexLock mu(self, lock_);
+    // Check the page map size which might have changed due to grow/shrink.
+    size_t pm_end = page_map_size_;
+    if (i >= pm_end) {
+      // Reached the end.
+      break;
+    }
+    byte pm = page_map_[i];
+    switch (pm) {
+      case kPageMapEmpty: {
+        // The start of a free page run. Release pages.
+        FreePageRun* fpr = reinterpret_cast<FreePageRun*>(base_ + i * kPageSize);
+        DCHECK(free_page_runs_.find(fpr) != free_page_runs_.end());
+        size_t fpr_size = fpr->ByteSize(this);
+        DCHECK(IsAligned<kPageSize>(fpr_size));
+        byte* start = reinterpret_cast<byte*>(fpr);
+        if (kIsDebugBuild) {
+          // In the debug build, the first page of a free page run
+          // contains a magic number for debugging. Exclude it.
+          start = reinterpret_cast<byte*>(fpr) + kPageSize;
+        }
+        byte* end = reinterpret_cast<byte*>(fpr) + fpr_size;
+        CHECK_EQ(madvise(start, end - start, MADV_DONTNEED), 0);
+        reclaimed_bytes += fpr_size;
+        size_t num_pages = fpr_size / kPageSize;
+        if (kIsDebugBuild) {
+          for (size_t j = i + 1; j < i + num_pages; ++j) {
+            DCHECK_EQ(page_map_[j], kPageMapEmpty);
+          }
+        }
+        i += num_pages;
+        DCHECK_LE(i, pm_end);
+        break;
+      }
+      case kPageMapLargeObject:      // Fall through.
+      case kPageMapLargeObjectPart:  // Fall through.
+      case kPageMapRun:              // Fall through.
+      case kPageMapRunPart:          // Fall through.
+        ++i;
+        break;  // Skip.
+      default:
+        LOG(FATAL) << "Unreachable - page map type: " << pm;
+        break;
+    }
+  }
+  return reclaimed_bytes;
+}
+
 }  // namespace allocator
 }  // namespace gc
 }  // namespace art
diff --git a/runtime/gc/allocator/rosalloc.h b/runtime/gc/allocator/rosalloc.h
index 0b4b189..5d9d75c 100644
--- a/runtime/gc/allocator/rosalloc.h
+++ b/runtime/gc/allocator/rosalloc.h
@@ -539,6 +539,8 @@
   void InspectAll(void (*handler)(void* start, void* end, size_t used_bytes, void* callback_arg),
                   void* arg)
       LOCKS_EXCLUDED(lock_);
+  // Release empty pages.
+  size_t ReleasePages() LOCKS_EXCLUDED(lock_);
   // Returns the current footprint.
   size_t Footprint() LOCKS_EXCLUDED(lock_);
   // Returns the current capacity, maximum footprint.
diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc
index 07951e0..82340f5 100644
--- a/runtime/gc/collector/garbage_collector.cc
+++ b/runtime/gc/collector/garbage_collector.cc
@@ -201,7 +201,7 @@
 
 uint64_t GarbageCollector::GetEstimatedLastIterationThroughput() const {
   // Add 1ms to prevent possible division by 0.
-  return (freed_bytes_ * 1000) / (NsToMs(GetDurationNs()) + 1);
+  return (static_cast<uint64_t>(freed_bytes_) * 1000) / (NsToMs(GetDurationNs()) + 1);
 }
 
 }  // namespace collector
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 915e54f..e3fa834 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -914,8 +914,16 @@
   // Transition the collector if the desired collector type is not the same as the current
   // collector type.
   TransitionCollector(desired_collector_type);
-  // Do a heap trim if it is needed.
-  Trim();
+  if (!CareAboutPauseTimes()) {
+    // Deflate the monitors, this can cause a pause but shouldn't matter since we don't care
+    // about pauses.
+    Runtime* runtime = Runtime::Current();
+    runtime->GetThreadList()->SuspendAll();
+    runtime->GetMonitorList()->DeflateMonitors();
+    runtime->GetThreadList()->ResumeAll();
+    // Do a heap trim if it is needed.
+    Trim();
+  }
 }
 
 void Heap::Trim() {
@@ -2663,6 +2671,10 @@
 }
 
 void Heap::RequestHeapTrim() {
+  // Request a heap trim only if we do not currently care about pause times.
+  if (CareAboutPauseTimes()) {
+    return;
+  }
   // GC completed and now we must decide whether to request a heap trim (advising pages back to the
   // kernel) or not. Issuing a request will also cause trimming of the libc heap. As a trim scans
   // a space it will hold its lock and can become a cause of jank.
@@ -2684,21 +2696,17 @@
     // as we don't hold the lock while requesting the trim).
     return;
   }
-
-  // Request a heap trim only if we do not currently care about pause times.
-  if (!CareAboutPauseTimes()) {
-    {
-      MutexLock mu(self, *heap_trim_request_lock_);
-      if (last_trim_time_ + kHeapTrimWait >= NanoTime()) {
-        // We have done a heap trim in the last kHeapTrimWait nanosecs, don't request another one
-        // just yet.
-        return;
-      }
-      heap_trim_request_pending_ = true;
+  {
+    MutexLock mu(self, *heap_trim_request_lock_);
+    if (last_trim_time_ + kHeapTrimWait >= NanoTime()) {
+      // We have done a heap trim in the last kHeapTrimWait nanosecs, don't request another one
+      // just yet.
+      return;
     }
-    // Notify the daemon thread which will actually do the heap trim.
-    SignalHeapTrimDaemon(self);
+    heap_trim_request_pending_ = true;
   }
+  // Notify the daemon thread which will actually do the heap trim.
+  SignalHeapTrimDaemon(self);
 }
 
 void Heap::SignalHeapTrimDaemon(Thread* self) {
diff --git a/runtime/gc/space/rosalloc_space.cc b/runtime/gc/space/rosalloc_space.cc
index 012267b..5c5e7f8 100644
--- a/runtime/gc/space/rosalloc_space.cc
+++ b/runtime/gc/space/rosalloc_space.cc
@@ -222,6 +222,7 @@
 }
 
 size_t RosAllocSpace::Trim() {
+  VLOG(heap) << "RosAllocSpace::Trim() ";
   {
     MutexLock mu(Thread::Current(), lock_);
     // Trim to release memory at the end of the space.
@@ -229,10 +230,7 @@
   }
   // Attempt to release pages if it does not release all empty pages.
   if (!rosalloc_->DoesReleaseAllPages()) {
-    VLOG(heap) << "RosAllocSpace::Trim() ";
-    size_t reclaimed = 0;
-    InspectAllRosAlloc(DlmallocMadviseCallback, &reclaimed, false);
-    return reclaimed;
+    return rosalloc_->ReleasePages();
   }
   return 0;
 }
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index bcaf8ec..bbc7dd0 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -205,7 +205,7 @@
 void Monitor::Lock(Thread* self) {
   MutexLock mu(self, monitor_lock_);
   while (true) {
-    if (owner_ == NULL) {  // Unowned.
+    if (owner_ == nullptr) {  // Unowned.
       owner_ = self;
       CHECK_EQ(lock_count_, 0);
       // When debugging, save the current monitor holder for future
@@ -223,15 +223,15 @@
     uint64_t wait_start_ms = log_contention ? 0 : MilliTime();
     mirror::ArtMethod* owners_method = locking_method_;
     uint32_t owners_dex_pc = locking_dex_pc_;
+    // Do this before releasing the lock so that we don't get deflated.
+    ++num_waiters_;
     monitor_lock_.Unlock(self);  // Let go of locks in order.
     {
       ScopedThreadStateChange tsc(self, kBlocked);  // Change to blocked and give up mutator_lock_.
       self->SetMonitorEnterObject(obj_);
       MutexLock mu2(self, monitor_lock_);  // Reacquire monitor_lock_ without mutator_lock_ for Wait.
       if (owner_ != NULL) {  // Did the owner_ give the lock up?
-        ++num_waiters_;
         monitor_contenders_.Wait(self);  // Still contended so wait.
-        --num_waiters_;
         // Woken from contention.
         if (log_contention) {
           uint64_t wait_ms = MilliTime() - wait_start_ms;
@@ -252,6 +252,7 @@
       self->SetMonitorEnterObject(nullptr);
     }
     monitor_lock_.Lock(self);  // Reacquire locks in order.
+    --num_waiters_;
   }
 }
 
@@ -431,6 +432,7 @@
    * not order sensitive as we hold the pthread mutex.
    */
   AppendToWaitSet(self);
+  ++num_waiters_;
   int prev_lock_count = lock_count_;
   lock_count_ = 0;
   owner_ = NULL;
@@ -507,6 +509,7 @@
   lock_count_ = prev_lock_count;
   locking_method_ = saved_method;
   locking_dex_pc_ = saved_dex_pc;
+  --num_waiters_;
   RemoveFromWaitSet(self);
 
   if (was_interrupted) {
@@ -575,8 +578,12 @@
   // If the lock isn't an inflated monitor, then we don't need to deflate anything.
   if (lw.GetState() == LockWord::kFatLocked) {
     Monitor* monitor = lw.FatLockMonitor();
-    CHECK(monitor != nullptr);
+    DCHECK(monitor != nullptr);
     MutexLock mu(self, monitor->monitor_lock_);
+    // Can't deflate if we have anybody waiting on the CV.
+    if (monitor->num_waiters_ > 0) {
+      return false;
+    }
     Thread* owner = monitor->owner_;
     if (owner != nullptr) {
       // Can't deflate if we are locked and have a hash code.
@@ -587,17 +594,16 @@
       if (monitor->lock_count_ > LockWord::kThinLockMaxCount) {
         return false;
       }
-      // Can't deflate if we have anybody waiting on the CV.
-      if (monitor->num_waiters_ > 0) {
-        return false;
-      }
       // Deflate to a thin lock.
-      obj->SetLockWord(LockWord::FromThinLockId(owner->GetTid(), monitor->lock_count_));
+      obj->SetLockWord(LockWord::FromThinLockId(owner->GetThreadId(), monitor->lock_count_));
+      VLOG(monitor) << "Deflated " << obj << " to thin lock " << owner->GetTid() << " / " << monitor->lock_count_;
     } else if (monitor->HasHashCode()) {
       obj->SetLockWord(LockWord::FromHashCode(monitor->GetHashCode()));
+      VLOG(monitor) << "Deflated " << obj << " to hash monitor " << monitor->GetHashCode();
     } else {
       // No lock and no hash, just put an empty lock word inside the object.
       obj->SetLockWord(LockWord());
+      VLOG(monitor) << "Deflated" << obj << " to empty lock word";
     }
     // The monitor is deflated, mark the object as nullptr so that we know to delete it during the
     // next GC.
@@ -1054,7 +1060,7 @@
 }
 
 MonitorList::MonitorList()
-    : allow_new_monitors_(true), monitor_list_lock_("MonitorList lock"),
+    : allow_new_monitors_(true), monitor_list_lock_("MonitorList lock", kMonitorListLock),
       monitor_add_condition_("MonitorList disallow condition", monitor_list_lock_) {
 }
 
@@ -1103,6 +1109,22 @@
   }
 }
 
+static mirror::Object* MonitorDeflateCallback(mirror::Object* object, void* arg)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  if (Monitor::Deflate(reinterpret_cast<Thread*>(arg), object)) {
+    DCHECK_NE(object->GetLockWord().GetState(), LockWord::kFatLocked);
+    // If we deflated, return nullptr so that the monitor gets removed from the array.
+    return nullptr;
+  }
+  return object;  // Monitor was not deflated.
+}
+
+void MonitorList::DeflateMonitors() {
+  Thread* self = Thread::Current();
+  Locks::mutator_lock_->AssertExclusiveHeld(self);
+  SweepMonitorList(MonitorDeflateCallback, reinterpret_cast<Thread*>(self));
+}
+
 MonitorInfo::MonitorInfo(mirror::Object* obj) : owner_(NULL), entry_count_(0) {
   DCHECK(obj != NULL);
 
diff --git a/runtime/monitor.h b/runtime/monitor.h
index 55504b5..c459278 100644
--- a/runtime/monitor.h
+++ b/runtime/monitor.h
@@ -224,9 +224,11 @@
   void Add(Monitor* m);
 
   void SweepMonitorList(IsMarkedCallback* callback, void* arg)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  void DisallowNewMonitors();
-  void AllowNewMonitors();
+      LOCKS_EXCLUDED(monitor_list_lock_) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  void DisallowNewMonitors() LOCKS_EXCLUDED(monitor_list_lock_);
+  void AllowNewMonitors() LOCKS_EXCLUDED(monitor_list_lock_);
+  void DeflateMonitors() LOCKS_EXCLUDED(monitor_list_lock_)
+      EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_);
 
  private:
   bool allow_new_monitors_ GUARDED_BY(monitor_list_lock_);
diff --git a/runtime/quick/inline_method_analyser.cc b/runtime/quick/inline_method_analyser.cc
index a9072d8..8bd8dba 100644
--- a/runtime/quick/inline_method_analyser.cc
+++ b/runtime/quick/inline_method_analyser.cc
@@ -135,6 +135,12 @@
   }
 }
 
+bool InlineMethodAnalyser::IsSyntheticAccessor(MethodReference ref) {
+  const DexFile::MethodId& method_id = ref.dex_file->GetMethodId(ref.dex_method_index);
+  const char* method_name = ref.dex_file->GetMethodName(method_id);
+  return strncmp(method_name, "access$", strlen("access$")) == 0;
+}
+
 bool InlineMethodAnalyser::AnalyseReturnMethod(const DexFile::CodeItem* code_item,
                                                InlineMethod* result) {
   const Instruction* return_instruction = Instruction::At(code_item->insns_);
@@ -218,13 +224,24 @@
   uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_;
   DCHECK_GE(object_reg, arg_start);
   DCHECK_LT(object_reg, code_item->registers_size_);
+  uint32_t object_arg = object_reg - arg_start;
+
   DCHECK_LT(opcode == Instruction::IGET_WIDE ? dst_reg + 1 : dst_reg, code_item->registers_size_);
   if (dst_reg != return_reg) {
     return false;  // Not returning the value retrieved by IGET?
   }
 
-  if ((verifier->GetAccessFlags() & kAccStatic) != 0 || object_reg != arg_start) {
-    // TODO: Support inlining IGET on other register than "this".
+  if ((verifier->GetAccessFlags() & kAccStatic) != 0u || object_arg != 0u) {
+    // TODO: Implement inlining of IGET on non-"this" registers (needs correct stack trace for NPE).
+    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
+    if (!IsSyntheticAccessor(verifier->GetMethodReference())) {
+      return false;
+    }
+  }
+
+  // InlineIGetIPutData::object_arg is only 4 bits wide.
+  static constexpr uint16_t kMaxObjectArg = 15u;
+  if (object_arg > kMaxObjectArg) {
     return false;
   }
 
@@ -236,10 +253,10 @@
     result->opcode = kInlineOpIGet;
     result->flags = kInlineSpecial;
     data->op_variant = IGetVariant(opcode);
-    data->object_arg = object_reg - arg_start;  // Allow IGET on any register, not just "this".
-    data->src_arg = 0;
-    data->method_is_static = (verifier->GetAccessFlags() & kAccStatic) != 0;
-    data->reserved = 0;
+    data->method_is_static = (verifier->GetAccessFlags() & kAccStatic) != 0u ? 1u : 0u;
+    data->object_arg = object_arg;  // Allow IGET on any register, not just "this".
+    data->src_arg = 0u;
+    data->return_arg_plus1 = 0u;
   }
   return true;
 }
@@ -253,26 +270,45 @@
 
   const Instruction* return_instruction = instruction->Next();
   Instruction::Code return_opcode = return_instruction->Opcode();
+  uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_;
+  uint16_t return_arg_plus1 = 0u;
   if (return_opcode != Instruction::RETURN_VOID) {
-    // TODO: Support returning an argument.
-    // This is needed by builder classes and generated accessor setters.
-    //    builder.setX(value): iput value, this, fieldX; return-object this;
-    //    object.access$nnn(value): iput value, this, fieldX; return value;
-    // Use InlineIGetIPutData::reserved to hold the information.
-    return false;
+    if (return_opcode != Instruction::RETURN &&
+        return_opcode != Instruction::RETURN_OBJECT &&
+        return_opcode != Instruction::RETURN_WIDE) {
+      return false;
+    }
+    // Returning an argument.
+    uint32_t return_reg = return_instruction->VRegA_11x();
+    DCHECK_GE(return_reg, arg_start);
+    DCHECK_LT(return_opcode == Instruction::RETURN_WIDE ? return_reg + 1u : return_reg,
+              code_item->registers_size_);
+    return_arg_plus1 = return_reg - arg_start + 1u;
   }
 
   uint32_t src_reg = instruction->VRegA_22c();
   uint32_t object_reg = instruction->VRegB_22c();
   uint32_t field_idx = instruction->VRegC_22c();
-  uint32_t arg_start = code_item->registers_size_ - code_item->ins_size_;
   DCHECK_GE(object_reg, arg_start);
   DCHECK_LT(object_reg, code_item->registers_size_);
   DCHECK_GE(src_reg, arg_start);
   DCHECK_LT(opcode == Instruction::IPUT_WIDE ? src_reg + 1 : src_reg, code_item->registers_size_);
+  uint32_t object_arg = object_reg - arg_start;
+  uint32_t src_arg = src_reg - arg_start;
 
-  if ((verifier->GetAccessFlags() & kAccStatic) != 0 || object_reg != arg_start) {
-    // TODO: Support inlining IPUT on other register than "this".
+  if ((verifier->GetAccessFlags() & kAccStatic) != 0u || object_arg != 0u) {
+    // TODO: Implement inlining of IPUT on non-"this" registers (needs correct stack trace for NPE).
+    // Allow synthetic accessors. We don't care about losing their stack frame in NPE.
+    if (!IsSyntheticAccessor(verifier->GetMethodReference())) {
+      return false;
+    }
+  }
+
+  // InlineIGetIPutData::object_arg/src_arg/return_arg_plus1 are each only 4 bits wide.
+  static constexpr uint16_t kMaxObjectArg = 15u;
+  static constexpr uint16_t kMaxSrcArg = 15u;
+  static constexpr uint16_t kMaxReturnArgPlus1 = 15u;
+  if (object_arg > kMaxObjectArg || src_arg > kMaxSrcArg || return_arg_plus1 > kMaxReturnArgPlus1) {
     return false;
   }
 
@@ -284,10 +320,10 @@
     result->opcode = kInlineOpIPut;
     result->flags = kInlineSpecial;
     data->op_variant = IPutVariant(opcode);
-    data->object_arg = object_reg - arg_start;  // Allow IPUT on any register, not just "this".
-    data->src_arg = src_reg - arg_start;
-    data->method_is_static = (verifier->GetAccessFlags() & kAccStatic) != 0;
-    data->reserved = 0;
+    data->method_is_static = (verifier->GetAccessFlags() & kAccStatic) != 0u ? 1u : 0u;
+    data->object_arg = object_arg;  // Allow IPUT on any register, not just "this".
+    data->src_arg = src_arg;
+    data->return_arg_plus1 = return_arg_plus1;
   }
   return true;
 }
diff --git a/runtime/quick/inline_method_analyser.h b/runtime/quick/inline_method_analyser.h
index 8e1a408..ddee89b 100644
--- a/runtime/quick/inline_method_analyser.h
+++ b/runtime/quick/inline_method_analyser.h
@@ -21,6 +21,7 @@
 #include "base/mutex.h"
 #include "dex_file.h"
 #include "dex_instruction.h"
+#include "method_reference.h"
 
 /*
  * NOTE: This code is part of the quick compiler. It lives in the runtime
@@ -98,10 +99,10 @@
   // opcode-Instruction::IPUT for IPUTs. This is because the runtime
   // doesn't know the OpSize enumeration.
   uint16_t op_variant : 3;
+  uint16_t method_is_static : 1;
   uint16_t object_arg : 4;
   uint16_t src_arg : 4;  // iput only
-  uint16_t method_is_static : 1;
-  uint16_t reserved : 4;
+  uint16_t return_arg_plus1 : 4;  // iput only, method argument to return + 1, 0 = return void.
   uint16_t field_idx;
   uint32_t is_volatile : 1;
   uint32_t field_offset : 31;
@@ -156,6 +157,9 @@
     return opcode - Instruction::IPUT;
   }
 
+  // Determines whether the method is a synthetic accessor (method name starts with "access$").
+  static bool IsSyntheticAccessor(MethodReference ref);
+
  private:
   static bool AnalyseReturnMethod(const DexFile::CodeItem* code_item, InlineMethod* result);
   static bool AnalyseConstMethod(const DexFile::CodeItem* code_item, InlineMethod* result);
diff --git a/test/083-compiler-regressions/expected.txt b/test/083-compiler-regressions/expected.txt
index c1e30bc..db50300 100644
--- a/test/083-compiler-regressions/expected.txt
+++ b/test/083-compiler-regressions/expected.txt
@@ -21,6 +21,10 @@
 wideGetterSetterTest passes
 wideIdentityTest passes
 returnConstantTest passes
+setterTestWithReturnArgIgnoreReturn passes
+setterTestWithReturnArgUseReturn passes
+wideSetterTestWithReturnArgIgnoreReturn passes
+wideSetterTestWithReturnArgUseReturn passes
 LVNTests.testNPE1 passes
 LVNTests.testNPE2 passes
 longDivTest passes
diff --git a/test/083-compiler-regressions/src/Main.java b/test/083-compiler-regressions/src/Main.java
index 586ff87..d32c037 100644
--- a/test/083-compiler-regressions/src/Main.java
+++ b/test/083-compiler-regressions/src/Main.java
@@ -43,6 +43,10 @@
         wideGetterSetterTest();
         wideIdentityTest();
         returnConstantTest();
+        setterTestWithReturnArgIgnoreReturn();
+        setterTestWithReturnArgUseReturn();
+        wideSetterTestWithReturnArgIgnoreReturn();
+        wideSetterTestWithReturnArgUseReturn();
         LVNTests.testNPE1();
         LVNTests.testNPE2();
         ZeroTests.longDivTest();
@@ -179,6 +183,576 @@
         }
     }
 
+    static void setterTestWithReturnArgIgnoreReturn() {
+        Foo foo = new Foo();
+        int sum = foo.getBar0();
+        sum += foo.getBar0();
+        foo.setBar1ReturnThis(sum);
+        sum += foo.getBar0();
+        foo.setBar2ReturnThis(1,sum);
+        sum += foo.getBar0();
+        foo.setBar3ReturnThis(1,2,sum);
+        sum += foo.getBar0();
+        foo.setBar4ReturnThis(1,2,3,sum);
+        sum += foo.getBar0();
+        foo.setBar5ReturnThis(1,2,3,4,sum);
+        sum += foo.getBar0();
+        foo.setBar1ReturnBarArg(sum);
+        sum += foo.getBar0();
+        foo.setBar2ReturnBarArg(1,sum);
+        sum += foo.getBar0();
+        foo.setBar3ReturnBarArg(1,2,sum);
+        sum += foo.getBar0();
+        foo.setBar4ReturnBarArg(1,2,3,sum);
+        sum += foo.getBar0();
+        foo.setBar5ReturnBarArg(1,2,3,4,sum);
+        sum += foo.getBar0();
+        foo.setBar2ReturnDummyArg1(1,sum);
+        sum += foo.getBar0();
+        foo.setBar3ReturnDummyArg2(1,2,sum);
+        sum += foo.getBar0();
+        foo.setBar4ReturnDummyArg3(1,2,3,sum);
+        sum += foo.getBar0();
+        foo.setBar5ReturnDummyArg4(1,2,3,4,sum);
+        sum += foo.getBar0();
+        Foo nullFoo = Foo.getNullFoo();
+        try {
+            nullFoo.setBar1ReturnThis(sum);
+        } catch(NullPointerException npe) {
+            sum += 404;
+        }
+        try {
+            nullFoo.setBar2ReturnThis(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 2 * 404;
+        }
+        try {
+            nullFoo.setBar3ReturnThis(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 3 * 404;
+        }
+        try {
+            nullFoo.setBar4ReturnThis(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 4 * 404;
+        }
+        try {
+            nullFoo.setBar5ReturnThis(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 5 * 404;
+        }
+        try {
+            nullFoo.setBar1ReturnBarArg(sum);
+        } catch(NullPointerException npe) {
+            sum += 6 * 404;
+        }
+        try {
+            nullFoo.setBar2ReturnBarArg(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 7 * 404;
+        }
+        try {
+            nullFoo.setBar3ReturnBarArg(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 8 * 404;
+        }
+        try {
+            nullFoo.setBar4ReturnBarArg(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 9 * 404;
+        }
+        try {
+            nullFoo.setBar5ReturnBarArg(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 10 * 404;
+        }
+        try {
+            nullFoo.setBar2ReturnDummyArg1(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 11 * 404;
+        }
+        try {
+            nullFoo.setBar3ReturnDummyArg2(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 12 * 404;
+        }
+        try {
+            nullFoo.setBar4ReturnDummyArg3(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 13 * 404;
+        }
+        try {
+            nullFoo.setBar5ReturnDummyArg4(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 14 * 404;
+        }
+        int expected = (1234 << 15) + 404 * (15 * 14 / 2);
+        if (sum == expected) {
+            System.out.println("setterTestWithReturnArgIgnoreReturn passes");
+        }
+        else {
+            System.out.println("setterTestWithReturnArgIgnoreReturn fails: " + sum +
+                               " (expecting " + expected + ")");
+        }
+    }
+
+    static void setterTestWithReturnArgUseReturn() {
+        Foo foo = new Foo();
+        int sum = foo.getBar0();
+        int sumDummy = 0;
+        sum += foo.getBar0();
+        Foo foo2 = foo.setBar1ReturnThis(sum);
+        sum += foo2.getBar0();
+        foo = foo2.setBar2ReturnThis(1,sum);
+        sum += foo.getBar0();
+        foo2 = foo.setBar3ReturnThis(1,2,sum);
+        sum += foo2.getBar0();
+        foo = foo2.setBar4ReturnThis(1,2,3,sum);
+        sum += foo.getBar0();
+        foo = foo.setBar5ReturnThis(1,2,3,4,sum);
+        sum += foo.getBar0();
+        sum += foo.setBar1ReturnBarArg(sum);
+        sum += foo.getBar0();
+        sum += foo.setBar2ReturnBarArg(1,sum);
+        sum += foo.getBar0();
+        sum += foo.setBar3ReturnBarArg(1,2,sum);
+        sum += foo.getBar0();
+        sum += foo.setBar4ReturnBarArg(1,2,3,sum);
+        sum += foo.getBar0();
+        sum += foo.setBar5ReturnBarArg(1,2,3,4,sum);
+        sum += foo.getBar0();
+        sumDummy += foo.setBar2ReturnDummyArg1(1,sum);
+        sum += foo.getBar0();
+        sumDummy += foo.setBar3ReturnDummyArg2(1,2,sum);
+        sum += foo.getBar0();
+        sumDummy += foo.setBar4ReturnDummyArg3(1,2,3,sum);
+        sum += foo.getBar0();
+        sumDummy += foo.setBar5ReturnDummyArg4(1,2,3,4,sum);
+        sum += foo.getBar0();
+        Foo nullFoo = Foo.getNullFoo();
+        try {
+            foo = nullFoo.setBar1ReturnThis(sum);
+        } catch(NullPointerException npe) {
+            sum += 404;
+        }
+        try {
+            foo = nullFoo.setBar2ReturnThis(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 2 * 404;
+        }
+        try {
+            foo = nullFoo.setBar3ReturnThis(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 3 * 404;
+        }
+        try {
+            foo = nullFoo.setBar4ReturnThis(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 4 * 404;
+        }
+        try {
+            foo = nullFoo.setBar5ReturnThis(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 5 * 404;
+        }
+        try {
+            sum += nullFoo.setBar1ReturnBarArg(sum);
+        } catch(NullPointerException npe) {
+            sum += 6 * 404;
+        }
+        try {
+            sum += nullFoo.setBar2ReturnBarArg(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 7 * 404;
+        }
+        try {
+            sum += nullFoo.setBar3ReturnBarArg(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 8 * 404;
+        }
+        try {
+            sum += nullFoo.setBar4ReturnBarArg(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 9 * 404;
+        }
+        try {
+            sum += nullFoo.setBar5ReturnBarArg(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 10 * 404;
+        }
+        try {
+            sumDummy += nullFoo.setBar2ReturnDummyArg1(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 11 * 404;
+        }
+        try {
+            sumDummy += nullFoo.setBar3ReturnDummyArg2(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 12 * 404;
+        }
+        try {
+            sumDummy += nullFoo.setBar4ReturnDummyArg3(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 13 * 404;
+        }
+        try {
+            sumDummy += nullFoo.setBar5ReturnDummyArg4(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 14 * 404;
+        }
+        int expected = (1234 << 10) * 3 * 3 * 3 * 3 * 3 + 404 * (15 * 14 / 2);
+        int expectedDummy = 5 * 4 / 2;
+        if (sum == expected && sumDummy == expectedDummy) {
+            System.out.println("setterTestWithReturnArgUseReturn passes");
+        }
+        else {
+            System.out.println("setterTestWithReturnArgUseReturn fails: " + sum +
+                               " (expecting " + expected + "), sumDummy = " + sumDummy +
+                               "(expecting " + expectedDummy + ")");
+        }
+    }
+
+    static void wideSetterTestWithReturnArgIgnoreReturn() {
+        Foo foo = new Foo();
+        long sum = foo.wideGetBar0();
+        sum += foo.wideGetBar0();
+        foo.wideSetBar1ReturnThis(sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar2ReturnThis(1,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar3ReturnThis(1,2,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar4ReturnThis(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar5ReturnThis(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar1ReturnBarArg(sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar2ReturnBarArg(1,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar3ReturnBarArg(1,2,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar4ReturnBarArg(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar5ReturnBarArg(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar2iReturnBarArg(1,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar3iReturnBarArg(1,2,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar4iReturnBarArg(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar5iReturnBarArg(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar2ReturnDummyArg1(1,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar3ReturnDummyArg2(1,2,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar4ReturnDummyArg3(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar5ReturnDummyArg4(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar2iReturnDummyArg1(1,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar3iReturnDummyArg2(1,2,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar4iReturnDummyArg3(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        foo.wideSetBar5iReturnDummyArg4(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        Foo nullFoo = Foo.getNullFoo();
+        try {
+            nullFoo.wideSetBar1ReturnThis(sum);
+        } catch(NullPointerException npe) {
+            sum += 404;
+        }
+        try {
+            nullFoo.wideSetBar2ReturnThis(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 2 * 404;
+        }
+        try {
+            nullFoo.wideSetBar3ReturnThis(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 3 * 404;
+        }
+        try {
+            nullFoo.wideSetBar4ReturnThis(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 4 * 404;
+        }
+        try {
+            nullFoo.wideSetBar5ReturnThis(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 5 * 404;
+        }
+        try {
+            nullFoo.wideSetBar1ReturnBarArg(sum);
+        } catch(NullPointerException npe) {
+            sum += 6 * 404;
+        }
+        try {
+            nullFoo.wideSetBar2ReturnBarArg(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 7 * 404;
+        }
+        try {
+            nullFoo.wideSetBar3ReturnBarArg(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 8 * 404;
+        }
+        try {
+            nullFoo.wideSetBar4ReturnBarArg(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 9 * 404;
+        }
+        try {
+            nullFoo.wideSetBar5ReturnBarArg(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 10 * 404;
+        }
+        try {
+            nullFoo.wideSetBar2iReturnBarArg(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 11 * 404;
+        }
+        try {
+            nullFoo.wideSetBar3iReturnBarArg(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 12 * 404;
+        }
+        try {
+            nullFoo.wideSetBar4iReturnBarArg(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 13 * 404;
+        }
+        try {
+            nullFoo.wideSetBar5iReturnBarArg(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 14 * 404;
+        }
+        try {
+            nullFoo.wideSetBar2ReturnDummyArg1(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 15 * 404;
+        }
+        try {
+            nullFoo.wideSetBar3ReturnDummyArg2(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 16 * 404;
+        }
+        try {
+            nullFoo.wideSetBar4ReturnDummyArg3(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 17 * 404;
+        }
+        try {
+            nullFoo.wideSetBar5ReturnDummyArg4(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 18 * 404;
+        }
+        try {
+            nullFoo.wideSetBar2iReturnDummyArg1(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 19 * 404;
+        }
+        try {
+            nullFoo.wideSetBar3iReturnDummyArg2(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 20 * 404;
+        }
+        try {
+            nullFoo.wideSetBar4iReturnDummyArg3(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 21 * 404;
+        }
+        try {
+            nullFoo.wideSetBar5iReturnDummyArg4(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 22 * 404;
+        }
+        long expected = (1234L << 23) + 404 * (23 * 22 / 2);
+        if (sum == expected) {
+            System.out.println("wideSetterTestWithReturnArgIgnoreReturn passes");
+        }
+        else {
+            System.out.println("wideSetterTestWithReturnArgIgnoreReturn fails: " + sum +
+                               " (expecting " + expected + ")");
+        }
+    }
+
+    static void wideSetterTestWithReturnArgUseReturn() {
+        Foo foo = new Foo();
+        long sum = foo.wideGetBar0();
+        long sumDummy = 0;
+        sum += foo.wideGetBar0();
+        Foo foo2 = foo.wideSetBar1ReturnThis(sum);
+        sum += foo2.wideGetBar0();
+        foo = foo2.wideSetBar2ReturnThis(1,sum);
+        sum += foo.wideGetBar0();
+        foo2 = foo.wideSetBar3ReturnThis(1,2,sum);
+        sum += foo2.wideGetBar0();
+        foo = foo2.wideSetBar4ReturnThis(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        foo = foo.wideSetBar5ReturnThis(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar1ReturnBarArg(sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar2ReturnBarArg(1,sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar3ReturnBarArg(1,2,sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar4ReturnBarArg(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar5ReturnBarArg(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar2iReturnBarArg(1,sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar3iReturnBarArg(1,2,sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar4iReturnBarArg(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        sum += foo.wideSetBar5iReturnBarArg(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        sumDummy += foo.wideSetBar2ReturnDummyArg1(1,sum);
+        sum += foo.wideGetBar0();
+        sumDummy += foo.wideSetBar3ReturnDummyArg2(1,2,sum);
+        sum += foo.wideGetBar0();
+        sumDummy += foo.wideSetBar4ReturnDummyArg3(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        sumDummy += foo.wideSetBar5ReturnDummyArg4(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        sumDummy += foo.wideSetBar2iReturnDummyArg1(1,sum);
+        sum += foo.wideGetBar0();
+        sumDummy += foo.wideSetBar3iReturnDummyArg2(1,2,sum);
+        sum += foo.wideGetBar0();
+        sumDummy += foo.wideSetBar4iReturnDummyArg3(1,2,3,sum);
+        sum += foo.wideGetBar0();
+        sumDummy += foo.wideSetBar5iReturnDummyArg4(1,2,3,4,sum);
+        sum += foo.wideGetBar0();
+        Foo nullFoo = Foo.getNullFoo();
+        try {
+            foo = nullFoo.wideSetBar1ReturnThis(sum);
+        } catch(NullPointerException npe) {
+            sum += 404;
+        }
+        try {
+            foo = nullFoo.wideSetBar2ReturnThis(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 2 * 404;
+        }
+        try {
+            foo = nullFoo.wideSetBar3ReturnThis(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 3 * 404;
+        }
+        try {
+            foo = nullFoo.wideSetBar4ReturnThis(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 4 * 404;
+        }
+        try {
+            foo = nullFoo.wideSetBar5ReturnThis(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 5 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar1ReturnBarArg(sum);
+        } catch(NullPointerException npe) {
+            sum += 6 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar2ReturnBarArg(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 7 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar3ReturnBarArg(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 8 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar4ReturnBarArg(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 9 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar5ReturnBarArg(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 10 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar2iReturnBarArg(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 11 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar3iReturnBarArg(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 12 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar4iReturnBarArg(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 13 * 404;
+        }
+        try {
+            sum += nullFoo.wideSetBar5iReturnBarArg(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 14 * 404;
+        }
+        try {
+            sumDummy += nullFoo.wideSetBar2ReturnDummyArg1(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 15 * 404;
+        }
+        try {
+            sumDummy += nullFoo.wideSetBar3ReturnDummyArg2(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 16 * 404;
+        }
+        try {
+            sumDummy += nullFoo.wideSetBar4ReturnDummyArg3(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 17 * 404;
+        }
+        try {
+            sumDummy += nullFoo.wideSetBar5ReturnDummyArg4(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 18 * 404;
+        }
+        try {
+            sumDummy += nullFoo.wideSetBar2iReturnDummyArg1(1, sum);
+        } catch(NullPointerException npe) {
+            sum += 19 * 404;
+        }
+        try {
+            sumDummy += nullFoo.wideSetBar3iReturnDummyArg2(1, 2, sum);
+        } catch(NullPointerException npe) {
+            sum += 20 * 404;
+        }
+        try {
+            sumDummy += nullFoo.wideSetBar4iReturnDummyArg3(1, 2, 3, sum);
+        } catch(NullPointerException npe) {
+            sum += 21 * 404;
+        }
+        try {
+            sumDummy += nullFoo.wideSetBar5iReturnDummyArg4(1, 2, 3, 4, sum);
+        } catch(NullPointerException npe) {
+            sum += 22 * 404;
+        }
+        long expected = (1234L << 14) * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3  + 404 * (23 * 22 / 2);
+        long expectedDummy = 2 * (5 * 4 / 2);
+        if (sum == expected && sumDummy == expectedDummy) {
+            System.out.println("wideSetterTestWithReturnArgUseReturn passes");
+        }
+        else {
+            System.out.println("wideSetterTestWithReturnArgUseReturn fails: " + sum +
+                               " (expecting " + expected + "), sumDummy = " + sumDummy +
+                               "(expecting " + expectedDummy + ")");
+        }
+    }
+
     static void mulBy1Test() {
         long res;
         long j = 1;
@@ -8645,6 +9219,12 @@
     private int bar = 1234;
     private long lbar = 1234;
 
+    public static Foo getNullFoo() {
+      // Make this a bit complicated so that it's not inlined.
+      Foo foo = new Foo();
+      return (barBar(foo) != 0) ? null : foo;
+    }
+
     // Looks similar to a direct method, make sure we're null checking
     static int barBar(Foo foo) {
         return foo.bar;
@@ -8786,6 +9366,166 @@
     public long wideIdent5(int a6, int a5, int a4, int a3, int a2, long a1) {
         return a1;
     }
+    public Foo setBar1ReturnThis(int a1) {
+        bar = a1;
+        return this;
+    }
+    public Foo setBar2ReturnThis(int a1, int a2) {
+        bar = a2;
+        return this;
+    }
+    public Foo setBar3ReturnThis(int a1, int a2, int a3) {
+        bar = a3;
+        return this;
+    }
+    public Foo setBar4ReturnThis(int a1, int a2, int a3, int a4) {
+        bar = a4;
+        return this;
+    }
+    public Foo setBar5ReturnThis(int a1, int a2, int a3, int a4, int a5) {
+        bar = a5;
+        return this;
+    }
+    public Foo wideSetBar1ReturnThis(long a1) {
+        lbar = a1;
+        return this;
+    }
+    public Foo wideSetBar2ReturnThis(long a1, long a2) {
+        lbar = a2;
+        return this;
+    }
+    public Foo wideSetBar3ReturnThis(long a1, long a2, long a3) {
+        lbar = a3;
+        return this;
+    }
+    public Foo wideSetBar4ReturnThis(long a1, long a2, long a3, long a4) {
+        lbar = a4;
+        return this;
+    }
+    public Foo wideSetBar5ReturnThis(long a1, long a2, long a3, long a4, long a5) {
+        lbar = a5;
+        return this;
+    }
+    public Foo wideSetBar2iReturnThis(int a1, long a2) {
+        lbar = a2;
+        return this;
+    }
+    public Foo wideSetBar3iReturnThis(int a1, int a2, long a3) {
+        lbar = a3;
+        return this;
+    }
+    public Foo wideSetBar4iReturnThis(int a1, int a2, int a3, long a4) {
+        lbar = a4;
+        return this;
+    }
+    public Foo wideSetBar5iReturnThis(int a1, int a2, int a3, int a4, long a5) {
+        lbar = a5;
+        return this;
+    }
+    public int setBar1ReturnBarArg(int a1) {
+        bar = a1;
+        return a1;
+    }
+    public int setBar2ReturnBarArg(int a1, int a2) {
+        bar = a2;
+        return a2;
+    }
+    public int setBar3ReturnBarArg(int a1, int a2, int a3) {
+        bar = a3;
+        return a3;
+    }
+    public int setBar4ReturnBarArg(int a1, int a2, int a3, int a4) {
+        bar = a4;
+        return a4;
+    }
+    public int setBar5ReturnBarArg(int a1, int a2, int a3, int a4, int a5) {
+        bar = a5;
+        return a5;
+    }
+    public long wideSetBar1ReturnBarArg(long a1) {
+        lbar = a1;
+        return a1;
+    }
+    public long wideSetBar2ReturnBarArg(long a1, long a2) {
+        lbar = a2;
+        return a2;
+    }
+    public long wideSetBar3ReturnBarArg(long a1, long a2, long a3) {
+        lbar = a3;
+        return a3;
+    }
+    public long wideSetBar4ReturnBarArg(long a1, long a2, long a3, long a4) {
+        lbar = a4;
+        return a4;
+    }
+    public long wideSetBar5ReturnBarArg(long a1, long a2, long a3, long a4, long a5) {
+        lbar = a5;
+        return a5;
+    }
+    public long wideSetBar2iReturnBarArg(int a1, long a2) {
+        lbar = a2;
+        return a2;
+    }
+    public long wideSetBar3iReturnBarArg(int a1, int a2, long a3) {
+        lbar = a3;
+        return a3;
+    }
+    public long wideSetBar4iReturnBarArg(int a1, int a2, int a3, long a4) {
+        lbar = a4;
+        return a4;
+    }
+    public long wideSetBar5iReturnBarArg(int a1, int a2, int a3, int a4, long a5) {
+        lbar = a5;
+        return a5;
+    }
+    public int setBar2ReturnDummyArg1(int a1, int a2) {
+        bar = a2;
+        return a1;
+    }
+    public int setBar3ReturnDummyArg2(int a1, int a2, int a3) {
+        bar = a3;
+        return a2;
+    }
+    public int setBar4ReturnDummyArg3(int a1, int a2, int a3, int a4) {
+        bar = a4;
+        return a3;
+    }
+    public int setBar5ReturnDummyArg4(int a1, int a2, int a3, int a4, int a5) {
+        bar = a5;
+        return a4;
+    }
+    public long wideSetBar2ReturnDummyArg1(long a1, long a2) {
+        lbar = a2;
+        return a1;
+    }
+    public long wideSetBar3ReturnDummyArg2(long a1, long a2, long a3) {
+        lbar = a3;
+        return a2;
+    }
+    public long wideSetBar4ReturnDummyArg3(long a1, long a2, long a3, long a4) {
+        lbar = a4;
+        return a3;
+    }
+    public long wideSetBar5ReturnDummyArg4(long a1, long a2, long a3, long a4, long a5) {
+        lbar = a5;
+        return a4;
+    }
+    public int wideSetBar2iReturnDummyArg1(int a1, long a2) {
+        lbar = a2;
+        return a1;
+    }
+    public int wideSetBar3iReturnDummyArg2(int a1, int a2, long a3) {
+        lbar = a3;
+        return a2;
+    }
+    public int wideSetBar4iReturnDummyArg3(int a1, int a2, int a3, long a4) {
+        lbar = a4;
+        return a3;
+    }
+    public int wideSetBar5iReturnDummyArg4(int a1, int a2, int a3, int a4, long a5) {
+        lbar = a5;
+        return a4;
+    }
 }
 
 class LVNTests {