Merge "Fix longstanding bug around implicit NPEs and GC, version 2."
diff --git a/build/art.go b/build/art.go
index 3dabce3..61b1a4e 100644
--- a/build/art.go
+++ b/build/art.go
@@ -66,8 +66,12 @@
 			"-DART_READ_BARRIER_TYPE_IS_"+barrierType+"=1")
 	}
 
-  cdexLevel := envDefault(ctx, "ART_DEFAULT_COMPACT_DEX_LEVEL", "fast")
-  cflags = append(cflags, "-DART_DEFAULT_COMPACT_DEX_LEVEL="+cdexLevel)
+	if envTrue(ctx, "ART_USE_GENERATIONAL_CC") {
+		cflags = append(cflags, "-DART_USE_GENERATIONAL_CC=1")
+	}
+
+	cdexLevel := envDefault(ctx, "ART_DEFAULT_COMPACT_DEX_LEVEL", "fast")
+	cflags = append(cflags, "-DART_DEFAULT_COMPACT_DEX_LEVEL="+cdexLevel)
 
 	// We need larger stack overflow guards for ASAN, as the compiled code will have
 	// larger frame sizes. For simplicity, just use global not-target-specific cflags.
@@ -312,19 +316,19 @@
 		codegen(ctx, c, true)
 
 		type props struct {
-		  Target struct {
-		    Android struct {
-		      Shared_libs []string
-		    }
-		  }
+			Target struct {
+				Android struct {
+					Shared_libs []string
+				}
+			}
 		}
 
 		p := &props{}
 		// TODO: express this in .bp instead b/79671158
 		if !envTrue(ctx, "ART_TARGET_LINUX") {
-		  p.Target.Android.Shared_libs = []string {
-		    "libmetricslogger",
-		  }
+			p.Target.Android.Shared_libs = []string{
+				"libmetricslogger",
+			}
 		}
 		ctx.AppendProperties(p)
 	})
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index a52e163..a33d537 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -59,7 +59,7 @@
   template <typename T>
   bool UsuallyEquals(const T& expected, const T& actual,
                      typename std::enable_if<
-                         detail::SupportsEqualityOperator<T>::value>::type* = 0) {
+                         detail::SupportsEqualityOperator<T>::value>::type* = nullptr) {
     return expected == actual;
   }
 
@@ -73,8 +73,8 @@
   template <typename T, typename ... Ignore>
   bool UsuallyEquals(const T& expected, const T& actual,
                      const Ignore& ... more ATTRIBUTE_UNUSED,
-                     typename std::enable_if<std::is_pod<T>::value>::type* = 0,
-                     typename std::enable_if<!detail::SupportsEqualityOperator<T>::value>::type* = 0
+                     typename std::enable_if<std::is_pod<T>::value>::type* = nullptr,
+                     typename std::enable_if<!detail::SupportsEqualityOperator<T>::value>::type* = nullptr
                      ) {
     return memcmp(std::addressof(expected), std::addressof(actual), sizeof(T)) == 0;
   }
diff --git a/cmdline/detail/cmdline_parse_argument_detail.h b/cmdline/detail/cmdline_parse_argument_detail.h
index 65c1114..d011e7f 100644
--- a/cmdline/detail/cmdline_parse_argument_detail.h
+++ b/cmdline/detail/cmdline_parse_argument_detail.h
@@ -90,7 +90,7 @@
 struct CmdlineParserArgumentInfo {
   // This version will only be used if TArg is arithmetic and thus has the <= operators.
   template <typename T = TArg>  // Necessary to get SFINAE to kick in.
-  bool CheckRange(const TArg& value, typename EnableIfNumeric<T>::type* = 0) {
+  bool CheckRange(const TArg& value, typename EnableIfNumeric<T>::type* = nullptr) {
     if (has_range_) {
       return min_ <= value && value <= max_;
     }
@@ -99,7 +99,7 @@
 
   // This version will be used at other times when TArg is not arithmetic.
   template <typename T = TArg>
-  bool CheckRange(const TArg&, typename DisableIfNumeric<T>::type* = 0) {
+  bool CheckRange(const TArg&, typename DisableIfNumeric<T>::type* = nullptr) {
     assert(!has_range_);
     return true;
   }
diff --git a/cmdline/detail/cmdline_parser_detail.h b/cmdline/detail/cmdline_parser_detail.h
index 4c26ba3..2078d7a 100644
--- a/cmdline/detail/cmdline_parser_detail.h
+++ b/cmdline/detail/cmdline_parser_detail.h
@@ -90,7 +90,7 @@
 template <typename T>
 std::string ToStringAny(const T& value,
                         typename std::enable_if<
-                            SupportsInsertionOperator<T>::value>::type* = 0) {
+                            SupportsInsertionOperator<T>::value>::type* = nullptr) {
   std::stringstream stream;
   stream << value;
   return stream.str();
@@ -99,7 +99,7 @@
 template <typename T>
 std::string ToStringAny(const std::vector<T> value,
                         typename std::enable_if<
-                            SupportsInsertionOperator<T>::value>::type* = 0) {
+                            SupportsInsertionOperator<T>::value>::type* = nullptr) {
   std::stringstream stream;
   stream << "vector{";
 
@@ -118,7 +118,7 @@
 template <typename T>
 std::string ToStringAny(const T&,
                         typename std::enable_if<
-                            !SupportsInsertionOperator<T>::value>::type* = 0
+                            !SupportsInsertionOperator<T>::value>::type* = nullptr
 ) {
   return std::string("(unknown type [no operator<< implemented] for )");
 }
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index 87197be..d603d96 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -221,7 +221,7 @@
   callbacks_.reset();
   verification_results_.reset();
   compiler_options_.reset();
-  image_reservation_.reset();
+  image_reservation_.Reset();
 
   CommonRuntimeTest::TearDown();
 }
@@ -323,18 +323,17 @@
   // accidentally end up colliding with the fixed memory address when we need to load the image.
   std::string error_msg;
   MemMap::Init();
-  image_reservation_.reset(MemMap::MapAnonymous("image reservation",
-                                                reinterpret_cast<uint8_t*>(ART_BASE_ADDRESS),
-                                                (size_t)120 * 1024 * 1024,  // 120MB
-                                                PROT_NONE,
-                                                false /* no need for 4gb flag with fixed mmap*/,
-                                                false /* not reusing existing reservation */,
-                                                &error_msg));
-  CHECK(image_reservation_.get() != nullptr) << error_msg;
+  image_reservation_ = MemMap::MapAnonymous("image reservation",
+                                            reinterpret_cast<uint8_t*>(ART_BASE_ADDRESS),
+                                            (size_t)120 * 1024 * 1024,  // 120MB
+                                            PROT_NONE,
+                                            false /* no need for 4gb flag with fixed mmap */,
+                                            &error_msg);
+  CHECK(image_reservation_.IsValid()) << error_msg;
 }
 
 void CommonCompilerTest::UnreserveImageSpace() {
-  image_reservation_.reset();
+  image_reservation_.Reset();
 }
 
 void CommonCompilerTest::SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files) {
diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h
index db38110..366489c 100644
--- a/compiler/common_compiler_test.h
+++ b/compiler/common_compiler_test.h
@@ -115,7 +115,7 @@
   std::unique_ptr<CompilerDriver> compiler_driver_;
 
  private:
-  std::unique_ptr<MemMap> image_reservation_;
+  MemMap image_reservation_;
 
   // Chunks must not move their storage after being created - use the node-based std::list.
   std::list<std::vector<uint8_t>> header_code_and_maps_chunks_;
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index a881c5e..3fc559e 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -88,7 +88,7 @@
 JitCompiler::JitCompiler() {
   compiler_options_.reset(new CompilerOptions());
   // Special case max code units for inlining, whose default is "unset" (implictly
-  // meaning no limit). Do this before parsing the actuall passed options.
+  // meaning no limit). Do this before parsing the actual passed options.
   compiler_options_->SetInlineMaxCodeUnits(CompilerOptions::kDefaultInlineMaxCodeUnits);
   {
     std::string error_msg;
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 4ebe12e..a460f77 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -89,25 +89,10 @@
 
 // Reference load (except object array loads) is using LDR Wt, [Xn, #offset] which can handle
 // offset < 16KiB. For offsets >= 16KiB, the load shall be emitted as two or more instructions.
-// For the Baker read barrier implementation using link-generated thunks we need to split
+// For the Baker read barrier implementation using link-time generated thunks we need to split
 // the offset explicitly.
 constexpr uint32_t kReferenceLoadMinFarOffset = 16 * KB;
 
-// Flags controlling the use of link-time generated thunks for Baker read barriers.
-constexpr bool kBakerReadBarrierLinkTimeThunksEnableForFields = true;
-constexpr bool kBakerReadBarrierLinkTimeThunksEnableForArrays = true;
-constexpr bool kBakerReadBarrierLinkTimeThunksEnableForGcRoots = true;
-
-// Some instructions have special requirements for a temporary, for example
-// LoadClass/kBssEntry and LoadString/kBssEntry for Baker read barrier require
-// temp that's not an R0 (to avoid an extra move) and Baker read barrier field
-// loads with large offsets need a fixed register to limit the number of link-time
-// thunks we generate. For these and similar cases, we want to reserve a specific
-// register that's neither callee-save nor an argument register. We choose x15.
-inline Location FixedTempLocation() {
-  return Location::RegisterLocation(x15.GetCode());
-}
-
 inline Condition ARM64Condition(IfCondition cond) {
   switch (cond) {
     case kCondEQ: return eq;
@@ -614,503 +599,6 @@
   }
 }
 
-// Abstract base class for read barrier slow paths marking a reference
-// `ref`.
-//
-// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked or an empty
-// location; in the latter case, the read barrier marking runtime
-// entry point will be loaded by the slow path code itself.
-class ReadBarrierMarkSlowPathBaseARM64 : public SlowPathCodeARM64 {
- protected:
-  ReadBarrierMarkSlowPathBaseARM64(HInstruction* instruction, Location ref, Location entrypoint)
-      : SlowPathCodeARM64(instruction), ref_(ref), entrypoint_(entrypoint) {
-    DCHECK(kEmitCompilerReadBarrier);
-  }
-
-  const char* GetDescription() const OVERRIDE { return "ReadBarrierMarkSlowPathBaseARM64"; }
-
-  // Generate assembly code calling the read barrier marking runtime
-  // entry point (ReadBarrierMarkRegX).
-  void GenerateReadBarrierMarkRuntimeCall(CodeGenerator* codegen) {
-    // No need to save live registers; it's taken care of by the
-    // entrypoint. Also, there is no need to update the stack mask,
-    // as this runtime call will not trigger a garbage collection.
-    CodeGeneratorARM64* arm64_codegen = down_cast<CodeGeneratorARM64*>(codegen);
-    DCHECK_NE(ref_.reg(), LR);
-    DCHECK_NE(ref_.reg(), WSP);
-    DCHECK_NE(ref_.reg(), WZR);
-    // IP0 is used internally by the ReadBarrierMarkRegX entry point
-    // as a temporary, it cannot be the entry point's input/output.
-    DCHECK_NE(ref_.reg(), IP0);
-    DCHECK(0 <= ref_.reg() && ref_.reg() < kNumberOfWRegisters) << ref_.reg();
-    // "Compact" slow path, saving two moves.
-    //
-    // Instead of using the standard runtime calling convention (input
-    // and output in W0):
-    //
-    //   W0 <- ref
-    //   W0 <- ReadBarrierMark(W0)
-    //   ref <- W0
-    //
-    // we just use rX (the register containing `ref`) as input and output
-    // of a dedicated entrypoint:
-    //
-    //   rX <- ReadBarrierMarkRegX(rX)
-    //
-    if (entrypoint_.IsValid()) {
-      arm64_codegen->ValidateInvokeRuntimeWithoutRecordingPcInfo(instruction_, this);
-      __ Blr(XRegisterFrom(entrypoint_));
-    } else {
-      // Entrypoint is not already loaded, load from the thread.
-      int32_t entry_point_offset =
-          Thread::ReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ref_.reg());
-      // This runtime call does not require a stack map.
-      arm64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
-    }
-  }
-
-  // The location (register) of the marked object reference.
-  const Location ref_;
-
-  // The location of the entrypoint if it is already loaded.
-  const Location entrypoint_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ReadBarrierMarkSlowPathBaseARM64);
-};
-
-// Slow path marking an object reference `ref` during a read
-// barrier. The field `obj.field` in the object `obj` holding this
-// reference does not get updated by this slow path after marking.
-//
-// This means that after the execution of this slow path, `ref` will
-// always be up-to-date, but `obj.field` may not; i.e., after the
-// flip, `ref` will be a to-space reference, but `obj.field` will
-// probably still be a from-space reference (unless it gets updated by
-// another thread, or if another thread installed another object
-// reference (different from `ref`) in `obj.field`).
-//
-// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked or an empty
-// location; in the latter case, the read barrier marking runtime
-// entry point will be loaded by the slow path code itself.
-class ReadBarrierMarkSlowPathARM64 : public ReadBarrierMarkSlowPathBaseARM64 {
- public:
-  ReadBarrierMarkSlowPathARM64(HInstruction* instruction,
-                               Location ref,
-                               Location entrypoint = Location::NoLocation())
-      : ReadBarrierMarkSlowPathBaseARM64(instruction, ref, entrypoint) {
-    DCHECK(kEmitCompilerReadBarrier);
-  }
-
-  const char* GetDescription() const OVERRIDE { return "ReadBarrierMarkSlowPathARM64"; }
-
-  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
-    LocationSummary* locations = instruction_->GetLocations();
-    DCHECK(locations->CanCall());
-    DCHECK(ref_.IsRegister()) << ref_;
-    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_.reg())) << ref_.reg();
-    DCHECK(instruction_->IsLoadClass() || instruction_->IsLoadString())
-        << "Unexpected instruction in read barrier marking slow path: "
-        << instruction_->DebugName();
-
-    __ Bind(GetEntryLabel());
-    GenerateReadBarrierMarkRuntimeCall(codegen);
-    __ B(GetExitLabel());
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ReadBarrierMarkSlowPathARM64);
-};
-
-// Slow path loading `obj`'s lock word, loading a reference from
-// object `*(obj + offset + (index << scale_factor))` into `ref`, and
-// marking `ref` if `obj` is gray according to the lock word (Baker
-// read barrier). The field `obj.field` in the object `obj` holding
-// this reference does not get updated by this slow path after marking
-// (see LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64
-// below for that).
-//
-// This means that after the execution of this slow path, `ref` will
-// always be up-to-date, but `obj.field` may not; i.e., after the
-// flip, `ref` will be a to-space reference, but `obj.field` will
-// probably still be a from-space reference (unless it gets updated by
-// another thread, or if another thread installed another object
-// reference (different from `ref`) in `obj.field`).
-//
-// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked or an empty
-// location; in the latter case, the read barrier marking runtime
-// entry point will be loaded by the slow path code itself.
-class LoadReferenceWithBakerReadBarrierSlowPathARM64 : public ReadBarrierMarkSlowPathBaseARM64 {
- public:
-  LoadReferenceWithBakerReadBarrierSlowPathARM64(HInstruction* instruction,
-                                                 Location ref,
-                                                 Register obj,
-                                                 uint32_t offset,
-                                                 Location index,
-                                                 size_t scale_factor,
-                                                 bool needs_null_check,
-                                                 bool use_load_acquire,
-                                                 Register temp,
-                                                 Location entrypoint = Location::NoLocation())
-      : ReadBarrierMarkSlowPathBaseARM64(instruction, ref, entrypoint),
-        obj_(obj),
-        offset_(offset),
-        index_(index),
-        scale_factor_(scale_factor),
-        needs_null_check_(needs_null_check),
-        use_load_acquire_(use_load_acquire),
-        temp_(temp) {
-    DCHECK(kEmitCompilerReadBarrier);
-    DCHECK(kUseBakerReadBarrier);
-  }
-
-  const char* GetDescription() const OVERRIDE {
-    return "LoadReferenceWithBakerReadBarrierSlowPathARM64";
-  }
-
-  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
-    LocationSummary* locations = instruction_->GetLocations();
-    DCHECK(locations->CanCall());
-    DCHECK(ref_.IsRegister()) << ref_;
-    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_.reg())) << ref_.reg();
-    DCHECK(obj_.IsW());
-    DCHECK_NE(ref_.reg(), LocationFrom(temp_).reg());
-    DCHECK(instruction_->IsInstanceFieldGet() ||
-           instruction_->IsStaticFieldGet() ||
-           instruction_->IsArrayGet() ||
-           instruction_->IsArraySet() ||
-           instruction_->IsInstanceOf() ||
-           instruction_->IsCheckCast() ||
-           (instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()) ||
-           (instruction_->IsInvokeStaticOrDirect() && instruction_->GetLocations()->Intrinsified()))
-        << "Unexpected instruction in read barrier marking slow path: "
-        << instruction_->DebugName();
-    // The read barrier instrumentation of object ArrayGet
-    // instructions does not support the HIntermediateAddress
-    // instruction.
-    DCHECK(!(instruction_->IsArrayGet() &&
-             instruction_->AsArrayGet()->GetArray()->IsIntermediateAddress()));
-
-    // Temporary register `temp_`, used to store the lock word, must
-    // not be IP0 nor IP1, as we may use them to emit the reference
-    // load (in the call to GenerateRawReferenceLoad below), and we
-    // need the lock word to still be in `temp_` after the reference
-    // load.
-    DCHECK_NE(LocationFrom(temp_).reg(), IP0);
-    DCHECK_NE(LocationFrom(temp_).reg(), IP1);
-
-    __ Bind(GetEntryLabel());
-
-    // When using MaybeGenerateReadBarrierSlow, the read barrier call is
-    // inserted after the original load. However, in fast path based
-    // Baker's read barriers, we need to perform the load of
-    // mirror::Object::monitor_ *before* the original reference load.
-    // This load-load ordering is required by the read barrier.
-    // The slow path (for Baker's algorithm) should look like:
-    //
-    //   uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
-    //   lfence;  // Load fence or artificial data dependency to prevent load-load reordering
-    //   HeapReference<mirror::Object> ref = *src;  // Original reference load.
-    //   bool is_gray = (rb_state == ReadBarrier::GrayState());
-    //   if (is_gray) {
-    //     ref = entrypoint(ref);  // ref = ReadBarrier::Mark(ref);  // Runtime entry point call.
-    //   }
-    //
-    // Note: the original implementation in ReadBarrier::Barrier is
-    // slightly more complex as it performs additional checks that we do
-    // not do here for performance reasons.
-
-    // /* int32_t */ monitor = obj->monitor_
-    uint32_t monitor_offset = mirror::Object::MonitorOffset().Int32Value();
-    __ Ldr(temp_, HeapOperand(obj_, monitor_offset));
-    if (needs_null_check_) {
-      codegen->MaybeRecordImplicitNullCheck(instruction_);
-    }
-    // /* LockWord */ lock_word = LockWord(monitor)
-    static_assert(sizeof(LockWord) == sizeof(int32_t),
-                  "art::LockWord and int32_t have different sizes.");
-
-    // Introduce a dependency on the lock_word including rb_state,
-    // to prevent load-load reordering, and without using
-    // a memory barrier (which would be more expensive).
-    // `obj` is unchanged by this operation, but its value now depends
-    // on `temp`.
-    __ Add(obj_.X(), obj_.X(), Operand(temp_.X(), LSR, 32));
-
-    // The actual reference load.
-    // A possible implicit null check has already been handled above.
-    CodeGeneratorARM64* arm64_codegen = down_cast<CodeGeneratorARM64*>(codegen);
-    arm64_codegen->GenerateRawReferenceLoad(instruction_,
-                                            ref_,
-                                            obj_,
-                                            offset_,
-                                            index_,
-                                            scale_factor_,
-                                            /* needs_null_check */ false,
-                                            use_load_acquire_);
-
-    // Mark the object `ref` when `obj` is gray.
-    //
-    //   if (rb_state == ReadBarrier::GrayState())
-    //     ref = ReadBarrier::Mark(ref);
-    //
-    // Given the numeric representation, it's enough to check the low bit of the rb_state.
-    static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
-    static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
-    __ Tbz(temp_, LockWord::kReadBarrierStateShift, GetExitLabel());
-    GenerateReadBarrierMarkRuntimeCall(codegen);
-
-    __ B(GetExitLabel());
-  }
-
- private:
-  // The register containing the object holding the marked object reference field.
-  Register obj_;
-  // The offset, index and scale factor to access the reference in `obj_`.
-  uint32_t offset_;
-  Location index_;
-  size_t scale_factor_;
-  // Is a null check required?
-  bool needs_null_check_;
-  // Should this reference load use Load-Acquire semantics?
-  bool use_load_acquire_;
-  // A temporary register used to hold the lock word of `obj_`.
-  Register temp_;
-
-  DISALLOW_COPY_AND_ASSIGN(LoadReferenceWithBakerReadBarrierSlowPathARM64);
-};
-
-// Slow path loading `obj`'s lock word, loading a reference from
-// object `*(obj + offset + (index << scale_factor))` into `ref`, and
-// marking `ref` if `obj` is gray according to the lock word (Baker
-// read barrier). If needed, this slow path also atomically updates
-// the field `obj.field` in the object `obj` holding this reference
-// after marking (contrary to
-// LoadReferenceWithBakerReadBarrierSlowPathARM64 above, which never
-// tries to update `obj.field`).
-//
-// This means that after the execution of this slow path, both `ref`
-// and `obj.field` will be up-to-date; i.e., after the flip, both will
-// hold the same to-space reference (unless another thread installed
-// another object reference (different from `ref`) in `obj.field`).
-//
-// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked or an empty
-// location; in the latter case, the read barrier marking runtime
-// entry point will be loaded by the slow path code itself.
-class LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64
-    : public ReadBarrierMarkSlowPathBaseARM64 {
- public:
-  LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64(
-      HInstruction* instruction,
-      Location ref,
-      Register obj,
-      uint32_t offset,
-      Location index,
-      size_t scale_factor,
-      bool needs_null_check,
-      bool use_load_acquire,
-      Register temp,
-      Location entrypoint = Location::NoLocation())
-      : ReadBarrierMarkSlowPathBaseARM64(instruction, ref, entrypoint),
-        obj_(obj),
-        offset_(offset),
-        index_(index),
-        scale_factor_(scale_factor),
-        needs_null_check_(needs_null_check),
-        use_load_acquire_(use_load_acquire),
-        temp_(temp) {
-    DCHECK(kEmitCompilerReadBarrier);
-    DCHECK(kUseBakerReadBarrier);
-  }
-
-  const char* GetDescription() const OVERRIDE {
-    return "LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64";
-  }
-
-  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
-    LocationSummary* locations = instruction_->GetLocations();
-    Register ref_reg = WRegisterFrom(ref_);
-    DCHECK(locations->CanCall());
-    DCHECK(ref_.IsRegister()) << ref_;
-    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_.reg())) << ref_.reg();
-    DCHECK(obj_.IsW());
-    DCHECK_NE(ref_.reg(), LocationFrom(temp_).reg());
-
-    // This slow path is only used by the UnsafeCASObject intrinsic at the moment.
-    DCHECK((instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()))
-        << "Unexpected instruction in read barrier marking and field updating slow path: "
-        << instruction_->DebugName();
-    DCHECK(instruction_->GetLocations()->Intrinsified());
-    DCHECK_EQ(instruction_->AsInvoke()->GetIntrinsic(), Intrinsics::kUnsafeCASObject);
-    DCHECK_EQ(offset_, 0u);
-    DCHECK_EQ(scale_factor_, 0u);
-    DCHECK_EQ(use_load_acquire_, false);
-    // The location of the offset of the marked reference field within `obj_`.
-    Location field_offset = index_;
-    DCHECK(field_offset.IsRegister()) << field_offset;
-
-    // Temporary register `temp_`, used to store the lock word, must
-    // not be IP0 nor IP1, as we may use them to emit the reference
-    // load (in the call to GenerateRawReferenceLoad below), and we
-    // need the lock word to still be in `temp_` after the reference
-    // load.
-    DCHECK_NE(LocationFrom(temp_).reg(), IP0);
-    DCHECK_NE(LocationFrom(temp_).reg(), IP1);
-
-    __ Bind(GetEntryLabel());
-
-    // The implementation is similar to LoadReferenceWithBakerReadBarrierSlowPathARM64's:
-    //
-    //   uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
-    //   lfence;  // Load fence or artificial data dependency to prevent load-load reordering
-    //   HeapReference<mirror::Object> ref = *src;  // Original reference load.
-    //   bool is_gray = (rb_state == ReadBarrier::GrayState());
-    //   if (is_gray) {
-    //     old_ref = ref;
-    //     ref = entrypoint(ref);  // ref = ReadBarrier::Mark(ref);  // Runtime entry point call.
-    //     compareAndSwapObject(obj, field_offset, old_ref, ref);
-    //   }
-
-    // /* int32_t */ monitor = obj->monitor_
-    uint32_t monitor_offset = mirror::Object::MonitorOffset().Int32Value();
-    __ Ldr(temp_, HeapOperand(obj_, monitor_offset));
-    if (needs_null_check_) {
-      codegen->MaybeRecordImplicitNullCheck(instruction_);
-    }
-    // /* LockWord */ lock_word = LockWord(monitor)
-    static_assert(sizeof(LockWord) == sizeof(int32_t),
-                  "art::LockWord and int32_t have different sizes.");
-
-    // Introduce a dependency on the lock_word including rb_state,
-    // to prevent load-load reordering, and without using
-    // a memory barrier (which would be more expensive).
-    // `obj` is unchanged by this operation, but its value now depends
-    // on `temp`.
-    __ Add(obj_.X(), obj_.X(), Operand(temp_.X(), LSR, 32));
-
-    // The actual reference load.
-    // A possible implicit null check has already been handled above.
-    CodeGeneratorARM64* arm64_codegen = down_cast<CodeGeneratorARM64*>(codegen);
-    arm64_codegen->GenerateRawReferenceLoad(instruction_,
-                                            ref_,
-                                            obj_,
-                                            offset_,
-                                            index_,
-                                            scale_factor_,
-                                            /* needs_null_check */ false,
-                                            use_load_acquire_);
-
-    // Mark the object `ref` when `obj` is gray.
-    //
-    //   if (rb_state == ReadBarrier::GrayState())
-    //     ref = ReadBarrier::Mark(ref);
-    //
-    // Given the numeric representation, it's enough to check the low bit of the rb_state.
-    static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
-    static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
-    __ Tbz(temp_, LockWord::kReadBarrierStateShift, GetExitLabel());
-
-    // Save the old value of the reference before marking it.
-    // Note that we cannot use IP to save the old reference, as IP is
-    // used internally by the ReadBarrierMarkRegX entry point, and we
-    // need the old reference after the call to that entry point.
-    DCHECK_NE(LocationFrom(temp_).reg(), IP0);
-    __ Mov(temp_.W(), ref_reg);
-
-    GenerateReadBarrierMarkRuntimeCall(codegen);
-
-    // If the new reference is different from the old reference,
-    // update the field in the holder (`*(obj_ + field_offset)`).
-    //
-    // Note that this field could also hold a different object, if
-    // another thread had concurrently changed it. In that case, the
-    // LDXR/CMP/BNE sequence of instructions in the compare-and-set
-    // (CAS) operation below would abort the CAS, leaving the field
-    // as-is.
-    __ Cmp(temp_.W(), ref_reg);
-    __ B(eq, GetExitLabel());
-
-    // Update the the holder's field atomically.  This may fail if
-    // mutator updates before us, but it's OK.  This is achieved
-    // using a strong compare-and-set (CAS) operation with relaxed
-    // memory synchronization ordering, where the expected value is
-    // the old reference and the desired value is the new reference.
-
-    MacroAssembler* masm = arm64_codegen->GetVIXLAssembler();
-    UseScratchRegisterScope temps(masm);
-
-    // Convenience aliases.
-    Register base = obj_.W();
-    Register offset = XRegisterFrom(field_offset);
-    Register expected = temp_.W();
-    Register value = ref_reg;
-    Register tmp_ptr = temps.AcquireX();    // Pointer to actual memory.
-    Register tmp_value = temps.AcquireW();  // Value in memory.
-
-    __ Add(tmp_ptr, base.X(), Operand(offset));
-
-    if (kPoisonHeapReferences) {
-      arm64_codegen->GetAssembler()->PoisonHeapReference(expected);
-      if (value.Is(expected)) {
-        // Do not poison `value`, as it is the same register as
-        // `expected`, which has just been poisoned.
-      } else {
-        arm64_codegen->GetAssembler()->PoisonHeapReference(value);
-      }
-    }
-
-    // do {
-    //   tmp_value = [tmp_ptr] - expected;
-    // } while (tmp_value == 0 && failure([tmp_ptr] <- r_new_value));
-
-    vixl::aarch64::Label loop_head, comparison_failed, exit_loop;
-    __ Bind(&loop_head);
-    __ Ldxr(tmp_value, MemOperand(tmp_ptr));
-    __ Cmp(tmp_value, expected);
-    __ B(&comparison_failed, ne);
-    __ Stxr(tmp_value, value, MemOperand(tmp_ptr));
-    __ Cbnz(tmp_value, &loop_head);
-    __ B(&exit_loop);
-    __ Bind(&comparison_failed);
-    __ Clrex();
-    __ Bind(&exit_loop);
-
-    if (kPoisonHeapReferences) {
-      arm64_codegen->GetAssembler()->UnpoisonHeapReference(expected);
-      if (value.Is(expected)) {
-        // Do not unpoison `value`, as it is the same register as
-        // `expected`, which has just been unpoisoned.
-      } else {
-        arm64_codegen->GetAssembler()->UnpoisonHeapReference(value);
-      }
-    }
-
-    __ B(GetExitLabel());
-  }
-
- private:
-  // The register containing the object holding the marked object reference field.
-  const Register obj_;
-  // The offset, index and scale factor to access the reference in `obj_`.
-  uint32_t offset_;
-  Location index_;
-  size_t scale_factor_;
-  // Is a null check required?
-  bool needs_null_check_;
-  // Should this reference load use Load-Acquire semantics?
-  bool use_load_acquire_;
-  // A temporary register used to hold the lock word of `obj_`; and
-  // also to hold the original reference value, when the reference is
-  // marked.
-  const Register temp_;
-
-  DISALLOW_COPY_AND_ASSIGN(LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64);
-};
-
 // Slow path generating a read barrier for a heap reference.
 class ReadBarrierForHeapReferenceSlowPathARM64 : public SlowPathCodeARM64 {
  public:
@@ -1466,13 +954,20 @@
       BakerReadBarrierKind kind = BakerReadBarrierKindField::Decode(encoded_data);
       // Check that the next instruction matches the expected LDR.
       switch (kind) {
-        case BakerReadBarrierKind::kField: {
+        case BakerReadBarrierKind::kField:
+        case BakerReadBarrierKind::kAcquire: {
           DCHECK_GE(code.size() - literal_offset, 8u);
           uint32_t next_insn = GetInsn(literal_offset + 4u);
-          // LDR (immediate) with correct base_reg.
           CheckValidReg(next_insn & 0x1fu);  // Check destination register.
           const uint32_t base_reg = BakerReadBarrierFirstRegField::Decode(encoded_data);
-          CHECK_EQ(next_insn & 0xffc003e0u, 0xb9400000u | (base_reg << 5));
+          if (kind == BakerReadBarrierKind::kField) {
+            // LDR (immediate) with correct base_reg.
+            CHECK_EQ(next_insn & 0xffc003e0u, 0xb9400000u | (base_reg << 5));
+          } else {
+            DCHECK(kind == BakerReadBarrierKind::kAcquire);
+            // LDAR with correct base_reg.
+            CHECK_EQ(next_insn & 0xffffffe0u, 0x88dffc00u | (base_reg << 5));
+          }
           break;
         }
         case BakerReadBarrierKind::kArray: {
@@ -1489,9 +984,12 @@
         case BakerReadBarrierKind::kGcRoot: {
           DCHECK_GE(literal_offset, 4u);
           uint32_t prev_insn = GetInsn(literal_offset - 4u);
-          // LDR (immediate) with correct root_reg.
           const uint32_t root_reg = BakerReadBarrierFirstRegField::Decode(encoded_data);
-          CHECK_EQ(prev_insn & 0xffc0001fu, 0xb9400000u | root_reg);
+          // Usually LDR (immediate) with correct root_reg but
+          // we may have a "MOV marked, old_value" for UnsafeCASObject.
+          if ((prev_insn & 0xffe0ffff) != (0x2a0003e0 | root_reg)) {    // MOV?
+            CHECK_EQ(prev_insn & 0xffc0001fu, 0xb9400000u | root_reg);  // LDR?
+          }
           break;
         }
         default:
@@ -1676,8 +1174,24 @@
   if (value_can_be_null) {
     __ Cbz(value, &done);
   }
+  // Load the address of the card table into `card`.
   __ Ldr(card, MemOperand(tr, Thread::CardTableOffset<kArm64PointerSize>().Int32Value()));
+  // Calculate the offset (in the card table) of the card corresponding to
+  // `object`.
   __ Lsr(temp, object, gc::accounting::CardTable::kCardShift);
+  // Write the `art::gc::accounting::CardTable::kCardDirty` value into the
+  // `object`'s card.
+  //
+  // Register `card` contains the address of the card table. Note that the card
+  // table's base is biased during its creation so that it always starts at an
+  // address whose least-significant byte is equal to `kCardDirty` (see
+  // art::gc::accounting::CardTable::Create). Therefore the STRB instruction
+  // below writes the `kCardDirty` (byte) value into the `object`'s card
+  // (located at `card + object >> kCardShift`).
+  //
+  // This dual use of the value in register `card` (1. to calculate the location
+  // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
+  // (no need to explicitly load `kCardDirty` as an immediate value).
   __ Strb(card, MemOperand(card, temp.X()));
   if (value_can_be_null) {
     __ Bind(&done);
@@ -2324,18 +1838,12 @@
                                                            : LocationSummary::kNoCall);
   if (object_field_get_with_read_barrier && kUseBakerReadBarrier) {
     locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty());  // No caller-save registers.
-    // We need a temporary register for the read barrier marking slow
-    // path in CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier.
-    if (kBakerReadBarrierLinkTimeThunksEnableForFields &&
-        !Runtime::Current()->UseJitCompilation() &&
-        !field_info.IsVolatile()) {
-      // If link-time thunks for the Baker read barrier are enabled, for AOT
-      // non-volatile loads we need a temporary only if the offset is too big.
-      if (field_info.GetFieldOffset().Uint32Value() >= kReferenceLoadMinFarOffset) {
-        locations->AddTemp(FixedTempLocation());
-      }
-    } else {
-      locations->AddTemp(Location::RequiresRegister());
+    // We need a temporary register for the read barrier load in
+    // CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier()
+    // only if the field is volatile or the offset is too big.
+    if (field_info.IsVolatile() ||
+        field_info.GetFieldOffset().Uint32Value() >= kReferenceLoadMinFarOffset) {
+      locations->AddTemp(FixedTempLocation());
     }
   }
   locations->SetInAt(0, Location::RequiresRegister());
@@ -2798,14 +2306,11 @@
                                                            : LocationSummary::kNoCall);
   if (object_array_get_with_read_barrier && kUseBakerReadBarrier) {
     locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty());  // No caller-save registers.
-    // We need a temporary register for the read barrier marking slow
-    // path in CodeGeneratorARM64::GenerateArrayLoadWithBakerReadBarrier.
-    if (kBakerReadBarrierLinkTimeThunksEnableForFields &&
-        !Runtime::Current()->UseJitCompilation() &&
-        instruction->GetIndex()->IsConstant()) {
+    if (instruction->GetIndex()->IsConstant()) {
       // Array loads with constant index are treated as field loads.
-      // If link-time thunks for the Baker read barrier are enabled, for AOT
-      // constant index loads we need a temporary only if the offset is too big.
+      // We need a temporary register for the read barrier load in
+      // CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier()
+      // only if the offset is too big.
       uint32_t offset = CodeGenerator::GetArrayDataOffset(instruction);
       uint32_t index = instruction->GetIndex()->AsIntConstant()->GetValue();
       offset += index << DataType::SizeShift(DataType::Type::kReference);
@@ -2813,6 +2318,8 @@
         locations->AddTemp(FixedTempLocation());
       }
     } else {
+      // We need a non-scratch temporary for the array data pointer in
+      // CodeGeneratorARM64::GenerateArrayLoadWithBakerReadBarrier().
       locations->AddTemp(Location::RequiresRegister());
     }
   }
@@ -2868,7 +2375,7 @@
     } else {
       Register temp = WRegisterFrom(locations->GetTemp(0));
       codegen_->GenerateArrayLoadWithBakerReadBarrier(
-          instruction, out, obj.W(), offset, index, temp, /* needs_null_check */ false);
+          out, obj.W(), offset, index, temp, /* needs_null_check */ false);
     }
   } else {
     // General case.
@@ -4759,7 +4266,7 @@
 }
 
 void CodeGeneratorARM64::EmitBakerReadBarrierCbnz(uint32_t custom_data) {
-  ExactAssemblyScope guard(GetVIXLAssembler(), 1 * vixl::aarch64::kInstructionSize);
+  DCHECK(!__ AllowMacroInstructions());  // In ExactAssemblyScope.
   if (Runtime::Current()->UseJitCompilation()) {
     auto it = jit_baker_read_barrier_slow_paths_.FindOrAdd(custom_data);
     vixl::aarch64::Label* slow_path_entry = &it->second.label;
@@ -6276,74 +5783,39 @@
     if (kUseBakerReadBarrier) {
       // Fast path implementation of art::ReadBarrier::BarrierForRoot when
       // Baker's read barrier are used.
-      if (kBakerReadBarrierLinkTimeThunksEnableForGcRoots) {
-        // Query `art::Thread::Current()->GetIsGcMarking()` (stored in
-        // the Marking Register) to decide whether we need to enter
-        // the slow path to mark the GC root.
-        //
-        // We use shared thunks for the slow path; shared within the method
-        // for JIT, across methods for AOT. That thunk checks the reference
-        // and jumps to the entrypoint if needed.
-        //
-        //     lr = &return_address;
-        //     GcRoot<mirror::Object> root = *(obj+offset);  // Original reference load.
-        //     if (mr) {  // Thread::Current()->GetIsGcMarking()
-        //       goto gc_root_thunk<root_reg>(lr)
-        //     }
-        //   return_address:
 
-        UseScratchRegisterScope temps(GetVIXLAssembler());
-        DCHECK(temps.IsAvailable(ip0));
-        DCHECK(temps.IsAvailable(ip1));
-        temps.Exclude(ip0, ip1);
-        uint32_t custom_data = EncodeBakerReadBarrierGcRootData(root_reg.GetCode());
+      // Query `art::Thread::Current()->GetIsGcMarking()` (stored in
+      // the Marking Register) to decide whether we need to enter
+      // the slow path to mark the GC root.
+      //
+      // We use shared thunks for the slow path; shared within the method
+      // for JIT, across methods for AOT. That thunk checks the reference
+      // and jumps to the entrypoint if needed.
+      //
+      //     lr = &return_address;
+      //     GcRoot<mirror::Object> root = *(obj+offset);  // Original reference load.
+      //     if (mr) {  // Thread::Current()->GetIsGcMarking()
+      //       goto gc_root_thunk<root_reg>(lr)
+      //     }
+      //   return_address:
 
-        ExactAssemblyScope guard(GetVIXLAssembler(), 3 * vixl::aarch64::kInstructionSize);
-        vixl::aarch64::Label return_address;
-        __ adr(lr, &return_address);
-        if (fixup_label != nullptr) {
-          __ bind(fixup_label);
-        }
-        static_assert(BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_OFFSET == -8,
-                      "GC root LDR must be 2 instruction (8B) before the return address label.");
-        __ ldr(root_reg, MemOperand(obj.X(), offset));
-        EmitBakerReadBarrierCbnz(custom_data);
-        __ bind(&return_address);
-      } else {
-        // Query `art::Thread::Current()->GetIsGcMarking()` (stored in
-        // the Marking Register) to decide whether we need to enter
-        // the slow path to mark the GC root.
-        //
-        //   GcRoot<mirror::Object> root = *(obj+offset);  // Original reference load.
-        //   if (mr) {  // Thread::Current()->GetIsGcMarking()
-        //     // Slow path.
-        //     entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
-        //     root = entrypoint(root);  // root = ReadBarrier::Mark(root);  // Entry point call.
-        //   }
+      UseScratchRegisterScope temps(GetVIXLAssembler());
+      DCHECK(temps.IsAvailable(ip0));
+      DCHECK(temps.IsAvailable(ip1));
+      temps.Exclude(ip0, ip1);
+      uint32_t custom_data = EncodeBakerReadBarrierGcRootData(root_reg.GetCode());
 
-        // Slow path marking the GC root `root`. The entrypoint will
-        // be loaded by the slow path code.
-        SlowPathCodeARM64* slow_path =
-            new (GetScopedAllocator()) ReadBarrierMarkSlowPathARM64(instruction, root);
-        AddSlowPath(slow_path);
-
-        // /* GcRoot<mirror::Object> */ root = *(obj + offset)
-        if (fixup_label == nullptr) {
-          __ Ldr(root_reg, MemOperand(obj, offset));
-        } else {
-          EmitLdrOffsetPlaceholder(fixup_label, root_reg, obj);
-        }
-        static_assert(
-            sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(GcRoot<mirror::Object>),
-            "art::mirror::CompressedReference<mirror::Object> and art::GcRoot<mirror::Object> "
-            "have different sizes.");
-        static_assert(sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(int32_t),
-                      "art::mirror::CompressedReference<mirror::Object> and int32_t "
-                      "have different sizes.");
-
-        __ Cbnz(mr, slow_path->GetEntryLabel());
-        __ Bind(slow_path->GetExitLabel());
+      ExactAssemblyScope guard(GetVIXLAssembler(), 3 * vixl::aarch64::kInstructionSize);
+      vixl::aarch64::Label return_address;
+      __ adr(lr, &return_address);
+      if (fixup_label != nullptr) {
+        __ bind(fixup_label);
       }
+      static_assert(BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_OFFSET == -8,
+                    "GC root LDR must be 2 instructions (8B) before the return address label.");
+      __ ldr(root_reg, MemOperand(obj.X(), offset));
+      EmitBakerReadBarrierCbnz(custom_data);
+      __ bind(&return_address);
     } else {
       // GC root loaded through a slow path for read barriers other
       // than Baker's.
@@ -6370,6 +5842,95 @@
   MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__);
 }
 
+void CodeGeneratorARM64::GenerateUnsafeCasOldValueMovWithBakerReadBarrier(
+    vixl::aarch64::Register marked,
+    vixl::aarch64::Register old_value) {
+  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(kUseBakerReadBarrier);
+
+  // Similar to the Baker RB path in GenerateGcRootFieldLoad(), with a MOV instead of LDR.
+  uint32_t custom_data = EncodeBakerReadBarrierGcRootData(marked.GetCode());
+
+  ExactAssemblyScope guard(GetVIXLAssembler(), 3 * vixl::aarch64::kInstructionSize);
+  vixl::aarch64::Label return_address;
+  __ adr(lr, &return_address);
+  static_assert(BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_OFFSET == -8,
+                "GC root LDR must be 2 instructions (8B) before the return address label.");
+  __ mov(marked, old_value);
+  EmitBakerReadBarrierCbnz(custom_data);
+  __ bind(&return_address);
+}
+
+void CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
+                                                               Location ref,
+                                                               vixl::aarch64::Register obj,
+                                                               const vixl::aarch64::MemOperand& src,
+                                                               bool needs_null_check,
+                                                               bool use_load_acquire) {
+  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(kUseBakerReadBarrier);
+
+  // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
+  // Marking Register) to decide whether we need to enter the slow
+  // path to mark the reference. Then, in the slow path, check the
+  // gray bit in the lock word of the reference's holder (`obj`) to
+  // decide whether to mark `ref` or not.
+  //
+  // We use shared thunks for the slow path; shared within the method
+  // for JIT, across methods for AOT. That thunk checks the holder
+  // and jumps to the entrypoint if needed. If the holder is not gray,
+  // it creates a fake dependency and returns to the LDR instruction.
+  //
+  //     lr = &gray_return_address;
+  //     if (mr) {  // Thread::Current()->GetIsGcMarking()
+  //       goto field_thunk<holder_reg, base_reg, use_load_acquire>(lr)
+  //     }
+  //   not_gray_return_address:
+  //     // Original reference load. If the offset is too large to fit
+  //     // into LDR, we use an adjusted base register here.
+  //     HeapReference<mirror::Object> reference = *(obj+offset);
+  //   gray_return_address:
+
+  DCHECK(src.GetAddrMode() == vixl::aarch64::Offset);
+  DCHECK_ALIGNED(src.GetOffset(), sizeof(mirror::HeapReference<mirror::Object>));
+
+  UseScratchRegisterScope temps(GetVIXLAssembler());
+  DCHECK(temps.IsAvailable(ip0));
+  DCHECK(temps.IsAvailable(ip1));
+  temps.Exclude(ip0, ip1);
+  uint32_t custom_data = use_load_acquire
+      ? EncodeBakerReadBarrierAcquireData(src.GetBaseRegister().GetCode(), obj.GetCode())
+      : EncodeBakerReadBarrierFieldData(src.GetBaseRegister().GetCode(), obj.GetCode());
+
+  {
+    ExactAssemblyScope guard(GetVIXLAssembler(),
+                             (kPoisonHeapReferences ? 4u : 3u) * vixl::aarch64::kInstructionSize);
+    vixl::aarch64::Label return_address;
+    __ adr(lr, &return_address);
+    EmitBakerReadBarrierCbnz(custom_data);
+    static_assert(BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4),
+                  "Field LDR must be 1 instruction (4B) before the return address label; "
+                  " 2 instructions (8B) for heap poisoning.");
+    Register ref_reg = RegisterFrom(ref, DataType::Type::kReference);
+    if (use_load_acquire) {
+      DCHECK_EQ(src.GetOffset(), 0);
+      __ ldar(ref_reg, src);
+    } else {
+      __ ldr(ref_reg, src);
+    }
+    if (needs_null_check) {
+      MaybeRecordImplicitNullCheck(instruction);
+    }
+    // Unpoison the reference explicitly if needed. MaybeUnpoisonHeapReference() uses
+    // macro instructions disallowed in ExactAssemblyScope.
+    if (kPoisonHeapReferences) {
+      __ neg(ref_reg, Operand(ref_reg));
+    }
+    __ bind(&return_address);
+  }
+  MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__, /* temp_loc */ LocationFrom(ip1));
+}
+
 void CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
                                                                Location ref,
                                                                Register obj,
@@ -6377,88 +5938,26 @@
                                                                Location maybe_temp,
                                                                bool needs_null_check,
                                                                bool use_load_acquire) {
-  DCHECK(kEmitCompilerReadBarrier);
-  DCHECK(kUseBakerReadBarrier);
-
-  if (kBakerReadBarrierLinkTimeThunksEnableForFields && !use_load_acquire) {
-    // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
-    // Marking Register) to decide whether we need to enter the slow
-    // path to mark the reference. Then, in the slow path, check the
-    // gray bit in the lock word of the reference's holder (`obj`) to
-    // decide whether to mark `ref` or not.
-    //
-    // We use shared thunks for the slow path; shared within the method
-    // for JIT, across methods for AOT. That thunk checks the holder
-    // and jumps to the entrypoint if needed. If the holder is not gray,
-    // it creates a fake dependency and returns to the LDR instruction.
-    //
-    //     lr = &gray_return_address;
-    //     if (mr) {  // Thread::Current()->GetIsGcMarking()
-    //       goto field_thunk<holder_reg, base_reg>(lr)
-    //     }
-    //   not_gray_return_address:
-    //     // Original reference load. If the offset is too large to fit
-    //     // into LDR, we use an adjusted base register here.
-    //     HeapReference<mirror::Object> reference = *(obj+offset);
-    //   gray_return_address:
-
-    DCHECK_ALIGNED(offset, sizeof(mirror::HeapReference<mirror::Object>));
-    Register base = obj;
-    if (offset >= kReferenceLoadMinFarOffset) {
-      DCHECK(maybe_temp.IsRegister());
-      base = WRegisterFrom(maybe_temp);
-      static_assert(IsPowerOfTwo(kReferenceLoadMinFarOffset), "Expecting a power of 2.");
-      __ Add(base, obj, Operand(offset & ~(kReferenceLoadMinFarOffset - 1u)));
-      offset &= (kReferenceLoadMinFarOffset - 1u);
-    }
-    UseScratchRegisterScope temps(GetVIXLAssembler());
-    DCHECK(temps.IsAvailable(ip0));
-    DCHECK(temps.IsAvailable(ip1));
-    temps.Exclude(ip0, ip1);
-    uint32_t custom_data = EncodeBakerReadBarrierFieldData(base.GetCode(), obj.GetCode());
-
-    {
-      ExactAssemblyScope guard(GetVIXLAssembler(),
-                               (kPoisonHeapReferences ? 4u : 3u) * vixl::aarch64::kInstructionSize);
-      vixl::aarch64::Label return_address;
-      __ adr(lr, &return_address);
-      EmitBakerReadBarrierCbnz(custom_data);
-      static_assert(BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4),
-                    "Field LDR must be 1 instruction (4B) before the return address label; "
-                    " 2 instructions (8B) for heap poisoning.");
-      Register ref_reg = RegisterFrom(ref, DataType::Type::kReference);
-      __ ldr(ref_reg, MemOperand(base.X(), offset));
-      if (needs_null_check) {
-        MaybeRecordImplicitNullCheck(instruction);
-      }
-      // Unpoison the reference explicitly if needed. MaybeUnpoisonHeapReference() uses
-      // macro instructions disallowed in ExactAssemblyScope.
-      if (kPoisonHeapReferences) {
-        __ neg(ref_reg, Operand(ref_reg));
-      }
-      __ bind(&return_address);
-    }
-    MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__, /* temp_loc */ LocationFrom(ip1));
-    return;
+  DCHECK_ALIGNED(offset, sizeof(mirror::HeapReference<mirror::Object>));
+  Register base = obj;
+  if (use_load_acquire) {
+    DCHECK(maybe_temp.IsRegister());
+    base = WRegisterFrom(maybe_temp);
+    __ Add(base, obj, offset);
+    offset = 0u;
+  } else if (offset >= kReferenceLoadMinFarOffset) {
+    DCHECK(maybe_temp.IsRegister());
+    base = WRegisterFrom(maybe_temp);
+    static_assert(IsPowerOfTwo(kReferenceLoadMinFarOffset), "Expecting a power of 2.");
+    __ Add(base, obj, Operand(offset & ~(kReferenceLoadMinFarOffset - 1u)));
+    offset &= (kReferenceLoadMinFarOffset - 1u);
   }
-
-  // /* HeapReference<Object> */ ref = *(obj + offset)
-  Register temp = WRegisterFrom(maybe_temp);
-  Location no_index = Location::NoLocation();
-  size_t no_scale_factor = 0u;
-  GenerateReferenceLoadWithBakerReadBarrier(instruction,
-                                            ref,
-                                            obj,
-                                            offset,
-                                            no_index,
-                                            no_scale_factor,
-                                            temp,
-                                            needs_null_check,
-                                            use_load_acquire);
+  MemOperand src(base.X(), offset);
+  GenerateFieldLoadWithBakerReadBarrier(
+      instruction, ref, obj, src, needs_null_check, use_load_acquire);
 }
 
-void CodeGeneratorARM64::GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction,
-                                                               Location ref,
+void CodeGeneratorARM64::GenerateArrayLoadWithBakerReadBarrier(Location ref,
                                                                Register obj,
                                                                uint32_t data_offset,
                                                                Location index,
@@ -6472,268 +5971,57 @@
       "art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
   size_t scale_factor = DataType::SizeShift(DataType::Type::kReference);
 
-  if (kBakerReadBarrierLinkTimeThunksEnableForArrays) {
-    // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
-    // Marking Register) to decide whether we need to enter the slow
-    // path to mark the reference. Then, in the slow path, check the
-    // gray bit in the lock word of the reference's holder (`obj`) to
-    // decide whether to mark `ref` or not.
-    //
-    // We use shared thunks for the slow path; shared within the method
-    // for JIT, across methods for AOT. That thunk checks the holder
-    // and jumps to the entrypoint if needed. If the holder is not gray,
-    // it creates a fake dependency and returns to the LDR instruction.
-    //
-    //     lr = &gray_return_address;
-    //     if (mr) {  // Thread::Current()->GetIsGcMarking()
-    //       goto array_thunk<base_reg>(lr)
-    //     }
-    //   not_gray_return_address:
-    //     // Original reference load. If the offset is too large to fit
-    //     // into LDR, we use an adjusted base register here.
-    //     HeapReference<mirror::Object> reference = data[index];
-    //   gray_return_address:
-
-    DCHECK(index.IsValid());
-    Register index_reg = RegisterFrom(index, DataType::Type::kInt32);
-    Register ref_reg = RegisterFrom(ref, DataType::Type::kReference);
-
-    UseScratchRegisterScope temps(GetVIXLAssembler());
-    DCHECK(temps.IsAvailable(ip0));
-    DCHECK(temps.IsAvailable(ip1));
-    temps.Exclude(ip0, ip1);
-    uint32_t custom_data = EncodeBakerReadBarrierArrayData(temp.GetCode());
-
-    __ Add(temp.X(), obj.X(), Operand(data_offset));
-    {
-      ExactAssemblyScope guard(GetVIXLAssembler(),
-                               (kPoisonHeapReferences ? 4u : 3u) * vixl::aarch64::kInstructionSize);
-      vixl::aarch64::Label return_address;
-      __ adr(lr, &return_address);
-      EmitBakerReadBarrierCbnz(custom_data);
-      static_assert(BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4),
-                    "Array LDR must be 1 instruction (4B) before the return address label; "
-                    " 2 instructions (8B) for heap poisoning.");
-      __ ldr(ref_reg, MemOperand(temp.X(), index_reg.X(), LSL, scale_factor));
-      DCHECK(!needs_null_check);  // The thunk cannot handle the null check.
-      // Unpoison the reference explicitly if needed. MaybeUnpoisonHeapReference() uses
-      // macro instructions disallowed in ExactAssemblyScope.
-      if (kPoisonHeapReferences) {
-        __ neg(ref_reg, Operand(ref_reg));
-      }
-      __ bind(&return_address);
-    }
-    MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__, /* temp_loc */ LocationFrom(ip1));
-    return;
-  }
-
-  // Array cells are never volatile variables, therefore array loads
-  // never use Load-Acquire instructions on ARM64.
-  const bool use_load_acquire = false;
-
-  // /* HeapReference<Object> */ ref =
-  //     *(obj + data_offset + index * sizeof(HeapReference<Object>))
-  GenerateReferenceLoadWithBakerReadBarrier(instruction,
-                                            ref,
-                                            obj,
-                                            data_offset,
-                                            index,
-                                            scale_factor,
-                                            temp,
-                                            needs_null_check,
-                                            use_load_acquire);
-}
-
-void CodeGeneratorARM64::GenerateReferenceLoadWithBakerReadBarrier(HInstruction* instruction,
-                                                                   Location ref,
-                                                                   Register obj,
-                                                                   uint32_t offset,
-                                                                   Location index,
-                                                                   size_t scale_factor,
-                                                                   Register temp,
-                                                                   bool needs_null_check,
-                                                                   bool use_load_acquire) {
-  DCHECK(kEmitCompilerReadBarrier);
-  DCHECK(kUseBakerReadBarrier);
-  // If we are emitting an array load, we should not be using a
-  // Load Acquire instruction.  In other words:
-  // `instruction->IsArrayGet()` => `!use_load_acquire`.
-  DCHECK(!instruction->IsArrayGet() || !use_load_acquire);
-
   // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
   // Marking Register) to decide whether we need to enter the slow
   // path to mark the reference. Then, in the slow path, check the
   // gray bit in the lock word of the reference's holder (`obj`) to
   // decide whether to mark `ref` or not.
   //
-  //   if (mr) {  // Thread::Current()->GetIsGcMarking()
-  //     // Slow path.
-  //     uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
-  //     lfence;  // Load fence or artificial data dependency to prevent load-load reordering
-  //     HeapReference<mirror::Object> ref = *src;  // Original reference load.
-  //     bool is_gray = (rb_state == ReadBarrier::GrayState());
-  //     if (is_gray) {
-  //       entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
-  //       ref = entrypoint(ref);  // ref = ReadBarrier::Mark(ref);  // Runtime entry point call.
-  //     }
-  //   } else {
-  //     HeapReference<mirror::Object> ref = *src;  // Original reference load.
-  //   }
-
-  // Slow path marking the object `ref` when the GC is marking. The
-  // entrypoint will be loaded by the slow path code.
-  SlowPathCodeARM64* slow_path =
-      new (GetScopedAllocator()) LoadReferenceWithBakerReadBarrierSlowPathARM64(
-          instruction,
-          ref,
-          obj,
-          offset,
-          index,
-          scale_factor,
-          needs_null_check,
-          use_load_acquire,
-          temp);
-  AddSlowPath(slow_path);
-
-  __ Cbnz(mr, slow_path->GetEntryLabel());
-  // Fast path: the GC is not marking: just load the reference.
-  GenerateRawReferenceLoad(
-      instruction, ref, obj, offset, index, scale_factor, needs_null_check, use_load_acquire);
-  __ Bind(slow_path->GetExitLabel());
-  MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__);
-}
-
-void CodeGeneratorARM64::UpdateReferenceFieldWithBakerReadBarrier(HInstruction* instruction,
-                                                                  Location ref,
-                                                                  Register obj,
-                                                                  Location field_offset,
-                                                                  Register temp,
-                                                                  bool needs_null_check,
-                                                                  bool use_load_acquire) {
-  DCHECK(kEmitCompilerReadBarrier);
-  DCHECK(kUseBakerReadBarrier);
-  // If we are emitting an array load, we should not be using a
-  // Load Acquire instruction.  In other words:
-  // `instruction->IsArrayGet()` => `!use_load_acquire`.
-  DCHECK(!instruction->IsArrayGet() || !use_load_acquire);
-
-  // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
-  // Marking Register) to decide whether we need to enter the slow
-  // path to update the reference field within `obj`. Then, in the
-  // slow path, check the gray bit in the lock word of the reference's
-  // holder (`obj`) to decide whether to mark `ref` and update the
-  // field or not.
+  // We use shared thunks for the slow path; shared within the method
+  // for JIT, across methods for AOT. That thunk checks the holder
+  // and jumps to the entrypoint if needed. If the holder is not gray,
+  // it creates a fake dependency and returns to the LDR instruction.
   //
-  //   if (mr) {  // Thread::Current()->GetIsGcMarking()
-  //     // Slow path.
-  //     uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
-  //     lfence;  // Load fence or artificial data dependency to prevent load-load reordering
-  //     HeapReference<mirror::Object> ref = *(obj + field_offset);  // Reference load.
-  //     bool is_gray = (rb_state == ReadBarrier::GrayState());
-  //     if (is_gray) {
-  //       old_ref = ref;
-  //       entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
-  //       ref = entrypoint(ref);  // ref = ReadBarrier::Mark(ref);  // Runtime entry point call.
-  //       compareAndSwapObject(obj, field_offset, old_ref, ref);
+  //     lr = &gray_return_address;
+  //     if (mr) {  // Thread::Current()->GetIsGcMarking()
+  //       goto array_thunk<base_reg>(lr)
   //     }
-  //   }
+  //   not_gray_return_address:
+  //     // Original reference load. If the offset is too large to fit
+  //     // into LDR, we use an adjusted base register here.
+  //     HeapReference<mirror::Object> reference = data[index];
+  //   gray_return_address:
 
-  // Slow path updating the object reference at address `obj + field_offset`
-  // when the GC is marking. The entrypoint will be loaded by the slow path code.
-  SlowPathCodeARM64* slow_path =
-      new (GetScopedAllocator()) LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64(
-          instruction,
-          ref,
-          obj,
-          /* offset */ 0u,
-          /* index */ field_offset,
-          /* scale_factor */ 0u /* "times 1" */,
-          needs_null_check,
-          use_load_acquire,
-          temp);
-  AddSlowPath(slow_path);
+  DCHECK(index.IsValid());
+  Register index_reg = RegisterFrom(index, DataType::Type::kInt32);
+  Register ref_reg = RegisterFrom(ref, DataType::Type::kReference);
 
-  __ Cbnz(mr, slow_path->GetEntryLabel());
-  // Fast path: the GC is not marking: nothing to do (the field is
-  // up-to-date, and we don't need to load the reference).
-  __ Bind(slow_path->GetExitLabel());
-  MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__);
-}
+  UseScratchRegisterScope temps(GetVIXLAssembler());
+  DCHECK(temps.IsAvailable(ip0));
+  DCHECK(temps.IsAvailable(ip1));
+  temps.Exclude(ip0, ip1);
+  uint32_t custom_data = EncodeBakerReadBarrierArrayData(temp.GetCode());
 
-void CodeGeneratorARM64::GenerateRawReferenceLoad(HInstruction* instruction,
-                                                  Location ref,
-                                                  Register obj,
-                                                  uint32_t offset,
-                                                  Location index,
-                                                  size_t scale_factor,
-                                                  bool needs_null_check,
-                                                  bool use_load_acquire) {
-  DCHECK(obj.IsW());
-  DataType::Type type = DataType::Type::kReference;
-  Register ref_reg = RegisterFrom(ref, type);
-
-  // If needed, vixl::EmissionCheckScope guards are used to ensure
-  // that no pools are emitted between the load (macro) instruction
-  // and MaybeRecordImplicitNullCheck.
-
-  if (index.IsValid()) {
-    // Load types involving an "index": ArrayGet,
-    // UnsafeGetObject/UnsafeGetObjectVolatile and UnsafeCASObject
-    // intrinsics.
-    if (use_load_acquire) {
-      // UnsafeGetObjectVolatile intrinsic case.
-      // Register `index` is not an index in an object array, but an
-      // offset to an object reference field within object `obj`.
-      DCHECK(instruction->IsInvoke()) << instruction->DebugName();
-      DCHECK(instruction->GetLocations()->Intrinsified());
-      DCHECK(instruction->AsInvoke()->GetIntrinsic() == Intrinsics::kUnsafeGetObjectVolatile)
-          << instruction->AsInvoke()->GetIntrinsic();
-      DCHECK_EQ(offset, 0u);
-      DCHECK_EQ(scale_factor, 0u);
-      DCHECK_EQ(needs_null_check, false);
-      // /* HeapReference<mirror::Object> */ ref = *(obj + index)
-      MemOperand field = HeapOperand(obj, XRegisterFrom(index));
-      LoadAcquire(instruction, ref_reg, field, /* needs_null_check */ false);
-    } else {
-      // ArrayGet and UnsafeGetObject and UnsafeCASObject intrinsics cases.
-      // /* HeapReference<mirror::Object> */ ref = *(obj + offset + (index << scale_factor))
-      if (index.IsConstant()) {
-        uint32_t computed_offset = offset + (Int64FromLocation(index) << scale_factor);
-        EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes);
-        Load(type, ref_reg, HeapOperand(obj, computed_offset));
-        if (needs_null_check) {
-          MaybeRecordImplicitNullCheck(instruction);
-        }
-      } else {
-        UseScratchRegisterScope temps(GetVIXLAssembler());
-        Register temp = temps.AcquireW();
-        __ Add(temp, obj, offset);
-        {
-          EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes);
-          Load(type, ref_reg, HeapOperand(temp, XRegisterFrom(index), LSL, scale_factor));
-          if (needs_null_check) {
-            MaybeRecordImplicitNullCheck(instruction);
-          }
-        }
-      }
+  __ Add(temp.X(), obj.X(), Operand(data_offset));
+  {
+    ExactAssemblyScope guard(GetVIXLAssembler(),
+                             (kPoisonHeapReferences ? 4u : 3u) * vixl::aarch64::kInstructionSize);
+    vixl::aarch64::Label return_address;
+    __ adr(lr, &return_address);
+    EmitBakerReadBarrierCbnz(custom_data);
+    static_assert(BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4),
+                  "Array LDR must be 1 instruction (4B) before the return address label; "
+                  " 2 instructions (8B) for heap poisoning.");
+    __ ldr(ref_reg, MemOperand(temp.X(), index_reg.X(), LSL, scale_factor));
+    DCHECK(!needs_null_check);  // The thunk cannot handle the null check.
+    // Unpoison the reference explicitly if needed. MaybeUnpoisonHeapReference() uses
+    // macro instructions disallowed in ExactAssemblyScope.
+    if (kPoisonHeapReferences) {
+      __ neg(ref_reg, Operand(ref_reg));
     }
-  } else {
-    // /* HeapReference<mirror::Object> */ ref = *(obj + offset)
-    MemOperand field = HeapOperand(obj, offset);
-    if (use_load_acquire) {
-      // Implicit null checks are handled by CodeGeneratorARM64::LoadAcquire.
-      LoadAcquire(instruction, ref_reg, field, needs_null_check);
-    } else {
-      EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes);
-      Load(type, ref_reg, field);
-      if (needs_null_check) {
-        MaybeRecordImplicitNullCheck(instruction);
-      }
-    }
+    __ bind(&return_address);
   }
-
-  // Object* ref = ref_addr->AsMirrorPtr()
-  GetAssembler()->MaybeUnpoisonHeapReference(ref_reg);
+  MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__, /* temp_loc */ LocationFrom(ip1));
 }
 
 void CodeGeneratorARM64::MaybeGenerateMarkingRegisterCheck(int code, Location temp_loc) {
@@ -6874,7 +6162,7 @@
   // Load the lock word containing the rb_state.
   __ Ldr(ip0.W(), lock_word);
   // Given the numeric representation, it's enough to check the low bit of the rb_state.
-  static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+  static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
   static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
   __ Tbnz(ip0.W(), LockWord::kReadBarrierStateShift, slow_path);
   static_assert(
@@ -6912,7 +6200,8 @@
                                                       /*out*/ std::string* debug_name) {
   BakerReadBarrierKind kind = BakerReadBarrierKindField::Decode(encoded_data);
   switch (kind) {
-    case BakerReadBarrierKind::kField: {
+    case BakerReadBarrierKind::kField:
+    case BakerReadBarrierKind::kAcquire: {
       auto base_reg =
           Register::GetXRegFromCode(BakerReadBarrierFirstRegField::Decode(encoded_data));
       CheckValidReg(base_reg.GetCode());
@@ -6938,11 +6227,18 @@
       MemOperand lock_word(holder_reg, mirror::Object::MonitorOffset().Int32Value());
       EmitGrayCheckAndFastPath(assembler, base_reg, lock_word, &slow_path, throw_npe);
       __ Bind(&slow_path);
-      MemOperand ldr_address(lr, BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET);
-      __ Ldr(ip0.W(), ldr_address);         // Load the LDR (immediate) unsigned offset.
-      LoadReadBarrierMarkIntrospectionEntrypoint(assembler, ip1);
-      __ Ubfx(ip0.W(), ip0.W(), 10, 12);    // Extract the offset.
-      __ Ldr(ip0.W(), MemOperand(base_reg, ip0, LSL, 2));   // Load the reference.
+      if (kind == BakerReadBarrierKind::kField) {
+        MemOperand ldr_address(lr, BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET);
+        __ Ldr(ip0.W(), ldr_address);         // Load the LDR (immediate) unsigned offset.
+        LoadReadBarrierMarkIntrospectionEntrypoint(assembler, ip1);
+        __ Ubfx(ip0.W(), ip0.W(), 10, 12);    // Extract the offset.
+        __ Ldr(ip0.W(), MemOperand(base_reg, ip0, LSL, 2));   // Load the reference.
+      } else {
+        DCHECK(kind == BakerReadBarrierKind::kAcquire);
+        DCHECK(!base_reg.Is(holder_reg));
+        LoadReadBarrierMarkIntrospectionEntrypoint(assembler, ip1);
+        __ Ldar(ip0.W(), MemOperand(base_reg));
+      }
       // Do not unpoison. With heap poisoning enabled, the entrypoint expects a poisoned reference.
       __ Br(ip1);                           // Jump to the entrypoint.
       break;
@@ -7023,6 +6319,10 @@
         oss << "Field_r" << BakerReadBarrierFirstRegField::Decode(encoded_data)
             << "_r" << BakerReadBarrierSecondRegField::Decode(encoded_data);
         break;
+      case BakerReadBarrierKind::kAcquire:
+        oss << "Acquire_r" << BakerReadBarrierFirstRegField::Decode(encoded_data)
+            << "_r" << BakerReadBarrierSecondRegField::Decode(encoded_data);
+        break;
       case BakerReadBarrierKind::kArray:
         oss << "Array_r" << BakerReadBarrierFirstRegField::Decode(encoded_data);
         DCHECK_EQ(kBakerReadBarrierInvalidEncodedReg,
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index c07d1ea..4f6a44f 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -92,6 +92,16 @@
         ((kEmitCompilerReadBarrier && kUseBakerReadBarrier) ? mr : vixl::aarch64::NoCPUReg),
         vixl::aarch64::lr);
 
+// Some instructions have special requirements for a temporary, for example
+// LoadClass/kBssEntry and LoadString/kBssEntry for Baker read barrier require
+// temp that's not an R0 (to avoid an extra move) and Baker read barrier field
+// loads with large offsets need a fixed register to limit the number of link-time
+// thunks we generate. For these and similar cases, we want to reserve a specific
+// register that's neither callee-save nor an argument register. We choose x15.
+inline Location FixedTempLocation() {
+  return Location::RegisterLocation(vixl::aarch64::x15.GetCode());
+}
+
 // Callee-save registers AAPCS64, without x19 (Thread Register) (nor
 // x20 (Marking Register) when emitting Baker read barriers).
 const vixl::aarch64::CPURegList callee_saved_core_registers(
@@ -661,6 +671,18 @@
                                uint32_t offset,
                                vixl::aarch64::Label* fixup_label,
                                ReadBarrierOption read_barrier_option);
+  // Generate MOV for the `old_value` in UnsafeCASObject and mark it with Baker read barrier.
+  void GenerateUnsafeCasOldValueMovWithBakerReadBarrier(vixl::aarch64::Register marked,
+                                                        vixl::aarch64::Register old_value);
+  // Fast path implementation of ReadBarrier::Barrier for a heap
+  // reference field load when Baker's read barriers are used.
+  // Overload suitable for Unsafe.getObject/-Volatile() intrinsic.
+  void GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
+                                             Location ref,
+                                             vixl::aarch64::Register obj,
+                                             const vixl::aarch64::MemOperand& src,
+                                             bool needs_null_check,
+                                             bool use_load_acquire);
   // Fast path implementation of ReadBarrier::Barrier for a heap
   // reference field load when Baker's read barriers are used.
   void GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
@@ -672,58 +694,12 @@
                                              bool use_load_acquire);
   // Fast path implementation of ReadBarrier::Barrier for a heap
   // reference array load when Baker's read barriers are used.
-  void GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction,
-                                             Location ref,
+  void GenerateArrayLoadWithBakerReadBarrier(Location ref,
                                              vixl::aarch64::Register obj,
                                              uint32_t data_offset,
                                              Location index,
                                              vixl::aarch64::Register temp,
                                              bool needs_null_check);
-  // Factored implementation, used by GenerateFieldLoadWithBakerReadBarrier,
-  // GenerateArrayLoadWithBakerReadBarrier and some intrinsics.
-  //
-  // Load the object reference located at the address
-  // `obj + offset + (index << scale_factor)`, held by object `obj`, into
-  // `ref`, and mark it if needed.
-  void GenerateReferenceLoadWithBakerReadBarrier(HInstruction* instruction,
-                                                 Location ref,
-                                                 vixl::aarch64::Register obj,
-                                                 uint32_t offset,
-                                                 Location index,
-                                                 size_t scale_factor,
-                                                 vixl::aarch64::Register temp,
-                                                 bool needs_null_check,
-                                                 bool use_load_acquire);
-
-  // Generate code checking whether the the reference field at the
-  // address `obj + field_offset`, held by object `obj`, needs to be
-  // marked, and if so, marking it and updating the field within `obj`
-  // with the marked value.
-  //
-  // This routine is used for the implementation of the
-  // UnsafeCASObject intrinsic with Baker read barriers.
-  //
-  // This method has a structure similar to
-  // GenerateReferenceLoadWithBakerReadBarrier, but note that argument
-  // `ref` is only as a temporary here, and thus its value should not
-  // be used afterwards.
-  void UpdateReferenceFieldWithBakerReadBarrier(HInstruction* instruction,
-                                                Location ref,
-                                                vixl::aarch64::Register obj,
-                                                Location field_offset,
-                                                vixl::aarch64::Register temp,
-                                                bool needs_null_check,
-                                                bool use_load_acquire);
-
-  // Generate a heap reference load (with no read barrier).
-  void GenerateRawReferenceLoad(HInstruction* instruction,
-                                Location ref,
-                                vixl::aarch64::Register obj,
-                                uint32_t offset,
-                                Location index,
-                                size_t scale_factor,
-                                bool needs_null_check,
-                                bool use_load_acquire);
 
   // Emit code checking the status of the Marking Register, and
   // aborting the program if MR does not match the value stored in the
@@ -798,9 +774,10 @@
   // Encoding of thunk type and data for link-time generated thunks for Baker read barriers.
 
   enum class BakerReadBarrierKind : uint8_t {
-    kField,   // Field get or array get with constant offset (i.e. constant index).
-    kArray,   // Array get with index in register.
-    kGcRoot,  // GC root load.
+    kField,     // Field get or array get with constant offset (i.e. constant index).
+    kAcquire,   // Volatile field get.
+    kArray,     // Array get with index in register.
+    kGcRoot,    // GC root load.
     kLast = kGcRoot
   };
 
@@ -833,6 +810,15 @@
            BakerReadBarrierSecondRegField::Encode(holder_reg);
   }
 
+  static inline uint32_t EncodeBakerReadBarrierAcquireData(uint32_t base_reg, uint32_t holder_reg) {
+    CheckValidReg(base_reg);
+    CheckValidReg(holder_reg);
+    DCHECK_NE(base_reg, holder_reg);
+    return BakerReadBarrierKindField::Encode(BakerReadBarrierKind::kAcquire) |
+           BakerReadBarrierFirstRegField::Encode(base_reg) |
+           BakerReadBarrierSecondRegField::Encode(holder_reg);
+  }
+
   static inline uint32_t EncodeBakerReadBarrierArrayData(uint32_t base_reg) {
     CheckValidReg(base_reg);
     return BakerReadBarrierKindField::Encode(BakerReadBarrierKind::kArray) |
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 47d7360..8c5eafd 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -85,15 +85,10 @@
 
 // Reference load (except object array loads) is using LDR Rt, [Rn, #offset] which can handle
 // offset < 4KiB. For offsets >= 4KiB, the load shall be emitted as two or more instructions.
-// For the Baker read barrier implementation using link-generated thunks we need to split
+// For the Baker read barrier implementation using link-time generated thunks we need to split
 // the offset explicitly.
 constexpr uint32_t kReferenceLoadMinFarOffset = 4 * KB;
 
-// Flags controlling the use of link-time generated thunks for Baker read barriers.
-constexpr bool kBakerReadBarrierLinkTimeThunksEnableForFields = true;
-constexpr bool kBakerReadBarrierLinkTimeThunksEnableForArrays = true;
-constexpr bool kBakerReadBarrierLinkTimeThunksEnableForGcRoots = true;
-
 // Using a base helps identify when we hit Marking Register check breakpoints.
 constexpr int kMarkingRegisterCheckBreakCodeBaseCode = 0x10;
 
@@ -116,7 +111,7 @@
  public:
   EmitAdrCode(ArmVIXLMacroAssembler* assembler, vixl32::Register rd, vixl32::Label* label)
       : assembler_(assembler), rd_(rd), label_(label) {
-    ExactAssemblyScope aas(assembler, kMaxInstructionSizeInBytes);
+    DCHECK(!assembler->AllowMacroInstructions());  // In ExactAssemblyScope.
     adr_location_ = assembler->GetCursorOffset();
     assembler->adr(EncodingSize(Wide), rd, label);
   }
@@ -720,489 +715,6 @@
   DISALLOW_COPY_AND_ASSIGN(ArraySetSlowPathARMVIXL);
 };
 
-// Abstract base class for read barrier slow paths marking a reference
-// `ref`.
-//
-// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked or an empty
-// location; in the latter case, the read barrier marking runtime
-// entry point will be loaded by the slow path code itself.
-class ReadBarrierMarkSlowPathBaseARMVIXL : public SlowPathCodeARMVIXL {
- protected:
-  ReadBarrierMarkSlowPathBaseARMVIXL(HInstruction* instruction, Location ref, Location entrypoint)
-      : SlowPathCodeARMVIXL(instruction), ref_(ref), entrypoint_(entrypoint) {
-    DCHECK(kEmitCompilerReadBarrier);
-  }
-
-  const char* GetDescription() const OVERRIDE { return "ReadBarrierMarkSlowPathBaseARMVIXL"; }
-
-  // Generate assembly code calling the read barrier marking runtime
-  // entry point (ReadBarrierMarkRegX).
-  void GenerateReadBarrierMarkRuntimeCall(CodeGenerator* codegen) {
-    vixl32::Register ref_reg = RegisterFrom(ref_);
-
-    // No need to save live registers; it's taken care of by the
-    // entrypoint. Also, there is no need to update the stack mask,
-    // as this runtime call will not trigger a garbage collection.
-    CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen);
-    DCHECK(!ref_reg.Is(sp));
-    DCHECK(!ref_reg.Is(lr));
-    DCHECK(!ref_reg.Is(pc));
-    // IP is used internally by the ReadBarrierMarkRegX entry point
-    // as a temporary, it cannot be the entry point's input/output.
-    DCHECK(!ref_reg.Is(ip));
-    DCHECK(ref_reg.IsRegister()) << ref_reg;
-    // "Compact" slow path, saving two moves.
-    //
-    // Instead of using the standard runtime calling convention (input
-    // and output in R0):
-    //
-    //   R0 <- ref
-    //   R0 <- ReadBarrierMark(R0)
-    //   ref <- R0
-    //
-    // we just use rX (the register containing `ref`) as input and output
-    // of a dedicated entrypoint:
-    //
-    //   rX <- ReadBarrierMarkRegX(rX)
-    //
-    if (entrypoint_.IsValid()) {
-      arm_codegen->ValidateInvokeRuntimeWithoutRecordingPcInfo(instruction_, this);
-      __ Blx(RegisterFrom(entrypoint_));
-    } else {
-      // Entrypoint is not already loaded, load from the thread.
-      int32_t entry_point_offset =
-          Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref_reg.GetCode());
-      // This runtime call does not require a stack map.
-      arm_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
-    }
-  }
-
-  // The location (register) of the marked object reference.
-  const Location ref_;
-
-  // The location of the entrypoint if already loaded.
-  const Location entrypoint_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ReadBarrierMarkSlowPathBaseARMVIXL);
-};
-
-// Slow path marking an object reference `ref` during a read
-// barrier. The field `obj.field` in the object `obj` holding this
-// reference does not get updated by this slow path after marking.
-//
-// This means that after the execution of this slow path, `ref` will
-// always be up-to-date, but `obj.field` may not; i.e., after the
-// flip, `ref` will be a to-space reference, but `obj.field` will
-// probably still be a from-space reference (unless it gets updated by
-// another thread, or if another thread installed another object
-// reference (different from `ref`) in `obj.field`).
-//
-// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked or an empty
-// location; in the latter case, the read barrier marking runtime
-// entry point will be loaded by the slow path code itself.
-class ReadBarrierMarkSlowPathARMVIXL : public ReadBarrierMarkSlowPathBaseARMVIXL {
- public:
-  ReadBarrierMarkSlowPathARMVIXL(HInstruction* instruction,
-                                 Location ref,
-                                 Location entrypoint = Location::NoLocation())
-      : ReadBarrierMarkSlowPathBaseARMVIXL(instruction, ref, entrypoint) {
-    DCHECK(kEmitCompilerReadBarrier);
-  }
-
-  const char* GetDescription() const OVERRIDE { return "ReadBarrierMarkSlowPathARMVIXL"; }
-
-  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
-    LocationSummary* locations = instruction_->GetLocations();
-    DCHECK(locations->CanCall());
-    DCHECK(ref_.IsRegister()) << ref_;
-    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_.reg())) << ref_.reg();
-    DCHECK(instruction_->IsLoadClass() || instruction_->IsLoadString())
-        << "Unexpected instruction in read barrier marking slow path: "
-        << instruction_->DebugName();
-
-    __ Bind(GetEntryLabel());
-    GenerateReadBarrierMarkRuntimeCall(codegen);
-    __ B(GetExitLabel());
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ReadBarrierMarkSlowPathARMVIXL);
-};
-
-// Slow path loading `obj`'s lock word, loading a reference from
-// object `*(obj + offset + (index << scale_factor))` into `ref`, and
-// marking `ref` if `obj` is gray according to the lock word (Baker
-// read barrier). The field `obj.field` in the object `obj` holding
-// this reference does not get updated by this slow path after marking
-// (see LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARMVIXL
-// below for that).
-//
-// This means that after the execution of this slow path, `ref` will
-// always be up-to-date, but `obj.field` may not; i.e., after the
-// flip, `ref` will be a to-space reference, but `obj.field` will
-// probably still be a from-space reference (unless it gets updated by
-// another thread, or if another thread installed another object
-// reference (different from `ref`) in `obj.field`).
-//
-// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked or an empty
-// location; in the latter case, the read barrier marking runtime
-// entry point will be loaded by the slow path code itself.
-class LoadReferenceWithBakerReadBarrierSlowPathARMVIXL : public ReadBarrierMarkSlowPathBaseARMVIXL {
- public:
-  LoadReferenceWithBakerReadBarrierSlowPathARMVIXL(HInstruction* instruction,
-                                                   Location ref,
-                                                   vixl32::Register obj,
-                                                   uint32_t offset,
-                                                   Location index,
-                                                   ScaleFactor scale_factor,
-                                                   bool needs_null_check,
-                                                   vixl32::Register temp,
-                                                   Location entrypoint = Location::NoLocation())
-      : ReadBarrierMarkSlowPathBaseARMVIXL(instruction, ref, entrypoint),
-        obj_(obj),
-        offset_(offset),
-        index_(index),
-        scale_factor_(scale_factor),
-        needs_null_check_(needs_null_check),
-        temp_(temp) {
-    DCHECK(kEmitCompilerReadBarrier);
-    DCHECK(kUseBakerReadBarrier);
-  }
-
-  const char* GetDescription() const OVERRIDE {
-    return "LoadReferenceWithBakerReadBarrierSlowPathARMVIXL";
-  }
-
-  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
-    LocationSummary* locations = instruction_->GetLocations();
-    vixl32::Register ref_reg = RegisterFrom(ref_);
-    DCHECK(locations->CanCall());
-    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_reg.GetCode())) << ref_reg;
-    DCHECK(instruction_->IsInstanceFieldGet() ||
-           instruction_->IsStaticFieldGet() ||
-           instruction_->IsArrayGet() ||
-           instruction_->IsArraySet() ||
-           instruction_->IsInstanceOf() ||
-           instruction_->IsCheckCast() ||
-           (instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()) ||
-           (instruction_->IsInvokeStaticOrDirect() && instruction_->GetLocations()->Intrinsified()))
-        << "Unexpected instruction in read barrier marking slow path: "
-        << instruction_->DebugName();
-    // The read barrier instrumentation of object ArrayGet
-    // instructions does not support the HIntermediateAddress
-    // instruction.
-    DCHECK(!(instruction_->IsArrayGet() &&
-             instruction_->AsArrayGet()->GetArray()->IsIntermediateAddress()));
-
-    // Temporary register `temp_`, used to store the lock word, must
-    // not be IP, as we may use it to emit the reference load (in the
-    // call to GenerateRawReferenceLoad below), and we need the lock
-    // word to still be in `temp_` after the reference load.
-    DCHECK(!temp_.Is(ip));
-
-    __ Bind(GetEntryLabel());
-
-    // When using MaybeGenerateReadBarrierSlow, the read barrier call is
-    // inserted after the original load. However, in fast path based
-    // Baker's read barriers, we need to perform the load of
-    // mirror::Object::monitor_ *before* the original reference load.
-    // This load-load ordering is required by the read barrier.
-    // The slow path (for Baker's algorithm) should look like:
-    //
-    //   uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
-    //   lfence;  // Load fence or artificial data dependency to prevent load-load reordering
-    //   HeapReference<mirror::Object> ref = *src;  // Original reference load.
-    //   bool is_gray = (rb_state == ReadBarrier::GrayState());
-    //   if (is_gray) {
-    //     ref = entrypoint(ref);  // ref = ReadBarrier::Mark(ref);  // Runtime entry point call.
-    //   }
-    //
-    // Note: the original implementation in ReadBarrier::Barrier is
-    // slightly more complex as it performs additional checks that we do
-    // not do here for performance reasons.
-
-    CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen);
-
-    // /* int32_t */ monitor = obj->monitor_
-    uint32_t monitor_offset = mirror::Object::MonitorOffset().Int32Value();
-    arm_codegen->GetAssembler()->LoadFromOffset(kLoadWord, temp_, obj_, monitor_offset);
-    if (needs_null_check_) {
-      codegen->MaybeRecordImplicitNullCheck(instruction_);
-    }
-    // /* LockWord */ lock_word = LockWord(monitor)
-    static_assert(sizeof(LockWord) == sizeof(int32_t),
-                  "art::LockWord and int32_t have different sizes.");
-
-    // Introduce a dependency on the lock_word including the rb_state,
-    // which shall prevent load-load reordering without using
-    // a memory barrier (which would be more expensive).
-    // `obj` is unchanged by this operation, but its value now depends
-    // on `temp`.
-    __ Add(obj_, obj_, Operand(temp_, ShiftType::LSR, 32));
-
-    // The actual reference load.
-    // A possible implicit null check has already been handled above.
-    arm_codegen->GenerateRawReferenceLoad(
-        instruction_, ref_, obj_, offset_, index_, scale_factor_, /* needs_null_check */ false);
-
-    // Mark the object `ref` when `obj` is gray.
-    //
-    //   if (rb_state == ReadBarrier::GrayState())
-    //     ref = ReadBarrier::Mark(ref);
-    //
-    // Given the numeric representation, it's enough to check the low bit of the
-    // rb_state. We do that by shifting the bit out of the lock word with LSRS
-    // which can be a 16-bit instruction unlike the TST immediate.
-    static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
-    static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
-    __ Lsrs(temp_, temp_, LockWord::kReadBarrierStateShift + 1);
-    __ B(cc, GetExitLabel());  // Carry flag is the last bit shifted out by LSRS.
-    GenerateReadBarrierMarkRuntimeCall(codegen);
-
-    __ B(GetExitLabel());
-  }
-
- private:
-  // The register containing the object holding the marked object reference field.
-  vixl32::Register obj_;
-  // The offset, index and scale factor to access the reference in `obj_`.
-  uint32_t offset_;
-  Location index_;
-  ScaleFactor scale_factor_;
-  // Is a null check required?
-  bool needs_null_check_;
-  // A temporary register used to hold the lock word of `obj_`.
-  vixl32::Register temp_;
-
-  DISALLOW_COPY_AND_ASSIGN(LoadReferenceWithBakerReadBarrierSlowPathARMVIXL);
-};
-
-// Slow path loading `obj`'s lock word, loading a reference from
-// object `*(obj + offset + (index << scale_factor))` into `ref`, and
-// marking `ref` if `obj` is gray according to the lock word (Baker
-// read barrier). If needed, this slow path also atomically updates
-// the field `obj.field` in the object `obj` holding this reference
-// after marking (contrary to
-// LoadReferenceWithBakerReadBarrierSlowPathARMVIXL above, which never
-// tries to update `obj.field`).
-//
-// This means that after the execution of this slow path, both `ref`
-// and `obj.field` will be up-to-date; i.e., after the flip, both will
-// hold the same to-space reference (unless another thread installed
-// another object reference (different from `ref`) in `obj.field`).
-//
-// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked or an empty
-// location; in the latter case, the read barrier marking runtime
-// entry point will be loaded by the slow path code itself.
-class LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARMVIXL
-    : public ReadBarrierMarkSlowPathBaseARMVIXL {
- public:
-  LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARMVIXL(
-      HInstruction* instruction,
-      Location ref,
-      vixl32::Register obj,
-      uint32_t offset,
-      Location index,
-      ScaleFactor scale_factor,
-      bool needs_null_check,
-      vixl32::Register temp1,
-      vixl32::Register temp2,
-      Location entrypoint = Location::NoLocation())
-      : ReadBarrierMarkSlowPathBaseARMVIXL(instruction, ref, entrypoint),
-        obj_(obj),
-        offset_(offset),
-        index_(index),
-        scale_factor_(scale_factor),
-        needs_null_check_(needs_null_check),
-        temp1_(temp1),
-        temp2_(temp2) {
-    DCHECK(kEmitCompilerReadBarrier);
-    DCHECK(kUseBakerReadBarrier);
-  }
-
-  const char* GetDescription() const OVERRIDE {
-    return "LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARMVIXL";
-  }
-
-  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
-    LocationSummary* locations = instruction_->GetLocations();
-    vixl32::Register ref_reg = RegisterFrom(ref_);
-    DCHECK(locations->CanCall());
-    DCHECK(!locations->GetLiveRegisters()->ContainsCoreRegister(ref_reg.GetCode())) << ref_reg;
-    DCHECK_NE(ref_.reg(), LocationFrom(temp1_).reg());
-
-    // This slow path is only used by the UnsafeCASObject intrinsic at the moment.
-    DCHECK((instruction_->IsInvokeVirtual() && instruction_->GetLocations()->Intrinsified()))
-        << "Unexpected instruction in read barrier marking and field updating slow path: "
-        << instruction_->DebugName();
-    DCHECK(instruction_->GetLocations()->Intrinsified());
-    DCHECK_EQ(instruction_->AsInvoke()->GetIntrinsic(), Intrinsics::kUnsafeCASObject);
-    DCHECK_EQ(offset_, 0u);
-    DCHECK_EQ(scale_factor_, ScaleFactor::TIMES_1);
-    Location field_offset = index_;
-    DCHECK(field_offset.IsRegisterPair()) << field_offset;
-
-    // Temporary register `temp1_`, used to store the lock word, must
-    // not be IP, as we may use it to emit the reference load (in the
-    // call to GenerateRawReferenceLoad below), and we need the lock
-    // word to still be in `temp1_` after the reference load.
-    DCHECK(!temp1_.Is(ip));
-
-    __ Bind(GetEntryLabel());
-
-    // The implementation is similar to LoadReferenceWithBakerReadBarrierSlowPathARMVIXL's:
-    //
-    //   uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
-    //   lfence;  // Load fence or artificial data dependency to prevent load-load reordering
-    //   HeapReference<mirror::Object> ref = *src;  // Original reference load.
-    //   bool is_gray = (rb_state == ReadBarrier::GrayState());
-    //   if (is_gray) {
-    //     old_ref = ref;
-    //     ref = entrypoint(ref);  // ref = ReadBarrier::Mark(ref);  // Runtime entry point call.
-    //     compareAndSwapObject(obj, field_offset, old_ref, ref);
-    //   }
-
-    CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen);
-
-    // /* int32_t */ monitor = obj->monitor_
-    uint32_t monitor_offset = mirror::Object::MonitorOffset().Int32Value();
-    arm_codegen->GetAssembler()->LoadFromOffset(kLoadWord, temp1_, obj_, monitor_offset);
-    if (needs_null_check_) {
-      codegen->MaybeRecordImplicitNullCheck(instruction_);
-    }
-    // /* LockWord */ lock_word = LockWord(monitor)
-    static_assert(sizeof(LockWord) == sizeof(int32_t),
-                  "art::LockWord and int32_t have different sizes.");
-
-    // Introduce a dependency on the lock_word including the rb_state,
-    // which shall prevent load-load reordering without using
-    // a memory barrier (which would be more expensive).
-    // `obj` is unchanged by this operation, but its value now depends
-    // on `temp`.
-    __ Add(obj_, obj_, Operand(temp1_, ShiftType::LSR, 32));
-
-    // The actual reference load.
-    // A possible implicit null check has already been handled above.
-    arm_codegen->GenerateRawReferenceLoad(
-        instruction_, ref_, obj_, offset_, index_, scale_factor_, /* needs_null_check */ false);
-
-    // Mark the object `ref` when `obj` is gray.
-    //
-    //   if (rb_state == ReadBarrier::GrayState())
-    //     ref = ReadBarrier::Mark(ref);
-    //
-    // Given the numeric representation, it's enough to check the low bit of the
-    // rb_state. We do that by shifting the bit out of the lock word with LSRS
-    // which can be a 16-bit instruction unlike the TST immediate.
-    static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
-    static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
-    __ Lsrs(temp1_, temp1_, LockWord::kReadBarrierStateShift + 1);
-    __ B(cc, GetExitLabel());  // Carry flag is the last bit shifted out by LSRS.
-
-    // Save the old value of the reference before marking it.
-    // Note that we cannot use IP to save the old reference, as IP is
-    // used internally by the ReadBarrierMarkRegX entry point, and we
-    // need the old reference after the call to that entry point.
-    DCHECK(!temp1_.Is(ip));
-    __ Mov(temp1_, ref_reg);
-
-    GenerateReadBarrierMarkRuntimeCall(codegen);
-
-    // If the new reference is different from the old reference,
-    // update the field in the holder (`*(obj_ + field_offset)`).
-    //
-    // Note that this field could also hold a different object, if
-    // another thread had concurrently changed it. In that case, the
-    // LDREX/CMP/BNE sequence of instructions in the compare-and-set
-    // (CAS) operation below would abort the CAS, leaving the field
-    // as-is.
-    __ Cmp(temp1_, ref_reg);
-    __ B(eq, GetExitLabel());
-
-    // Update the the holder's field atomically.  This may fail if
-    // mutator updates before us, but it's OK.  This is achieved
-    // using a strong compare-and-set (CAS) operation with relaxed
-    // memory synchronization ordering, where the expected value is
-    // the old reference and the desired value is the new reference.
-
-    UseScratchRegisterScope temps(arm_codegen->GetVIXLAssembler());
-    // Convenience aliases.
-    vixl32::Register base = obj_;
-    // The UnsafeCASObject intrinsic uses a register pair as field
-    // offset ("long offset"), of which only the low part contains
-    // data.
-    vixl32::Register offset = LowRegisterFrom(field_offset);
-    vixl32::Register expected = temp1_;
-    vixl32::Register value = ref_reg;
-    vixl32::Register tmp_ptr = temps.Acquire();       // Pointer to actual memory.
-    vixl32::Register tmp = temp2_;                    // Value in memory.
-
-    __ Add(tmp_ptr, base, offset);
-
-    if (kPoisonHeapReferences) {
-      arm_codegen->GetAssembler()->PoisonHeapReference(expected);
-      if (value.Is(expected)) {
-        // Do not poison `value`, as it is the same register as
-        // `expected`, which has just been poisoned.
-      } else {
-        arm_codegen->GetAssembler()->PoisonHeapReference(value);
-      }
-    }
-
-    // do {
-    //   tmp = [r_ptr] - expected;
-    // } while (tmp == 0 && failure([r_ptr] <- r_new_value));
-
-    vixl32::Label loop_head, comparison_failed, exit_loop;
-    __ Bind(&loop_head);
-    __ Ldrex(tmp, MemOperand(tmp_ptr));
-    __ Cmp(tmp, expected);
-    __ B(ne, &comparison_failed, /* far_target */ false);
-    __ Strex(tmp, value, MemOperand(tmp_ptr));
-    __ CompareAndBranchIfZero(tmp, &exit_loop, /* far_target */ false);
-    __ B(&loop_head);
-    __ Bind(&comparison_failed);
-    __ Clrex();
-    __ Bind(&exit_loop);
-
-    if (kPoisonHeapReferences) {
-      arm_codegen->GetAssembler()->UnpoisonHeapReference(expected);
-      if (value.Is(expected)) {
-        // Do not unpoison `value`, as it is the same register as
-        // `expected`, which has just been unpoisoned.
-      } else {
-        arm_codegen->GetAssembler()->UnpoisonHeapReference(value);
-      }
-    }
-
-    __ B(GetExitLabel());
-  }
-
- private:
-  // The register containing the object holding the marked object reference field.
-  const vixl32::Register obj_;
-  // The offset, index and scale factor to access the reference in `obj_`.
-  uint32_t offset_;
-  Location index_;
-  ScaleFactor scale_factor_;
-  // Is a null check required?
-  bool needs_null_check_;
-  // A temporary register used to hold the lock word of `obj_`; and
-  // also to hold the original reference value, when the reference is
-  // marked.
-  const vixl32::Register temp1_;
-  // A temporary register used in the implementation of the CAS, to
-  // update the object's reference field.
-  const vixl32::Register temp2_;
-
-  DISALLOW_COPY_AND_ASSIGN(LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARMVIXL);
-};
-
 // Slow path generating a read barrier for a heap reference.
 class ReadBarrierForHeapReferenceSlowPathARMVIXL : public SlowPathCodeARMVIXL {
  public:
@@ -2495,6 +2007,14 @@
           }
           break;
         }
+        case BakerReadBarrierKind::kUnsafeCas: {
+          DCHECK_GE(literal_offset, 4u);
+          uint32_t prev_insn = GetInsn32(literal_offset - 4u);
+          // ADD (register), encoding T3, with correct root_reg.
+          const uint32_t root_reg = BakerReadBarrierFirstRegField::Decode(encoded_data);
+          CHECK_EQ(prev_insn & 0xfff0fff0u, 0xeb000000u | (root_reg << 8));
+          break;
+        }
         default:
           LOG(FATAL) << "Unexpected kind: " << static_cast<uint32_t>(kind);
           UNREACHABLE();
@@ -5964,16 +5484,10 @@
     locations->AddTemp(Location::RequiresRegister());
     locations->AddTemp(Location::RequiresRegister());
   } else if (object_field_get_with_read_barrier && kUseBakerReadBarrier) {
-    // We need a temporary register for the read barrier marking slow
-    // path in CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier.
-    if (kBakerReadBarrierLinkTimeThunksEnableForFields &&
-        !Runtime::Current()->UseJitCompilation()) {
-      // If link-time thunks for the Baker read barrier are enabled, for AOT
-      // loads we need a temporary only if the offset is too big.
-      if (field_info.GetFieldOffset().Uint32Value() >= kReferenceLoadMinFarOffset) {
-        locations->AddTemp(Location::RequiresRegister());
-      }
-    } else {
+    // We need a temporary register for the read barrier load in
+    // CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier()
+    // only if the offset is too big.
+    if (field_info.GetFieldOffset().Uint32Value() >= kReferenceLoadMinFarOffset) {
       locations->AddTemp(Location::RequiresRegister());
     }
   }
@@ -6388,12 +5902,11 @@
         object_array_get_with_read_barrier ? Location::kOutputOverlap : Location::kNoOutputOverlap);
   }
   if (object_array_get_with_read_barrier && kUseBakerReadBarrier) {
-    if (kBakerReadBarrierLinkTimeThunksEnableForFields &&
-        !Runtime::Current()->UseJitCompilation() &&
-        instruction->GetIndex()->IsConstant()) {
+    if (instruction->GetIndex()->IsConstant()) {
       // Array loads with constant index are treated as field loads.
-      // If link-time thunks for the Baker read barrier are enabled, for AOT
-      // constant index loads we need a temporary only if the offset is too big.
+      // We need a temporary register for the read barrier load in
+      // CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier()
+      // only if the offset is too big.
       uint32_t offset = CodeGenerator::GetArrayDataOffset(instruction);
       uint32_t index = instruction->GetIndex()->AsIntConstant()->GetValue();
       offset += index << DataType::SizeShift(DataType::Type::kReference);
@@ -6401,9 +5914,8 @@
         locations->AddTemp(Location::RequiresRegister());
       }
     } else {
-      // If using introspection, we need a non-scratch temporary for the array data pointer.
-      // Otherwise, we need a temporary register for the read barrier marking slow
-      // path in CodeGeneratorARMVIXL::GenerateArrayLoadWithBakerReadBarrier.
+      // We need a non-scratch temporary for the array data pointer in
+      // CodeGeneratorARMVIXL::GenerateArrayLoadWithBakerReadBarrier().
       locations->AddTemp(Location::RequiresRegister());
     }
   } else if (mirror::kUseStringCompression && instruction->IsStringCharAt()) {
@@ -6533,7 +6045,7 @@
         } else {
           Location temp = locations->GetTemp(0);
           codegen_->GenerateArrayLoadWithBakerReadBarrier(
-              instruction, out_loc, obj, data_offset, index, temp, /* needs_null_check */ false);
+              out_loc, obj, data_offset, index, temp, /* needs_null_check */ false);
         }
       } else {
         vixl32::Register out = OutputRegister(instruction);
@@ -7052,9 +6564,25 @@
   if (can_be_null) {
     __ CompareAndBranchIfZero(value, &is_null);
   }
+  // Load the address of the card table into `card`.
   GetAssembler()->LoadFromOffset(
       kLoadWord, card, tr, Thread::CardTableOffset<kArmPointerSize>().Int32Value());
+  // Calculate the offset (in the card table) of the card corresponding to
+  // `object`.
   __ Lsr(temp, object, Operand::From(gc::accounting::CardTable::kCardShift));
+  // Write the `art::gc::accounting::CardTable::kCardDirty` value into the
+  // `object`'s card.
+  //
+  // Register `card` contains the address of the card table. Note that the card
+  // table's base is biased during its creation so that it always starts at an
+  // address whose least-significant byte is equal to `kCardDirty` (see
+  // art::gc::accounting::CardTable::Create). Therefore the STRB instruction
+  // below writes the `kCardDirty` (byte) value into the `object`'s card
+  // (located at `card + object >> kCardShift`).
+  //
+  // This dual use of the value in register `card` (1. to calculate the location
+  // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
+  // (no need to explicitly load `kCardDirty` as an immediate value).
   __ Strb(card, MemOperand(card, temp));
   if (can_be_null) {
     __ Bind(&is_null);
@@ -8797,72 +8325,45 @@
     if (kUseBakerReadBarrier) {
       // Fast path implementation of art::ReadBarrier::BarrierForRoot when
       // Baker's read barrier are used.
-      if (kBakerReadBarrierLinkTimeThunksEnableForGcRoots) {
-        // Query `art::Thread::Current()->GetIsGcMarking()` (stored in
-        // the Marking Register) to decide whether we need to enter
-        // the slow path to mark the GC root.
-        //
-        // We use shared thunks for the slow path; shared within the method
-        // for JIT, across methods for AOT. That thunk checks the reference
-        // and jumps to the entrypoint if needed.
-        //
-        //     lr = &return_address;
-        //     GcRoot<mirror::Object> root = *(obj+offset);  // Original reference load.
-        //     if (mr) {  // Thread::Current()->GetIsGcMarking()
-        //       goto gc_root_thunk<root_reg>(lr)
-        //     }
-        //   return_address:
 
-        UseScratchRegisterScope temps(GetVIXLAssembler());
-        temps.Exclude(ip);
-        bool narrow = CanEmitNarrowLdr(root_reg, obj, offset);
-        uint32_t custom_data = EncodeBakerReadBarrierGcRootData(root_reg.GetCode(), narrow);
+      // Query `art::Thread::Current()->GetIsGcMarking()` (stored in
+      // the Marking Register) to decide whether we need to enter
+      // the slow path to mark the GC root.
+      //
+      // We use shared thunks for the slow path; shared within the method
+      // for JIT, across methods for AOT. That thunk checks the reference
+      // and jumps to the entrypoint if needed.
+      //
+      //     lr = &return_address;
+      //     GcRoot<mirror::Object> root = *(obj+offset);  // Original reference load.
+      //     if (mr) {  // Thread::Current()->GetIsGcMarking()
+      //       goto gc_root_thunk<root_reg>(lr)
+      //     }
+      //   return_address:
 
-        vixl::EmissionCheckScope guard(GetVIXLAssembler(), 4 * vixl32::kMaxInstructionSizeInBytes);
-        vixl32::Label return_address;
-        EmitAdrCode adr(GetVIXLAssembler(), lr, &return_address);
-        __ cmp(mr, Operand(0));
-        // Currently the offset is always within range. If that changes,
-        // we shall have to split the load the same way as for fields.
-        DCHECK_LT(offset, kReferenceLoadMinFarOffset);
-        ptrdiff_t old_offset = GetVIXLAssembler()->GetBuffer()->GetCursorOffset();
-        __ ldr(EncodingSize(narrow ? Narrow : Wide), root_reg, MemOperand(obj, offset));
-        EmitBakerReadBarrierBne(custom_data);
-        __ Bind(&return_address);
-        DCHECK_EQ(old_offset - GetVIXLAssembler()->GetBuffer()->GetCursorOffset(),
-                  narrow ? BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_OFFSET
-                         : BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_OFFSET);
-      } else {
-        // Query `art::Thread::Current()->GetIsGcMarking()` (stored in
-        // the Marking Register) to decide whether we need to enter
-        // the slow path to mark the GC root.
-        //
-        //   GcRoot<mirror::Object> root = *(obj+offset);  // Original reference load.
-        //   if (mr) {  // Thread::Current()->GetIsGcMarking()
-        //     // Slow path.
-        //     entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
-        //     root = entrypoint(root);  // root = ReadBarrier::Mark(root);  // Entry point call.
-        //   }
+      UseScratchRegisterScope temps(GetVIXLAssembler());
+      temps.Exclude(ip);
+      bool narrow = CanEmitNarrowLdr(root_reg, obj, offset);
+      uint32_t custom_data = EncodeBakerReadBarrierGcRootData(root_reg.GetCode(), narrow);
 
-        // Slow path marking the GC root `root`. The entrypoint will
-        // be loaded by the slow path code.
-        SlowPathCodeARMVIXL* slow_path =
-            new (GetScopedAllocator()) ReadBarrierMarkSlowPathARMVIXL(instruction, root);
-        AddSlowPath(slow_path);
-
-        // /* GcRoot<mirror::Object> */ root = *(obj + offset)
-        GetAssembler()->LoadFromOffset(kLoadWord, root_reg, obj, offset);
-        static_assert(
-            sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(GcRoot<mirror::Object>),
-            "art::mirror::CompressedReference<mirror::Object> and art::GcRoot<mirror::Object> "
-            "have different sizes.");
-        static_assert(sizeof(mirror::CompressedReference<mirror::Object>) == sizeof(int32_t),
-                      "art::mirror::CompressedReference<mirror::Object> and int32_t "
-                      "have different sizes.");
-
-        __ CompareAndBranchIfNonZero(mr, slow_path->GetEntryLabel());
-        __ Bind(slow_path->GetExitLabel());
-      }
+      size_t narrow_instructions = /* CMP */ (mr.IsLow() ? 1u : 0u) + /* LDR */ (narrow ? 1u : 0u);
+      size_t wide_instructions = /* ADR+CMP+LDR+BNE */ 4u - narrow_instructions;
+      size_t exact_size = wide_instructions * vixl32::k32BitT32InstructionSizeInBytes +
+                          narrow_instructions * vixl32::k16BitT32InstructionSizeInBytes;
+      ExactAssemblyScope guard(GetVIXLAssembler(), exact_size);
+      vixl32::Label return_address;
+      EmitAdrCode adr(GetVIXLAssembler(), lr, &return_address);
+      __ cmp(mr, Operand(0));
+      // Currently the offset is always within range. If that changes,
+      // we shall have to split the load the same way as for fields.
+      DCHECK_LT(offset, kReferenceLoadMinFarOffset);
+      ptrdiff_t old_offset = GetVIXLAssembler()->GetBuffer()->GetCursorOffset();
+      __ ldr(EncodingSize(narrow ? Narrow : Wide), root_reg, MemOperand(obj, offset));
+      EmitBakerReadBarrierBne(custom_data);
+      __ bind(&return_address);
+      DCHECK_EQ(old_offset - GetVIXLAssembler()->GetBuffer()->GetCursorOffset(),
+                narrow ? BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_OFFSET
+                       : BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_OFFSET);
     } else {
       // GC root loaded through a slow path for read barriers other
       // than Baker's.
@@ -8881,95 +8382,125 @@
   MaybeGenerateMarkingRegisterCheck(/* code */ 19);
 }
 
+void CodeGeneratorARMVIXL::GenerateUnsafeCasOldValueAddWithBakerReadBarrier(
+    vixl::aarch32::Register old_value,
+    vixl::aarch32::Register adjusted_old_value,
+    vixl::aarch32::Register expected) {
+  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(kUseBakerReadBarrier);
+
+  // Similar to the Baker RB path in GenerateGcRootFieldLoad(), with an ADD instead of LDR.
+  uint32_t custom_data = EncodeBakerReadBarrierUnsafeCasData(old_value.GetCode());
+
+  size_t narrow_instructions = /* CMP */ (mr.IsLow() ? 1u : 0u);
+  size_t wide_instructions = /* ADR+CMP+ADD+BNE */ 4u - narrow_instructions;
+  size_t exact_size = wide_instructions * vixl32::k32BitT32InstructionSizeInBytes +
+                      narrow_instructions * vixl32::k16BitT32InstructionSizeInBytes;
+  ExactAssemblyScope guard(GetVIXLAssembler(), exact_size);
+  vixl32::Label return_address;
+  EmitAdrCode adr(GetVIXLAssembler(), lr, &return_address);
+  __ cmp(mr, Operand(0));
+  ptrdiff_t old_offset = GetVIXLAssembler()->GetBuffer()->GetCursorOffset();
+  __ add(EncodingSize(Wide), old_value, adjusted_old_value, Operand(expected));  // Preserves flags.
+  EmitBakerReadBarrierBne(custom_data);
+  __ bind(&return_address);
+  DCHECK_EQ(old_offset - GetVIXLAssembler()->GetBuffer()->GetCursorOffset(),
+            BAKER_MARK_INTROSPECTION_UNSAFE_CAS_ADD_OFFSET);
+}
+
+void CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
+                                                                 Location ref,
+                                                                 vixl32::Register obj,
+                                                                 const vixl32::MemOperand& src,
+                                                                 bool needs_null_check) {
+  DCHECK(kEmitCompilerReadBarrier);
+  DCHECK(kUseBakerReadBarrier);
+
+  // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
+  // Marking Register) to decide whether we need to enter the slow
+  // path to mark the reference. Then, in the slow path, check the
+  // gray bit in the lock word of the reference's holder (`obj`) to
+  // decide whether to mark `ref` or not.
+  //
+  // We use shared thunks for the slow path; shared within the method
+  // for JIT, across methods for AOT. That thunk checks the holder
+  // and jumps to the entrypoint if needed. If the holder is not gray,
+  // it creates a fake dependency and returns to the LDR instruction.
+  //
+  //     lr = &gray_return_address;
+  //     if (mr) {  // Thread::Current()->GetIsGcMarking()
+  //       goto field_thunk<holder_reg, base_reg>(lr)
+  //     }
+  //   not_gray_return_address:
+  //     // Original reference load. If the offset is too large to fit
+  //     // into LDR, we use an adjusted base register here.
+  //     HeapReference<mirror::Object> reference = *(obj+offset);
+  //   gray_return_address:
+
+  DCHECK(src.GetAddrMode() == vixl32::Offset);
+  DCHECK_ALIGNED(src.GetOffsetImmediate(), sizeof(mirror::HeapReference<mirror::Object>));
+  vixl32::Register ref_reg = RegisterFrom(ref, DataType::Type::kReference);
+  bool narrow = CanEmitNarrowLdr(ref_reg, src.GetBaseRegister(), src.GetOffsetImmediate());
+
+  UseScratchRegisterScope temps(GetVIXLAssembler());
+  temps.Exclude(ip);
+  uint32_t custom_data =
+      EncodeBakerReadBarrierFieldData(src.GetBaseRegister().GetCode(), obj.GetCode(), narrow);
+
+  {
+    size_t narrow_instructions =
+        /* CMP */ (mr.IsLow() ? 1u : 0u) +
+        /* LDR+unpoison? */ (narrow ? (kPoisonHeapReferences ? 2u : 1u) : 0u);
+    size_t wide_instructions =
+        /* ADR+CMP+LDR+BNE+unpoison? */ (kPoisonHeapReferences ? 5u : 4u) - narrow_instructions;
+    size_t exact_size = wide_instructions * vixl32::k32BitT32InstructionSizeInBytes +
+                        narrow_instructions * vixl32::k16BitT32InstructionSizeInBytes;
+    ExactAssemblyScope guard(GetVIXLAssembler(), exact_size);
+    vixl32::Label return_address;
+    EmitAdrCode adr(GetVIXLAssembler(), lr, &return_address);
+    __ cmp(mr, Operand(0));
+    EmitBakerReadBarrierBne(custom_data);
+    ptrdiff_t old_offset = GetVIXLAssembler()->GetBuffer()->GetCursorOffset();
+    __ ldr(EncodingSize(narrow ? Narrow : Wide), ref_reg, src);
+    if (needs_null_check) {
+      MaybeRecordImplicitNullCheck(instruction);
+    }
+    // Note: We need a specific width for the unpoisoning NEG.
+    if (kPoisonHeapReferences) {
+      if (narrow) {
+        // The only 16-bit encoding is T1 which sets flags outside IT block (i.e. RSBS, not RSB).
+        __ rsbs(EncodingSize(Narrow), ref_reg, ref_reg, Operand(0));
+      } else {
+        __ rsb(EncodingSize(Wide), ref_reg, ref_reg, Operand(0));
+      }
+    }
+    __ bind(&return_address);
+    DCHECK_EQ(old_offset - GetVIXLAssembler()->GetBuffer()->GetCursorOffset(),
+              narrow ? BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET
+                     : BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET);
+  }
+  MaybeGenerateMarkingRegisterCheck(/* code */ 20, /* temp_loc */ LocationFrom(ip));
+}
+
 void CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
                                                                  Location ref,
                                                                  vixl32::Register obj,
                                                                  uint32_t offset,
                                                                  Location temp,
                                                                  bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
-  DCHECK(kUseBakerReadBarrier);
-
-  if (kBakerReadBarrierLinkTimeThunksEnableForFields) {
-    // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
-    // Marking Register) to decide whether we need to enter the slow
-    // path to mark the reference. Then, in the slow path, check the
-    // gray bit in the lock word of the reference's holder (`obj`) to
-    // decide whether to mark `ref` or not.
-    //
-    // We use shared thunks for the slow path; shared within the method
-    // for JIT, across methods for AOT. That thunk checks the holder
-    // and jumps to the entrypoint if needed. If the holder is not gray,
-    // it creates a fake dependency and returns to the LDR instruction.
-    //
-    //     lr = &gray_return_address;
-    //     if (mr) {  // Thread::Current()->GetIsGcMarking()
-    //       goto field_thunk<holder_reg, base_reg>(lr)
-    //     }
-    //   not_gray_return_address:
-    //     // Original reference load. If the offset is too large to fit
-    //     // into LDR, we use an adjusted base register here.
-    //     HeapReference<mirror::Object> reference = *(obj+offset);
-    //   gray_return_address:
-
-    DCHECK_ALIGNED(offset, sizeof(mirror::HeapReference<mirror::Object>));
-    vixl32::Register ref_reg = RegisterFrom(ref, DataType::Type::kReference);
-    bool narrow = CanEmitNarrowLdr(ref_reg, obj, offset);
-    vixl32::Register base = obj;
-    if (offset >= kReferenceLoadMinFarOffset) {
-      base = RegisterFrom(temp);
-      static_assert(IsPowerOfTwo(kReferenceLoadMinFarOffset), "Expecting a power of 2.");
-      __ Add(base, obj, Operand(offset & ~(kReferenceLoadMinFarOffset - 1u)));
-      offset &= (kReferenceLoadMinFarOffset - 1u);
-      // Use narrow LDR only for small offsets. Generating narrow encoding LDR for the large
-      // offsets with `(offset & (kReferenceLoadMinFarOffset - 1u)) < 32u` would most likely
-      // increase the overall code size when taking the generated thunks into account.
-      DCHECK(!narrow);
-    }
-    UseScratchRegisterScope temps(GetVIXLAssembler());
-    temps.Exclude(ip);
-    uint32_t custom_data = EncodeBakerReadBarrierFieldData(base.GetCode(), obj.GetCode(), narrow);
-
-    {
-      vixl::EmissionCheckScope guard(
-          GetVIXLAssembler(),
-          (kPoisonHeapReferences ? 5u : 4u) * vixl32::kMaxInstructionSizeInBytes);
-      vixl32::Label return_address;
-      EmitAdrCode adr(GetVIXLAssembler(), lr, &return_address);
-      __ cmp(mr, Operand(0));
-      EmitBakerReadBarrierBne(custom_data);
-      ptrdiff_t old_offset = GetVIXLAssembler()->GetBuffer()->GetCursorOffset();
-      __ ldr(EncodingSize(narrow ? Narrow : Wide), ref_reg, MemOperand(base, offset));
-      if (needs_null_check) {
-        MaybeRecordImplicitNullCheck(instruction);
-      }
-      // Note: We need a specific width for the unpoisoning NEG.
-      if (kPoisonHeapReferences) {
-        if (narrow) {
-          // The only 16-bit encoding is T1 which sets flags outside IT block (i.e. RSBS, not RSB).
-          __ rsbs(EncodingSize(Narrow), ref_reg, ref_reg, Operand(0));
-        } else {
-          __ rsb(EncodingSize(Wide), ref_reg, ref_reg, Operand(0));
-        }
-      }
-      __ Bind(&return_address);
-      DCHECK_EQ(old_offset - GetVIXLAssembler()->GetBuffer()->GetCursorOffset(),
-                narrow ? BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET
-                       : BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET);
-    }
-    MaybeGenerateMarkingRegisterCheck(/* code */ 20, /* temp_loc */ LocationFrom(ip));
-    return;
+  DCHECK_ALIGNED(offset, sizeof(mirror::HeapReference<mirror::Object>));
+  vixl32::Register base = obj;
+  if (offset >= kReferenceLoadMinFarOffset) {
+    base = RegisterFrom(temp);
+    static_assert(IsPowerOfTwo(kReferenceLoadMinFarOffset), "Expecting a power of 2.");
+    __ Add(base, obj, Operand(offset & ~(kReferenceLoadMinFarOffset - 1u)));
+    offset &= (kReferenceLoadMinFarOffset - 1u);
   }
-
-  // /* HeapReference<Object> */ ref = *(obj + offset)
-  Location no_index = Location::NoLocation();
-  ScaleFactor no_scale_factor = TIMES_1;
-  GenerateReferenceLoadWithBakerReadBarrier(
-      instruction, ref, obj, offset, no_index, no_scale_factor, temp, needs_null_check);
+  GenerateFieldLoadWithBakerReadBarrier(
+      instruction, ref, obj, MemOperand(base, offset), needs_null_check);
 }
 
-void CodeGeneratorARMVIXL::GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction,
-                                                                 Location ref,
+void CodeGeneratorARMVIXL::GenerateArrayLoadWithBakerReadBarrier(Location ref,
                                                                  vixl32::Register obj,
                                                                  uint32_t data_offset,
                                                                  Location index,
@@ -8983,226 +8514,60 @@
       "art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
   ScaleFactor scale_factor = TIMES_4;
 
-  if (kBakerReadBarrierLinkTimeThunksEnableForArrays) {
-    // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
-    // Marking Register) to decide whether we need to enter the slow
-    // path to mark the reference. Then, in the slow path, check the
-    // gray bit in the lock word of the reference's holder (`obj`) to
-    // decide whether to mark `ref` or not.
-    //
-    // We use shared thunks for the slow path; shared within the method
-    // for JIT, across methods for AOT. That thunk checks the holder
-    // and jumps to the entrypoint if needed. If the holder is not gray,
-    // it creates a fake dependency and returns to the LDR instruction.
-    //
-    //     lr = &gray_return_address;
-    //     if (mr) {  // Thread::Current()->GetIsGcMarking()
-    //       goto array_thunk<base_reg>(lr)
-    //     }
-    //   not_gray_return_address:
-    //     // Original reference load. If the offset is too large to fit
-    //     // into LDR, we use an adjusted base register here.
-    //     HeapReference<mirror::Object> reference = data[index];
-    //   gray_return_address:
-
-    DCHECK(index.IsValid());
-    vixl32::Register index_reg = RegisterFrom(index, DataType::Type::kInt32);
-    vixl32::Register ref_reg = RegisterFrom(ref, DataType::Type::kReference);
-    vixl32::Register data_reg = RegisterFrom(temp, DataType::Type::kInt32);  // Raw pointer.
-
-    UseScratchRegisterScope temps(GetVIXLAssembler());
-    temps.Exclude(ip);
-    uint32_t custom_data = EncodeBakerReadBarrierArrayData(data_reg.GetCode());
-
-    __ Add(data_reg, obj, Operand(data_offset));
-    {
-      vixl::EmissionCheckScope guard(
-          GetVIXLAssembler(),
-          (kPoisonHeapReferences ? 5u : 4u) * vixl32::kMaxInstructionSizeInBytes);
-      vixl32::Label return_address;
-      EmitAdrCode adr(GetVIXLAssembler(), lr, &return_address);
-      __ cmp(mr, Operand(0));
-      EmitBakerReadBarrierBne(custom_data);
-      ptrdiff_t old_offset = GetVIXLAssembler()->GetBuffer()->GetCursorOffset();
-      __ ldr(ref_reg, MemOperand(data_reg, index_reg, vixl32::LSL, scale_factor));
-      DCHECK(!needs_null_check);  // The thunk cannot handle the null check.
-      // Note: We need a Wide NEG for the unpoisoning.
-      if (kPoisonHeapReferences) {
-        __ rsb(EncodingSize(Wide), ref_reg, ref_reg, Operand(0));
-      }
-      __ Bind(&return_address);
-      DCHECK_EQ(old_offset - GetVIXLAssembler()->GetBuffer()->GetCursorOffset(),
-                BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET);
-    }
-    MaybeGenerateMarkingRegisterCheck(/* code */ 21, /* temp_loc */ LocationFrom(ip));
-    return;
-  }
-
-  // /* HeapReference<Object> */ ref =
-  //     *(obj + data_offset + index * sizeof(HeapReference<Object>))
-  GenerateReferenceLoadWithBakerReadBarrier(
-      instruction, ref, obj, data_offset, index, scale_factor, temp, needs_null_check);
-}
-
-void CodeGeneratorARMVIXL::GenerateReferenceLoadWithBakerReadBarrier(HInstruction* instruction,
-                                                                     Location ref,
-                                                                     vixl32::Register obj,
-                                                                     uint32_t offset,
-                                                                     Location index,
-                                                                     ScaleFactor scale_factor,
-                                                                     Location temp,
-                                                                     bool needs_null_check) {
-  DCHECK(kEmitCompilerReadBarrier);
-  DCHECK(kUseBakerReadBarrier);
-
   // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
   // Marking Register) to decide whether we need to enter the slow
   // path to mark the reference. Then, in the slow path, check the
   // gray bit in the lock word of the reference's holder (`obj`) to
   // decide whether to mark `ref` or not.
   //
-  //   if (mr) {  // Thread::Current()->GetIsGcMarking()
-  //     // Slow path.
-  //     uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
-  //     lfence;  // Load fence or artificial data dependency to prevent load-load reordering
-  //     HeapReference<mirror::Object> ref = *src;  // Original reference load.
-  //     bool is_gray = (rb_state == ReadBarrier::GrayState());
-  //     if (is_gray) {
-  //       entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
-  //       ref = entrypoint(ref);  // ref = ReadBarrier::Mark(ref);  // Runtime entry point call.
-  //     }
-  //   } else {
-  //     HeapReference<mirror::Object> ref = *src;  // Original reference load.
-  //   }
-
-  vixl32::Register temp_reg = RegisterFrom(temp);
-
-  // Slow path marking the object `ref` when the GC is marking. The
-  // entrypoint will be loaded by the slow path code.
-  SlowPathCodeARMVIXL* slow_path =
-      new (GetScopedAllocator()) LoadReferenceWithBakerReadBarrierSlowPathARMVIXL(
-          instruction, ref, obj, offset, index, scale_factor, needs_null_check, temp_reg);
-  AddSlowPath(slow_path);
-
-  __ CompareAndBranchIfNonZero(mr, slow_path->GetEntryLabel());
-  // Fast path: the GC is not marking: just load the reference.
-  GenerateRawReferenceLoad(instruction, ref, obj, offset, index, scale_factor, needs_null_check);
-  __ Bind(slow_path->GetExitLabel());
-  MaybeGenerateMarkingRegisterCheck(/* code */ 22);
-}
-
-void CodeGeneratorARMVIXL::UpdateReferenceFieldWithBakerReadBarrier(HInstruction* instruction,
-                                                                    Location ref,
-                                                                    vixl32::Register obj,
-                                                                    Location field_offset,
-                                                                    Location temp,
-                                                                    bool needs_null_check,
-                                                                    vixl32::Register temp2) {
-  DCHECK(kEmitCompilerReadBarrier);
-  DCHECK(kUseBakerReadBarrier);
-
-  // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
-  // Marking Register) to decide whether we need to enter the slow
-  // path to update the reference field within `obj`. Then, in the
-  // slow path, check the gray bit in the lock word of the reference's
-  // holder (`obj`) to decide whether to mark `ref` and update the
-  // field or not.
+  // We use shared thunks for the slow path; shared within the method
+  // for JIT, across methods for AOT. That thunk checks the holder
+  // and jumps to the entrypoint if needed. If the holder is not gray,
+  // it creates a fake dependency and returns to the LDR instruction.
   //
-  //   if (mr) {  // Thread::Current()->GetIsGcMarking()
-  //     // Slow path.
-  //     uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
-  //     lfence;  // Load fence or artificial data dependency to prevent load-load reordering
-  //     HeapReference<mirror::Object> ref = *(obj + field_offset);  // Reference load.
-  //     bool is_gray = (rb_state == ReadBarrier::GrayState());
-  //     if (is_gray) {
-  //       old_ref = ref;
-  //       entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
-  //       ref = entrypoint(ref);  // ref = ReadBarrier::Mark(ref);  // Runtime entry point call.
-  //       compareAndSwapObject(obj, field_offset, old_ref, ref);
+  //     lr = &gray_return_address;
+  //     if (mr) {  // Thread::Current()->GetIsGcMarking()
+  //       goto array_thunk<base_reg>(lr)
   //     }
-  //   }
+  //   not_gray_return_address:
+  //     // Original reference load. If the offset is too large to fit
+  //     // into LDR, we use an adjusted base register here.
+  //     HeapReference<mirror::Object> reference = data[index];
+  //   gray_return_address:
 
-  vixl32::Register temp_reg = RegisterFrom(temp);
+  DCHECK(index.IsValid());
+  vixl32::Register index_reg = RegisterFrom(index, DataType::Type::kInt32);
+  vixl32::Register ref_reg = RegisterFrom(ref, DataType::Type::kReference);
+  vixl32::Register data_reg = RegisterFrom(temp, DataType::Type::kInt32);  // Raw pointer.
 
-  // Slow path updating the object reference at address `obj + field_offset`
-  // when the GC is marking. The entrypoint will be loaded by the slow path code.
-  SlowPathCodeARMVIXL* slow_path =
-      new (GetScopedAllocator()) LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARMVIXL(
-          instruction,
-          ref,
-          obj,
-          /* offset */ 0u,
-          /* index */ field_offset,
-          /* scale_factor */ ScaleFactor::TIMES_1,
-          needs_null_check,
-          temp_reg,
-          temp2);
-  AddSlowPath(slow_path);
+  UseScratchRegisterScope temps(GetVIXLAssembler());
+  temps.Exclude(ip);
+  uint32_t custom_data = EncodeBakerReadBarrierArrayData(data_reg.GetCode());
 
-  __ CompareAndBranchIfNonZero(mr, slow_path->GetEntryLabel());
-  // Fast path: the GC is not marking: nothing to do (the field is
-  // up-to-date, and we don't need to load the reference).
-  __ Bind(slow_path->GetExitLabel());
-  MaybeGenerateMarkingRegisterCheck(/* code */ 23);
-}
-
-void CodeGeneratorARMVIXL::GenerateRawReferenceLoad(HInstruction* instruction,
-                                                    Location ref,
-                                                    vixl32::Register obj,
-                                                    uint32_t offset,
-                                                    Location index,
-                                                    ScaleFactor scale_factor,
-                                                    bool needs_null_check) {
-  DataType::Type type = DataType::Type::kReference;
-  vixl32::Register ref_reg = RegisterFrom(ref, type);
-
-  // If needed, vixl::EmissionCheckScope guards are used to ensure
-  // that no pools are emitted between the load (macro) instruction
-  // and MaybeRecordImplicitNullCheck.
-
-  if (index.IsValid()) {
-    // Load types involving an "index": ArrayGet,
-    // UnsafeGetObject/UnsafeGetObjectVolatile and UnsafeCASObject
-    // intrinsics.
-    // /* HeapReference<mirror::Object> */ ref = *(obj + offset + (index << scale_factor))
-    if (index.IsConstant()) {
-      size_t computed_offset =
-          (Int32ConstantFrom(index) << scale_factor) + offset;
-      vixl::EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes);
-      GetAssembler()->LoadFromOffset(kLoadWord, ref_reg, obj, computed_offset);
-      if (needs_null_check) {
-        MaybeRecordImplicitNullCheck(instruction);
-      }
-    } else {
-      // Handle the special case of the
-      // UnsafeGetObject/UnsafeGetObjectVolatile and UnsafeCASObject
-      // intrinsics, which use a register pair as index ("long
-      // offset"), of which only the low part contains data.
-      vixl32::Register index_reg = index.IsRegisterPair()
-          ? LowRegisterFrom(index)
-          : RegisterFrom(index);
-      UseScratchRegisterScope temps(GetVIXLAssembler());
-      vixl32::Register temp = temps.Acquire();
-      __ Add(temp, obj, Operand(index_reg, ShiftType::LSL, scale_factor));
-      {
-        vixl::EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes);
-        GetAssembler()->LoadFromOffset(kLoadWord, ref_reg, temp, offset);
-        if (needs_null_check) {
-          MaybeRecordImplicitNullCheck(instruction);
-        }
-      }
+  __ Add(data_reg, obj, Operand(data_offset));
+  {
+    size_t narrow_instructions = /* CMP */ (mr.IsLow() ? 1u : 0u);
+    size_t wide_instructions =
+        /* ADR+CMP+BNE+LDR+unpoison? */ (kPoisonHeapReferences ? 5u : 4u) - narrow_instructions;
+    size_t exact_size = wide_instructions * vixl32::k32BitT32InstructionSizeInBytes +
+                        narrow_instructions * vixl32::k16BitT32InstructionSizeInBytes;
+    ExactAssemblyScope guard(GetVIXLAssembler(), exact_size);
+    vixl32::Label return_address;
+    EmitAdrCode adr(GetVIXLAssembler(), lr, &return_address);
+    __ cmp(mr, Operand(0));
+    EmitBakerReadBarrierBne(custom_data);
+    ptrdiff_t old_offset = GetVIXLAssembler()->GetBuffer()->GetCursorOffset();
+    __ ldr(ref_reg, MemOperand(data_reg, index_reg, vixl32::LSL, scale_factor));
+    DCHECK(!needs_null_check);  // The thunk cannot handle the null check.
+    // Note: We need a Wide NEG for the unpoisoning.
+    if (kPoisonHeapReferences) {
+      __ rsb(EncodingSize(Wide), ref_reg, ref_reg, Operand(0));
     }
-  } else {
-    // /* HeapReference<mirror::Object> */ ref = *(obj + offset)
-    vixl::EmissionCheckScope guard(GetVIXLAssembler(), kMaxMacroInstructionSizeInBytes);
-    GetAssembler()->LoadFromOffset(kLoadWord, ref_reg, obj, offset);
-    if (needs_null_check) {
-      MaybeRecordImplicitNullCheck(instruction);
-    }
+    __ bind(&return_address);
+    DCHECK_EQ(old_offset - GetVIXLAssembler()->GetBuffer()->GetCursorOffset(),
+              BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET);
   }
-
-  // Object* ref = ref_addr->AsMirrorPtr()
-  GetAssembler()->MaybeUnpoisonHeapReference(ref_reg);
+  MaybeGenerateMarkingRegisterCheck(/* code */ 21, /* temp_loc */ LocationFrom(ip));
 }
 
 void CodeGeneratorARMVIXL::MaybeGenerateMarkingRegisterCheck(int code, Location temp_loc) {
@@ -9494,7 +8859,7 @@
 }
 
 void CodeGeneratorARMVIXL::EmitBakerReadBarrierBne(uint32_t custom_data) {
-  ExactAssemblyScope eas(GetVIXLAssembler(), 1 * k32BitT32InstructionSizeInBytes);
+  DCHECK(!__ AllowMacroInstructions());  // In ExactAssemblyScope.
   if (Runtime::Current()->UseJitCompilation()) {
     auto it = jit_baker_read_barrier_slow_paths_.FindOrAdd(custom_data);
     vixl::aarch32::Label* slow_path_entry = &it->second.label;
@@ -9940,7 +9305,7 @@
   // Load the lock word containing the rb_state.
   __ Ldr(ip, lock_word);
   // Given the numeric representation, it's enough to check the low bit of the rb_state.
-  static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+  static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
   static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
   __ Tst(ip, Operand(LockWord::kReadBarrierStateMaskShifted));
   __ B(ne, slow_path, /* is_far_target */ false);
@@ -10053,7 +9418,8 @@
       __ Bx(ep_reg);                          // Jump to the entrypoint's array switch case.
       break;
     }
-    case BakerReadBarrierKind::kGcRoot: {
+    case BakerReadBarrierKind::kGcRoot:
+    case BakerReadBarrierKind::kUnsafeCas: {
       // Check if the reference needs to be marked and if so (i.e. not null, not marked yet
       // and it does not have a forwarding address), call the correct introspection entrypoint;
       // otherwise return the reference (or the extracted forwarding address).
@@ -10081,10 +9447,14 @@
       __ B(hs, &forwarding_address);
       vixl32::Register ep_reg = LoadReadBarrierMarkIntrospectionEntrypoint(assembler);
       // Adjust the art_quick_read_barrier_mark_introspection address in kBakerCcEntrypointRegister
-      // to art_quick_read_barrier_mark_introspection_gc_roots.
-      int32_t entrypoint_offset = (width == BakerReadBarrierWidth::kWide)
-          ? BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_ENTRYPOINT_OFFSET
-          : BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_ENTRYPOINT_OFFSET;
+      // to one of art_quick_read_barrier_mark_introspection_{gc_roots_{wide,narrow},unsafe_cas}.
+      DCHECK(kind != BakerReadBarrierKind::kUnsafeCas || width == BakerReadBarrierWidth::kWide);
+      int32_t entrypoint_offset =
+          (kind == BakerReadBarrierKind::kGcRoot)
+              ? (width == BakerReadBarrierWidth::kWide)
+                  ? BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_ENTRYPOINT_OFFSET
+                  : BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_ENTRYPOINT_OFFSET
+              : BAKER_MARK_INTROSPECTION_UNSAFE_CAS_ENTRYPOINT_OFFSET;
       __ Add(ep_reg, ep_reg, Operand(entrypoint_offset));
       __ Mov(ip, root_reg);
       __ Bx(ep_reg);
@@ -10130,6 +9500,12 @@
         DCHECK_EQ(kBakerReadBarrierInvalidEncodedReg,
                   BakerReadBarrierSecondRegField::Decode(encoded_data));
         break;
+      case BakerReadBarrierKind::kUnsafeCas:
+        oss << "UnsafeCas_r" << BakerReadBarrierFirstRegField::Decode(encoded_data);
+        DCHECK_EQ(kBakerReadBarrierInvalidEncodedReg,
+                  BakerReadBarrierSecondRegField::Decode(encoded_data));
+        DCHECK(BakerReadBarrierWidthField::Decode(encoded_data) == BakerReadBarrierWidth::kWide);
+        break;
     }
     *debug_name = oss.str();
   }
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index ef82f2e..cb131a7 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -622,6 +622,19 @@
                                vixl::aarch32::Register obj,
                                uint32_t offset,
                                ReadBarrierOption read_barrier_option);
+  // Generate ADD for UnsafeCASObject to reconstruct the old value from
+  // `old_value - expected` and mark it with Baker read barrier.
+  void GenerateUnsafeCasOldValueAddWithBakerReadBarrier(vixl::aarch32::Register old_value,
+                                                        vixl::aarch32::Register adjusted_old_value,
+                                                        vixl::aarch32::Register expected);
+  // Fast path implementation of ReadBarrier::Barrier for a heap
+  // reference field load when Baker's read barriers are used.
+  // Overload suitable for Unsafe.getObject/-Volatile() intrinsic.
+  void GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
+                                             Location ref,
+                                             vixl::aarch32::Register obj,
+                                             const vixl::aarch32::MemOperand& src,
+                                             bool needs_null_check);
   // Fast path implementation of ReadBarrier::Barrier for a heap
   // reference field load when Baker's read barriers are used.
   void GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
@@ -632,56 +645,12 @@
                                              bool needs_null_check);
   // Fast path implementation of ReadBarrier::Barrier for a heap
   // reference array load when Baker's read barriers are used.
-  void GenerateArrayLoadWithBakerReadBarrier(HInstruction* instruction,
-                                             Location ref,
+  void GenerateArrayLoadWithBakerReadBarrier(Location ref,
                                              vixl::aarch32::Register obj,
                                              uint32_t data_offset,
                                              Location index,
                                              Location temp,
                                              bool needs_null_check);
-  // Factored implementation, used by GenerateFieldLoadWithBakerReadBarrier,
-  // GenerateArrayLoadWithBakerReadBarrier and some intrinsics.
-  //
-  // Load the object reference located at the address
-  // `obj + offset + (index << scale_factor)`, held by object `obj`, into
-  // `ref`, and mark it if needed.
-  void GenerateReferenceLoadWithBakerReadBarrier(HInstruction* instruction,
-                                                 Location ref,
-                                                 vixl::aarch32::Register obj,
-                                                 uint32_t offset,
-                                                 Location index,
-                                                 ScaleFactor scale_factor,
-                                                 Location temp,
-                                                 bool needs_null_check);
-
-  // Generate code checking whether the the reference field at the
-  // address `obj + field_offset`, held by object `obj`, needs to be
-  // marked, and if so, marking it and updating the field within `obj`
-  // with the marked value.
-  //
-  // This routine is used for the implementation of the
-  // UnsafeCASObject intrinsic with Baker read barriers.
-  //
-  // This method has a structure similar to
-  // GenerateReferenceLoadWithBakerReadBarrier, but note that argument
-  // `ref` is only as a temporary here, and thus its value should not
-  // be used afterwards.
-  void UpdateReferenceFieldWithBakerReadBarrier(HInstruction* instruction,
-                                                Location ref,
-                                                vixl::aarch32::Register obj,
-                                                Location field_offset,
-                                                Location temp,
-                                                bool needs_null_check,
-                                                vixl::aarch32::Register temp2);
-
-  // Generate a heap reference load (with no read barrier).
-  void GenerateRawReferenceLoad(HInstruction* instruction,
-                                Location ref,
-                                vixl::aarch32::Register obj,
-                                uint32_t offset,
-                                Location index,
-                                ScaleFactor scale_factor,
-                                bool needs_null_check);
 
   // Emit code checking the status of the Marking Register, and
   // aborting the program if MR does not match the value stored in the
@@ -779,10 +748,11 @@
   // Encoding of thunk type and data for link-time generated thunks for Baker read barriers.
 
   enum class BakerReadBarrierKind : uint8_t {
-    kField,   // Field get or array get with constant offset (i.e. constant index).
-    kArray,   // Array get with index in register.
-    kGcRoot,  // GC root load.
-    kLast = kGcRoot
+    kField,       // Field get or array get with constant offset (i.e. constant index).
+    kArray,       // Array get with index in register.
+    kGcRoot,      // GC root load.
+    kUnsafeCas,   // UnsafeCASObject intrinsic.
+    kLast = kUnsafeCas
   };
 
   enum class BakerReadBarrierWidth : uint8_t {
@@ -849,6 +819,14 @@
            BakerReadBarrierWidthField::Encode(width);
   }
 
+  static uint32_t EncodeBakerReadBarrierUnsafeCasData(uint32_t root_reg) {
+    CheckValidReg(root_reg);
+    return BakerReadBarrierKindField::Encode(BakerReadBarrierKind::kUnsafeCas) |
+           BakerReadBarrierFirstRegField::Encode(root_reg) |
+           BakerReadBarrierSecondRegField::Encode(kBakerReadBarrierInvalidEncodedReg) |
+           BakerReadBarrierWidthField::Encode(BakerReadBarrierWidth::kWide);
+  }
+
   void CompileBakerReadBarrierThunk(ArmVIXLAssembler& assembler,
                                     uint32_t encoded_data,
                                     /*out*/ std::string* debug_name);
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index 0ed5756..aed334b 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -1868,12 +1868,27 @@
   if (value_can_be_null) {
     __ Beqz(value, &done);
   }
+  // Load the address of the card table into `card`.
   __ LoadFromOffset(kLoadWord,
                     card,
                     TR,
                     Thread::CardTableOffset<kMipsPointerSize>().Int32Value());
+  // Calculate the address of the card corresponding to `object`.
   __ Srl(temp, object, gc::accounting::CardTable::kCardShift);
   __ Addu(temp, card, temp);
+  // Write the `art::gc::accounting::CardTable::kCardDirty` value into the
+  // `object`'s card.
+  //
+  // Register `card` contains the address of the card table. Note that the card
+  // table's base is biased during its creation so that it always starts at an
+  // address whose least-significant byte is equal to `kCardDirty` (see
+  // art::gc::accounting::CardTable::Create). Therefore the SB instruction
+  // below writes the `kCardDirty` (byte) value into the `object`'s card
+  // (located at `card + object >> kCardShift`).
+  //
+  // This dual use of the value in register `card` (1. to calculate the location
+  // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
+  // (no need to explicitly load `kCardDirty` as an immediate value).
   __ Sb(card, temp, 0);
   if (value_can_be_null) {
     __ Bind(&done);
@@ -7439,7 +7454,7 @@
   // Given the numeric representation, it's enough to check the low bit of the
   // rb_state. We do that by shifting the bit into the sign bit (31) and
   // performing a branch on less than zero.
-  static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+  static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
   static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
   static_assert(LockWord::kReadBarrierStateSize == 1, "Expecting 1-bit read barrier state size");
   __ Sll(temp_reg, temp_reg, 31 - LockWord::kReadBarrierStateShift);
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 2b6928e..72318e9 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -1490,12 +1490,27 @@
   if (value_can_be_null) {
     __ Beqzc(value, &done);
   }
+  // Load the address of the card table into `card`.
   __ LoadFromOffset(kLoadDoubleword,
                     card,
                     TR,
                     Thread::CardTableOffset<kMips64PointerSize>().Int32Value());
+  // Calculate the address of the card corresponding to `object`.
   __ Dsrl(temp, object, gc::accounting::CardTable::kCardShift);
   __ Daddu(temp, card, temp);
+  // Write the `art::gc::accounting::CardTable::kCardDirty` value into the
+  // `object`'s card.
+  //
+  // Register `card` contains the address of the card table. Note that the card
+  // table's base is biased during its creation so that it always starts at an
+  // address whose least-significant byte is equal to `kCardDirty` (see
+  // art::gc::accounting::CardTable::Create). Therefore the SB instruction
+  // below writes the `kCardDirty` (byte) value into the `object`'s card
+  // (located at `card + object >> kCardShift`).
+  //
+  // This dual use of the value in register `card` (1. to calculate the location
+  // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
+  // (no need to explicitly load `kCardDirty` as an immediate value).
   __ Sb(card, temp, 0);
   if (value_can_be_null) {
     __ Bind(&done);
@@ -5555,7 +5570,7 @@
   // Given the numeric representation, it's enough to check the low bit of the
   // rb_state. We do that by shifting the bit into the sign bit (31) and
   // performing a branch on less than zero.
-  static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+  static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
   static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
   static_assert(LockWord::kReadBarrierStateSize == 1, "Expecting 1-bit read barrier state size");
   __ Sll(temp_reg, temp_reg, 31 - LockWord::kReadBarrierStateShift);
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index a835aed..df00ec7 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -5104,9 +5104,25 @@
     __ testl(value, value);
     __ j(kEqual, &is_null);
   }
+  // Load the address of the card table into `card`.
   __ fs()->movl(card, Address::Absolute(Thread::CardTableOffset<kX86PointerSize>().Int32Value()));
+  // Calculate the offset (in the card table) of the card corresponding to
+  // `object`.
   __ movl(temp, object);
   __ shrl(temp, Immediate(gc::accounting::CardTable::kCardShift));
+  // Write the `art::gc::accounting::CardTable::kCardDirty` value into the
+  // `object`'s card.
+  //
+  // Register `card` contains the address of the card table. Note that the card
+  // table's base is biased during its creation so that it always starts at an
+  // address whose least-significant byte is equal to `kCardDirty` (see
+  // art::gc::accounting::CardTable::Create). Therefore the MOVB instruction
+  // below writes the `kCardDirty` (byte) value into the `object`'s card
+  // (located at `card + object >> kCardShift`).
+  //
+  // This dual use of the value in register `card` (1. to calculate the location
+  // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
+  // (no need to explicitly load `kCardDirty` as an immediate value).
   __ movb(Address(temp, card, TIMES_1, 0),
           X86ManagedRegister::FromCpuRegister(card).AsByteRegister());
   if (value_can_be_null) {
@@ -7741,7 +7757,7 @@
   uint32_t monitor_offset = mirror::Object::MonitorOffset().Int32Value();
 
   // Given the numeric representation, it's enough to check the low bit of the rb_state.
-  static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+  static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
   static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
   constexpr uint32_t gray_byte_position = LockWord::kReadBarrierStateShift / kBitsPerByte;
   constexpr uint32_t gray_bit_position = LockWord::kReadBarrierStateShift % kBitsPerByte;
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index dee891b..ae2a000 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -5436,10 +5436,26 @@
     __ testl(value, value);
     __ j(kEqual, &is_null);
   }
+  // Load the address of the card table into `card`.
   __ gs()->movq(card, Address::Absolute(Thread::CardTableOffset<kX86_64PointerSize>().Int32Value(),
                                         /* no_rip */ true));
+  // Calculate the offset (in the card table) of the card corresponding to
+  // `object`.
   __ movq(temp, object);
   __ shrq(temp, Immediate(gc::accounting::CardTable::kCardShift));
+  // Write the `art::gc::accounting::CardTable::kCardDirty` value into the
+  // `object`'s card.
+  //
+  // Register `card` contains the address of the card table. Note that the card
+  // table's base is biased during its creation so that it always starts at an
+  // address whose least-significant byte is equal to `kCardDirty` (see
+  // art::gc::accounting::CardTable::Create). Therefore the MOVB instruction
+  // below writes the `kCardDirty` (byte) value into the `object`'s card
+  // (located at `card + object >> kCardShift`).
+  //
+  // This dual use of the value in register `card` (1. to calculate the location
+  // of the card to mark; and 2. to load the `kCardDirty` value) saves a load
+  // (no need to explicitly load `kCardDirty` as an immediate value).
   __ movb(Address(temp, card, TIMES_1, 0), card);
   if (value_can_be_null) {
     __ Bind(&is_null);
@@ -7034,7 +7050,7 @@
   uint32_t monitor_offset = mirror::Object::MonitorOffset().Int32Value();
 
   // Given the numeric representation, it's enough to check the low bit of the rb_state.
-  static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+  static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
   static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
   constexpr uint32_t gray_byte_position = LockWord::kReadBarrierStateShift / kBitsPerByte;
   constexpr uint32_t gray_bit_position = LockWord::kReadBarrierStateShift % kBitsPerByte;
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index 4b2bcc8..a657b58 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -745,15 +745,15 @@
   if (type == DataType::Type::kReference && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
     // UnsafeGetObject/UnsafeGetObjectVolatile with Baker's read barrier case.
     Register temp = WRegisterFrom(locations->GetTemp(0));
-    codegen->GenerateReferenceLoadWithBakerReadBarrier(invoke,
-                                                       trg_loc,
-                                                       base,
-                                                       /* offset */ 0u,
-                                                       /* index */ offset_loc,
-                                                       /* scale_factor */ 0u,
-                                                       temp,
-                                                       /* needs_null_check */ false,
-                                                       is_volatile);
+    MacroAssembler* masm = codegen->GetVIXLAssembler();
+    // Piggy-back on the field load path using introspection for the Baker read barrier.
+    __ Add(temp, base, offset.W());  // Offset should not exceed 32 bits.
+    codegen->GenerateFieldLoadWithBakerReadBarrier(invoke,
+                                                   trg_loc,
+                                                   base,
+                                                   MemOperand(temp.X()),
+                                                   /* needs_null_check */ false,
+                                                   is_volatile);
   } else {
     // Other cases.
     MemOperand mem_op(base.X(), offset);
@@ -782,9 +782,9 @@
                                       kIntrinsified);
   if (can_call && kUseBakerReadBarrier) {
     locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty());  // No caller-save registers.
-    // We need a temporary register for the read barrier marking slow
-    // path in CodeGeneratorARM64::GenerateReferenceLoadWithBakerReadBarrier.
-    locations->AddTemp(Location::RequiresRegister());
+    // We need a temporary register for the read barrier load in order to use
+    // CodeGeneratorARM64::GenerateFieldLoadWithBakerReadBarrier().
+    locations->AddTemp(FixedTempLocation());
   }
   locations->SetInAt(0, Location::NoLocation());        // Unused receiver.
   locations->SetInAt(1, Location::RequiresRegister());
@@ -984,106 +984,155 @@
                                           ? LocationSummary::kCallOnSlowPath
                                           : LocationSummary::kNoCall,
                                       kIntrinsified);
+  if (can_call) {
+    locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty());  // No caller-save registers.
+  }
   locations->SetInAt(0, Location::NoLocation());        // Unused receiver.
   locations->SetInAt(1, Location::RequiresRegister());
   locations->SetInAt(2, Location::RequiresRegister());
   locations->SetInAt(3, Location::RequiresRegister());
   locations->SetInAt(4, Location::RequiresRegister());
 
-  // If heap poisoning is enabled, we don't want the unpoisoning
-  // operations to potentially clobber the output. Likewise when
-  // emitting a (Baker) read barrier, which may call.
-  Location::OutputOverlap overlaps =
-      ((kPoisonHeapReferences && type == DataType::Type::kReference) || can_call)
-      ? Location::kOutputOverlap
-      : Location::kNoOutputOverlap;
-  locations->SetOut(Location::RequiresRegister(), overlaps);
+  locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
   if (type == DataType::Type::kReference && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
-    // Temporary register for (Baker) read barrier.
+    // We need two non-scratch temporary registers for (Baker) read barrier.
+    locations->AddTemp(Location::RequiresRegister());
     locations->AddTemp(Location::RequiresRegister());
   }
 }
 
+class BakerReadBarrierCasSlowPathARM64 : public SlowPathCodeARM64 {
+ public:
+  explicit BakerReadBarrierCasSlowPathARM64(HInvoke* invoke)
+      : SlowPathCodeARM64(invoke) {}
+
+  const char* GetDescription() const OVERRIDE { return "BakerReadBarrierCasSlowPathARM64"; }
+
+  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
+    CodeGeneratorARM64* arm64_codegen = down_cast<CodeGeneratorARM64*>(codegen);
+    Arm64Assembler* assembler = arm64_codegen->GetAssembler();
+    MacroAssembler* masm = assembler->GetVIXLAssembler();
+    __ Bind(GetEntryLabel());
+
+    // Get the locations.
+    LocationSummary* locations = instruction_->GetLocations();
+    Register base = WRegisterFrom(locations->InAt(1));              // Object pointer.
+    Register offset = XRegisterFrom(locations->InAt(2));            // Long offset.
+    Register expected = WRegisterFrom(locations->InAt(3));          // Expected.
+    Register value = WRegisterFrom(locations->InAt(4));             // Value.
+
+    Register old_value = WRegisterFrom(locations->GetTemp(0));      // The old value from main path.
+    Register marked = WRegisterFrom(locations->GetTemp(1));         // The marked old value.
+
+    // Mark the `old_value` from the main path and compare with `expected`. This clobbers the
+    // `tmp_ptr` scratch register but we do not want to allocate another non-scratch temporary.
+    arm64_codegen->GenerateUnsafeCasOldValueMovWithBakerReadBarrier(marked, old_value);
+    __ Cmp(marked, expected);
+    __ B(GetExitLabel(), ne);  // If taken, Z=false indicates failure.
+
+    // The `old_value` we have read did not match `expected` (which is always a to-space reference)
+    // but after the read barrier in GenerateUnsafeCasOldValueMovWithBakerReadBarrier() the marked
+    // to-space value matched, so the `old_value` must be a from-space reference to the same
+    // object. Do the same CAS loop as the main path but check for both `expected` and the unmarked
+    // old value representing the to-space and from-space references for the same object.
+
+    UseScratchRegisterScope temps(masm);
+    Register tmp_ptr = temps.AcquireX();
+    Register tmp = temps.AcquireSameSizeAs(value);
+
+    // Recalculate the `tmp_ptr` clobbered above.
+    __ Add(tmp_ptr, base.X(), Operand(offset));
+
+    // do {
+    //   tmp_value = [tmp_ptr];
+    // } while ((tmp_value == expected || tmp == old_value) && failure([tmp_ptr] <- r_new_value));
+    // result = (tmp_value == expected || tmp == old_value);
+
+    vixl::aarch64::Label loop_head;
+    __ Bind(&loop_head);
+    __ Ldaxr(tmp, MemOperand(tmp_ptr));
+    assembler->MaybeUnpoisonHeapReference(tmp);
+    __ Cmp(tmp, expected);
+    __ Ccmp(tmp, old_value, ZFlag, ne);
+    __ B(GetExitLabel(), ne);  // If taken, Z=false indicates failure.
+    assembler->MaybePoisonHeapReference(value);
+    __ Stlxr(tmp.W(), value, MemOperand(tmp_ptr));
+    assembler->MaybeUnpoisonHeapReference(value);
+    __ Cbnz(tmp.W(), &loop_head);
+
+    // Z=true from the above CMP+CCMP indicates success.
+    __ B(GetExitLabel());
+  }
+};
+
 static void GenCas(HInvoke* invoke, DataType::Type type, CodeGeneratorARM64* codegen) {
-  MacroAssembler* masm = codegen->GetVIXLAssembler();
+  Arm64Assembler* assembler = codegen->GetAssembler();
+  MacroAssembler* masm = assembler->GetVIXLAssembler();
   LocationSummary* locations = invoke->GetLocations();
 
-  Location out_loc = locations->Out();
-  Register out = WRegisterFrom(out_loc);                           // Boolean result.
-
-  Register base = WRegisterFrom(locations->InAt(1));               // Object pointer.
-  Location offset_loc = locations->InAt(2);
-  Register offset = XRegisterFrom(offset_loc);                     // Long offset.
-  Register expected = RegisterFrom(locations->InAt(3), type);      // Expected.
-  Register value = RegisterFrom(locations->InAt(4), type);         // Value.
+  Register out = WRegisterFrom(locations->Out());                 // Boolean result.
+  Register base = WRegisterFrom(locations->InAt(1));              // Object pointer.
+  Register offset = XRegisterFrom(locations->InAt(2));            // Long offset.
+  Register expected = RegisterFrom(locations->InAt(3), type);     // Expected.
+  Register value = RegisterFrom(locations->InAt(4), type);        // Value.
 
   // This needs to be before the temp registers, as MarkGCCard also uses VIXL temps.
   if (type == DataType::Type::kReference) {
     // Mark card for object assuming new value is stored.
     bool value_can_be_null = true;  // TODO: Worth finding out this information?
     codegen->MarkGCCard(base, value, value_can_be_null);
-
-    // The only read barrier implementation supporting the
-    // UnsafeCASObject intrinsic is the Baker-style read barriers.
-    DCHECK(!kEmitCompilerReadBarrier || kUseBakerReadBarrier);
-
-    if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
-      Register temp = WRegisterFrom(locations->GetTemp(0));
-      // Need to make sure the reference stored in the field is a to-space
-      // one before attempting the CAS or the CAS could fail incorrectly.
-      codegen->UpdateReferenceFieldWithBakerReadBarrier(
-          invoke,
-          out_loc,  // Unused, used only as a "temporary" within the read barrier.
-          base,
-          /* field_offset */ offset_loc,
-          temp,
-          /* needs_null_check */ false,
-          /* use_load_acquire */ false);
-    }
   }
 
   UseScratchRegisterScope temps(masm);
   Register tmp_ptr = temps.AcquireX();                             // Pointer to actual memory.
-  Register tmp_value = temps.AcquireSameSizeAs(value);             // Value in memory.
+  Register old_value;                                              // Value in memory.
 
-  Register tmp_32 = tmp_value.W();
+  vixl::aarch64::Label exit_loop_label;
+  vixl::aarch64::Label* exit_loop = &exit_loop_label;
+  vixl::aarch64::Label* failure = &exit_loop_label;
+
+  if (kEmitCompilerReadBarrier && type == DataType::Type::kReference) {
+    // The only read barrier implementation supporting the
+    // UnsafeCASObject intrinsic is the Baker-style read barriers.
+    DCHECK(kUseBakerReadBarrier);
+
+    BakerReadBarrierCasSlowPathARM64* slow_path =
+        new (codegen->GetScopedAllocator()) BakerReadBarrierCasSlowPathARM64(invoke);
+    codegen->AddSlowPath(slow_path);
+    exit_loop = slow_path->GetExitLabel();
+    failure = slow_path->GetEntryLabel();
+    // We need to store the `old_value` in a non-scratch register to make sure
+    // the Baker read barrier in the slow path does not clobber it.
+    old_value = WRegisterFrom(locations->GetTemp(0));
+  } else {
+    old_value = temps.AcquireSameSizeAs(value);
+  }
 
   __ Add(tmp_ptr, base.X(), Operand(offset));
 
-  if (kPoisonHeapReferences && type == DataType::Type::kReference) {
-    codegen->GetAssembler()->PoisonHeapReference(expected);
-    if (value.Is(expected)) {
-      // Do not poison `value`, as it is the same register as
-      // `expected`, which has just been poisoned.
-    } else {
-      codegen->GetAssembler()->PoisonHeapReference(value);
-    }
-  }
-
   // do {
-  //   tmp_value = [tmp_ptr] - expected;
-  // } while (tmp_value == 0 && failure([tmp_ptr] <- r_new_value));
-  // result = tmp_value != 0;
+  //   tmp_value = [tmp_ptr];
+  // } while (tmp_value == expected && failure([tmp_ptr] <- r_new_value));
+  // result = tmp_value == expected;
 
-  vixl::aarch64::Label loop_head, exit_loop;
+  vixl::aarch64::Label loop_head;
   __ Bind(&loop_head);
-  __ Ldaxr(tmp_value, MemOperand(tmp_ptr));
-  __ Cmp(tmp_value, expected);
-  __ B(&exit_loop, ne);
-  __ Stlxr(tmp_32, value, MemOperand(tmp_ptr));
-  __ Cbnz(tmp_32, &loop_head);
-  __ Bind(&exit_loop);
-  __ Cset(out, eq);
-
-  if (kPoisonHeapReferences && type == DataType::Type::kReference) {
-    codegen->GetAssembler()->UnpoisonHeapReference(expected);
-    if (value.Is(expected)) {
-      // Do not unpoison `value`, as it is the same register as
-      // `expected`, which has just been unpoisoned.
-    } else {
-      codegen->GetAssembler()->UnpoisonHeapReference(value);
-    }
+  __ Ldaxr(old_value, MemOperand(tmp_ptr));
+  if (type == DataType::Type::kReference) {
+    assembler->MaybeUnpoisonHeapReference(old_value);
   }
+  __ Cmp(old_value, expected);
+  __ B(failure, ne);
+  if (type == DataType::Type::kReference) {
+    assembler->MaybePoisonHeapReference(value);
+  }
+  __ Stlxr(old_value.W(), value, MemOperand(tmp_ptr));  // Reuse `old_value` for STLXR result.
+  if (type == DataType::Type::kReference) {
+    assembler->MaybeUnpoisonHeapReference(value);
+  }
+  __ Cbnz(old_value.W(), &loop_head);
+  __ Bind(exit_loop);
+  __ Cset(out, eq);
 }
 
 void IntrinsicLocationsBuilderARM64::VisitUnsafeCASInt(HInvoke* invoke) {
@@ -2690,7 +2739,7 @@
         codegen_->AddSlowPath(read_barrier_slow_path);
 
         // Given the numeric representation, it's enough to check the low bit of the rb_state.
-        static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+        static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
         static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
         __ Tbnz(tmp, LockWord::kReadBarrierStateShift, read_barrier_slow_path->GetEntryLabel());
 
diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc
index 2963308..74a779d 100644
--- a/compiler/optimizing/intrinsics_arm_vixl.cc
+++ b/compiler/optimizing/intrinsics_arm_vixl.cc
@@ -638,8 +638,11 @@
       if (kEmitCompilerReadBarrier) {
         if (kUseBakerReadBarrier) {
           Location temp = locations->GetTemp(0);
-          codegen->GenerateReferenceLoadWithBakerReadBarrier(
-              invoke, trg_loc, base, 0U, offset_loc, TIMES_1, temp, /* needs_null_check */ false);
+          // Piggy-back on the field load path using introspection for the Baker read barrier.
+          __ Add(RegisterFrom(temp), base, Operand(offset));
+          MemOperand src(RegisterFrom(temp), 0);
+          codegen->GenerateFieldLoadWithBakerReadBarrier(
+              invoke, trg_loc, base, src, /* needs_null_check */ false);
           if (is_volatile) {
             __ Dmb(vixl32::ISH);
           }
@@ -933,9 +936,7 @@
                codegen_);
 }
 
-static void CreateIntIntIntIntIntToIntPlusTemps(ArenaAllocator* allocator,
-                                                HInvoke* invoke,
-                                                DataType::Type type) {
+static void CreateIntIntIntIntIntToIntPlusTemps(ArenaAllocator* allocator, HInvoke* invoke) {
   bool can_call = kEmitCompilerReadBarrier &&
       kUseBakerReadBarrier &&
       (invoke->GetIntrinsic() == Intrinsics::kUnsafeCASObject);
@@ -945,20 +946,16 @@
                                           ? LocationSummary::kCallOnSlowPath
                                           : LocationSummary::kNoCall,
                                       kIntrinsified);
+  if (can_call) {
+    locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty());  // No caller-save registers.
+  }
   locations->SetInAt(0, Location::NoLocation());        // Unused receiver.
   locations->SetInAt(1, Location::RequiresRegister());
   locations->SetInAt(2, Location::RequiresRegister());
   locations->SetInAt(3, Location::RequiresRegister());
   locations->SetInAt(4, Location::RequiresRegister());
 
-  // If heap poisoning is enabled, we don't want the unpoisoning
-  // operations to potentially clobber the output. Likewise when
-  // emitting a (Baker) read barrier, which may call.
-  Location::OutputOverlap overlaps =
-      ((kPoisonHeapReferences && type == DataType::Type::kReference) || can_call)
-      ? Location::kOutputOverlap
-      : Location::kNoOutputOverlap;
-  locations->SetOut(Location::RequiresRegister(), overlaps);
+  locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
 
   // Temporary registers used in CAS. In the object case
   // (UnsafeCASObject intrinsic), these are also used for
@@ -967,24 +964,92 @@
   locations->AddTemp(Location::RequiresRegister());  // Temp 1.
 }
 
+class BakerReadBarrierCasSlowPathARMVIXL : public SlowPathCodeARMVIXL {
+ public:
+  explicit BakerReadBarrierCasSlowPathARMVIXL(HInvoke* invoke)
+      : SlowPathCodeARMVIXL(invoke) {}
+
+  const char* GetDescription() const OVERRIDE { return "BakerReadBarrierCasSlowPathARMVIXL"; }
+
+  void EmitNativeCode(CodeGenerator* codegen) OVERRIDE {
+    CodeGeneratorARMVIXL* arm_codegen = down_cast<CodeGeneratorARMVIXL*>(codegen);
+    ArmVIXLAssembler* assembler = arm_codegen->GetAssembler();
+    __ Bind(GetEntryLabel());
+
+    LocationSummary* locations = instruction_->GetLocations();
+    vixl32::Register base = InputRegisterAt(instruction_, 1);           // Object pointer.
+    vixl32::Register offset = LowRegisterFrom(locations->InAt(2));      // Offset (discard high 4B).
+    vixl32::Register expected = InputRegisterAt(instruction_, 3);       // Expected.
+    vixl32::Register value = InputRegisterAt(instruction_, 4);          // Value.
+
+    vixl32::Register tmp_ptr = RegisterFrom(locations->GetTemp(0));     // Pointer to actual memory.
+    vixl32::Register tmp = RegisterFrom(locations->GetTemp(1));         // Temporary.
+
+    // The `tmp` is initialized to `[tmp_ptr] - expected` in the main path. Reconstruct
+    // and mark the old value and compare with `expected`. We clobber `tmp_ptr` in the
+    // process due to lack of other temps suitable for the read barrier.
+    arm_codegen->GenerateUnsafeCasOldValueAddWithBakerReadBarrier(tmp_ptr, tmp, expected);
+    __ Cmp(tmp_ptr, expected);
+    __ B(ne, GetExitLabel());
+
+    // The old value we have read did not match `expected` (which is always a to-space reference)
+    // but after the read barrier in GenerateUnsafeCasOldValueAddWithBakerReadBarrier() the marked
+    // to-space value matched, so the old value must be a from-space reference to the same object.
+    // Do the same CAS loop as the main path but check for both `expected` and the unmarked
+    // old value representing the to-space and from-space references for the same object.
+
+    UseScratchRegisterScope temps(assembler->GetVIXLAssembler());
+    vixl32::Register adjusted_old_value = temps.Acquire();      // For saved `tmp` from main path.
+
+    // Recalculate the `tmp_ptr` clobbered above and store the `adjusted_old_value`, i.e. IP.
+    __ Add(tmp_ptr, base, offset);
+    __ Mov(adjusted_old_value, tmp);
+
+    // do {
+    //   tmp = [r_ptr] - expected;
+    // } while ((tmp == 0 || tmp == adjusted_old_value) && failure([r_ptr] <- r_new_value));
+    // result = (tmp == 0 || tmp == adjusted_old_value);
+
+    vixl32::Label loop_head;
+    __ Bind(&loop_head);
+    __ Ldrex(tmp, MemOperand(tmp_ptr));  // This can now load null stored by another thread.
+    assembler->MaybeUnpoisonHeapReference(tmp);
+    __ Subs(tmp, tmp, expected);         // Use SUBS to get non-zero value if both compares fail.
+    {
+      // If the newly loaded value did not match `expected`, compare with `adjusted_old_value`.
+      ExactAssemblyScope aas(assembler->GetVIXLAssembler(), 2 * k16BitT32InstructionSizeInBytes);
+      __ it(ne);
+      __ cmp(ne, tmp, adjusted_old_value);
+    }
+    __ B(ne, GetExitLabel());
+    assembler->MaybePoisonHeapReference(value);
+    __ Strex(tmp, value, MemOperand(tmp_ptr));
+    assembler->MaybeUnpoisonHeapReference(value);
+    __ Cmp(tmp, 0);
+    __ B(ne, &loop_head, /* far_target */ false);
+    __ B(GetExitLabel());
+  }
+};
+
 static void GenCas(HInvoke* invoke, DataType::Type type, CodeGeneratorARMVIXL* codegen) {
   DCHECK_NE(type, DataType::Type::kInt64);
 
   ArmVIXLAssembler* assembler = codegen->GetAssembler();
   LocationSummary* locations = invoke->GetLocations();
 
-  Location out_loc = locations->Out();
   vixl32::Register out = OutputRegister(invoke);                      // Boolean result.
 
   vixl32::Register base = InputRegisterAt(invoke, 1);                 // Object pointer.
-  Location offset_loc = locations->InAt(2);
-  vixl32::Register offset = LowRegisterFrom(offset_loc);              // Offset (discard high 4B).
+  vixl32::Register offset = LowRegisterFrom(locations->InAt(2));      // Offset (discard high 4B).
   vixl32::Register expected = InputRegisterAt(invoke, 3);             // Expected.
   vixl32::Register value = InputRegisterAt(invoke, 4);                // Value.
 
-  Location tmp_ptr_loc = locations->GetTemp(0);
-  vixl32::Register tmp_ptr = RegisterFrom(tmp_ptr_loc);               // Pointer to actual memory.
-  vixl32::Register tmp = RegisterFrom(locations->GetTemp(1));         // Value in memory.
+  vixl32::Register tmp_ptr = RegisterFrom(locations->GetTemp(0));     // Pointer to actual memory.
+  vixl32::Register tmp = RegisterFrom(locations->GetTemp(1));         // Temporary.
+
+  vixl32::Label loop_exit_label;
+  vixl32::Label* loop_exit = &loop_exit_label;
+  vixl32::Label* failure = &loop_exit_label;
 
   if (type == DataType::Type::kReference) {
     // The only read barrier implementation supporting the
@@ -997,87 +1062,62 @@
     codegen->MarkGCCard(tmp_ptr, tmp, base, value, value_can_be_null);
 
     if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
-      // Need to make sure the reference stored in the field is a to-space
-      // one before attempting the CAS or the CAS could fail incorrectly.
-      codegen->UpdateReferenceFieldWithBakerReadBarrier(
-          invoke,
-          out_loc,  // Unused, used only as a "temporary" within the read barrier.
-          base,
-          /* field_offset */ offset_loc,
-          tmp_ptr_loc,
-          /* needs_null_check */ false,
-          tmp);
+      // If marking, check if the stored reference is a from-space reference to the same
+      // object as the to-space reference `expected`. If so, perform a custom CAS loop.
+      BakerReadBarrierCasSlowPathARMVIXL* slow_path =
+          new (codegen->GetScopedAllocator()) BakerReadBarrierCasSlowPathARMVIXL(invoke);
+      codegen->AddSlowPath(slow_path);
+      failure = slow_path->GetEntryLabel();
+      loop_exit = slow_path->GetExitLabel();
     }
   }
 
   // Prevent reordering with prior memory operations.
   // Emit a DMB ISH instruction instead of an DMB ISHST one, as the
-  // latter allows a preceding load to be delayed past the STXR
+  // latter allows a preceding load to be delayed past the STREX
   // instruction below.
   __ Dmb(vixl32::ISH);
 
   __ Add(tmp_ptr, base, offset);
 
-  if (kPoisonHeapReferences && type == DataType::Type::kReference) {
-    codegen->GetAssembler()->PoisonHeapReference(expected);
-    if (value.Is(expected)) {
-      // Do not poison `value`, as it is the same register as
-      // `expected`, which has just been poisoned.
-    } else {
-      codegen->GetAssembler()->PoisonHeapReference(value);
-    }
-  }
-
   // do {
   //   tmp = [r_ptr] - expected;
   // } while (tmp == 0 && failure([r_ptr] <- r_new_value));
-  // result = tmp != 0;
+  // result = tmp == 0;
 
   vixl32::Label loop_head;
   __ Bind(&loop_head);
-
   __ Ldrex(tmp, MemOperand(tmp_ptr));
-
-  __ Subs(tmp, tmp, expected);
-
-  {
-    ExactAssemblyScope aas(assembler->GetVIXLAssembler(),
-                           3 * kMaxInstructionSizeInBytes,
-                           CodeBufferCheckScope::kMaximumSize);
-
-    __ itt(eq);
-    __ strex(eq, tmp, value, MemOperand(tmp_ptr));
-    __ cmp(eq, tmp, 1);
+  if (type == DataType::Type::kReference) {
+    assembler->MaybeUnpoisonHeapReference(tmp);
   }
+  __ Subs(tmp, tmp, expected);
+  __ B(ne, failure, (failure == loop_exit) ? kNear : kBranchWithoutHint);
+  if (type == DataType::Type::kReference) {
+    assembler->MaybePoisonHeapReference(value);
+  }
+  __ Strex(tmp, value, MemOperand(tmp_ptr));
+  if (type == DataType::Type::kReference) {
+    assembler->MaybeUnpoisonHeapReference(value);
+  }
+  __ Cmp(tmp, 0);
+  __ B(ne, &loop_head, /* far_target */ false);
 
-  __ B(eq, &loop_head, /* far_target */ false);
+  __ Bind(loop_exit);
 
   __ Dmb(vixl32::ISH);
 
-  __ Rsbs(out, tmp, 1);
+  // out = tmp == 0.
+  __ Clz(out, tmp);
+  __ Lsr(out, out, WhichPowerOf2(out.GetSizeInBits()));
 
-  {
-    ExactAssemblyScope aas(assembler->GetVIXLAssembler(),
-                           2 * kMaxInstructionSizeInBytes,
-                           CodeBufferCheckScope::kMaximumSize);
-
-    __ it(cc);
-    __ mov(cc, out, 0);
-  }
-
-  if (kPoisonHeapReferences && type == DataType::Type::kReference) {
-    codegen->GetAssembler()->UnpoisonHeapReference(expected);
-    if (value.Is(expected)) {
-      // Do not unpoison `value`, as it is the same register as
-      // `expected`, which has just been unpoisoned.
-    } else {
-      codegen->GetAssembler()->UnpoisonHeapReference(value);
-    }
+  if (type == DataType::Type::kReference) {
+    codegen->MaybeGenerateMarkingRegisterCheck(/* code */ 128);
   }
 }
 
 void IntrinsicLocationsBuilderARMVIXL::VisitUnsafeCASInt(HInvoke* invoke) {
-  CreateIntIntIntIntIntToIntPlusTemps(allocator_, invoke, DataType::Type::kInt32);
+  CreateIntIntIntIntIntToIntPlusTemps(allocator_, invoke);
 }
 void IntrinsicLocationsBuilderARMVIXL::VisitUnsafeCASObject(HInvoke* invoke) {
   // The only read barrier implementation supporting the
@@ -1086,7 +1126,7 @@
     return;
   }
 
-  CreateIntIntIntIntIntToIntPlusTemps(allocator_, invoke, DataType::Type::kReference);
+  CreateIntIntIntIntIntToIntPlusTemps(allocator_, invoke);
 }
 void IntrinsicCodeGeneratorARMVIXL::VisitUnsafeCASInt(HInvoke* invoke) {
   GenCas(invoke, DataType::Type::kInt32, codegen_);
@@ -2202,7 +2242,7 @@
       // Given the numeric representation, it's enough to check the low bit of the
       // rb_state. We do that by shifting the bit out of the lock word with LSRS
       // which can be a 16-bit instruction unlike the TST immediate.
-      static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+      static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
       static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
       __ Lsrs(temp2, temp2, LockWord::kReadBarrierStateShift + 1);
       // Carry flag is the last bit shifted out by LSRS.
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index 98cea35..5c7be54 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -2781,7 +2781,7 @@
     __ j(kEqual, &done);
 
     // Given the numeric representation, it's enough to check the low bit of the rb_state.
-    static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+    static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
     static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
     constexpr uint32_t gray_byte_position = LockWord::kReadBarrierStateShift / kBitsPerByte;
     constexpr uint32_t gray_bit_position = LockWord::kReadBarrierStateShift % kBitsPerByte;
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index ac6eab0..b5afe93 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -1143,7 +1143,7 @@
     __ j(kEqual, &done);
 
     // Given the numeric representation, it's enough to check the low bit of the rb_state.
-    static_assert(ReadBarrier::WhiteState() == 0, "Expecting white to have value 0");
+    static_assert(ReadBarrier::NonGrayState() == 0, "Expecting non-gray to have value 0");
     static_assert(ReadBarrier::GrayState() == 1, "Expecting gray to have value 1");
     constexpr uint32_t gray_byte_position = LockWord::kReadBarrierStateShift / kBitsPerByte;
     constexpr uint32_t gray_bit_position = LockWord::kReadBarrierStateShift % kBitsPerByte;
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index 8f822cc..79a7e2c 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -1301,6 +1301,15 @@
     ++it;
     if (dominator->StrictlyDominates(user)) {
       user->ReplaceInput(replacement, index);
+    } else if (user->IsPhi() && !user->AsPhi()->IsCatchPhi()) {
+      // If the input flows from a block dominated by `dominator`, we can replace it.
+      // We do not perform this for catch phis as we don't have control flow support
+      // for their inputs.
+      const ArenaVector<HBasicBlock*>& predecessors = user->GetBlock()->GetPredecessors();
+      HBasicBlock* predecessor = predecessors[index];
+      if (dominator->GetBlock()->Dominates(predecessor)) {
+        user->ReplaceInput(replacement, index);
+      }
     }
   }
 }
diff --git a/compiler/optimizing/scheduler.cc b/compiler/optimizing/scheduler.cc
index 1aa16f4..df897a4 100644
--- a/compiler/optimizing/scheduler.cc
+++ b/compiler/optimizing/scheduler.cc
@@ -280,6 +280,23 @@
   return false;
 }
 
+// Check if the specified instruction is a better candidate which more likely will
+// have other instructions depending on it.
+static bool IsBetterCandidateWithMoreLikelyDependencies(HInstruction* new_candidate,
+                                                        HInstruction* old_candidate) {
+  if (!new_candidate->GetSideEffects().Includes(old_candidate->GetSideEffects())) {
+    // Weaker side effects.
+    return false;
+  }
+  if (old_candidate->GetSideEffects().Includes(new_candidate->GetSideEffects())) {
+    // Same side effects, check if `new_candidate` has stronger `CanThrow()`.
+    return new_candidate->CanThrow() && !old_candidate->CanThrow();
+  } else {
+    // Stronger side effects, check if `new_candidate` has at least as strong `CanThrow()`.
+    return new_candidate->CanThrow() || !old_candidate->CanThrow();
+  }
+}
+
 void SchedulingGraph::AddDependencies(HInstruction* instruction, bool is_scheduling_barrier) {
   SchedulingNode* instruction_node = GetNode(instruction);
 
@@ -331,6 +348,7 @@
 
   // Side effect dependencies.
   if (!instruction->GetSideEffects().DoesNothing() || instruction->CanThrow()) {
+    HInstruction* dep_chain_candidate = nullptr;
     for (HInstruction* other = instruction->GetNext(); other != nullptr; other = other->GetNext()) {
       SchedulingNode* other_node = GetNode(other);
       if (other_node->IsSchedulingBarrier()) {
@@ -340,7 +358,18 @@
         break;
       }
       if (HasSideEffectDependency(other, instruction)) {
-        AddOtherDependency(other_node, instruction_node);
+        if (dep_chain_candidate != nullptr &&
+            HasSideEffectDependency(other, dep_chain_candidate)) {
+          // Skip an explicit dependency to reduce memory usage, rely on the transitive dependency.
+        } else {
+          AddOtherDependency(other_node, instruction_node);
+        }
+        // Check if `other` is a better candidate which more likely will have other instructions
+        // depending on it.
+        if (dep_chain_candidate == nullptr ||
+            IsBetterCandidateWithMoreLikelyDependencies(other, dep_chain_candidate)) {
+          dep_chain_candidate = other;
+        }
       }
     }
   }
diff --git a/compiler/optimizing/scheduler_test.cc b/compiler/optimizing/scheduler_test.cc
index fe23fb4..981fcc4 100644
--- a/compiler/optimizing/scheduler_test.cc
+++ b/compiler/optimizing/scheduler_test.cc
@@ -171,7 +171,9 @@
     ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_set1, array_get1));
     ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_set2, array_get2));
     ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_get2, array_set1));
-    ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(array_set2, array_set1));
+    // Unnecessary dependency is not stored, we rely on transitive dependencies.
+    // The array_set2 -> array_get2 -> array_set1 dependencies are tested above.
+    ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(array_set2, array_set1));
 
     // Env dependency.
     ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(div_check, mul));
@@ -308,7 +310,9 @@
     loc1 = heap_location_collector.GetArrayHeapLocation(arr_set_i);
     loc2 = heap_location_collector.GetArrayHeapLocation(arr_set_j);
     ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2));
-    ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_i));
+    // Unnecessary dependency is not stored, we rely on transitive dependencies.
+    // The arr_set_j -> arr_set_sub0 -> arr_set_add0 -> arr_set_i dependencies are tested below.
+    ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_i));
 
     // Test side effect dependency based on LSA analysis: array[i] and array[i+0]
     loc1 = heap_location_collector.GetArrayHeapLocation(arr_set_i);
@@ -320,7 +324,10 @@
     loc1 = heap_location_collector.GetArrayHeapLocation(arr_set_i);
     loc2 = heap_location_collector.GetArrayHeapLocation(arr_set_sub0);
     ASSERT_TRUE(heap_location_collector.MayAlias(loc1, loc2));
-    ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_sub0, arr_set_i));
+    // Unnecessary dependency is not stored, we rely on transitive dependencies.
+    ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_sub0, arr_set_i));
+    // Instead, we rely on arr_set_sub0 -> arr_set_add0 -> arr_set_i, the latter is tested above.
+    ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_sub0, arr_set_add0));
 
     // Test side effect dependency based on LSA analysis: array[i] and array[i+1]
     loc1 = heap_location_collector.GetArrayHeapLocation(arr_set_i);
@@ -335,11 +342,12 @@
     ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_sub1, arr_set_add1));
 
     // Test side effect dependency based on LSA analysis: array[j] and all others array accesses
-    ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_i));
-    ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_add0));
     ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_sub0));
     ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_add1));
     ASSERT_TRUE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_sub1));
+    // Unnecessary dependencies are not stored, we rely on transitive dependencies.
+    ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_i));
+    ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_j, arr_set_add0));
 
     // Test that ArraySet and FieldSet should not have side effect dependency
     ASSERT_FALSE(scheduling_graph.HasImmediateOtherDependency(arr_set_i, set_field10));
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index 3918b65..60ca61c 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -296,10 +296,10 @@
 
   ScopedArenaVector<uint8_t> buffer(allocator_->Adapter(kArenaAllocStackMapStream));
   BitMemoryWriter<ScopedArenaVector<uint8_t>> out(&buffer);
-  EncodeVarintBits(out, packed_frame_size_);
-  EncodeVarintBits(out, core_spill_mask_);
-  EncodeVarintBits(out, fp_spill_mask_);
-  EncodeVarintBits(out, num_dex_registers_);
+  out.WriteVarint(packed_frame_size_);
+  out.WriteVarint(core_spill_mask_);
+  out.WriteVarint(fp_spill_mask_);
+  out.WriteVarint(num_dex_registers_);
   EncodeTable(out, stack_maps_);
   EncodeTable(out, register_masks_);
   EncodeTable(out, stack_masks_);
diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h
index df11709..01c6bf9 100644
--- a/compiler/optimizing/stack_map_stream.h
+++ b/compiler/optimizing/stack_map_stream.h
@@ -109,7 +109,7 @@
   BitTableBuilder<RegisterMask> register_masks_;
   BitmapTableBuilder stack_masks_;
   BitmapTableBuilder dex_register_masks_;
-  BitTableBuilder<MaskInfo> dex_register_maps_;
+  BitTableBuilder<DexRegisterMapInfo> dex_register_maps_;
   BitTableBuilder<DexRegisterInfo> dex_register_catalog_;
 
   ScopedArenaVector<BitVector*> lazy_stack_masks_;
diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc
index a281bb3..d28f09f 100644
--- a/compiler/optimizing/stack_map_test.cc
+++ b/compiler/optimizing/stack_map_test.cc
@@ -750,9 +750,9 @@
   ScopedArenaVector<uint8_t> memory = stream.Encode();
 
   std::vector<uint8_t> out;
-  CodeInfo::DedupeMap dedupe_map;
-  size_t deduped1 = CodeInfo::Dedupe(&out, memory.data(), &dedupe_map);
-  size_t deduped2 = CodeInfo::Dedupe(&out, memory.data(), &dedupe_map);
+  CodeInfo::Deduper deduper(&out);
+  size_t deduped1 = deduper.Dedupe(memory.data());
+  size_t deduped2 = deduper.Dedupe(memory.data());
 
   for (size_t deduped : { deduped1, deduped2 }) {
     CodeInfo code_info(out.data() + deduped);
diff --git a/compiler/utils/swap_space.cc b/compiler/utils/swap_space.cc
index 1f9ad42..dee83d1 100644
--- a/compiler/utils/swap_space.cc
+++ b/compiler/utils/swap_space.cc
@@ -141,6 +141,7 @@
           it->size -= size;
         } else {
           // Changing in place would break the std::set<> ordering, we need to remove and insert.
+          // TODO: When C++17 becomes available, use std::map<>::extract(), modify, insert.
           free_by_size_.erase(it);
           free_by_size_.insert(new_value);
         }
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 0b68620..29df067 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -669,9 +669,7 @@
       for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files_) {
         dex_file.release();
       }
-      for (std::unique_ptr<MemMap>& map : opened_dex_files_maps_) {
-        map.release();
-      }
+      new std::vector<MemMap>(std::move(opened_dex_files_maps_));  // Leak MemMaps.
       for (std::unique_ptr<File>& vdex_file : vdex_files_) {
         vdex_file.release();
       }
@@ -1449,14 +1447,14 @@
         LOG(INFO) << "No " << VdexFile::kVdexNameInDmFile << " file in DexMetadata archive. "
                   << "Not doing fast verification.";
       } else {
-        std::unique_ptr<MemMap> input_file(zip_entry->MapDirectlyOrExtract(
+        MemMap input_file = zip_entry->MapDirectlyOrExtract(
             VdexFile::kVdexNameInDmFile,
             kDexMetadata,
-            &error_msg));
-        if (input_file == nullptr) {
+            &error_msg);
+        if (!input_file.IsValid()) {
           LOG(WARNING) << "Could not open vdex file in DexMetadata archive: " << error_msg;
         } else {
-          input_vdex_file_ = std::make_unique<VdexFile>(input_file.release());
+          input_vdex_file_ = std::make_unique<VdexFile>(std::move(input_file));
         }
       }
     }
@@ -1631,7 +1629,7 @@
       for (size_t i = 0, size = oat_writers_.size(); i != size; ++i) {
         rodata_.push_back(elf_writers_[i]->StartRoData());
         // Unzip or copy dex files straight to the oat file.
-        std::vector<std::unique_ptr<MemMap>> opened_dex_files_map;
+        std::vector<MemMap> opened_dex_files_map;
         std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
         // No need to verify the dex file when we have a vdex file, which means it was already
         // verified.
@@ -1651,7 +1649,7 @@
         if (opened_dex_files_map.empty()) {
           DCHECK(opened_dex_files.empty());
         } else {
-          for (std::unique_ptr<MemMap>& map : opened_dex_files_map) {
+          for (MemMap& map : opened_dex_files_map) {
             opened_dex_files_maps_.push_back(std::move(map));
           }
           for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files) {
@@ -1732,8 +1730,8 @@
     }
 
     // Ensure opened dex files are writable for dex-to-dex transformations.
-    for (const std::unique_ptr<MemMap>& map : opened_dex_files_maps_) {
-      if (!map->Protect(PROT_READ | PROT_WRITE)) {
+    for (MemMap& map : opened_dex_files_maps_) {
+      if (!map.Protect(PROT_READ | PROT_WRITE)) {
         PLOG(ERROR) << "Failed to make .dex files writeable.";
         return dex2oat::ReturnCode::kOther;
       }
@@ -2002,9 +2000,9 @@
     TimingLogger::ScopedTiming t("dex2oat Oat", timings_);
 
     // Sync the data to the file, in case we did dex2dex transformations.
-    for (const std::unique_ptr<MemMap>& map : opened_dex_files_maps_) {
-      if (!map->Sync()) {
-        PLOG(ERROR) << "Failed to Sync() dex2dex output. Map: " << map->GetName();
+    for (MemMap& map : opened_dex_files_maps_) {
+      if (!map.Sync()) {
+        PLOG(ERROR) << "Failed to Sync() dex2dex output. Map: " << map.GetName();
         return false;
       }
     }
@@ -2737,16 +2735,13 @@
                                 zip_filename, error_msg->c_str());
       return nullptr;
     }
-    std::unique_ptr<MemMap> input_file(zip_entry->ExtractToMemMap(zip_filename,
-                                                                  input_filename,
-                                                                  error_msg));
-    if (input_file.get() == nullptr) {
+    MemMap input_file = zip_entry->ExtractToMemMap(zip_filename, input_filename, error_msg);
+    if (!input_file.IsValid()) {
       *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", input_filename,
                                 zip_filename, error_msg->c_str());
       return nullptr;
     }
-    const std::string input_string(reinterpret_cast<char*>(input_file->Begin()),
-                                   input_file->Size());
+    const std::string input_string(reinterpret_cast<char*>(input_file.Begin()), input_file.Size());
     std::istringstream input_stream(input_string);
     return ReadCommentedInputStream<T>(input_stream, process);
   }
@@ -2873,7 +2868,7 @@
   std::unique_ptr<linker::ImageWriter> image_writer_;
   std::unique_ptr<CompilerDriver> driver_;
 
-  std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps_;
+  std::vector<MemMap> opened_dex_files_maps_;
   std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
 
   bool avoid_storing_invocation_;
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index fa8c778..440b3a4 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -252,7 +252,7 @@
       }
 
       std::vector<OutputStream*> rodata;
-      std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps;
+      std::vector<MemMap> opened_dex_files_maps;
       std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
       // Now that we have finalized key_value_store_, start writing the oat file.
       for (size_t i = 0, size = oat_writers.size(); i != size; ++i) {
@@ -265,7 +265,7 @@
                                             dex_file->GetLocation().c_str(),
                                             dex_file->GetLocationChecksum());
 
-        std::vector<std::unique_ptr<MemMap>> cur_opened_dex_files_maps;
+        std::vector<MemMap> cur_opened_dex_files_maps;
         std::vector<std::unique_ptr<const DexFile>> cur_opened_dex_files;
         bool dex_files_ok = oat_writers[i]->WriteAndOpenDexFiles(
             out_helper.vdex_files[i].GetFile(),
@@ -279,7 +279,7 @@
         ASSERT_TRUE(dex_files_ok);
 
         if (!cur_opened_dex_files_maps.empty()) {
-          for (std::unique_ptr<MemMap>& cur_map : cur_opened_dex_files_maps) {
+          for (MemMap& cur_map : cur_opened_dex_files_maps) {
             opened_dex_files_maps.push_back(std::move(cur_map));
           }
           for (std::unique_ptr<const DexFile>& cur_dex_file : cur_opened_dex_files) {
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index e10f9b3..67ded32 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -303,8 +303,8 @@
     }
 
     // Image data size excludes the bitmap and the header.
-    ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info.image_->Begin());
-    ArrayRef<const uint8_t> raw_image_data(image_info.image_->Begin() + sizeof(ImageHeader),
+    ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info.image_.Begin());
+    ArrayRef<const uint8_t> raw_image_data(image_info.image_.Begin() + sizeof(ImageHeader),
                                            image_header->GetImageSize() - sizeof(ImageHeader));
 
     CHECK_EQ(image_header->storage_mode_, image_storage_mode_);
@@ -362,7 +362,7 @@
     // We do not want to have a corrupted image with a valid header.
     // The header is uncompressed since it contains whether the image is compressed or not.
     image_header->data_size_ = image_data.size();
-    if (!image_file->PwriteFully(reinterpret_cast<char*>(image_info.image_->Begin()),
+    if (!image_file->PwriteFully(reinterpret_cast<char*>(image_info.image_.Begin()),
                                  sizeof(ImageHeader),
                                  0)) {
       PLOG(ERROR) << "Failed to write image file header " << image_filename;
@@ -730,14 +730,13 @@
         image_info.CreateImageSections(unused_sections), kPageSize);
 
     std::string error_msg;
-    image_info.image_.reset(MemMap::MapAnonymous("image writer image",
-                                                 nullptr,
-                                                 length,
-                                                 PROT_READ | PROT_WRITE,
-                                                 false,
-                                                 false,
-                                                 &error_msg));
-    if (UNLIKELY(image_info.image_.get() == nullptr)) {
+    image_info.image_ = MemMap::MapAnonymous("image writer image",
+                                             /* addr */ nullptr,
+                                             length,
+                                             PROT_READ | PROT_WRITE,
+                                             /* low_4gb */ false,
+                                             &error_msg);
+    if (UNLIKELY(!image_info.image_.IsValid())) {
       LOG(ERROR) << "Failed to allocate memory for image file generation: " << error_msg;
       return false;
     }
@@ -745,7 +744,7 @@
     // Create the image bitmap, only needs to cover mirror object section which is up to image_end_.
     CHECK_LE(image_info.image_end_, length);
     image_info.image_bitmap_.reset(gc::accounting::ContinuousSpaceBitmap::Create(
-        "image bitmap", image_info.image_->Begin(), RoundUp(image_info.image_end_, kPageSize)));
+        "image bitmap", image_info.image_.Begin(), RoundUp(image_info.image_end_, kPageSize)));
     if (image_info.image_bitmap_.get() == nullptr) {
       LOG(ERROR) << "Failed to allocate memory for image bitmap";
       return false;
@@ -2025,7 +2024,7 @@
 
   // Create the header, leave 0 for data size since we will fill this in as we are writing the
   // image.
-  ImageHeader* header = new (image_info.image_->Begin()) ImageHeader(
+  ImageHeader* header = new (image_info.image_.Begin()) ImageHeader(
       PointerToLowMemUInt32(image_info.image_begin_),
       image_end,
       sections,
@@ -2163,8 +2162,8 @@
     if (relocation.oat_index != oat_index) {
       continue;
     }
-    auto* dest = image_info.image_->Begin() + relocation.offset;
-    DCHECK_GE(dest, image_info.image_->Begin() + image_info.image_end_);
+    auto* dest = image_info.image_.Begin() + relocation.offset;
+    DCHECK_GE(dest, image_info.image_.Begin() + image_info.image_end_);
     DCHECK(!IsInBootImage(pair.first));
     switch (relocation.type) {
       case NativeObjectRelocationType::kArtField: {
@@ -2219,7 +2218,7 @@
     }
   }
   // Fixup the image method roots.
-  auto* image_header = reinterpret_cast<ImageHeader*>(image_info.image_->Begin());
+  auto* image_header = reinterpret_cast<ImageHeader*>(image_info.image_.Begin());
   for (size_t i = 0; i < ImageHeader::kImageMethodsCount; ++i) {
     ArtMethod* method = image_methods_[i];
     CHECK(method != nullptr);
@@ -2235,7 +2234,7 @@
     const ImageSection& intern_table_section = image_header->GetInternedStringsSection();
     InternTable* const intern_table = image_info.intern_table_.get();
     uint8_t* const intern_table_memory_ptr =
-        image_info.image_->Begin() + intern_table_section.Offset();
+        image_info.image_.Begin() + intern_table_section.Offset();
     const size_t intern_table_bytes = intern_table->WriteToMemory(intern_table_memory_ptr);
     CHECK_EQ(intern_table_bytes, image_info.intern_table_bytes_);
     // Fixup the pointers in the newly written intern table to contain image addresses.
@@ -2260,7 +2259,7 @@
   if (image_info.class_table_bytes_ > 0u) {
     const ImageSection& class_table_section = image_header->GetClassTableSection();
     uint8_t* const class_table_memory_ptr =
-        image_info.image_->Begin() + class_table_section.Offset();
+        image_info.image_.Begin() + class_table_section.Offset();
     Thread* self = Thread::Current();
     ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
 
@@ -2342,14 +2341,14 @@
   size_t offset = GetImageOffset(obj);
   size_t oat_index = GetOatIndex(obj);
   ImageInfo& image_info = GetImageInfo(oat_index);
-  auto* dst = reinterpret_cast<Object*>(image_info.image_->Begin() + offset);
+  auto* dst = reinterpret_cast<Object*>(image_info.image_.Begin() + offset);
   DCHECK_LT(offset, image_info.image_end_);
   const auto* src = reinterpret_cast<const uint8_t*>(obj);
 
   image_info.image_bitmap_->Set(dst);  // Mark the obj as live.
 
   const size_t n = obj->SizeOf();
-  DCHECK_LE(offset + n, image_info.image_->Size());
+  DCHECK_LE(offset + n, image_info.image_.Size());
   memcpy(dst, src, n);
 
   // Write in a hash code of objects which have inflated monitors or a hash code in their monitor
@@ -2456,7 +2455,7 @@
 T* ImageWriter::NativeCopyLocation(T* obj) {
   const NativeObjectRelocation relocation = GetNativeRelocation(obj);
   const ImageInfo& image_info = GetImageInfo(relocation.oat_index);
-  return reinterpret_cast<T*>(image_info.image_->Begin() + relocation.offset);
+  return reinterpret_cast<T*>(image_info.image_.Begin() + relocation.offset);
 }
 
 class ImageWriter::NativeLocationVisitor {
@@ -3011,12 +3010,12 @@
   }
   // Calculate the offset within the image.
   ImageInfo* image_info = &image_infos_[oat_index];
-  DCHECK(image_info->image_->HasAddress(dest))
-      << "MemMap range " << static_cast<const void*>(image_info->image_->Begin())
-      << "-" << static_cast<const void*>(image_info->image_->End())
+  DCHECK(image_info->image_.HasAddress(dest))
+      << "MemMap range " << static_cast<const void*>(image_info->image_.Begin())
+      << "-" << static_cast<const void*>(image_info->image_.End())
       << " does not contain " << dest;
-  size_t offset = reinterpret_cast<const uint8_t*>(dest) - image_info->image_->Begin();
-  ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info->image_->Begin());
+  size_t offset = reinterpret_cast<const uint8_t*>(dest) - image_info->image_.Begin();
+  ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_info->image_.Begin());
   size_t image_end = image_header->GetClassTableSection().End();
   DCHECK_LT(offset, image_end);
   // Calculate the location index.
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index 9ab9c3e..7cf555b 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -307,7 +307,7 @@
     // Calculate the sum total of the bin slot sizes in [0, up_to). Defaults to all bins.
     size_t GetBinSizeSum(Bin up_to) const;
 
-    std::unique_ptr<MemMap> image_;  // Memory mapped for generating the image.
+    MemMap image_;  // Memory mapped for generating the image.
 
     // Target begin of this image. Notes: It is not valid to write here, this is the address
     // of the target image, not necessarily where image_ is mapped. The address is only valid
@@ -408,7 +408,7 @@
     size_t offset = GetImageOffset(object);
     size_t oat_index = GetOatIndex(object);
     const ImageInfo& image_info = GetImageInfo(oat_index);
-    uint8_t* dst = image_info.image_->Begin() + offset;
+    uint8_t* dst = image_info.image_.Begin() + offset;
     return reinterpret_cast<mirror::Object*>(dst);
   }
 
diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc
index 484554e..9045c43 100644
--- a/dex2oat/linker/oat_writer.cc
+++ b/dex2oat/linker/oat_writer.cc
@@ -654,7 +654,7 @@
     bool verify,
     bool update_input_vdex,
     CopyOption copy_dex_files,
-    /*out*/ std::vector<std::unique_ptr<MemMap>>* opened_dex_files_map,
+    /*out*/ std::vector<MemMap>* opened_dex_files_map,
     /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) {
   CHECK(write_state_ == WriteState::kAddingDexFileSources);
 
@@ -663,7 +663,7 @@
      return false;
   }
 
-  std::vector<std::unique_ptr<MemMap>> dex_files_map;
+  std::vector<MemMap> dex_files_map;
   std::vector<std::unique_ptr<const DexFile>> dex_files;
 
   // Initialize VDEX and OAT headers.
@@ -1438,10 +1438,10 @@
 
 class OatWriter::InitMapMethodVisitor : public OatDexMethodVisitor {
  public:
-  static constexpr bool kDebugVerifyDedupedCodeInfo = false;
-
   InitMapMethodVisitor(OatWriter* writer, size_t offset)
-      : OatDexMethodVisitor(writer, offset) {}
+      : OatDexMethodVisitor(writer, offset),
+        dedupe_bit_table_(&writer_->code_info_data_) {
+  }
 
   bool VisitMethod(size_t class_def_method_index,
                    const ClassAccessor::Method& method ATTRIBUTE_UNUSED)
@@ -1455,21 +1455,9 @@
 
       ArrayRef<const uint8_t> map = compiled_method->GetVmapTable();
       if (map.size() != 0u) {
-        // Deduplicate the inner bittables within the CodeInfo.
-        std::vector<uint8_t>* data = &writer_->code_info_data_;
         size_t offset = dedupe_code_info_.GetOrCreate(map.data(), [=]() {
-          size_t deduped_offset = CodeInfo::Dedupe(data, map.data(), &dedupe_bit_table_);
-          if (kDebugVerifyDedupedCodeInfo) {
-            InstructionSet isa = writer_->GetCompilerOptions().GetInstructionSet();
-            std::stringstream old_code_info;
-            VariableIndentationOutputStream old_vios(&old_code_info);
-            std::stringstream new_code_info;
-            VariableIndentationOutputStream new_vios(&new_code_info);
-            CodeInfo(map.data()).Dump(&old_vios, 0, true, isa);
-            CodeInfo(data->data() + deduped_offset).Dump(&new_vios, 0, true, isa);
-            DCHECK_EQ(old_code_info.str(), new_code_info.str());
-          }
-          return offset_ + deduped_offset;
+          // Deduplicate the inner BitTable<>s within the CodeInfo.
+          return offset_ + dedupe_bit_table_.Dedupe(map.data());
         });
         // Code offset is not initialized yet, so set the map offset to 0u-offset.
         DCHECK_EQ(oat_class->method_offsets_[method_offsets_index_].code_offset_, 0u);
@@ -1487,8 +1475,8 @@
   // The compiler already deduplicated the pointers but it did not dedupe the tables.
   SafeMap<const uint8_t*, size_t> dedupe_code_info_;
 
-  // Deduplicate at BitTable level. The value is bit offset within code_info_data_.
-  std::map<BitMemoryRegion, uint32_t, BitMemoryRegion::Less> dedupe_bit_table_;
+  // Deduplicate at BitTable level.
+  CodeInfo::Deduper dedupe_bit_table_;
 };
 
 class OatWriter::InitImageMethodVisitor : public OatDexMethodVisitor {
@@ -1908,7 +1896,7 @@
       DCHECK(target != nullptr);
       const void* oat_code_offset =
           target->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_);
-      if (oat_code_offset != 0) {
+      if (oat_code_offset != nullptr) {
         DCHECK(!writer_->GetCompilerOptions().IsBootImage());
         DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickResolutionStub(oat_code_offset));
         DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(oat_code_offset));
@@ -2082,6 +2070,7 @@
     InitMapMethodVisitor visitor(this, offset);
     bool success = VisitDexMethods(&visitor);
     DCHECK(success);
+    code_info_data_.shrink_to_fit();
     offset += code_info_data_.size();
   }
   return offset;
@@ -3435,12 +3424,12 @@
   const ArtDexFileLoader dex_file_loader;
   if (oat_dex_file->source_.IsZipEntry()) {
     ZipEntry* zip_entry = oat_dex_file->source_.GetZipEntry();
-    std::unique_ptr<MemMap> mem_map;
+    MemMap mem_map;
     {
       TimingLogger::ScopedTiming extract("Unzip", timings_);
-      mem_map.reset(zip_entry->ExtractToMemMap(location.c_str(), "classes.dex", &error_msg));
+      mem_map = zip_entry->ExtractToMemMap(location.c_str(), "classes.dex", &error_msg);
     }
-    if (mem_map == nullptr) {
+    if (!mem_map.IsValid()) {
       LOG(ERROR) << "Failed to extract dex file to mem map for layout: " << error_msg;
       return false;
     }
@@ -3695,7 +3684,7 @@
 bool OatWriter::OpenDexFiles(
     File* file,
     bool verify,
-    /*out*/ std::vector<std::unique_ptr<MemMap>>* opened_dex_files_map,
+    /*out*/ std::vector<MemMap>* opened_dex_files_map,
     /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files) {
   TimingLogger::ScopedTiming split("OpenDexFiles", timings_);
 
@@ -3706,16 +3695,16 @@
 
   if (!extract_dex_files_into_vdex_) {
     std::vector<std::unique_ptr<const DexFile>> dex_files;
-    std::vector<std::unique_ptr<MemMap>> maps;
+    std::vector<MemMap> maps;
     for (OatDexFile& oat_dex_file : oat_dex_files_) {
       std::string error_msg;
-      MemMap* map = oat_dex_file.source_.GetZipEntry()->MapDirectlyOrExtract(
-          oat_dex_file.dex_file_location_data_, "zipped dex", &error_msg);
-      if (map == nullptr) {
+      maps.emplace_back(oat_dex_file.source_.GetZipEntry()->MapDirectlyOrExtract(
+          oat_dex_file.dex_file_location_data_, "zipped dex", &error_msg));
+      MemMap* map = &maps.back();
+      if (!map->IsValid()) {
         LOG(ERROR) << error_msg;
         return false;
       }
-      maps.emplace_back(map);
       // Now, open the dex file.
       const ArtDexFileLoader dex_file_loader;
       dex_files.emplace_back(dex_file_loader.Open(map->Begin(),
@@ -3746,7 +3735,7 @@
   size_t length = vdex_size_ - map_offset;
 
   std::string error_msg;
-  std::unique_ptr<MemMap> dex_files_map(MemMap::MapFile(
+  MemMap dex_files_map = MemMap::MapFile(
       length,
       PROT_READ | PROT_WRITE,
       MAP_SHARED,
@@ -3754,8 +3743,8 @@
       map_offset,
       /* low_4gb */ false,
       file->GetPath().c_str(),
-      &error_msg));
-  if (dex_files_map == nullptr) {
+      &error_msg);
+  if (!dex_files_map.IsValid()) {
     LOG(ERROR) << "Failed to mmap() dex files from oat file. File: " << file->GetPath()
                << " error: " << error_msg;
     return false;
@@ -3764,7 +3753,7 @@
   std::vector<std::unique_ptr<const DexFile>> dex_files;
   for (OatDexFile& oat_dex_file : oat_dex_files_) {
     const uint8_t* raw_dex_file =
-        dex_files_map->Begin() + oat_dex_file.dex_file_offset_ - map_offset;
+        dex_files_map.Begin() + oat_dex_file.dex_file_offset_ - map_offset;
 
     if (kIsDebugBuild) {
       // Sanity check our input files.
diff --git a/dex2oat/linker/oat_writer.h b/dex2oat/linker/oat_writer.h
index 9470f8c..5202d39 100644
--- a/dex2oat/linker/oat_writer.h
+++ b/dex2oat/linker/oat_writer.h
@@ -177,7 +177,7 @@
                             bool verify,
                             bool update_input_vdex,
                             CopyOption copy_dex_files,
-                            /*out*/ std::vector<std::unique_ptr<MemMap>>* opened_dex_files_map,
+                            /*out*/ std::vector<MemMap>* opened_dex_files_map,
                             /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files);
   // Initialize the writer with the given parameters.
   void Initialize(const CompilerDriver* compiler_driver,
@@ -315,7 +315,7 @@
                     bool update_input_vdex);
   bool OpenDexFiles(File* file,
                     bool verify,
-                    /*out*/ std::vector<std::unique_ptr<MemMap>>* opened_dex_files_map,
+                    /*out*/ std::vector<MemMap>* opened_dex_files_map,
                     /*out*/ std::vector<std::unique_ptr<const DexFile>>* opened_dex_files);
 
   size_t InitOatHeader(uint32_t num_dex_files, SafeMap<std::string, std::string>* key_value_store);
diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc
index d73f10a..0264b09 100644
--- a/dex2oat/linker/oat_writer_test.cc
+++ b/dex2oat/linker/oat_writer_test.cc
@@ -169,7 +169,7 @@
         oat_file);
     elf_writer->Start();
     OutputStream* oat_rodata = elf_writer->StartRoData();
-    std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps;
+    std::vector<MemMap> opened_dex_files_maps;
     std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
     if (!oat_writer.WriteAndOpenDexFiles(
         vdex_file,
@@ -246,7 +246,7 @@
       return false;
     }
 
-    for (std::unique_ptr<MemMap>& map : opened_dex_files_maps) {
+    for (MemMap& map : opened_dex_files_maps) {
       opened_dex_files_maps_.emplace_back(std::move(map));
     }
     for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files) {
@@ -261,7 +261,7 @@
 
   std::unique_ptr<QuickCompilerCallbacks> callbacks_;
 
-  std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps_;
+  std::vector<MemMap> opened_dex_files_maps_;
   std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
 };
 
diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h
index 9f355ba..8f853ea 100644
--- a/dexlayout/dex_ir.h
+++ b/dexlayout/dex_ir.h
@@ -926,8 +926,6 @@
   ClassData* GetClassData() { return class_data_; }
   EncodedArrayItem* StaticValues() { return static_values_; }
 
-  MethodItem* GenerateMethodItem(Header& header, ClassDataItemIterator& cdii);
-
   void Accept(AbstractDispatcher* dispatch) { dispatch->Dispatch(this); }
 
  private:
diff --git a/libartbase/base/bit_memory_region.h b/libartbase/base/bit_memory_region.h
index 7d8de39..5668b6c 100644
--- a/libartbase/base/bit_memory_region.h
+++ b/libartbase/base/bit_memory_region.h
@@ -29,47 +29,43 @@
 class BitMemoryRegion FINAL : public ValueObject {
  public:
   struct Less {
-    constexpr bool operator()(const BitMemoryRegion& lhs, const BitMemoryRegion& rhs) const {
-      if (lhs.size_in_bits() != rhs.size_in_bits()) {
-        return lhs.size_in_bits() < rhs.size_in_bits();
-      }
-      size_t bit = 0;
-      constexpr size_t kNumBits = BitSizeOf<uint32_t>();
-      for (; bit + kNumBits <= lhs.size_in_bits(); bit += kNumBits) {
-        uint32_t lhs_bits = lhs.LoadBits(bit, kNumBits);
-        uint32_t rhs_bits = rhs.LoadBits(bit, kNumBits);
-        if (lhs_bits != rhs_bits) {
-          return lhs_bits < rhs_bits;
-        }
-      }
-      size_t num_bits = lhs.size_in_bits() - bit;
-      return lhs.LoadBits(bit, num_bits) < rhs.LoadBits(bit, num_bits);
+    bool operator()(const BitMemoryRegion& lhs, const BitMemoryRegion& rhs) const {
+      return Compare(lhs, rhs) < 0;
     }
   };
 
   BitMemoryRegion() = default;
-  ALWAYS_INLINE BitMemoryRegion(void* data, size_t bit_start, size_t bit_size)
-    : data_(reinterpret_cast<uintptr_t*>(AlignDown(data, sizeof(uintptr_t)))),
-      bit_start_(bit_start + 8 * (reinterpret_cast<uintptr_t>(data) % sizeof(uintptr_t))),
-      bit_size_(bit_size) {
+  ALWAYS_INLINE BitMemoryRegion(uint8_t* data, ssize_t bit_start, size_t bit_size) {
+    // Normalize the data pointer. Note that bit_start may be negative.
+    uint8_t* aligned_data = AlignDown(data + (bit_start >> kBitsPerByteLog2), sizeof(uintptr_t));
+    data_ = reinterpret_cast<uintptr_t*>(aligned_data);
+    bit_start_ = bit_start + kBitsPerByte * (data - aligned_data);
+    bit_size_ = bit_size;
+    DCHECK_LT(bit_start_, static_cast<size_t>(kBitsPerIntPtrT));
   }
   ALWAYS_INLINE explicit BitMemoryRegion(MemoryRegion region)
     : BitMemoryRegion(region.begin(), /* bit_start */ 0, region.size_in_bits()) {
   }
   ALWAYS_INLINE BitMemoryRegion(MemoryRegion region, size_t bit_offset, size_t bit_length)
     : BitMemoryRegion(region) {
-    DCHECK_LE(bit_offset, bit_size_);
-    DCHECK_LE(bit_length, bit_size_ - bit_offset);
-    bit_start_ += bit_offset;
-    bit_size_ = bit_length;
+    *this = Subregion(bit_offset, bit_length);
   }
 
   ALWAYS_INLINE bool IsValid() const { return data_ != nullptr; }
 
+  const uint8_t* data() const {
+    DCHECK_ALIGNED(bit_start_, kBitsPerByte);
+    return reinterpret_cast<const uint8_t*>(data_) + bit_start_ / kBitsPerByte;
+  }
+
   size_t size_in_bits() const {
     return bit_size_;
   }
 
+  void Resize(size_t bit_size) {
+    bit_size_ = bit_size;
+  }
+
   ALWAYS_INLINE BitMemoryRegion Subregion(size_t bit_offset, size_t bit_length) const {
     DCHECK_LE(bit_offset, bit_size_);
     DCHECK_LE(bit_length, bit_size_ - bit_offset);
@@ -79,12 +75,11 @@
     return result;
   }
 
-  // Increase the size of the region and return the newly added range (starting at the old end).
-  ALWAYS_INLINE BitMemoryRegion Extend(size_t bit_length) {
+  ALWAYS_INLINE BitMemoryRegion Subregion(size_t bit_offset) const {
+    DCHECK_LE(bit_offset, bit_size_);
     BitMemoryRegion result = *this;
-    result.bit_start_ += result.bit_size_;
-    result.bit_size_ = bit_length;
-    bit_size_ += bit_length;
+    result.bit_start_ += bit_offset;
+    result.bit_size_ -= bit_offset;
     return result;
   }
 
@@ -183,10 +178,26 @@
     return count;
   }
 
-  ALWAYS_INLINE bool Equals(const BitMemoryRegion& other) const {
-    return data_ == other.data_ &&
-           bit_start_ == other.bit_start_ &&
-           bit_size_ == other.bit_size_;
+  static int Compare(const BitMemoryRegion& lhs, const BitMemoryRegion& rhs) {
+    if (lhs.size_in_bits() != rhs.size_in_bits()) {
+      return (lhs.size_in_bits() < rhs.size_in_bits()) ? -1 : 1;
+    }
+    size_t bit = 0;
+    constexpr size_t kNumBits = BitSizeOf<uint32_t>();
+    for (; bit + kNumBits <= lhs.size_in_bits(); bit += kNumBits) {
+      uint32_t lhs_bits = lhs.LoadBits(bit, kNumBits);
+      uint32_t rhs_bits = rhs.LoadBits(bit, kNumBits);
+      if (lhs_bits != rhs_bits) {
+        return (lhs_bits < rhs_bits) ? -1 : 1;
+      }
+    }
+    size_t num_bits = lhs.size_in_bits() - bit;
+    uint32_t lhs_bits = lhs.LoadBits(bit, num_bits);
+    uint32_t rhs_bits = rhs.LoadBits(bit, num_bits);
+    if (lhs_bits != rhs_bits) {
+      return (lhs_bits < rhs_bits) ? -1 : 1;
+    }
+    return 0;
   }
 
  private:
@@ -196,30 +207,49 @@
   size_t bit_size_ = 0;
 };
 
+constexpr uint32_t kVarintHeaderBits = 4;
+constexpr uint32_t kVarintSmallValue = 11;  // Maximum value which is stored as-is.
+
 class BitMemoryReader {
  public:
-  explicit BitMemoryReader(const uint8_t* data, size_t bit_offset = 0)
-      : finished_region_(const_cast<uint8_t*>(data), /* bit_start */ 0, bit_offset) {
-    DCHECK_EQ(GetBitOffset(), bit_offset);
+  BitMemoryReader(BitMemoryReader&&) = default;
+  explicit BitMemoryReader(BitMemoryRegion data)
+      : finished_region_(data.Subregion(0, 0) /* set the length to zero */ ) {
+  }
+  explicit BitMemoryReader(const uint8_t* data, ssize_t bit_offset = 0)
+      : finished_region_(const_cast<uint8_t*>(data), bit_offset, /* bit_length */ 0) {
   }
 
-  size_t GetBitOffset() const { return finished_region_.size_in_bits(); }
+  const uint8_t* data() const { return finished_region_.data(); }
 
-  ALWAYS_INLINE BitMemoryRegion Skip(size_t bit_length) {
-    return finished_region_.Extend(bit_length);
-  }
+  BitMemoryRegion GetReadRegion() const { return finished_region_; }
 
-  // Get the most recently read bits.
-  ALWAYS_INLINE BitMemoryRegion Tail(size_t bit_length) {
-    return finished_region_.Subregion(finished_region_.size_in_bits() - bit_length, bit_length);
+  size_t NumberOfReadBits() const { return finished_region_.size_in_bits(); }
+
+  ALWAYS_INLINE BitMemoryRegion ReadRegion(size_t bit_length) {
+    size_t bit_offset = finished_region_.size_in_bits();
+    finished_region_.Resize(bit_offset + bit_length);
+    return finished_region_.Subregion(bit_offset, bit_length);
   }
 
   ALWAYS_INLINE uint32_t ReadBits(size_t bit_length) {
-    return finished_region_.Extend(bit_length).LoadBits(0, bit_length);
+    return ReadRegion(bit_length).LoadBits(/* bit_offset */ 0, bit_length);
   }
 
   ALWAYS_INLINE bool ReadBit() {
-    return finished_region_.Extend(1).LoadBit(0);
+    return ReadRegion(/* bit_length */ 1).LoadBit(/* bit_offset */ 0);
+  }
+
+  // Read variable-length bit-packed integer.
+  // The first four bits determine the variable length of the encoded integer:
+  //   Values 0..11 represent the result as-is, with no further following bits.
+  //   Values 12..15 mean the result is in the next 8/16/24/32-bits respectively.
+  ALWAYS_INLINE uint32_t ReadVarint() {
+    uint32_t x = ReadBits(kVarintHeaderBits);
+    if (x > kVarintSmallValue) {
+      x = ReadBits((x - kVarintSmallValue) * kBitsPerByte);
+    }
+    return x;
   }
 
  private:
@@ -234,36 +264,58 @@
 class BitMemoryWriter {
  public:
   explicit BitMemoryWriter(Vector* out, size_t bit_offset = 0)
-      : out_(out), bit_offset_(bit_offset) {
-    DCHECK_EQ(GetBitOffset(), bit_offset);
+      : out_(out), bit_start_(bit_offset), bit_offset_(bit_offset) {
+    DCHECK_EQ(NumberOfWrittenBits(), 0u);
+  }
+
+  BitMemoryRegion GetWrittenRegion() const {
+    return BitMemoryRegion(out_->data(), bit_start_, bit_offset_ - bit_start_);
   }
 
   const uint8_t* data() const { return out_->data(); }
 
-  size_t GetBitOffset() const { return bit_offset_; }
+  size_t NumberOfWrittenBits() const { return bit_offset_ - bit_start_; }
 
   ALWAYS_INLINE BitMemoryRegion Allocate(size_t bit_length) {
     out_->resize(BitsToBytesRoundUp(bit_offset_ + bit_length));
-    BitMemoryRegion region(MemoryRegion(out_->data(), out_->size()), bit_offset_, bit_length);
+    BitMemoryRegion region(out_->data(), bit_offset_, bit_length);
     DCHECK_LE(bit_length, std::numeric_limits<size_t>::max() - bit_offset_) << "Overflow";
     bit_offset_ += bit_length;
     return region;
   }
 
+  ALWAYS_INLINE void WriteRegion(const BitMemoryRegion& region) {
+    Allocate(region.size_in_bits()).StoreBits(/* bit_offset */ 0, region, region.size_in_bits());
+  }
+
   ALWAYS_INLINE void WriteBits(uint32_t value, size_t bit_length) {
-    Allocate(bit_length).StoreBits(0, value, bit_length);
+    Allocate(bit_length).StoreBits(/* bit_offset */ 0, value, bit_length);
   }
 
   ALWAYS_INLINE void WriteBit(bool value) {
-    Allocate(1).StoreBit(0, value);
+    Allocate(1).StoreBit(/* bit_offset */ 0, value);
   }
 
-  ALWAYS_INLINE void WriteRegion(const BitMemoryRegion& region) {
-    Allocate(region.size_in_bits()).StoreBits(0, region, region.size_in_bits());
+  // Write variable-length bit-packed integer.
+  ALWAYS_INLINE void WriteVarint(uint32_t value) {
+    if (value <= kVarintSmallValue) {
+      WriteBits(value, kVarintHeaderBits);
+    } else {
+      uint32_t num_bits = RoundUp(MinimumBitsToStore(value), kBitsPerByte);
+      uint32_t header = kVarintSmallValue + num_bits / kBitsPerByte;
+      WriteBits(header, kVarintHeaderBits);
+      WriteBits(value, num_bits);
+    }
+  }
+
+  ALWAYS_INLINE void ByteAlign() {
+    size_t end = bit_start_ + bit_offset_;
+    bit_offset_ += RoundUp(end, kBitsPerByte) - end;
   }
 
  private:
   Vector* out_;
+  size_t bit_start_;
   size_t bit_offset_;
 
   DISALLOW_COPY_AND_ASSIGN(BitMemoryWriter);
diff --git a/libartbase/base/bit_memory_region_test.cc b/libartbase/base/bit_memory_region_test.cc
index b754698..02623bf 100644
--- a/libartbase/base/bit_memory_region_test.cc
+++ b/libartbase/base/bit_memory_region_test.cc
@@ -33,6 +33,24 @@
   }
 }
 
+TEST(BitMemoryRegion, TestVarint) {
+  for (size_t start_bit_offset = 0; start_bit_offset <= 32; start_bit_offset++) {
+    uint32_t values[] = { 0, 1, 11, 12, 15, 16, 255, 256, 1u << 16, 1u << 24, ~1u, ~0u };
+    for (uint32_t value : values) {
+      std::vector<uint8_t> buffer;
+      BitMemoryWriter<std::vector<uint8_t>> writer(&buffer, start_bit_offset);
+      writer.WriteVarint(value);
+
+      BitMemoryReader reader(buffer.data(), start_bit_offset);
+      uint32_t result = reader.ReadVarint();
+      uint32_t upper_bound = RoundUp(MinimumBitsToStore(value), kBitsPerByte) + kVarintHeaderBits;
+      EXPECT_EQ(writer.NumberOfWrittenBits(), reader.NumberOfReadBits());
+      EXPECT_EQ(value, result);
+      EXPECT_GE(upper_bound, writer.NumberOfWrittenBits());
+    }
+  }
+}
+
 TEST(BitMemoryRegion, TestBit) {
   uint8_t data[sizeof(uint32_t) * 2];
   for (size_t bit_offset = 0; bit_offset < 2 * sizeof(uint32_t) * kBitsPerByte; ++bit_offset) {
diff --git a/libartbase/base/bit_struct_detail.h b/libartbase/base/bit_struct_detail.h
index 68c2e44..60de1b6 100644
--- a/libartbase/base/bit_struct_detail.h
+++ b/libartbase/base/bit_struct_detail.h
@@ -85,7 +85,7 @@
   static constexpr FalseT Test(...);
 
  public:
-  static constexpr bool value = decltype(Test<T>(0))::value;
+  static constexpr bool value = decltype(Test<T>(nullptr))::value;
 };
 
 // Infer the type of the member of &T::M.
diff --git a/libartbase/base/bit_table.h b/libartbase/base/bit_table.h
index 1c7614b..54e8861 100644
--- a/libartbase/base/bit_table.h
+++ b/libartbase/base/bit_table.h
@@ -33,34 +33,6 @@
 
 namespace art {
 
-constexpr uint32_t kVarintHeaderBits = 4;
-constexpr uint32_t kVarintSmallValue = 11;  // Maximum value which is stored as-is.
-
-// Load variable-length bit-packed integer from `data` starting at `bit_offset`.
-// The first four bits determine the variable length of the encoded integer:
-//   Values 0..11 represent the result as-is, with no further following bits.
-//   Values 12..15 mean the result is in the next 8/16/24/32-bits respectively.
-ALWAYS_INLINE static inline uint32_t DecodeVarintBits(BitMemoryReader& reader) {
-  uint32_t x = reader.ReadBits(kVarintHeaderBits);
-  if (x > kVarintSmallValue) {
-    x = reader.ReadBits((x - kVarintSmallValue) * kBitsPerByte);
-  }
-  return x;
-}
-
-// Store variable-length bit-packed integer from `data` starting at `bit_offset`.
-template<typename Vector>
-ALWAYS_INLINE static inline void EncodeVarintBits(BitMemoryWriter<Vector>& out, uint32_t value) {
-  if (value <= kVarintSmallValue) {
-    out.WriteBits(value, kVarintHeaderBits);
-  } else {
-    uint32_t num_bits = RoundUp(MinimumBitsToStore(value), kBitsPerByte);
-    uint32_t header = kVarintSmallValue + num_bits / kBitsPerByte;
-    out.WriteBits(header, kVarintHeaderBits);
-    out.WriteBits(value, num_bits);
-  }
-}
-
 // Generic purpose table of uint32_t values, which are tightly packed at bit level.
 // It has its own header with the number of rows and the bit-widths of all columns.
 // The values are accessible by (row, column).  The value -1 is stored efficiently.
@@ -77,23 +49,21 @@
 
   ALWAYS_INLINE void Decode(BitMemoryReader& reader) {
     // Decode row count and column sizes from the table header.
-    size_t initial_bit_offset = reader.GetBitOffset();
-    num_rows_ = DecodeVarintBits(reader);
+    num_rows_ = reader.ReadVarint();
     if (num_rows_ != 0) {
       column_offset_[0] = 0;
       for (uint32_t i = 0; i < kNumColumns; i++) {
-        size_t column_end = column_offset_[i] + DecodeVarintBits(reader);
+        size_t column_end = column_offset_[i] + reader.ReadVarint();
         column_offset_[i + 1] = dchecked_integral_cast<uint16_t>(column_end);
       }
     }
-    header_bit_size_ = reader.GetBitOffset() - initial_bit_offset;
 
     // Record the region which contains the table data and skip past it.
-    table_data_ = reader.Skip(num_rows_ * NumRowBits());
+    table_data_ = reader.ReadRegion(num_rows_ * NumRowBits());
   }
 
   ALWAYS_INLINE uint32_t Get(uint32_t row, uint32_t column = 0) const {
-    DCHECK_NE(header_bit_size_, 0u) << "Table has not been loaded";
+    DCHECK(table_data_.IsValid()) << "Table has not been loaded";
     DCHECK_LT(row, num_rows_);
     DCHECK_LT(column, kNumColumns);
     size_t offset = row * NumRowBits() + column_offset_[column];
@@ -101,7 +71,7 @@
   }
 
   ALWAYS_INLINE BitMemoryRegion GetBitMemoryRegion(uint32_t row, uint32_t column = 0) const {
-    DCHECK_NE(header_bit_size_, 0u) << "Table has not been loaded";
+    DCHECK(table_data_.IsValid()) << "Table has not been loaded";
     DCHECK_LT(row, num_rows_);
     DCHECK_LT(column, kNumColumns);
     size_t offset = row * NumRowBits() + column_offset_[column];
@@ -118,16 +88,20 @@
     return column_offset_[column + 1] - column_offset_[column];
   }
 
-  size_t HeaderBitSize() const { return header_bit_size_; }
+  size_t DataBitSize() const { return table_data_.size_in_bits(); }
 
-  size_t BitSize() const { return header_bit_size_ + table_data_.size_in_bits(); }
+  bool Equals(const BitTableBase& other) const {
+    return num_rows_ == other.num_rows_ &&
+        std::equal(column_offset_, column_offset_ + kNumColumns, other.column_offset_) &&
+        BitMemoryRegion::Compare(table_data_, other.table_data_) == 0;
+  }
 
  protected:
   BitMemoryRegion table_data_;
   size_t num_rows_ = 0;
-
   uint16_t column_offset_[kNumColumns + 1] = {};
-  uint16_t header_bit_size_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(BitTableBase);
 };
 
 // Helper class which can be used to create BitTable accessors with named getters.
@@ -151,9 +125,10 @@
   }
 
 // Helper macro to create constructors and per-table utilities in derived class.
-#define BIT_TABLE_HEADER()                                                           \
+#define BIT_TABLE_HEADER(NAME)                                                       \
   using BitTableAccessor<kNumColumns>::BitTableAccessor; /* inherit constructors */  \
   template<int COLUMN, int UNUSED /*needed to compile*/> struct ColumnName;          \
+  static constexpr const char* kTableName = #NAME;                                   \
 
 // Helper macro to create named column accessors in derived class.
 #define BIT_TABLE_COLUMN(COLUMN, NAME)                                               \
@@ -176,12 +151,6 @@
   return names;
 }
 
-// Returns the names of all columns in the given accessor.
-template<typename Accessor>
-static const char* const* GetBitTableColumnNames() {
-  return GetBitTableColumnNamesImpl<Accessor>(std::make_index_sequence<Accessor::kNumColumns>());
-}
-
 // Wrapper which makes it easier to use named accessors for the individual rows.
 template<typename Accessor>
 class BitTable : public BitTableBase<Accessor::kNumColumns> {
@@ -239,6 +208,14 @@
   ALWAYS_INLINE Accessor GetInvalidRow() const {
     return Accessor(this, static_cast<uint32_t>(-1));
   }
+
+  const char* GetName() const {
+    return Accessor::kTableName;
+  }
+
+  const char* const* GetColumnNames() const {
+    return GetBitTableColumnNamesImpl<Accessor>(std::make_index_sequence<Accessor::kNumColumns>());
+  }
 };
 
 template<typename Accessor>
@@ -376,15 +353,15 @@
   // Encode the stored data into a BitTable.
   template<typename Vector>
   void Encode(BitMemoryWriter<Vector>& out) const {
-    size_t initial_bit_offset = out.GetBitOffset();
+    size_t initial_bit_offset = out.NumberOfWrittenBits();
 
     std::array<uint32_t, kNumColumns> column_bits;
     Measure(&column_bits);
-    EncodeVarintBits(out, size());
+    out.WriteVarint(size());
     if (size() != 0) {
       // Write table header.
       for (uint32_t c = 0; c < kNumColumns; c++) {
-        EncodeVarintBits(out, column_bits[c]);
+        out.WriteVarint(column_bits[c]);
       }
 
       // Write table data.
@@ -398,7 +375,7 @@
     // Verify the written data.
     if (kIsDebugBuild) {
       BitTableBase<kNumColumns> table;
-      BitMemoryReader reader(out.data(), initial_bit_offset);
+      BitMemoryReader reader(out.GetWrittenRegion().Subregion(initial_bit_offset));
       table.Decode(reader);
       DCHECK_EQ(size(), table.NumRows());
       for (uint32_t c = 0; c < kNumColumns; c++) {
@@ -467,11 +444,11 @@
   // Encode the stored data into a BitTable.
   template<typename Vector>
   void Encode(BitMemoryWriter<Vector>& out) const {
-    size_t initial_bit_offset = out.GetBitOffset();
+    size_t initial_bit_offset = out.NumberOfWrittenBits();
 
-    EncodeVarintBits(out, size());
+    out.WriteVarint(size());
     if (size() != 0) {
-      EncodeVarintBits(out, max_num_bits_);
+      out.WriteVarint(max_num_bits_);
 
       // Write table data.
       for (MemoryRegion row : rows_) {
@@ -484,7 +461,7 @@
     // Verify the written data.
     if (kIsDebugBuild) {
       BitTableBase<1> table;
-      BitMemoryReader reader(out.data(), initial_bit_offset);
+      BitMemoryReader reader(out.GetWrittenRegion().Subregion(initial_bit_offset));
       table.Decode(reader);
       DCHECK_EQ(size(), table.NumRows());
       DCHECK_EQ(max_num_bits_, table.NumColumnBits(0));
diff --git a/libartbase/base/bit_table_test.cc b/libartbase/base/bit_table_test.cc
index 2fd9052..bf32dc6 100644
--- a/libartbase/base/bit_table_test.cc
+++ b/libartbase/base/bit_table_test.cc
@@ -26,22 +26,6 @@
 
 namespace art {
 
-TEST(BitTableTest, TestVarint) {
-  for (size_t start_bit_offset = 0; start_bit_offset <= 32; start_bit_offset++) {
-    uint32_t values[] = { 0, 1, 11, 12, 15, 16, 255, 256, ~1u, ~0u };
-    for (uint32_t value : values) {
-      std::vector<uint8_t> buffer;
-      BitMemoryWriter<std::vector<uint8_t>> writer(&buffer, start_bit_offset);
-      EncodeVarintBits(writer, value);
-
-      BitMemoryReader reader(buffer.data(), start_bit_offset);
-      uint32_t result = DecodeVarintBits(reader);
-      EXPECT_EQ(writer.GetBitOffset(), reader.GetBitOffset());
-      EXPECT_EQ(value, result);
-    }
-  }
-}
-
 TEST(BitTableTest, TestEmptyTable) {
   MallocArenaPool pool;
   ArenaStack arena_stack(&pool);
@@ -54,7 +38,7 @@
 
   BitMemoryReader reader(buffer.data());
   BitTableBase<1> table(reader);
-  EXPECT_EQ(writer.GetBitOffset(), reader.GetBitOffset());
+  EXPECT_EQ(writer.NumberOfWrittenBits(), reader.NumberOfReadBits());
   EXPECT_EQ(0u, table.NumRows());
 }
 
@@ -75,7 +59,7 @@
 
   BitMemoryReader reader(buffer.data());
   BitTableBase<1> table(reader);
-  EXPECT_EQ(writer.GetBitOffset(), reader.GetBitOffset());
+  EXPECT_EQ(writer.NumberOfWrittenBits(), reader.NumberOfReadBits());
   EXPECT_EQ(4u, table.NumRows());
   EXPECT_EQ(42u, table.Get(0));
   EXPECT_EQ(kNoValue, table.Get(1));
@@ -98,7 +82,7 @@
 
     BitMemoryReader reader(buffer.data(), start_bit_offset);
     BitTableBase<1> table(reader);
-    EXPECT_EQ(writer.GetBitOffset(), reader.GetBitOffset());
+    EXPECT_EQ(writer.NumberOfWrittenBits(), reader.NumberOfReadBits());
     EXPECT_EQ(1u, table.NumRows());
     EXPECT_EQ(42u, table.Get(0));
   }
@@ -119,7 +103,7 @@
 
   BitMemoryReader reader(buffer.data());
   BitTableBase<4> table(reader);
-  EXPECT_EQ(writer.GetBitOffset(), reader.GetBitOffset());
+  EXPECT_EQ(writer.NumberOfWrittenBits(), reader.NumberOfReadBits());
   EXPECT_EQ(2u, table.NumRows());
   EXPECT_EQ(42u, table.Get(0, 0));
   EXPECT_EQ(kNoValue, table.Get(0, 1));
@@ -169,7 +153,7 @@
 
   BitMemoryReader reader(buffer.data());
   BitTableBase<1> table(reader);
-  EXPECT_EQ(writer.GetBitOffset(), reader.GetBitOffset());
+  EXPECT_EQ(writer.NumberOfWrittenBits(), reader.NumberOfReadBits());
   for (auto it : indicies) {
     uint64_t expected = it.first;
     BitMemoryRegion actual = table.GetBitMemoryRegion(it.second);
diff --git a/libartbase/base/globals.h b/libartbase/base/globals.h
index cd0bf8f..bc79ff2 100644
--- a/libartbase/base/globals.h
+++ b/libartbase/base/globals.h
@@ -122,6 +122,14 @@
 static constexpr bool kMarkCompactSupport = false && kMovingCollector;
 // True if we allow moving classes.
 static constexpr bool kMovingClasses = !kMarkCompactSupport;
+// If true, enable generational collection when using the Concurrent Copying
+// collector, i.e. use sticky-bit CC for minor collections and (full) CC for
+// major collections.
+#ifdef ART_USE_GENERATIONAL_CC
+static constexpr bool kEnableGenerationalConcurrentCopyingCollection = true;
+#else
+static constexpr bool kEnableGenerationalConcurrentCopyingCollection = false;
+#endif
 
 // If true, enable the tlab allocator by default.
 #ifdef ART_USE_TLAB
diff --git a/libartbase/base/hash_set.h b/libartbase/base/hash_set.h
index 2b1a5eb..42aa46f 100644
--- a/libartbase/base/hash_set.h
+++ b/libartbase/base/hash_set.h
@@ -359,6 +359,9 @@
   // and set the empty slot to be the location we just moved from.
   // Relies on maintaining the invariant that there's no empty slots from the 'ideal' index of an
   // element to its actual location/index.
+  // Note that since erase shuffles back elements, it may result in the same element being visited
+  // twice during HashSet iteration. This happens when an element already visited during iteration
+  // gets shuffled to the end of the bucket array.
   iterator erase(iterator it) {
     // empty_index is the index that will become empty.
     size_t empty_index = it.index_;
diff --git a/libartbase/base/mem_map.cc b/libartbase/base/mem_map.cc
index 5cea869..d9760bd 100644
--- a/libartbase/base/mem_map.cc
+++ b/libartbase/base/mem_map.cc
@@ -61,6 +61,21 @@
 // All the non-empty MemMaps. Use a multimap as we do a reserve-and-divide (eg ElfMap::Load()).
 static Maps* gMaps GUARDED_BY(MemMap::GetMemMapsLock()) = nullptr;
 
+// Retrieve iterator to a `gMaps` entry that is known to exist.
+Maps::iterator GetGMapsEntry(const MemMap& map) REQUIRES(MemMap::GetMemMapsLock()) {
+  DCHECK(map.IsValid());
+  DCHECK(gMaps != nullptr);
+  for (auto it = gMaps->lower_bound(map.BaseBegin()), end = gMaps->end();
+       it != end && it->first == map.BaseBegin();
+       ++it) {
+    if (it->second == &map) {
+      return it;
+    }
+  }
+  LOG(FATAL) << "MemMap not found";
+  UNREACHABLE();
+}
+
 std::ostream& operator<<(std::ostream& os, const Maps& mem_maps) {
   os << "MemMap:" << std::endl;
   for (auto it = mem_maps.begin(); it != mem_maps.end(); ++it) {
@@ -231,20 +246,21 @@
 }
 #endif
 
-MemMap* MemMap::MapAnonymous(const char* name,
-                             uint8_t* expected_ptr,
-                             size_t byte_count,
-                             int prot,
-                             bool low_4gb,
-                             bool reuse,
-                             std::string* error_msg,
-                             bool use_ashmem) {
+MemMap MemMap::MapAnonymous(const char* name,
+                            uint8_t* addr,
+                            size_t byte_count,
+                            int prot,
+                            bool low_4gb,
+                            bool reuse,
+                            std::string* error_msg,
+                            bool use_ashmem) {
 #ifndef __LP64__
   UNUSED(low_4gb);
 #endif
   use_ashmem = use_ashmem && !kIsTargetLinux && !kIsTargetFuchsia;
   if (byte_count == 0) {
-    return new MemMap(name, nullptr, 0, nullptr, 0, prot, false);
+    *error_msg = "Empty MemMap requested.";
+    return Invalid();
   }
   size_t page_aligned_byte_count = RoundUp(byte_count, kPageSize);
 
@@ -252,9 +268,9 @@
   if (reuse) {
     // reuse means it is okay that it overlaps an existing page mapping.
     // Only use this if you actually made the page reservation yourself.
-    CHECK(expected_ptr != nullptr);
+    CHECK(addr != nullptr);
 
-    DCHECK(ContainedWithinExistingMap(expected_ptr, byte_count, error_msg)) << *error_msg;
+    DCHECK(ContainedWithinExistingMap(addr, byte_count, error_msg)) << *error_msg;
     flags |= MAP_FIXED;
   }
 
@@ -296,7 +312,7 @@
   // We need to store and potentially set an error number for pretty printing of errors
   int saved_errno = 0;
 
-  void* actual = MapInternal(expected_ptr,
+  void* actual = MapInternal(addr,
                              page_aligned_byte_count,
                              prot,
                              flags,
@@ -313,28 +329,33 @@
 
       *error_msg = StringPrintf("Failed anonymous mmap(%p, %zd, 0x%x, 0x%x, %d, 0): %s. "
                                     "See process maps in the log.",
-                                expected_ptr,
+                                addr,
                                 page_aligned_byte_count,
                                 prot,
                                 flags,
                                 fd.get(),
                                 strerror(saved_errno));
     }
-    return nullptr;
+    return Invalid();
   }
-  if (!CheckMapRequest(expected_ptr, actual, page_aligned_byte_count, error_msg)) {
-    return nullptr;
+  if (!CheckMapRequest(addr, actual, page_aligned_byte_count, error_msg)) {
+    return Invalid();
   }
-  return new MemMap(name, reinterpret_cast<uint8_t*>(actual), byte_count, actual,
-                    page_aligned_byte_count, prot, reuse);
+  return MemMap(name,
+                reinterpret_cast<uint8_t*>(actual),
+                byte_count,
+                actual,
+                page_aligned_byte_count,
+                prot,
+                reuse);
 }
 
-MemMap* MemMap::MapDummy(const char* name, uint8_t* addr, size_t byte_count) {
+MemMap MemMap::MapDummy(const char* name, uint8_t* addr, size_t byte_count) {
   if (byte_count == 0) {
-    return new MemMap(name, nullptr, 0, nullptr, 0, 0, false);
+    return Invalid();
   }
   const size_t page_aligned_byte_count = RoundUp(byte_count, kPageSize);
-  return new MemMap(name, addr, byte_count, addr, page_aligned_byte_count, 0, true /* reuse */);
+  return MemMap(name, addr, byte_count, addr, page_aligned_byte_count, 0, true /* reuse */);
 }
 
 template<typename A, typename B>
@@ -342,19 +363,18 @@
   return static_cast<ptrdiff_t>(reinterpret_cast<intptr_t>(a) - reinterpret_cast<intptr_t>(b));
 }
 
-bool MemMap::ReplaceWith(MemMap** source_ptr, /*out*/std::string* error) {
+bool MemMap::ReplaceWith(MemMap* source, /*out*/std::string* error) {
 #if !HAVE_MREMAP_SYSCALL
-  UNUSED(source_ptr);
+  UNUSED(source);
   *error = "Cannot perform atomic replace because we are missing the required mremap syscall";
   return false;
 #else  // !HAVE_MREMAP_SYSCALL
-  CHECK(source_ptr != nullptr);
-  CHECK(*source_ptr != nullptr);
+  CHECK(source != nullptr);
+  CHECK(source->IsValid());
   if (!MemMap::kCanReplaceMapping) {
     *error = "Unable to perform atomic replace due to runtime environment!";
     return false;
   }
-  MemMap* source = *source_ptr;
   // neither can be reuse.
   if (source->reuse_ || reuse_) {
     *error = "One or both mappings is not a real mmap!";
@@ -406,12 +426,9 @@
   // them later.
   size_t new_base_size = std::max(source->base_size_, base_size_);
 
-  // Delete the old source, don't unmap it though (set reuse) since it is already gone.
-  *source_ptr = nullptr;
+  // Invalidate *source, don't unmap it though since it is already gone.
   size_t source_size = source->size_;
-  source->already_unmapped_ = true;
-  delete source;
-  source = nullptr;
+  source->Invalidate();
 
   size_ = source_size;
   base_size_ = new_base_size;
@@ -422,16 +439,16 @@
 #endif  // !HAVE_MREMAP_SYSCALL
 }
 
-MemMap* MemMap::MapFileAtAddress(uint8_t* expected_ptr,
-                                 size_t byte_count,
-                                 int prot,
-                                 int flags,
-                                 int fd,
-                                 off_t start,
-                                 bool low_4gb,
-                                 bool reuse,
-                                 const char* filename,
-                                 std::string* error_msg) {
+MemMap MemMap::MapFileAtAddress(uint8_t* expected_ptr,
+                                size_t byte_count,
+                                int prot,
+                                int flags,
+                                int fd,
+                                off_t start,
+                                bool low_4gb,
+                                bool reuse,
+                                const char* filename,
+                                std::string* error_msg) {
   CHECK_NE(0, prot);
   CHECK_NE(0, flags & (MAP_SHARED | MAP_PRIVATE));
 
@@ -452,7 +469,7 @@
   }
 
   if (byte_count == 0) {
-    return new MemMap(filename, nullptr, 0, nullptr, 0, prot, false);
+    return Invalid();
   }
   // Adjust 'offset' to be page-aligned as required by mmap.
   int page_offset = start % kPageSize;
@@ -491,10 +508,10 @@
                                 static_cast<int64_t>(page_aligned_offset), filename,
                                 strerror(saved_errno));
     }
-    return nullptr;
+    return Invalid();
   }
   if (!CheckMapRequest(expected_ptr, actual, page_aligned_byte_count, error_msg)) {
-    return nullptr;
+    return Invalid();
   }
   if (redzone_size != 0) {
     const uint8_t *real_start = actual + page_offset;
@@ -506,14 +523,27 @@
     page_aligned_byte_count -= redzone_size;
   }
 
-  return new MemMap(filename, actual + page_offset, byte_count, actual, page_aligned_byte_count,
-                    prot, reuse, redzone_size);
+  return MemMap(filename,
+                actual + page_offset,
+                byte_count,
+                actual,
+                page_aligned_byte_count,
+                prot,
+                reuse,
+                redzone_size);
+}
+
+MemMap::MemMap(MemMap&& other)
+    : MemMap() {
+  swap(other);
 }
 
 MemMap::~MemMap() {
-  if (base_begin_ == nullptr && base_size_ == 0) {
-    return;
-  }
+  Reset();
+}
+
+void MemMap::DoReset() {
+  DCHECK(IsValid());
 
   // Unlike Valgrind, AddressSanitizer requires that all manually poisoned memory is unpoisoned
   // before it is returned to the system.
@@ -533,19 +563,56 @@
     }
   }
 
+  Invalidate();
+}
+
+void MemMap::Invalidate() {
+  DCHECK(IsValid());
+
   // Remove it from gMaps.
   std::lock_guard<std::mutex> mu(*mem_maps_lock_);
-  bool found = false;
-  DCHECK(gMaps != nullptr);
-  for (auto it = gMaps->lower_bound(base_begin_), end = gMaps->end();
-       it != end && it->first == base_begin_; ++it) {
-    if (it->second == this) {
-      found = true;
-      gMaps->erase(it);
-      break;
+  auto it = GetGMapsEntry(*this);
+  gMaps->erase(it);
+
+  // Mark it as invalid.
+  base_size_ = 0u;
+  DCHECK(!IsValid());
+}
+
+void MemMap::swap(MemMap& other) {
+  if (IsValid() || other.IsValid()) {
+    std::lock_guard<std::mutex> mu(*mem_maps_lock_);
+    DCHECK(gMaps != nullptr);
+    auto this_it = IsValid() ? GetGMapsEntry(*this) : gMaps->end();
+    auto other_it = other.IsValid() ? GetGMapsEntry(other) : gMaps->end();
+    if (IsValid()) {
+      DCHECK(this_it != gMaps->end());
+      DCHECK_EQ(this_it->second, this);
+      this_it->second = &other;
     }
+    if (other.IsValid()) {
+      DCHECK(other_it != gMaps->end());
+      DCHECK_EQ(other_it->second, &other);
+      other_it->second = this;
+    }
+    // Swap members with the `mem_maps_lock_` held so that `base_begin_` matches
+    // with the `gMaps` key when other threads try to use `gMaps`.
+    SwapMembers(other);
+  } else {
+    SwapMembers(other);
   }
-  CHECK(found) << "MemMap not found";
+}
+
+void MemMap::SwapMembers(MemMap& other) {
+  name_.swap(other.name_);
+  std::swap(begin_, other.begin_);
+  std::swap(size_, other.size_);
+  std::swap(base_begin_, other.base_begin_);
+  std::swap(base_size_, other.base_size_);
+  std::swap(prot_, other.prot_);
+  std::swap(reuse_, other.reuse_);
+  std::swap(already_unmapped_, other.already_unmapped_);
+  std::swap(redzone_size_, other.redzone_size_);
 }
 
 MemMap::MemMap(const std::string& name, uint8_t* begin, size_t size, void* base_begin,
@@ -568,8 +635,11 @@
   }
 }
 
-MemMap* MemMap::RemapAtEnd(uint8_t* new_end, const char* tail_name, int tail_prot,
-                           std::string* error_msg, bool use_ashmem) {
+MemMap MemMap::RemapAtEnd(uint8_t* new_end,
+                          const char* tail_name,
+                          int tail_prot,
+                          std::string* error_msg,
+                          bool use_ashmem) {
   use_ashmem = use_ashmem && !kIsTargetLinux && !kIsTargetFuchsia;
   DCHECK_GE(new_end, Begin());
   DCHECK_LE(new_end, End());
@@ -583,11 +653,11 @@
   uint8_t* new_base_end = new_end;
   DCHECK_LE(new_base_end, old_base_end);
   if (new_base_end == old_base_end) {
-    return new MemMap(tail_name, nullptr, 0, nullptr, 0, tail_prot, false);
+    return Invalid();
   }
-  size_ = new_end - reinterpret_cast<uint8_t*>(begin_);
-  base_size_ = new_base_end - reinterpret_cast<uint8_t*>(base_begin_);
-  DCHECK_LE(begin_ + size_, reinterpret_cast<uint8_t*>(base_begin_) + base_size_);
+  size_t new_size = new_end - reinterpret_cast<uint8_t*>(begin_);
+  size_t new_base_size = new_base_end - reinterpret_cast<uint8_t*>(base_begin_);
+  DCHECK_LE(begin_ + new_size, reinterpret_cast<uint8_t*>(base_begin_) + new_base_size);
   size_t tail_size = old_end - new_end;
   uint8_t* tail_base_begin = new_base_end;
   size_t tail_base_size = old_base_end - new_base_end;
@@ -595,7 +665,7 @@
   DCHECK_ALIGNED(tail_base_size, kPageSize);
 
   unique_fd fd;
-  int flags = MAP_PRIVATE | MAP_ANONYMOUS;
+  int flags = MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS;
   if (use_ashmem) {
     // android_os_Debug.cpp read_mapinfo assumes all ashmem regions associated with the VM are
     // prefixed "dalvik-".
@@ -606,23 +676,14 @@
     if (fd.get() == -1) {
       *error_msg = StringPrintf("ashmem_create_region failed for '%s': %s",
                                 tail_name, strerror(errno));
-      return nullptr;
+      return Invalid();
     }
   }
 
   MEMORY_TOOL_MAKE_UNDEFINED(tail_base_begin, tail_base_size);
-  // Unmap/map the tail region.
-  int result = TargetMUnmap(tail_base_begin, tail_base_size);
-  if (result == -1) {
-    PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
-    *error_msg = StringPrintf("munmap(%p, %zd) failed for '%s'. See process maps in the log.",
-                              tail_base_begin, tail_base_size, name_.c_str());
-    return nullptr;
-  }
-  // Don't cause memory allocation between the munmap and the mmap
-  // calls. Otherwise, libc (or something else) might take this memory
-  // region. Note this isn't perfect as there's no way to prevent
-  // other threads to try to take this memory region here.
+  // Note: Do not explicitly unmap the tail region, mmap() with MAP_FIXED automatically
+  // removes old mappings for the overlapping region. This makes the operation atomic
+  // and prevents other threads from racing to allocate memory in the requested region.
   uint8_t* actual = reinterpret_cast<uint8_t*>(TargetMMap(tail_base_begin,
                                                           tail_base_size,
                                                           tail_prot,
@@ -634,9 +695,18 @@
     *error_msg = StringPrintf("anonymous mmap(%p, %zd, 0x%x, 0x%x, %d, 0) failed. See process "
                               "maps in the log.", tail_base_begin, tail_base_size, tail_prot, flags,
                               fd.get());
-    return nullptr;
+    return Invalid();
   }
-  return new MemMap(tail_name, actual, tail_size, actual, tail_base_size, tail_prot, false);
+  // Update *this.
+  if (new_base_size == 0u) {
+    std::lock_guard<std::mutex> mu(*mem_maps_lock_);
+    auto it = GetGMapsEntry(*this);
+    gMaps->erase(it);
+  }
+  size_ = new_size;
+  base_size_ = new_base_size;
+  // Return the new mapping.
+  return MemMap(tail_name, actual, tail_size, actual, tail_base_size, tail_prot, false);
 }
 
 void MemMap::MadviseDontNeedAndZero() {
@@ -675,15 +745,15 @@
   return false;
 }
 
-bool MemMap::CheckNoGaps(MemMap* begin_map, MemMap* end_map) {
+bool MemMap::CheckNoGaps(MemMap& begin_map, MemMap& end_map) {
   std::lock_guard<std::mutex> mu(*mem_maps_lock_);
-  CHECK(begin_map != nullptr);
-  CHECK(end_map != nullptr);
+  CHECK(begin_map.IsValid());
+  CHECK(end_map.IsValid());
   CHECK(HasMemMap(begin_map));
   CHECK(HasMemMap(end_map));
-  CHECK_LE(begin_map->BaseBegin(), end_map->BaseBegin());
-  MemMap* map = begin_map;
-  while (map->BaseBegin() != end_map->BaseBegin()) {
+  CHECK_LE(begin_map.BaseBegin(), end_map.BaseBegin());
+  MemMap* map = &begin_map;
+  while (map->BaseBegin() != end_map.BaseBegin()) {
     MemMap* next_map = GetLargestMemMapAt(map->BaseEnd());
     if (next_map == nullptr) {
       // Found a gap.
@@ -758,11 +828,11 @@
   }
 }
 
-bool MemMap::HasMemMap(MemMap* map) {
-  void* base_begin = map->BaseBegin();
+bool MemMap::HasMemMap(MemMap& map) {
+  void* base_begin = map.BaseBegin();
   for (auto it = gMaps->lower_bound(base_begin), end = gMaps->end();
        it != end && it->first == base_begin; ++it) {
-    if (it->second == map) {
+    if (it->second == &map) {
       return true;
     }
   }
@@ -1049,6 +1119,7 @@
   CHECK_EQ(size_, base_size_) << "Unsupported";
   CHECK_GT(size, static_cast<size_t>(kPageSize));
   CHECK_ALIGNED(size, kPageSize);
+  CHECK(!reuse_);
   if (IsAlignedParam(reinterpret_cast<uintptr_t>(base_begin_), size) &&
       IsAlignedParam(base_size_, size)) {
     // Already aligned.
@@ -1079,17 +1150,17 @@
         << " aligned_base_end=" << reinterpret_cast<void*>(aligned_base_end);
   }
   std::lock_guard<std::mutex> mu(*mem_maps_lock_);
+  if (base_begin < aligned_base_begin) {
+    auto it = GetGMapsEntry(*this);
+    // TODO: When C++17 becomes available, use std::map<>::extract(), modify, insert.
+    gMaps->erase(it);
+    gMaps->insert(std::make_pair(aligned_base_begin, this));
+  }
   base_begin_ = aligned_base_begin;
   base_size_ = aligned_base_size;
   begin_ = aligned_base_begin;
   size_ = aligned_base_size;
   DCHECK(gMaps != nullptr);
-  if (base_begin < aligned_base_begin) {
-    auto it = gMaps->find(base_begin);
-    CHECK(it != gMaps->end()) << "MemMap not found";
-    gMaps->erase(it);
-    gMaps->insert(std::make_pair(base_begin_, this));
-  }
 }
 
 }  // namespace art
diff --git a/libartbase/base/mem_map.h b/libartbase/base/mem_map.h
index 1979357..cd7d502 100644
--- a/libartbase/base/mem_map.h
+++ b/libartbase/base/mem_map.h
@@ -60,6 +60,37 @@
  public:
   static constexpr bool kCanReplaceMapping = HAVE_MREMAP_SYSCALL;
 
+  // Creates an invalid mapping.
+  MemMap() {}
+
+  // Creates an invalid mapping. Used when we want to be more explicit than MemMap().
+  static MemMap Invalid() {
+    return MemMap();
+  }
+
+  MemMap(MemMap&& other) REQUIRES(!MemMap::mem_maps_lock_);
+  MemMap& operator=(MemMap&& other) REQUIRES(!MemMap::mem_maps_lock_) {
+    Reset();
+    swap(other);
+    return *this;
+  }
+
+  // Releases the memory mapping.
+  ~MemMap() REQUIRES(!MemMap::mem_maps_lock_);
+
+  // Swap two MemMaps.
+  void swap(MemMap& other);
+
+  void Reset() {
+    if (IsValid()) {
+      DoReset();
+    }
+  }
+
+  bool IsValid() const {
+    return base_size_ != 0u;
+  }
+
   // Replace the data in this memmmap with the data in the memmap pointed to by source. The caller
   // relinquishes ownership of the source mmap.
   //
@@ -74,15 +105,14 @@
   //   * mremap must succeed when called on the mappings.
   //
   // If this call succeeds it will return true and:
-  //   * Deallocate *source
-  //   * Sets *source to nullptr
+  //   * Invalidate *source
   //   * The protection of this will remain the same.
   //   * The size of this will be the size of the source
   //   * The data in this will be the data from source.
   //
   // If this call fails it will return false and make no changes to *source or this. The ownership
   // of the source mmap is returned to the caller.
-  bool ReplaceWith(/*in-out*/MemMap** source, /*out*/std::string* error);
+  bool ReplaceWith(/*in-out*/MemMap* source, /*out*/std::string* error);
 
   // Request an anonymous region of length 'byte_count' and a requested base address.
   // Use null as the requested base address if you don't care.
@@ -92,34 +122,42 @@
   // 'name' will be used -- on systems that support it -- to give the mapping
   // a name.
   //
-  // On success, returns returns a MemMap instance.  On failure, returns null.
-  static MemMap* MapAnonymous(const char* name,
-                              uint8_t* addr,
-                              size_t byte_count,
-                              int prot,
-                              bool low_4gb,
-                              bool reuse,
-                              std::string* error_msg,
-                              bool use_ashmem = true);
+  // On success, returns returns a valid MemMap.  On failure, returns an invalid MemMap.
+  static MemMap MapAnonymous(const char* name,
+                             uint8_t* addr,
+                             size_t byte_count,
+                             int prot,
+                             bool low_4gb,
+                             bool reuse,
+                             std::string* error_msg,
+                             bool use_ashmem = true);
+  static MemMap MapAnonymous(const char* name,
+                             uint8_t* addr,
+                             size_t byte_count,
+                             int prot,
+                             bool low_4gb,
+                             std::string* error_msg) {
+    return MapAnonymous(name, addr, byte_count, prot, low_4gb, /* reuse */ false, error_msg);
+  }
 
   // Create placeholder for a region allocated by direct call to mmap.
   // This is useful when we do not have control over the code calling mmap,
   // but when we still want to keep track of it in the list.
   // The region is not considered to be owned and will not be unmmaped.
-  static MemMap* MapDummy(const char* name, uint8_t* addr, size_t byte_count);
+  static MemMap MapDummy(const char* name, uint8_t* addr, size_t byte_count);
 
   // Map part of a file, taking care of non-page aligned offsets.  The
   // "start" offset is absolute, not relative.
   //
-  // On success, returns returns a MemMap instance.  On failure, returns null.
-  static MemMap* MapFile(size_t byte_count,
-                         int prot,
-                         int flags,
-                         int fd,
-                         off_t start,
-                         bool low_4gb,
-                         const char* filename,
-                         std::string* error_msg) {
+  // On success, returns returns a valid MemMap.  On failure, returns an invalid MemMap.
+  static MemMap MapFile(size_t byte_count,
+                        int prot,
+                        int flags,
+                        int fd,
+                        off_t start,
+                        bool low_4gb,
+                        const char* filename,
+                        std::string* error_msg) {
     return MapFileAtAddress(nullptr,
                             byte_count,
                             prot,
@@ -139,20 +177,17 @@
   // MapFileAtAddress fails. This helps improve performance of the fail case since reading and
   // printing /proc/maps takes several milliseconds in the worst case.
   //
-  // On success, returns returns a MemMap instance.  On failure, returns null.
-  static MemMap* MapFileAtAddress(uint8_t* addr,
-                                  size_t byte_count,
-                                  int prot,
-                                  int flags,
-                                  int fd,
-                                  off_t start,
-                                  bool low_4gb,
-                                  bool reuse,
-                                  const char* filename,
-                                  std::string* error_msg);
-
-  // Releases the memory mapping.
-  ~MemMap() REQUIRES(!MemMap::mem_maps_lock_);
+  // On success, returns returns a valid MemMap.  On failure, returns an invalid MemMap.
+  static MemMap MapFileAtAddress(uint8_t* addr,
+                                 size_t byte_count,
+                                 int prot,
+                                 int flags,
+                                 int fd,
+                                 off_t start,
+                                 bool low_4gb,
+                                 bool reuse,
+                                 const char* filename,
+                                 std::string* error_msg);
 
   const std::string& GetName() const {
     return name_;
@@ -200,13 +235,13 @@
   }
 
   // Unmap the pages at end and remap them to create another memory map.
-  MemMap* RemapAtEnd(uint8_t* new_end,
-                     const char* tail_name,
-                     int tail_prot,
-                     std::string* error_msg,
-                     bool use_ashmem = true);
+  MemMap RemapAtEnd(uint8_t* new_end,
+                    const char* tail_name,
+                    int tail_prot,
+                    std::string* error_msg,
+                    bool use_ashmem = true);
 
-  static bool CheckNoGaps(MemMap* begin_map, MemMap* end_map)
+  static bool CheckNoGaps(MemMap& begin_map, MemMap& end_map)
       REQUIRES(!MemMap::mem_maps_lock_);
   static void DumpMaps(std::ostream& os, bool terse = false)
       REQUIRES(!MemMap::mem_maps_lock_);
@@ -240,9 +275,13 @@
          bool reuse,
          size_t redzone_size = 0) REQUIRES(!MemMap::mem_maps_lock_);
 
+  void DoReset();
+  void Invalidate();
+  void SwapMembers(MemMap& other);
+
   static void DumpMapsLocked(std::ostream& os, bool terse)
       REQUIRES(MemMap::mem_maps_lock_);
-  static bool HasMemMap(MemMap* map)
+  static bool HasMemMap(MemMap& map)
       REQUIRES(MemMap::mem_maps_lock_);
   static MemMap* GetLargestMemMapAt(void* address)
       REQUIRES(MemMap::mem_maps_lock_);
@@ -271,23 +310,23 @@
                               size_t byte_count,
                               std::string* error_msg);
 
-  const std::string name_;
-  uint8_t* begin_;  // Start of data. May be changed by AlignBy.
-  size_t size_;  // Length of data.
+  std::string name_;
+  uint8_t* begin_ = nullptr;    // Start of data. May be changed by AlignBy.
+  size_t size_ = 0u;            // Length of data.
 
-  void* base_begin_;  // Page-aligned base address. May be changed by AlignBy.
-  size_t base_size_;  // Length of mapping. May be changed by RemapAtEnd (ie Zygote).
-  int prot_;  // Protection of the map.
+  void* base_begin_ = nullptr;  // Page-aligned base address. May be changed by AlignBy.
+  size_t base_size_ = 0u;       // Length of mapping. May be changed by RemapAtEnd (ie Zygote).
+  int prot_ = 0;                // Protection of the map.
 
   // When reuse_ is true, this is just a view of an existing mapping
   // and we do not take ownership and are not responsible for
   // unmapping.
-  const bool reuse_;
+  bool reuse_ = false;
 
   // When already_unmapped_ is true the destructor will not call munmap.
-  bool already_unmapped_;
+  bool already_unmapped_ = false;
 
-  const size_t redzone_size_;
+  size_t redzone_size_ = 0u;
 
 #if USE_ART_LOW_4G_ALLOCATOR
   static uintptr_t next_mem_pos_;   // Next memory location to check for low_4g extent.
@@ -309,6 +348,10 @@
   friend class MemMapTest;  // To allow access to base_begin_ and base_size_.
 };
 
+inline void swap(MemMap& lhs, MemMap& rhs) {
+  lhs.swap(rhs);
+}
+
 std::ostream& operator<<(std::ostream& os, const MemMap& mem_map);
 
 // Zero and release pages if possible, no requirements on alignments.
diff --git a/libartbase/base/mem_map_fuchsia.cc b/libartbase/base/mem_map_fuchsia.cc
index db31efb..d1c92ce 100644
--- a/libartbase/base/mem_map_fuchsia.cc
+++ b/libartbase/base/mem_map_fuchsia.cc
@@ -41,8 +41,8 @@
                               ZX_INFO_VMAR,
                               &vmarinfo,
                               sizeof(vmarinfo),
-                              NULL,
-                              NULL), ZX_OK) << "could not find info from root vmar";
+                              nullptr,
+                              nullptr), ZX_OK) << "could not find info from root vmar";
 
   uintptr_t lower_mem_start = FUCHSIA_LOWER_MEM_START - vmarinfo.base;
   fuchsia_lowmem_size = FUCHSIA_LOWER_MEM_SIZE;
@@ -97,8 +97,8 @@
                                 ZX_INFO_VMAR,
                                 &vmarinfo,
                                 sizeof(vmarinfo),
-                                NULL,
-                                NULL);
+                                nullptr,
+                                nullptr);
     if (status < 0 || reinterpret_cast<uintptr_t>(start) < vmarinfo.base) {
       errno = EINVAL;
       return MAP_FAILED;
diff --git a/libartbase/base/mem_map_test.cc b/libartbase/base/mem_map_test.cc
index c575c7a..396f12b 100644
--- a/libartbase/base/mem_map_test.cc
+++ b/libartbase/base/mem_map_test.cc
@@ -30,14 +30,6 @@
 
 class MemMapTest : public CommonArtTest {
  public:
-  static uint8_t* BaseBegin(MemMap* mem_map) {
-    return reinterpret_cast<uint8_t*>(mem_map->base_begin_);
-  }
-
-  static size_t BaseSize(MemMap* mem_map) {
-    return mem_map->base_size_;
-  }
-
   static bool IsAddressMapped(void* addr) {
     bool res = msync(addr, 1, MS_SYNC) == 0;
     if (!res && errno != ENOMEM) {
@@ -60,15 +52,14 @@
   static uint8_t* GetValidMapAddress(size_t size, bool low_4gb) {
     // Find a valid map address and unmap it before returning.
     std::string error_msg;
-    std::unique_ptr<MemMap> map(MemMap::MapAnonymous("temp",
-                                                     nullptr,
-                                                     size,
-                                                     PROT_READ,
-                                                     low_4gb,
-                                                     false,
-                                                     &error_msg));
-    CHECK(map != nullptr);
-    return map->Begin();
+    MemMap map = MemMap::MapAnonymous("temp",
+                                      /* addr */ nullptr,
+                                      size,
+                                      PROT_READ,
+                                      low_4gb,
+                                      &error_msg);
+    CHECK(map.IsValid());
+    return map.Begin();
   }
 
   static void RemapAtEndTest(bool low_4gb) {
@@ -76,37 +67,37 @@
     // Cast the page size to size_t.
     const size_t page_size = static_cast<size_t>(kPageSize);
     // Map a two-page memory region.
-    MemMap* m0 = MemMap::MapAnonymous("MemMapTest_RemapAtEndTest_map0",
-                                      nullptr,
-                                      2 * page_size,
-                                      PROT_READ | PROT_WRITE,
-                                      low_4gb,
-                                      false,
-                                      &error_msg);
+    MemMap m0 = MemMap::MapAnonymous("MemMapTest_RemapAtEndTest_map0",
+                                     /* addr */ nullptr,
+                                     2 * page_size,
+                                     PROT_READ | PROT_WRITE,
+                                     low_4gb,
+                                     &error_msg);
     // Check its state and write to it.
-    uint8_t* base0 = m0->Begin();
+    ASSERT_TRUE(m0.IsValid());
+    uint8_t* base0 = m0.Begin();
     ASSERT_TRUE(base0 != nullptr) << error_msg;
-    size_t size0 = m0->Size();
-    EXPECT_EQ(m0->Size(), 2 * page_size);
-    EXPECT_EQ(BaseBegin(m0), base0);
-    EXPECT_EQ(BaseSize(m0), size0);
+    size_t size0 = m0.Size();
+    EXPECT_EQ(m0.Size(), 2 * page_size);
+    EXPECT_EQ(m0.BaseBegin(), base0);
+    EXPECT_EQ(m0.BaseSize(), size0);
     memset(base0, 42, 2 * page_size);
     // Remap the latter half into a second MemMap.
-    MemMap* m1 = m0->RemapAtEnd(base0 + page_size,
-                                "MemMapTest_RemapAtEndTest_map1",
-                                PROT_READ | PROT_WRITE,
-                                &error_msg);
+    MemMap m1 = m0.RemapAtEnd(base0 + page_size,
+                              "MemMapTest_RemapAtEndTest_map1",
+                              PROT_READ | PROT_WRITE,
+                              &error_msg);
     // Check the states of the two maps.
-    EXPECT_EQ(m0->Begin(), base0) << error_msg;
-    EXPECT_EQ(m0->Size(), page_size);
-    EXPECT_EQ(BaseBegin(m0), base0);
-    EXPECT_EQ(BaseSize(m0), page_size);
-    uint8_t* base1 = m1->Begin();
-    size_t size1 = m1->Size();
+    EXPECT_EQ(m0.Begin(), base0) << error_msg;
+    EXPECT_EQ(m0.Size(), page_size);
+    EXPECT_EQ(m0.BaseBegin(), base0);
+    EXPECT_EQ(m0.BaseSize(), page_size);
+    uint8_t* base1 = m1.Begin();
+    size_t size1 = m1.Size();
     EXPECT_EQ(base1, base0 + page_size);
     EXPECT_EQ(size1, page_size);
-    EXPECT_EQ(BaseBegin(m1), base1);
-    EXPECT_EQ(BaseSize(m1), size1);
+    EXPECT_EQ(m1.BaseBegin(), base1);
+    EXPECT_EQ(m1.BaseSize(), size1);
     // Write to the second region.
     memset(base1, 43, page_size);
     // Check the contents of the two regions.
@@ -117,13 +108,18 @@
       EXPECT_EQ(base1[i], 43);
     }
     // Unmap the first region.
-    delete m0;
+    m0.Reset();
     // Make sure the second region is still accessible after the first
     // region is unmapped.
     for (size_t i = 0; i < page_size; ++i) {
       EXPECT_EQ(base1[i], 43);
     }
-    delete m1;
+    MemMap m2 = m1.RemapAtEnd(m1.Begin(),
+                              "MemMapTest_RemapAtEndTest_map1",
+                              PROT_READ | PROT_WRITE,
+                              &error_msg);
+    ASSERT_TRUE(m2.IsValid()) << error_msg;
+    ASSERT_FALSE(m1.IsValid());
   }
 
   void CommonInit() {
@@ -168,232 +164,228 @@
 #if HAVE_MREMAP_SYSCALL
 TEST_F(MemMapTest, ReplaceMapping_SameSize) {
   std::string error_msg;
-  std::unique_ptr<MemMap> dest(MemMap::MapAnonymous("MapAnonymousEmpty-atomic-replace-dest",
-                                                    nullptr,
-                                                    kPageSize,
-                                                    PROT_READ,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(dest != nullptr);
-  MemMap* source = MemMap::MapAnonymous("MapAnonymous-atomic-replace-source",
-                                        nullptr,
-                                        kPageSize,
-                                        PROT_WRITE | PROT_READ,
-                                        false,
-                                        false,
-                                        &error_msg);
-  ASSERT_TRUE(source != nullptr);
-  void* source_addr = source->Begin();
-  void* dest_addr = dest->Begin();
+  MemMap dest = MemMap::MapAnonymous("MapAnonymousEmpty-atomic-replace-dest",
+                                     /* addr */ nullptr,
+                                     kPageSize,
+                                     PROT_READ,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_TRUE(dest.IsValid());
+  MemMap source = MemMap::MapAnonymous("MapAnonymous-atomic-replace-source",
+                                       /* addr */ nullptr,
+                                       kPageSize,
+                                       PROT_WRITE | PROT_READ,
+                                       /* low_4gb */ false,
+                                       &error_msg);
+  ASSERT_TRUE(source.IsValid());
+  void* source_addr = source.Begin();
+  void* dest_addr = dest.Begin();
   ASSERT_TRUE(IsAddressMapped(source_addr));
   ASSERT_TRUE(IsAddressMapped(dest_addr));
 
   std::vector<uint8_t> data = RandomData(kPageSize);
-  memcpy(source->Begin(), data.data(), data.size());
+  memcpy(source.Begin(), data.data(), data.size());
 
-  ASSERT_TRUE(dest->ReplaceWith(&source, &error_msg)) << error_msg;
+  ASSERT_TRUE(dest.ReplaceWith(&source, &error_msg)) << error_msg;
 
   ASSERT_FALSE(IsAddressMapped(source_addr));
   ASSERT_TRUE(IsAddressMapped(dest_addr));
-  ASSERT_TRUE(source == nullptr);
+  ASSERT_FALSE(source.IsValid());
 
-  ASSERT_EQ(dest->Size(), static_cast<size_t>(kPageSize));
+  ASSERT_EQ(dest.Size(), static_cast<size_t>(kPageSize));
 
-  ASSERT_EQ(memcmp(dest->Begin(), data.data(), dest->Size()), 0);
+  ASSERT_EQ(memcmp(dest.Begin(), data.data(), dest.Size()), 0);
 }
 
 TEST_F(MemMapTest, ReplaceMapping_MakeLarger) {
   std::string error_msg;
-  std::unique_ptr<MemMap> dest(MemMap::MapAnonymous("MapAnonymousEmpty-atomic-replace-dest",
-                                                    nullptr,
-                                                    5 * kPageSize,  // Need to make it larger
-                                                                    // initially so we know
-                                                                    // there won't be mappings
-                                                                    // in the way we we move
-                                                                    // source.
-                                                    PROT_READ,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(dest != nullptr);
-  MemMap* source = MemMap::MapAnonymous("MapAnonymous-atomic-replace-source",
-                                        nullptr,
-                                        3 * kPageSize,
-                                        PROT_WRITE | PROT_READ,
-                                        false,
-                                        false,
-                                        &error_msg);
-  ASSERT_TRUE(source != nullptr);
-  uint8_t* source_addr = source->Begin();
-  uint8_t* dest_addr = dest->Begin();
+  MemMap dest = MemMap::MapAnonymous("MapAnonymousEmpty-atomic-replace-dest",
+                                     /* addr */ nullptr,
+                                     5 * kPageSize,  // Need to make it larger
+                                                     // initially so we know
+                                                     // there won't be mappings
+                                                     // in the way we we move
+                                                     // source.
+                                     PROT_READ,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_TRUE(dest.IsValid());
+  MemMap source = MemMap::MapAnonymous("MapAnonymous-atomic-replace-source",
+                                       /* addr */ nullptr,
+                                       3 * kPageSize,
+                                       PROT_WRITE | PROT_READ,
+                                       /* low_4gb */ false,
+                                       &error_msg);
+  ASSERT_TRUE(source.IsValid());
+  uint8_t* source_addr = source.Begin();
+  uint8_t* dest_addr = dest.Begin();
   ASSERT_TRUE(IsAddressMapped(source_addr));
 
   // Fill the source with random data.
   std::vector<uint8_t> data = RandomData(3 * kPageSize);
-  memcpy(source->Begin(), data.data(), data.size());
+  memcpy(source.Begin(), data.data(), data.size());
 
   // Make the dest smaller so that we know we'll have space.
-  dest->SetSize(kPageSize);
+  dest.SetSize(kPageSize);
 
   ASSERT_TRUE(IsAddressMapped(dest_addr));
   ASSERT_FALSE(IsAddressMapped(dest_addr + 2 * kPageSize));
-  ASSERT_EQ(dest->Size(), static_cast<size_t>(kPageSize));
+  ASSERT_EQ(dest.Size(), static_cast<size_t>(kPageSize));
 
-  ASSERT_TRUE(dest->ReplaceWith(&source, &error_msg)) << error_msg;
+  ASSERT_TRUE(dest.ReplaceWith(&source, &error_msg)) << error_msg;
 
   ASSERT_FALSE(IsAddressMapped(source_addr));
-  ASSERT_EQ(dest->Size(), static_cast<size_t>(3 * kPageSize));
+  ASSERT_EQ(dest.Size(), static_cast<size_t>(3 * kPageSize));
   ASSERT_TRUE(IsAddressMapped(dest_addr));
   ASSERT_TRUE(IsAddressMapped(dest_addr + 2 * kPageSize));
-  ASSERT_TRUE(source == nullptr);
+  ASSERT_FALSE(source.IsValid());
 
-  ASSERT_EQ(memcmp(dest->Begin(), data.data(), dest->Size()), 0);
+  ASSERT_EQ(memcmp(dest.Begin(), data.data(), dest.Size()), 0);
 }
 
 TEST_F(MemMapTest, ReplaceMapping_MakeSmaller) {
   std::string error_msg;
-  std::unique_ptr<MemMap> dest(MemMap::MapAnonymous("MapAnonymousEmpty-atomic-replace-dest",
-                                                    nullptr,
-                                                    3 * kPageSize,
-                                                    PROT_READ,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(dest != nullptr);
-  MemMap* source = MemMap::MapAnonymous("MapAnonymous-atomic-replace-source",
-                                        nullptr,
-                                        kPageSize,
-                                        PROT_WRITE | PROT_READ,
-                                        false,
-                                        false,
-                                        &error_msg);
-  ASSERT_TRUE(source != nullptr);
-  uint8_t* source_addr = source->Begin();
-  uint8_t* dest_addr = dest->Begin();
+  MemMap dest = MemMap::MapAnonymous("MapAnonymousEmpty-atomic-replace-dest",
+                                     /* addr */ nullptr,
+                                     3 * kPageSize,
+                                     PROT_READ,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_TRUE(dest.IsValid());
+  MemMap source = MemMap::MapAnonymous("MapAnonymous-atomic-replace-source",
+                                       /* addr */ nullptr,
+                                       kPageSize,
+                                       PROT_WRITE | PROT_READ,
+                                       /* low_4gb */ false,
+                                       &error_msg);
+  ASSERT_TRUE(source.IsValid());
+  uint8_t* source_addr = source.Begin();
+  uint8_t* dest_addr = dest.Begin();
   ASSERT_TRUE(IsAddressMapped(source_addr));
   ASSERT_TRUE(IsAddressMapped(dest_addr));
   ASSERT_TRUE(IsAddressMapped(dest_addr + 2 * kPageSize));
-  ASSERT_EQ(dest->Size(), static_cast<size_t>(3 * kPageSize));
+  ASSERT_EQ(dest.Size(), static_cast<size_t>(3 * kPageSize));
 
   std::vector<uint8_t> data = RandomData(kPageSize);
-  memcpy(source->Begin(), data.data(), kPageSize);
+  memcpy(source.Begin(), data.data(), kPageSize);
 
-  ASSERT_TRUE(dest->ReplaceWith(&source, &error_msg)) << error_msg;
+  ASSERT_TRUE(dest.ReplaceWith(&source, &error_msg)) << error_msg;
 
   ASSERT_FALSE(IsAddressMapped(source_addr));
-  ASSERT_EQ(dest->Size(), static_cast<size_t>(kPageSize));
+  ASSERT_EQ(dest.Size(), static_cast<size_t>(kPageSize));
   ASSERT_TRUE(IsAddressMapped(dest_addr));
   ASSERT_FALSE(IsAddressMapped(dest_addr + 2 * kPageSize));
-  ASSERT_TRUE(source == nullptr);
+  ASSERT_FALSE(source.IsValid());
 
-  ASSERT_EQ(memcmp(dest->Begin(), data.data(), dest->Size()), 0);
+  ASSERT_EQ(memcmp(dest.Begin(), data.data(), dest.Size()), 0);
 }
 
 TEST_F(MemMapTest, ReplaceMapping_FailureOverlap) {
   std::string error_msg;
-  std::unique_ptr<MemMap> dest(
+  MemMap dest =
       MemMap::MapAnonymous(
           "MapAnonymousEmpty-atomic-replace-dest",
-          nullptr,
+          /* addr */ nullptr,
           3 * kPageSize,  // Need to make it larger initially so we know there won't be mappings in
                           // the way we we move source.
           PROT_READ | PROT_WRITE,
-          false,
-          false,
-          &error_msg));
-  ASSERT_TRUE(dest != nullptr);
+          /* low_4gb */ false,
+          &error_msg);
+  ASSERT_TRUE(dest.IsValid());
   // Resize down to 1 page so we can remap the rest.
-  dest->SetSize(kPageSize);
+  dest.SetSize(kPageSize);
   // Create source from the last 2 pages
-  MemMap* source = MemMap::MapAnonymous("MapAnonymous-atomic-replace-source",
-                                        dest->Begin() + kPageSize,
-                                        2 * kPageSize,
-                                        PROT_WRITE | PROT_READ,
-                                        false,
-                                        false,
-                                        &error_msg);
-  ASSERT_TRUE(source != nullptr);
-  MemMap* orig_source = source;
-  ASSERT_EQ(dest->Begin() + kPageSize, source->Begin());
-  uint8_t* source_addr = source->Begin();
-  uint8_t* dest_addr = dest->Begin();
+  MemMap source = MemMap::MapAnonymous("MapAnonymous-atomic-replace-source",
+                                       dest.Begin() + kPageSize,
+                                       2 * kPageSize,
+                                       PROT_WRITE | PROT_READ,
+                                       /* low_4gb */ false,
+                                       &error_msg);
+  ASSERT_TRUE(source.IsValid());
+  ASSERT_EQ(dest.Begin() + kPageSize, source.Begin());
+  uint8_t* source_addr = source.Begin();
+  uint8_t* dest_addr = dest.Begin();
   ASSERT_TRUE(IsAddressMapped(source_addr));
 
   // Fill the source and dest with random data.
   std::vector<uint8_t> data = RandomData(2 * kPageSize);
-  memcpy(source->Begin(), data.data(), data.size());
+  memcpy(source.Begin(), data.data(), data.size());
   std::vector<uint8_t> dest_data = RandomData(kPageSize);
-  memcpy(dest->Begin(), dest_data.data(), dest_data.size());
+  memcpy(dest.Begin(), dest_data.data(), dest_data.size());
 
   ASSERT_TRUE(IsAddressMapped(dest_addr));
-  ASSERT_EQ(dest->Size(), static_cast<size_t>(kPageSize));
+  ASSERT_EQ(dest.Size(), static_cast<size_t>(kPageSize));
 
-  ASSERT_FALSE(dest->ReplaceWith(&source, &error_msg)) << error_msg;
+  ASSERT_FALSE(dest.ReplaceWith(&source, &error_msg)) << error_msg;
 
-  ASSERT_TRUE(source == orig_source);
   ASSERT_TRUE(IsAddressMapped(source_addr));
   ASSERT_TRUE(IsAddressMapped(dest_addr));
-  ASSERT_EQ(source->Size(), data.size());
-  ASSERT_EQ(dest->Size(), dest_data.size());
+  ASSERT_EQ(source.Size(), data.size());
+  ASSERT_EQ(dest.Size(), dest_data.size());
 
-  ASSERT_EQ(memcmp(source->Begin(), data.data(), data.size()), 0);
-  ASSERT_EQ(memcmp(dest->Begin(), dest_data.data(), dest_data.size()), 0);
-
-  delete source;
+  ASSERT_EQ(memcmp(source.Begin(), data.data(), data.size()), 0);
+  ASSERT_EQ(memcmp(dest.Begin(), dest_data.data(), dest_data.size()), 0);
 }
 #endif  // HAVE_MREMAP_SYSCALL
 
 TEST_F(MemMapTest, MapAnonymousEmpty) {
   CommonInit();
   std::string error_msg;
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymousEmpty",
-                                                   nullptr,
-                                                   0,
-                                                   PROT_READ,
-                                                   false,
-                                                   false,
-                                                   &error_msg));
-  ASSERT_TRUE(map.get() != nullptr) << error_msg;
-  ASSERT_TRUE(error_msg.empty());
-  map.reset(MemMap::MapAnonymous("MapAnonymousEmpty",
-                                 nullptr,
-                                 kPageSize,
-                                 PROT_READ | PROT_WRITE,
-                                 false,
-                                 false,
-                                 &error_msg));
-  ASSERT_TRUE(map.get() != nullptr) << error_msg;
+  MemMap map = MemMap::MapAnonymous("MapAnonymousEmpty",
+                                    /* addr */ nullptr,
+                                    0,
+                                    PROT_READ,
+                                    /* low_4gb */ false,
+                                    &error_msg);
+  ASSERT_FALSE(map.IsValid()) << error_msg;
+  ASSERT_FALSE(error_msg.empty());
+
+  error_msg.clear();
+  map = MemMap::MapAnonymous("MapAnonymousNonEmpty",
+                             /* addr */ nullptr,
+                             kPageSize,
+                             PROT_READ | PROT_WRITE,
+                             /* low_4gb */ false,
+                             &error_msg);
+  ASSERT_TRUE(map.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
 }
 
 TEST_F(MemMapTest, MapAnonymousFailNullError) {
   CommonInit();
   // Test that we don't crash with a null error_str when mapping at an invalid location.
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymousInvalid",
-                                                    reinterpret_cast<uint8_t*>(kPageSize),
-                                                    0x20000,
-                                                    PROT_READ | PROT_WRITE,
-                                                    false,
-                                                    false,
-                                                    nullptr));
-  ASSERT_EQ(nullptr, map.get());
+  MemMap map = MemMap::MapAnonymous("MapAnonymousInvalid",
+                                    reinterpret_cast<uint8_t*>(kPageSize),
+                                    0x20000,
+                                    PROT_READ | PROT_WRITE,
+                                    /* low_4gb */ false,
+                                    nullptr);
+  ASSERT_FALSE(map.IsValid());
 }
 
 #ifdef __LP64__
 TEST_F(MemMapTest, MapAnonymousEmpty32bit) {
   CommonInit();
   std::string error_msg;
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymousEmpty",
-                                                   nullptr,
-                                                   kPageSize,
-                                                   PROT_READ | PROT_WRITE,
-                                                   true,
-                                                   false,
-                                                   &error_msg));
-  ASSERT_TRUE(map.get() != nullptr) << error_msg;
+  MemMap map = MemMap::MapAnonymous("MapAnonymousEmpty",
+                                    /* addr */ nullptr,
+                                    0,
+                                    PROT_READ,
+                                    /* low_4gb */ true,
+                                    &error_msg);
+  ASSERT_FALSE(map.IsValid()) << error_msg;
+  ASSERT_FALSE(error_msg.empty());
+
+  error_msg.clear();
+  map = MemMap::MapAnonymous("MapAnonymousNonEmpty",
+                             /* addr */ nullptr,
+                             kPageSize,
+                             PROT_READ | PROT_WRITE,
+                             /* low_4gb */ true,
+                             &error_msg);
+  ASSERT_TRUE(map.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
-  ASSERT_LT(reinterpret_cast<uintptr_t>(BaseBegin(map.get())), 1ULL << 32);
+  ASSERT_LT(reinterpret_cast<uintptr_t>(map.BaseBegin()), 1ULL << 32);
 }
 TEST_F(MemMapTest, MapFile32Bit) {
   CommonInit();
@@ -402,18 +394,18 @@
   constexpr size_t kMapSize = kPageSize;
   std::unique_ptr<uint8_t[]> data(new uint8_t[kMapSize]());
   ASSERT_TRUE(scratch_file.GetFile()->WriteFully(&data[0], kMapSize));
-  std::unique_ptr<MemMap> map(MemMap::MapFile(/*byte_count*/kMapSize,
-                                              PROT_READ,
-                                              MAP_PRIVATE,
-                                              scratch_file.GetFd(),
-                                              /*start*/0,
-                                              /*low_4gb*/true,
-                                              scratch_file.GetFilename().c_str(),
-                                              &error_msg));
-  ASSERT_TRUE(map != nullptr) << error_msg;
+  MemMap map = MemMap::MapFile(/*byte_count*/kMapSize,
+                               PROT_READ,
+                               MAP_PRIVATE,
+                               scratch_file.GetFd(),
+                               /*start*/0,
+                               /*low_4gb*/true,
+                               scratch_file.GetFilename().c_str(),
+                               &error_msg);
+  ASSERT_TRUE(map.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
-  ASSERT_EQ(map->Size(), kMapSize);
-  ASSERT_LT(reinterpret_cast<uintptr_t>(BaseBegin(map.get())), 1ULL << 32);
+  ASSERT_EQ(map.Size(), kMapSize);
+  ASSERT_LT(reinterpret_cast<uintptr_t>(map.BaseBegin()), 1ULL << 32);
 }
 #endif
 
@@ -423,36 +415,33 @@
   // Find a valid address.
   uint8_t* valid_address = GetValidMapAddress(kPageSize, /*low_4gb*/false);
   // Map at an address that should work, which should succeed.
-  std::unique_ptr<MemMap> map0(MemMap::MapAnonymous("MapAnonymous0",
-                                                    valid_address,
-                                                    kPageSize,
-                                                    PROT_READ | PROT_WRITE,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(map0.get() != nullptr) << error_msg;
+  MemMap map0 = MemMap::MapAnonymous("MapAnonymous0",
+                                     valid_address,
+                                     kPageSize,
+                                     PROT_READ | PROT_WRITE,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_TRUE(map0.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
-  ASSERT_TRUE(map0->BaseBegin() == valid_address);
+  ASSERT_TRUE(map0.BaseBegin() == valid_address);
   // Map at an unspecified address, which should succeed.
-  std::unique_ptr<MemMap> map1(MemMap::MapAnonymous("MapAnonymous1",
-                                                    nullptr,
-                                                    kPageSize,
-                                                    PROT_READ | PROT_WRITE,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(map1.get() != nullptr) << error_msg;
+  MemMap map1 = MemMap::MapAnonymous("MapAnonymous1",
+                                     /* addr */ nullptr,
+                                     kPageSize,
+                                     PROT_READ | PROT_WRITE,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_TRUE(map1.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
-  ASSERT_TRUE(map1->BaseBegin() != nullptr);
+  ASSERT_TRUE(map1.BaseBegin() != nullptr);
   // Attempt to map at the same address, which should fail.
-  std::unique_ptr<MemMap> map2(MemMap::MapAnonymous("MapAnonymous2",
-                                                    reinterpret_cast<uint8_t*>(map1->BaseBegin()),
-                                                    kPageSize,
-                                                    PROT_READ | PROT_WRITE,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(map2.get() == nullptr) << error_msg;
+  MemMap map2 = MemMap::MapAnonymous("MapAnonymous2",
+                                     reinterpret_cast<uint8_t*>(map1.BaseBegin()),
+                                     kPageSize,
+                                     PROT_READ | PROT_WRITE,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_FALSE(map2.IsValid()) << error_msg;
   ASSERT_TRUE(!error_msg.empty());
 }
 
@@ -480,23 +469,22 @@
   // Try all addresses starting from 2GB to 4GB.
   size_t start_addr = 2 * GB;
   std::string error_msg;
-  std::unique_ptr<MemMap> map;
+  MemMap map;
   for (; start_addr <= std::numeric_limits<uint32_t>::max() - size; start_addr += size) {
-    map.reset(MemMap::MapAnonymous("MapAnonymousExactAddr32bitHighAddr",
-                                   reinterpret_cast<uint8_t*>(start_addr),
-                                   size,
-                                   PROT_READ | PROT_WRITE,
-                                   /*low_4gb*/true,
-                                   false,
-                                   &error_msg));
-    if (map != nullptr) {
+    map = MemMap::MapAnonymous("MapAnonymousExactAddr32bitHighAddr",
+                               reinterpret_cast<uint8_t*>(start_addr),
+                               size,
+                               PROT_READ | PROT_WRITE,
+                               /*low_4gb*/ true,
+                               &error_msg);
+    if (map.IsValid()) {
       break;
     }
   }
-  ASSERT_TRUE(map.get() != nullptr) << error_msg;
-  ASSERT_GE(reinterpret_cast<uintptr_t>(map->End()), 2u * GB);
+  ASSERT_TRUE(map.IsValid()) << error_msg;
+  ASSERT_GE(reinterpret_cast<uintptr_t>(map.End()), 2u * GB);
   ASSERT_TRUE(error_msg.empty());
-  ASSERT_EQ(BaseBegin(map.get()), reinterpret_cast<void*>(start_addr));
+  ASSERT_EQ(map.BaseBegin(), reinterpret_cast<void*>(start_addr));
 }
 
 TEST_F(MemMapTest, MapAnonymousOverflow) {
@@ -504,14 +492,13 @@
   std::string error_msg;
   uintptr_t ptr = 0;
   ptr -= kPageSize;  // Now it's close to the top.
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymousOverflow",
-                                                   reinterpret_cast<uint8_t*>(ptr),
-                                                   2 * kPageSize,  // brings it over the top.
-                                                   PROT_READ | PROT_WRITE,
-                                                   false,
-                                                   false,
-                                                   &error_msg));
-  ASSERT_EQ(nullptr, map.get());
+  MemMap map = MemMap::MapAnonymous("MapAnonymousOverflow",
+                                    reinterpret_cast<uint8_t*>(ptr),
+                                    2 * kPageSize,  // brings it over the top.
+                                    PROT_READ | PROT_WRITE,
+                                    /* low_4gb */ false,
+                                    &error_msg);
+  ASSERT_FALSE(map.IsValid());
   ASSERT_FALSE(error_msg.empty());
 }
 
@@ -519,29 +506,27 @@
 TEST_F(MemMapTest, MapAnonymousLow4GBExpectedTooHigh) {
   CommonInit();
   std::string error_msg;
-  std::unique_ptr<MemMap> map(
+  MemMap map =
       MemMap::MapAnonymous("MapAnonymousLow4GBExpectedTooHigh",
                            reinterpret_cast<uint8_t*>(UINT64_C(0x100000000)),
                            kPageSize,
                            PROT_READ | PROT_WRITE,
-                           true,
-                           false,
-                           &error_msg));
-  ASSERT_EQ(nullptr, map.get());
+                           /* low_4gb */ true,
+                           &error_msg);
+  ASSERT_FALSE(map.IsValid());
   ASSERT_FALSE(error_msg.empty());
 }
 
 TEST_F(MemMapTest, MapAnonymousLow4GBRangeTooHigh) {
   CommonInit();
   std::string error_msg;
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymousLow4GBRangeTooHigh",
-                                                   reinterpret_cast<uint8_t*>(0xF0000000),
-                                                   0x20000000,
-                                                   PROT_READ | PROT_WRITE,
-                                                   true,
-                                                   false,
-                                                   &error_msg));
-  ASSERT_EQ(nullptr, map.get());
+  MemMap map = MemMap::MapAnonymous("MapAnonymousLow4GBRangeTooHigh",
+                                    reinterpret_cast<uint8_t*>(0xF0000000),
+                                    0x20000000,
+                                    PROT_READ | PROT_WRITE,
+                                    /* low_4gb */ true,
+                                    &error_msg);
+  ASSERT_FALSE(map.IsValid());
   ASSERT_FALSE(error_msg.empty());
 }
 #endif
@@ -549,23 +534,23 @@
 TEST_F(MemMapTest, MapAnonymousReuse) {
   CommonInit();
   std::string error_msg;
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymousReserve",
-                                                   nullptr,
-                                                   0x20000,
-                                                   PROT_READ | PROT_WRITE,
-                                                   false,
-                                                   false,
-                                                   &error_msg));
-  ASSERT_NE(nullptr, map.get());
+  MemMap map = MemMap::MapAnonymous("MapAnonymousReserve",
+                                    nullptr,
+                                    0x20000,
+                                    PROT_READ | PROT_WRITE,
+                                    /* low_4gb */ false,
+                                    /* reuse */ false,
+                                    &error_msg);
+  ASSERT_TRUE(map.IsValid());
   ASSERT_TRUE(error_msg.empty());
-  std::unique_ptr<MemMap> map2(MemMap::MapAnonymous("MapAnonymousReused",
-                                                    reinterpret_cast<uint8_t*>(map->BaseBegin()),
-                                                    0x10000,
-                                                    PROT_READ | PROT_WRITE,
-                                                    false,
-                                                    true,
-                                                    &error_msg));
-  ASSERT_NE(nullptr, map2.get());
+  MemMap map2 = MemMap::MapAnonymous("MapAnonymousReused",
+                                     reinterpret_cast<uint8_t*>(map.BaseBegin()),
+                                     0x10000,
+                                     PROT_READ | PROT_WRITE,
+                                     /* low_4gb */ false,
+                                     /* reuse */ true,
+                                     &error_msg);
+  ASSERT_TRUE(map2.IsValid());
   ASSERT_TRUE(error_msg.empty());
 }
 
@@ -574,65 +559,61 @@
   std::string error_msg;
   constexpr size_t kNumPages = 3;
   // Map a 3-page mem map.
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymous0",
-                                                   nullptr,
-                                                   kPageSize * kNumPages,
-                                                   PROT_READ | PROT_WRITE,
-                                                   false,
-                                                   false,
-                                                   &error_msg));
-  ASSERT_TRUE(map.get() != nullptr) << error_msg;
+  MemMap map = MemMap::MapAnonymous("MapAnonymous0",
+                                    /* addr */ nullptr,
+                                    kPageSize * kNumPages,
+                                    PROT_READ | PROT_WRITE,
+                                    /* low_4gb */ false,
+                                    &error_msg);
+  ASSERT_TRUE(map.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
   // Record the base address.
-  uint8_t* map_base = reinterpret_cast<uint8_t*>(map->BaseBegin());
+  uint8_t* map_base = reinterpret_cast<uint8_t*>(map.BaseBegin());
   // Unmap it.
-  map.reset();
+  map.Reset();
 
   // Map at the same address, but in page-sized separate mem maps,
   // assuming the space at the address is still available.
-  std::unique_ptr<MemMap> map0(MemMap::MapAnonymous("MapAnonymous0",
-                                                    map_base,
-                                                    kPageSize,
-                                                    PROT_READ | PROT_WRITE,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(map0.get() != nullptr) << error_msg;
+  MemMap map0 = MemMap::MapAnonymous("MapAnonymous0",
+                                     map_base,
+                                     kPageSize,
+                                     PROT_READ | PROT_WRITE,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_TRUE(map0.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
-  std::unique_ptr<MemMap> map1(MemMap::MapAnonymous("MapAnonymous1",
-                                                    map_base + kPageSize,
-                                                    kPageSize,
-                                                    PROT_READ | PROT_WRITE,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(map1.get() != nullptr) << error_msg;
+  MemMap map1 = MemMap::MapAnonymous("MapAnonymous1",
+                                     map_base + kPageSize,
+                                     kPageSize,
+                                     PROT_READ | PROT_WRITE,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_TRUE(map1.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
-  std::unique_ptr<MemMap> map2(MemMap::MapAnonymous("MapAnonymous2",
-                                                    map_base + kPageSize * 2,
-                                                    kPageSize,
-                                                    PROT_READ | PROT_WRITE,
-                                                    false,
-                                                    false,
-                                                    &error_msg));
-  ASSERT_TRUE(map2.get() != nullptr) << error_msg;
+  MemMap map2 = MemMap::MapAnonymous("MapAnonymous2",
+                                     map_base + kPageSize * 2,
+                                     kPageSize,
+                                     PROT_READ | PROT_WRITE,
+                                     /* low_4gb */ false,
+                                     &error_msg);
+  ASSERT_TRUE(map2.IsValid()) << error_msg;
   ASSERT_TRUE(error_msg.empty());
 
   // One-map cases.
-  ASSERT_TRUE(MemMap::CheckNoGaps(map0.get(), map0.get()));
-  ASSERT_TRUE(MemMap::CheckNoGaps(map1.get(), map1.get()));
-  ASSERT_TRUE(MemMap::CheckNoGaps(map2.get(), map2.get()));
+  ASSERT_TRUE(MemMap::CheckNoGaps(map0, map0));
+  ASSERT_TRUE(MemMap::CheckNoGaps(map1, map1));
+  ASSERT_TRUE(MemMap::CheckNoGaps(map2, map2));
 
   // Two or three-map cases.
-  ASSERT_TRUE(MemMap::CheckNoGaps(map0.get(), map1.get()));
-  ASSERT_TRUE(MemMap::CheckNoGaps(map1.get(), map2.get()));
-  ASSERT_TRUE(MemMap::CheckNoGaps(map0.get(), map2.get()));
+  ASSERT_TRUE(MemMap::CheckNoGaps(map0, map1));
+  ASSERT_TRUE(MemMap::CheckNoGaps(map1, map2));
+  ASSERT_TRUE(MemMap::CheckNoGaps(map0, map2));
 
   // Unmap the middle one.
-  map1.reset();
+  map1.Reset();
 
   // Should return false now that there's a gap in the middle.
-  ASSERT_FALSE(MemMap::CheckNoGaps(map0.get(), map2.get()));
+  ASSERT_FALSE(MemMap::CheckNoGaps(map0, map2));
 }
 
 TEST_F(MemMapTest, AlignBy) {
@@ -641,52 +622,52 @@
   // Cast the page size to size_t.
   const size_t page_size = static_cast<size_t>(kPageSize);
   // Map a region.
-  std::unique_ptr<MemMap> m0(MemMap::MapAnonymous("MemMapTest_AlignByTest_map0",
-                                                  nullptr,
-                                                  14 * page_size,
-                                                  PROT_READ | PROT_WRITE,
-                                                  false,
-                                                  false,
-                                                  &error_msg));
-  uint8_t* base0 = m0->Begin();
+  MemMap m0 = MemMap::MapAnonymous("MemMapTest_AlignByTest_map0",
+                                   /* addr */ nullptr,
+                                   14 * page_size,
+                                   PROT_READ | PROT_WRITE,
+                                   /* low_4gb */ false,
+                                   &error_msg);
+  ASSERT_TRUE(m0.IsValid());
+  uint8_t* base0 = m0.Begin();
   ASSERT_TRUE(base0 != nullptr) << error_msg;
-  ASSERT_EQ(m0->Size(), 14 * page_size);
-  ASSERT_EQ(BaseBegin(m0.get()), base0);
-  ASSERT_EQ(BaseSize(m0.get()), m0->Size());
+  ASSERT_EQ(m0.Size(), 14 * page_size);
+  ASSERT_EQ(m0.BaseBegin(), base0);
+  ASSERT_EQ(m0.BaseSize(), m0.Size());
 
   // Break it into several regions by using RemapAtEnd.
-  std::unique_ptr<MemMap> m1(m0->RemapAtEnd(base0 + 3 * page_size,
-                                            "MemMapTest_AlignByTest_map1",
-                                            PROT_READ | PROT_WRITE,
-                                            &error_msg));
-  uint8_t* base1 = m1->Begin();
+  MemMap m1 = m0.RemapAtEnd(base0 + 3 * page_size,
+                            "MemMapTest_AlignByTest_map1",
+                            PROT_READ | PROT_WRITE,
+                            &error_msg);
+  uint8_t* base1 = m1.Begin();
   ASSERT_TRUE(base1 != nullptr) << error_msg;
   ASSERT_EQ(base1, base0 + 3 * page_size);
-  ASSERT_EQ(m0->Size(), 3 * page_size);
+  ASSERT_EQ(m0.Size(), 3 * page_size);
 
-  std::unique_ptr<MemMap> m2(m1->RemapAtEnd(base1 + 4 * page_size,
-                                            "MemMapTest_AlignByTest_map2",
-                                            PROT_READ | PROT_WRITE,
-                                            &error_msg));
-  uint8_t* base2 = m2->Begin();
+  MemMap m2 = m1.RemapAtEnd(base1 + 4 * page_size,
+                            "MemMapTest_AlignByTest_map2",
+                            PROT_READ | PROT_WRITE,
+                            &error_msg);
+  uint8_t* base2 = m2.Begin();
   ASSERT_TRUE(base2 != nullptr) << error_msg;
   ASSERT_EQ(base2, base1 + 4 * page_size);
-  ASSERT_EQ(m1->Size(), 4 * page_size);
+  ASSERT_EQ(m1.Size(), 4 * page_size);
 
-  std::unique_ptr<MemMap> m3(m2->RemapAtEnd(base2 + 3 * page_size,
-                                            "MemMapTest_AlignByTest_map1",
-                                            PROT_READ | PROT_WRITE,
-                                            &error_msg));
-  uint8_t* base3 = m3->Begin();
+  MemMap m3 = m2.RemapAtEnd(base2 + 3 * page_size,
+                            "MemMapTest_AlignByTest_map1",
+                            PROT_READ | PROT_WRITE,
+                            &error_msg);
+  uint8_t* base3 = m3.Begin();
   ASSERT_TRUE(base3 != nullptr) << error_msg;
   ASSERT_EQ(base3, base2 + 3 * page_size);
-  ASSERT_EQ(m2->Size(), 3 * page_size);
-  ASSERT_EQ(m3->Size(), 4 * page_size);
+  ASSERT_EQ(m2.Size(), 3 * page_size);
+  ASSERT_EQ(m3.Size(), 4 * page_size);
 
-  uint8_t* end0 = base0 + m0->Size();
-  uint8_t* end1 = base1 + m1->Size();
-  uint8_t* end2 = base2 + m2->Size();
-  uint8_t* end3 = base3 + m3->Size();
+  uint8_t* end0 = base0 + m0.Size();
+  uint8_t* end1 = base1 + m1.Size();
+  uint8_t* end2 = base2 + m2.Size();
+  uint8_t* end3 = base3 + m3.Size();
 
   ASSERT_EQ(static_cast<size_t>(end3 - base0), 14 * page_size);
 
@@ -703,39 +684,39 @@
   }
 
   // Align by 2 * page_size;
-  m0->AlignBy(2 * page_size);
-  m1->AlignBy(2 * page_size);
-  m2->AlignBy(2 * page_size);
-  m3->AlignBy(2 * page_size);
+  m0.AlignBy(2 * page_size);
+  m1.AlignBy(2 * page_size);
+  m2.AlignBy(2 * page_size);
+  m3.AlignBy(2 * page_size);
 
-  EXPECT_TRUE(IsAlignedParam(m0->Begin(), 2 * page_size));
-  EXPECT_TRUE(IsAlignedParam(m1->Begin(), 2 * page_size));
-  EXPECT_TRUE(IsAlignedParam(m2->Begin(), 2 * page_size));
-  EXPECT_TRUE(IsAlignedParam(m3->Begin(), 2 * page_size));
+  EXPECT_TRUE(IsAlignedParam(m0.Begin(), 2 * page_size));
+  EXPECT_TRUE(IsAlignedParam(m1.Begin(), 2 * page_size));
+  EXPECT_TRUE(IsAlignedParam(m2.Begin(), 2 * page_size));
+  EXPECT_TRUE(IsAlignedParam(m3.Begin(), 2 * page_size));
 
-  EXPECT_TRUE(IsAlignedParam(m0->Begin() + m0->Size(), 2 * page_size));
-  EXPECT_TRUE(IsAlignedParam(m1->Begin() + m1->Size(), 2 * page_size));
-  EXPECT_TRUE(IsAlignedParam(m2->Begin() + m2->Size(), 2 * page_size));
-  EXPECT_TRUE(IsAlignedParam(m3->Begin() + m3->Size(), 2 * page_size));
+  EXPECT_TRUE(IsAlignedParam(m0.Begin() + m0.Size(), 2 * page_size));
+  EXPECT_TRUE(IsAlignedParam(m1.Begin() + m1.Size(), 2 * page_size));
+  EXPECT_TRUE(IsAlignedParam(m2.Begin() + m2.Size(), 2 * page_size));
+  EXPECT_TRUE(IsAlignedParam(m3.Begin() + m3.Size(), 2 * page_size));
 
   if (IsAlignedParam(base0, 2 * page_size)) {
-    EXPECT_EQ(m0->Begin(), base0);
-    EXPECT_EQ(m0->Begin() + m0->Size(), end0 - page_size);
-    EXPECT_EQ(m1->Begin(), base1 + page_size);
-    EXPECT_EQ(m1->Begin() + m1->Size(), end1 - page_size);
-    EXPECT_EQ(m2->Begin(), base2 + page_size);
-    EXPECT_EQ(m2->Begin() + m2->Size(), end2);
-    EXPECT_EQ(m3->Begin(), base3);
-    EXPECT_EQ(m3->Begin() + m3->Size(), end3);
+    EXPECT_EQ(m0.Begin(), base0);
+    EXPECT_EQ(m0.Begin() + m0.Size(), end0 - page_size);
+    EXPECT_EQ(m1.Begin(), base1 + page_size);
+    EXPECT_EQ(m1.Begin() + m1.Size(), end1 - page_size);
+    EXPECT_EQ(m2.Begin(), base2 + page_size);
+    EXPECT_EQ(m2.Begin() + m2.Size(), end2);
+    EXPECT_EQ(m3.Begin(), base3);
+    EXPECT_EQ(m3.Begin() + m3.Size(), end3);
   } else {
-    EXPECT_EQ(m0->Begin(), base0 + page_size);
-    EXPECT_EQ(m0->Begin() + m0->Size(), end0);
-    EXPECT_EQ(m1->Begin(), base1);
-    EXPECT_EQ(m1->Begin() + m1->Size(), end1);
-    EXPECT_EQ(m2->Begin(), base2);
-    EXPECT_EQ(m2->Begin() + m2->Size(), end2 - page_size);
-    EXPECT_EQ(m3->Begin(), base3 + page_size);
-    EXPECT_EQ(m3->Begin() + m3->Size(), end3 - page_size);
+    EXPECT_EQ(m0.Begin(), base0 + page_size);
+    EXPECT_EQ(m0.Begin() + m0.Size(), end0);
+    EXPECT_EQ(m1.Begin(), base1);
+    EXPECT_EQ(m1.Begin() + m1.Size(), end1);
+    EXPECT_EQ(m2.Begin(), base2);
+    EXPECT_EQ(m2.Begin() + m2.Size(), end2 - page_size);
+    EXPECT_EQ(m3.Begin(), base3 + page_size);
+    EXPECT_EQ(m3.Begin() + m3.Size(), end3 - page_size);
   }
 }
 
diff --git a/libartbase/base/zip_archive.cc b/libartbase/base/zip_archive.cc
index b5f946e..a841bae 100644
--- a/libartbase/base/zip_archive.cc
+++ b/libartbase/base/zip_archive.cc
@@ -68,31 +68,33 @@
   return true;
 }
 
-MemMap* ZipEntry::ExtractToMemMap(const char* zip_filename, const char* entry_filename,
-                                  std::string* error_msg) {
+MemMap ZipEntry::ExtractToMemMap(const char* zip_filename,
+                                 const char* entry_filename,
+                                 std::string* error_msg) {
   std::string name(entry_filename);
   name += " extracted in memory from ";
   name += zip_filename;
-  std::unique_ptr<MemMap> map(MemMap::MapAnonymous(name.c_str(),
-                                                   nullptr, GetUncompressedLength(),
-                                                   PROT_READ | PROT_WRITE, false, false,
-                                                   error_msg));
-  if (map.get() == nullptr) {
+  MemMap map = MemMap::MapAnonymous(name.c_str(),
+                                    /* addr */ nullptr,
+                                    GetUncompressedLength(),
+                                    PROT_READ | PROT_WRITE,
+                                    /* low_4gb */ false,
+                                    error_msg);
+  if (!map.IsValid()) {
     DCHECK(!error_msg->empty());
-    return nullptr;
+    return MemMap::Invalid();
   }
 
-  const int32_t error = ExtractToMemory(handle_, zip_entry_,
-                                        map->Begin(), map->Size());
+  const int32_t error = ExtractToMemory(handle_, zip_entry_, map.Begin(), map.Size());
   if (error) {
     *error_msg = std::string(ErrorCodeString(error));
-    return nullptr;
+    return MemMap::Invalid();
   }
 
-  return map.release();
+  return map;
 }
 
-MemMap* ZipEntry::MapDirectlyFromFile(const char* zip_filename, std::string* error_msg) {
+MemMap ZipEntry::MapDirectlyFromFile(const char* zip_filename, std::string* error_msg) {
   const int zip_fd = GetFileDescriptor(handle_);
   const char* entry_filename = entry_name_.c_str();
 
@@ -109,7 +111,7 @@
     *error_msg = StringPrintf("Cannot map '%s' (in zip '%s') directly because it is compressed.",
                               entry_filename,
                               zip_filename);
-    return nullptr;
+    return MemMap::Invalid();
   } else if (zip_entry_->uncompressed_length != zip_entry_->compressed_length) {
     *error_msg = StringPrintf("Cannot map '%s' (in zip '%s') directly because "
                               "entry has bad size (%u != %u).",
@@ -117,7 +119,7 @@
                               zip_filename,
                               zip_entry_->uncompressed_length,
                               zip_entry_->compressed_length);
-    return nullptr;
+    return MemMap::Invalid();
   }
 
   std::string name(entry_filename);
@@ -130,7 +132,7 @@
     LOG(INFO) << "zip_archive: " << "make mmap of " << name << " @ offset = " << offset;
   }
 
-  std::unique_ptr<MemMap> map(
+  MemMap map =
       MemMap::MapFileAtAddress(nullptr,  // Expected pointer address
                                GetUncompressedLength(),  // Byte count
                                PROT_READ | PROT_WRITE,
@@ -140,9 +142,9 @@
                                false,  // Don't restrict allocation to lower4GB
                                false,  // Doesn't overlap existing map (reuse=false)
                                name.c_str(),
-                               /*out*/error_msg));
+                               /*out*/error_msg);
 
-  if (map == nullptr) {
+  if (!map.IsValid()) {
     DCHECK(!error_msg->empty());
   }
 
@@ -169,12 +171,12 @@
     LOG(INFO) << "---------------------------";
 
     // Dump map contents.
-    if (map != nullptr) {
+    if (map.IsValid()) {
       tmp = "";
 
       count = kMaxDumpChars;
 
-      uint8_t* begin = map->Begin();
+      uint8_t* begin = map.Begin();
       for (i = 0; i < count; ++i) {
         tmp += StringPrintf("%3d ", (unsigned int)begin[i]);
       }
@@ -185,19 +187,20 @@
     }
   }
 
-  return map.release();
+  return map;
 }
 
-MemMap* ZipEntry::MapDirectlyOrExtract(const char* zip_filename,
-                                       const char* entry_filename,
-                                       std::string* error_msg) {
+MemMap ZipEntry::MapDirectlyOrExtract(const char* zip_filename,
+                                      const char* entry_filename,
+                                      std::string* error_msg) {
   if (IsUncompressed() && GetFileDescriptor(handle_) >= 0) {
-    MemMap* ret = MapDirectlyFromFile(zip_filename, error_msg);
-    if (ret != nullptr) {
+    std::string local_error_msg;
+    MemMap ret = MapDirectlyFromFile(zip_filename, &local_error_msg);
+    if (ret.IsValid()) {
       return ret;
     }
+    // Fall back to extraction for the failure case.
   }
-  // Fall back to extraction for the failure case.
   return ExtractToMemMap(zip_filename, entry_filename, error_msg);
 }
 
diff --git a/libartbase/base/zip_archive.h b/libartbase/base/zip_archive.h
index 73495da..8fc8b54 100644
--- a/libartbase/base/zip_archive.h
+++ b/libartbase/base/zip_archive.h
@@ -43,21 +43,22 @@
   bool ExtractToFile(File& file, std::string* error_msg);
   // Extract this entry to anonymous memory (R/W).
   // Returns null on failure and sets error_msg.
-  MemMap* ExtractToMemMap(const char* zip_filename, const char* entry_filename,
-                          std::string* error_msg);
+  MemMap ExtractToMemMap(const char* zip_filename,
+                         const char* entry_filename,
+                         std::string* error_msg);
   // Create a file-backed private (clean, R/W) memory mapping to this entry.
   // 'zip_filename' is used for diagnostics only,
   //   the original file that the ZipArchive was open with is used
   //   for the mapping.
   //
   // Will only succeed if the entry is stored uncompressed.
-  // Returns null on failure and sets error_msg.
-  MemMap* MapDirectlyFromFile(const char* zip_filename, /*out*/std::string* error_msg);
+  // Returns invalid MemMap on failure and sets error_msg.
+  MemMap MapDirectlyFromFile(const char* zip_filename, /*out*/std::string* error_msg);
   virtual ~ZipEntry();
 
-  MemMap* MapDirectlyOrExtract(const char* zip_filename,
-                               const char* entry_filename,
-                               std::string* error_msg);
+  MemMap MapDirectlyOrExtract(const char* zip_filename,
+                              const char* entry_filename,
+                              std::string* error_msg);
 
   uint32_t GetUncompressedLength();
   uint32_t GetCrc32();
diff --git a/libdexfile/dex/art_dex_file_loader.cc b/libdexfile/dex/art_dex_file_loader.cc
index cc7d7aa..1846a13 100644
--- a/libdexfile/dex/art_dex_file_loader.cc
+++ b/libdexfile/dex/art_dex_file_loader.cc
@@ -23,6 +23,7 @@
 
 #include "base/file_magic.h"
 #include "base/file_utils.h"
+#include "base/mem_map.h"
 #include "base/stl_util.h"
 #include "base/systrace.h"
 #include "base/unix_file/fd_file.h"
@@ -38,14 +39,14 @@
 
 class MemMapContainer : public DexFileContainer {
  public:
-  explicit MemMapContainer(std::unique_ptr<MemMap>&& mem_map) : mem_map_(std::move(mem_map)) { }
+  explicit MemMapContainer(MemMap&& mem_map) : mem_map_(std::move(mem_map)) { }
   virtual ~MemMapContainer() OVERRIDE { }
 
   int GetPermissions() OVERRIDE {
-    if (mem_map_.get() == nullptr) {
+    if (!mem_map_.IsValid()) {
       return 0;
     } else {
-      return mem_map_->GetProtect();
+      return mem_map_.GetProtect();
     }
   }
 
@@ -55,24 +56,24 @@
 
   bool EnableWrite() OVERRIDE {
     CHECK(IsReadOnly());
-    if (mem_map_.get() == nullptr) {
+    if (!mem_map_.IsValid()) {
       return false;
     } else {
-      return mem_map_->Protect(PROT_READ | PROT_WRITE);
+      return mem_map_.Protect(PROT_READ | PROT_WRITE);
     }
   }
 
   bool DisableWrite() OVERRIDE {
     CHECK(!IsReadOnly());
-    if (mem_map_.get() == nullptr) {
+    if (!mem_map_.IsValid()) {
       return false;
     } else {
-      return mem_map_->Protect(PROT_READ);
+      return mem_map_.Protect(PROT_READ);
     }
   }
 
  private:
-  std::unique_ptr<MemMap> mem_map_;
+  MemMap mem_map_;
   DISALLOW_COPY_AND_ASSIGN(MemMapContainer);
 };
 
@@ -180,22 +181,24 @@
 
 std::unique_ptr<const DexFile> ArtDexFileLoader::Open(const std::string& location,
                                                       uint32_t location_checksum,
-                                                      std::unique_ptr<MemMap> map,
+                                                      MemMap&& map,
                                                       bool verify,
                                                       bool verify_checksum,
                                                       std::string* error_msg) const {
   ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location);
-  CHECK(map.get() != nullptr);
+  CHECK(map.IsValid());
 
-  if (map->Size() < sizeof(DexFile::Header)) {
+  size_t size = map.Size();
+  if (size < sizeof(DexFile::Header)) {
     *error_msg = StringPrintf(
         "DexFile: failed to open dex file '%s' that is too short to have a header",
         location.c_str());
     return nullptr;
   }
 
-  std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
-                                                 map->Size(),
+  uint8_t* begin = map.Begin();
+  std::unique_ptr<DexFile> dex_file = OpenCommon(begin,
+                                                 size,
                                                  /*data_base*/ nullptr,
                                                  /*data_size*/ 0u,
                                                  location,
@@ -285,7 +288,7 @@
                                                           std::string* error_msg) const {
   ScopedTrace trace(std::string("Open dex file ") + std::string(location));
   CHECK(!location.empty());
-  std::unique_ptr<MemMap> map;
+  MemMap map;
   {
     File delayed_close(fd, /* check_usage */ false);
     struct stat sbuf;
@@ -300,31 +303,33 @@
       return nullptr;
     }
     size_t length = sbuf.st_size;
-    map.reset(MemMap::MapFile(length,
-                              PROT_READ,
-                              mmap_shared ? MAP_SHARED : MAP_PRIVATE,
-                              fd,
-                              0,
-                              /*low_4gb*/false,
-                              location.c_str(),
-                              error_msg));
-    if (map == nullptr) {
+    map = MemMap::MapFile(length,
+                          PROT_READ,
+                          mmap_shared ? MAP_SHARED : MAP_PRIVATE,
+                          fd,
+                          0,
+                          /*low_4gb*/false,
+                          location.c_str(),
+                          error_msg);
+    if (!map.IsValid()) {
       DCHECK(!error_msg->empty());
       return nullptr;
     }
   }
 
-  if (map->Size() < sizeof(DexFile::Header)) {
+  const uint8_t* begin = map.Begin();
+  size_t size = map.Size();
+  if (size < sizeof(DexFile::Header)) {
     *error_msg = StringPrintf(
         "DexFile: failed to open dex file '%s' that is too short to have a header",
         location.c_str());
     return nullptr;
   }
 
-  const DexFile::Header* dex_header = reinterpret_cast<const DexFile::Header*>(map->Begin());
+  const DexFile::Header* dex_header = reinterpret_cast<const DexFile::Header*>(begin);
 
-  std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
-                                                 map->Size(),
+  std::unique_ptr<DexFile> dex_file = OpenCommon(begin,
+                                                 size,
                                                  /*data_base*/ nullptr,
                                                  /*data_size*/ 0u,
                                                  location,
@@ -366,7 +371,7 @@
     return nullptr;
   }
 
-  std::unique_ptr<MemMap> map;
+  MemMap map;
   if (zip_entry->IsUncompressed()) {
     if (!zip_entry->IsAlignedTo(alignof(DexFile::Header))) {
       // Do not mmap unaligned ZIP entries because
@@ -376,8 +381,8 @@
                    << "Falling back to extracting file.";
     } else {
       // Map uncompressed files within zip as file-backed to avoid a dirty copy.
-      map.reset(zip_entry->MapDirectlyFromFile(location.c_str(), /*out*/error_msg));
-      if (map == nullptr) {
+      map = zip_entry->MapDirectlyFromFile(location.c_str(), /*out*/error_msg);
+      if (!map.IsValid()) {
         LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; "
                      << "is your ZIP file corrupted? Falling back to extraction.";
         // Try again with Extraction which still has a chance of recovery.
@@ -385,21 +390,23 @@
     }
   }
 
-  if (map == nullptr) {
+  if (!map.IsValid()) {
     // Default path for compressed ZIP entries,
     // and fallback for stored ZIP entries.
-    map.reset(zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg));
+    map = zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg);
   }
 
-  if (map == nullptr) {
+  if (!map.IsValid()) {
     *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(),
                               error_msg->c_str());
     *error_code = DexFileLoaderErrorCode::kExtractToMemoryError;
     return nullptr;
   }
   VerifyResult verify_result;
-  std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
-                                                 map->Size(),
+  uint8_t* begin = map.Begin();
+  size_t size = map.Size();
+  std::unique_ptr<DexFile> dex_file = OpenCommon(begin,
+                                                 size,
                                                  /*data_base*/ nullptr,
                                                  /*data_size*/ 0u,
                                                  location,
diff --git a/libdexfile/dex/art_dex_file_loader.h b/libdexfile/dex/art_dex_file_loader.h
index da2620f..420b347 100644
--- a/libdexfile/dex/art_dex_file_loader.h
+++ b/libdexfile/dex/art_dex_file_loader.h
@@ -66,7 +66,7 @@
   // Opens .dex file that has been memory-mapped by the caller.
   std::unique_ptr<const DexFile> Open(const std::string& location,
                                       uint32_t location_checkum,
-                                      std::unique_ptr<MemMap> mem_map,
+                                      MemMap&& mem_map,
                                       bool verify,
                                       bool verify_checksum,
                                       std::string* error_msg) const;
diff --git a/libdexfile/dex/class_accessor.h b/libdexfile/dex/class_accessor.h
index 9b9ac87..d40577f 100644
--- a/libdexfile/dex/class_accessor.h
+++ b/libdexfile/dex/class_accessor.h
@@ -31,7 +31,7 @@
 
 // Classes to access Dex data.
 class ClassAccessor {
- private:
+ public:
   class BaseItem {
    public:
     explicit BaseItem(const DexFile& dex_file,
@@ -65,6 +65,14 @@
       return ptr_pos_;
     }
 
+    bool MemberIsNative() const {
+      return GetRawAccessFlags() & kAccNative;
+    }
+
+    bool MemberIsFinal() const {
+      return GetRawAccessFlags() & kAccFinal;
+    }
+
    protected:
     // Internal data pointer for reading.
     const DexFile& dex_file_;
@@ -73,7 +81,6 @@
     uint32_t access_flags_ = 0u;
   };
 
- public:
   // A decoded version of the method of a class_data_item.
   class Method : public BaseItem {
    public:
@@ -350,6 +357,10 @@
     return class_def_index_;
   }
 
+  const DexFile::ClassDef& GetClassDef() const {
+    return dex_file_.GetClassDef(GetClassDefIndex());
+  }
+
  protected:
   // Template visitor to reduce copy paste for visiting elements.
   // No thread safety analysis since the visitor may require capabilities.
diff --git a/libdexfile/dex/code_item_accessors.h b/libdexfile/dex/code_item_accessors.h
index 5786d3f..695cc7b 100644
--- a/libdexfile/dex/code_item_accessors.h
+++ b/libdexfile/dex/code_item_accessors.h
@@ -80,7 +80,7 @@
   uint32_t insns_size_in_code_units_ = 0;
 
   // Pointer to the instructions, null if there is no code item.
-  const uint16_t* insns_ = 0;
+  const uint16_t* insns_ = nullptr;
 };
 
 // Abstracts accesses to code item fields other than debug info for CompactDexFile and
diff --git a/libdexfile/dex/dex_file-inl.h b/libdexfile/dex/dex_file-inl.h
index 0966859..c512361 100644
--- a/libdexfile/dex/dex_file-inl.h
+++ b/libdexfile/dex/dex_file-inl.h
@@ -204,26 +204,6 @@
   return true;
 }
 
-inline
-InvokeType ClassDataItemIterator::GetMethodInvokeType(const DexFile::ClassDef& class_def) const {
-  if (HasNextDirectMethod()) {
-    if ((GetRawMemberAccessFlags() & kAccStatic) != 0) {
-      return kStatic;
-    } else {
-      return kDirect;
-    }
-  } else {
-    DCHECK_EQ(GetRawMemberAccessFlags() & kAccStatic, 0U);
-    if ((class_def.access_flags_ & kAccInterface) != 0) {
-      return kInterface;
-    } else if ((GetRawMemberAccessFlags() & kAccConstructor) != 0) {
-      return kSuper;
-    } else {
-      return kVirtual;
-    }
-  }
-}
-
 template<typename NewLocalCallback, typename IndexToStringData, typename TypeIndexToStringData>
 bool DexFile::DecodeDebugLocalInfo(const uint8_t* stream,
                                    const std::string& location,
@@ -518,18 +498,6 @@
   return handler_data + offset;
 }
 
-template <typename Visitor>
-inline void DexFile::ClassDef::VisitMethods(const DexFile* dex_file, const Visitor& visitor) const {
-  const uint8_t* class_data = dex_file->GetClassData(*this);
-  if (class_data != nullptr) {
-    ClassDataItemIterator it(*dex_file, class_data);
-    it.SkipAllFields();
-    for (; it.HasNext(); it.Next()) {
-      visitor(it);
-    }
-  }
-}
-
 inline IterationRange<ClassIterator> DexFile::GetClasses() const {
   return { ClassIterator(*this, 0u), ClassIterator(*this, NumClassDefs()) };
 }
diff --git a/libdexfile/dex/dex_file.cc b/libdexfile/dex/dex_file.cc
index f1f8960..a2198b7 100644
--- a/libdexfile/dex/dex_file.cc
+++ b/libdexfile/dex/dex_file.cc
@@ -31,6 +31,7 @@
 #include "base/enums.h"
 #include "base/leb128.h"
 #include "base/stl_util.h"
+#include "class_accessor-inl.h"
 #include "descriptors_names.h"
 #include "dex_file-inl.h"
 #include "standard_dex_file.h"
@@ -219,21 +220,12 @@
 
 uint32_t DexFile::FindCodeItemOffset(const DexFile::ClassDef& class_def,
                                      uint32_t method_idx) const {
-  const uint8_t* class_data = GetClassData(class_def);
-  CHECK(class_data != nullptr);
-  ClassDataItemIterator it(*this, class_data);
-  it.SkipAllFields();
-  while (it.HasNextDirectMethod()) {
-    if (it.GetMemberIndex() == method_idx) {
-      return it.GetMethodCodeItemOffset();
+  ClassAccessor accessor(*this, class_def);
+  CHECK(accessor.HasClassData());
+  for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+    if (method.GetIndex() == method_idx) {
+      return method.GetCodeItemOffset();
     }
-    it.Next();
-  }
-  while (it.HasNextVirtualMethod()) {
-    if (it.GetMemberIndex() == method_idx) {
-      return it.GetMethodCodeItemOffset();
-    }
-    it.Next();
   }
   LOG(FATAL) << "Unable to find method " << method_idx;
   UNREACHABLE();
@@ -684,31 +676,6 @@
   return os << sig.ToString();
 }
 
-// Decodes the header section from the class data bytes.
-void ClassDataItemIterator::ReadClassDataHeader() {
-  CHECK(ptr_pos_ != nullptr);
-  header_.static_fields_size_ = DecodeUnsignedLeb128(&ptr_pos_);
-  header_.instance_fields_size_ = DecodeUnsignedLeb128(&ptr_pos_);
-  header_.direct_methods_size_ = DecodeUnsignedLeb128(&ptr_pos_);
-  header_.virtual_methods_size_ = DecodeUnsignedLeb128(&ptr_pos_);
-}
-
-void ClassDataItemIterator::ReadClassDataField() {
-  field_.field_idx_delta_ = DecodeUnsignedLeb128(&ptr_pos_);
-  field_.access_flags_ = DecodeUnsignedLeb128(&ptr_pos_);
-  // The user of the iterator is responsible for checking if there
-  // are unordered or duplicate indexes.
-}
-
-void ClassDataItemIterator::ReadClassDataMethod() {
-  method_.method_idx_delta_ = DecodeUnsignedLeb128(&ptr_pos_);
-  method_.access_flags_ = DecodeUnsignedLeb128(&ptr_pos_);
-  method_.code_off_ = DecodeUnsignedLeb128(&ptr_pos_);
-  if (last_idx_ != 0 && method_.method_idx_delta_ == 0) {
-    LOG(WARNING) << "Duplicate method in " << dex_file_.GetLocation();
-  }
-}
-
 EncodedArrayValueIterator::EncodedArrayValueIterator(const DexFile& dex_file,
                                                      const uint8_t* array_data)
     : dex_file_(dex_file),
diff --git a/libdexfile/dex/dex_file.h b/libdexfile/dex/dex_file.h
index 25cd2f4..98787d1 100644
--- a/libdexfile/dex/dex_file.h
+++ b/libdexfile/dex/dex_file.h
@@ -238,9 +238,6 @@
       }
     }
 
-    template <typename Visitor>
-    void VisitMethods(const DexFile* dex_file, const Visitor& visitor) const;
-
    private:
     DISALLOW_COPY_AND_ASSIGN(ClassDef);
   };
@@ -1174,211 +1171,6 @@
 };
 std::ostream& operator<<(std::ostream& os, const Signature& sig);
 
-// Iterate and decode class_data_item
-class ClassDataItemIterator {
- public:
-  ClassDataItemIterator(const DexFile& dex_file, const uint8_t* raw_class_data_item)
-      : dex_file_(dex_file), pos_(0), ptr_pos_(raw_class_data_item), last_idx_(0) {
-    ReadClassDataHeader();
-    if (EndOfInstanceFieldsPos() > 0) {
-      ReadClassDataField();
-    } else if (EndOfVirtualMethodsPos() > 0) {
-      ReadClassDataMethod();
-    }
-  }
-  uint32_t NumStaticFields() const {
-    return header_.static_fields_size_;
-  }
-  uint32_t NumInstanceFields() const {
-    return header_.instance_fields_size_;
-  }
-  uint32_t NumDirectMethods() const {
-    return header_.direct_methods_size_;
-  }
-  uint32_t NumVirtualMethods() const {
-    return header_.virtual_methods_size_;
-  }
-  bool IsAtMethod() const {
-    return pos_ >= EndOfInstanceFieldsPos();
-  }
-  bool IsAtVirtualMethod() const {
-    return pos_ >= EndOfDirectMethodsPos();
-  }
-  bool HasNextStaticField() const {
-    return pos_ < EndOfStaticFieldsPos();
-  }
-  bool HasNextInstanceField() const {
-    return pos_ >= EndOfStaticFieldsPos() && pos_ < EndOfInstanceFieldsPos();
-  }
-  bool HasNextDirectMethod() const {
-    return pos_ >= EndOfInstanceFieldsPos() && pos_ < EndOfDirectMethodsPos();
-  }
-  bool HasNextVirtualMethod() const {
-    return pos_ >= EndOfDirectMethodsPos() && pos_ < EndOfVirtualMethodsPos();
-  }
-  bool HasNextMethod() const {
-    const bool result = pos_ >= EndOfInstanceFieldsPos() && pos_ < EndOfVirtualMethodsPos();
-    DCHECK_EQ(result, HasNextDirectMethod() || HasNextVirtualMethod());
-    return result;
-  }
-  void SkipStaticFields() {
-    while (HasNextStaticField()) {
-      Next();
-    }
-  }
-  void SkipInstanceFields() {
-    while (HasNextInstanceField()) {
-      Next();
-    }
-  }
-  void SkipAllFields() {
-    SkipStaticFields();
-    SkipInstanceFields();
-  }
-  void SkipDirectMethods() {
-    while (HasNextDirectMethod()) {
-      Next();
-    }
-  }
-  void SkipVirtualMethods() {
-    while (HasNextVirtualMethod()) {
-      Next();
-    }
-  }
-  bool HasNext() const {
-    return pos_ < EndOfVirtualMethodsPos();
-  }
-  inline void Next() {
-    pos_++;
-    if (pos_ < EndOfStaticFieldsPos()) {
-      last_idx_ = GetMemberIndex();
-      ReadClassDataField();
-    } else if (pos_ == EndOfStaticFieldsPos() && NumInstanceFields() > 0) {
-      last_idx_ = 0;  // transition to next array, reset last index
-      ReadClassDataField();
-    } else if (pos_ < EndOfInstanceFieldsPos()) {
-      last_idx_ = GetMemberIndex();
-      ReadClassDataField();
-    } else if (pos_ == EndOfInstanceFieldsPos() && NumDirectMethods() > 0) {
-      last_idx_ = 0;  // transition to next array, reset last index
-      ReadClassDataMethod();
-    } else if (pos_ < EndOfDirectMethodsPos()) {
-      last_idx_ = GetMemberIndex();
-      ReadClassDataMethod();
-    } else if (pos_ == EndOfDirectMethodsPos() && NumVirtualMethods() > 0) {
-      last_idx_ = 0;  // transition to next array, reset last index
-      ReadClassDataMethod();
-    } else if (pos_ < EndOfVirtualMethodsPos()) {
-      last_idx_ = GetMemberIndex();
-      ReadClassDataMethod();
-    } else {
-      DCHECK(!HasNext());
-    }
-  }
-  uint32_t GetMemberIndex() const {
-    if (pos_ < EndOfInstanceFieldsPos()) {
-      return last_idx_ + field_.field_idx_delta_;
-    } else {
-      DCHECK_LT(pos_, EndOfVirtualMethodsPos());
-      return last_idx_ + method_.method_idx_delta_;
-    }
-  }
-  uint32_t GetRawMemberAccessFlags() const {
-    if (pos_ < EndOfInstanceFieldsPos()) {
-      return field_.access_flags_;
-    } else {
-      DCHECK_LT(pos_, EndOfVirtualMethodsPos());
-      return method_.access_flags_;
-    }
-  }
-  uint32_t GetFieldAccessFlags() const {
-    return GetMemberAccessFlags() & kAccValidFieldFlags;
-  }
-  uint32_t GetMethodAccessFlags() const {
-    return GetMemberAccessFlags() & kAccValidMethodFlags;
-  }
-  uint32_t GetMemberAccessFlags() const {
-    return HiddenApiAccessFlags::RemoveFromDex(GetRawMemberAccessFlags());
-  }
-  HiddenApiAccessFlags::ApiList DecodeHiddenAccessFlags() const {
-    return HiddenApiAccessFlags::DecodeFromDex(GetRawMemberAccessFlags());
-  }
-  bool MemberIsNative() const {
-    return GetRawMemberAccessFlags() & kAccNative;
-  }
-  bool MemberIsFinal() const {
-    return GetRawMemberAccessFlags() & kAccFinal;
-  }
-  ALWAYS_INLINE InvokeType GetMethodInvokeType(const DexFile::ClassDef& class_def) const;
-  const DexFile::CodeItem* GetMethodCodeItem() const {
-    return dex_file_.GetCodeItem(method_.code_off_);
-  }
-  uint32_t GetMethodCodeItemOffset() const {
-    return method_.code_off_;
-  }
-  const uint8_t* DataPointer() const {
-    return ptr_pos_;
-  }
-  const uint8_t* EndDataPointer() const {
-    CHECK(!HasNext());
-    return ptr_pos_;
-  }
-
- private:
-  // A dex file's class_data_item is leb128 encoded, this structure holds a decoded form of the
-  // header for a class_data_item
-  struct ClassDataHeader {
-    uint32_t static_fields_size_;  // the number of static fields
-    uint32_t instance_fields_size_;  // the number of instance fields
-    uint32_t direct_methods_size_;  // the number of direct methods
-    uint32_t virtual_methods_size_;  // the number of virtual methods
-  } header_;
-
-  // Read and decode header from a class_data_item stream into header
-  void ReadClassDataHeader();
-
-  uint32_t EndOfStaticFieldsPos() const {
-    return header_.static_fields_size_;
-  }
-  uint32_t EndOfInstanceFieldsPos() const {
-    return EndOfStaticFieldsPos() + header_.instance_fields_size_;
-  }
-  uint32_t EndOfDirectMethodsPos() const {
-    return EndOfInstanceFieldsPos() + header_.direct_methods_size_;
-  }
-  uint32_t EndOfVirtualMethodsPos() const {
-    return EndOfDirectMethodsPos() + header_.virtual_methods_size_;
-  }
-
-  // A decoded version of the field of a class_data_item
-  struct ClassDataField {
-    uint32_t field_idx_delta_;  // delta of index into the field_ids array for FieldId
-    uint32_t access_flags_;  // access flags for the field
-    ClassDataField() :  field_idx_delta_(0), access_flags_(0) {}
-  };
-  ClassDataField field_;
-
-  // Read and decode a field from a class_data_item stream into field
-  void ReadClassDataField();
-
-  // A decoded version of the method of a class_data_item
-  struct ClassDataMethod {
-    uint32_t method_idx_delta_;  // delta of index into the method_ids array for MethodId
-    uint32_t access_flags_;
-    uint32_t code_off_;
-    ClassDataMethod() : method_idx_delta_(0), access_flags_(0), code_off_(0) {}
-  };
-  ClassDataMethod method_;
-
-  // Read and decode a method from a class_data_item stream into method
-  void ReadClassDataMethod();
-
-  const DexFile& dex_file_;
-  size_t pos_;  // integral number of items passed
-  const uint8_t* ptr_pos_;  // pointer into stream of class_data_item
-  uint32_t last_idx_;  // last read field or method index to apply delta to
-};
-
 class EncodedArrayValueIterator {
  public:
   EncodedArrayValueIterator(const DexFile& dex_file, const uint8_t* array_data);
diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h
index be82fff..38f8455 100644
--- a/libdexfile/dex/modifiers.h
+++ b/libdexfile/dex/modifiers.h
@@ -42,9 +42,8 @@
 
 static constexpr uint32_t kAccJavaFlagsMask = 0xffff;  // bits set from Java sources (low 16)
 
-// The following flags are used to insert hidden API access flags into boot
-// class path dex files. They are decoded by DexFile::ClassDataItemIterator and
-// removed from the access flags before used by the runtime.
+// The following flags are used to insert hidden API access flags into boot class path dex files.
+// They are decoded by ClassAccessor and removed from the access flags before used by the runtime.
 static constexpr uint32_t kAccDexHiddenBit =          0x00000020;  // field, method (not native)
 static constexpr uint32_t kAccDexHiddenBitNative =    0x00000200;  // method (native)
 
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index 6f49adf..c765345 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -1183,7 +1183,7 @@
       // (e.g. dex metadata files)
       LOG(WARNING) << "Could not find entry " << kDexMetadataProfileEntry
           << " in the zip archive. Creating an empty profile.";
-      source->reset(ProfileSource::Create(nullptr));
+      source->reset(ProfileSource::Create(MemMap::Invalid()));
       return kProfileLoadSuccess;
     }
     if (zip_entry->GetUncompressedLength() == 0) {
@@ -1192,11 +1192,9 @@
     }
 
     // TODO(calin) pass along file names to assist with debugging.
-    std::unique_ptr<MemMap> map(zip_entry->MapDirectlyOrExtract(kDexMetadataProfileEntry,
-                                                                "profile file",
-                                                                error));
+    MemMap map = zip_entry->MapDirectlyOrExtract(kDexMetadataProfileEntry, "profile file", error);
 
-    if (map != nullptr) {
+    if (map.IsValid()) {
       source->reset(ProfileSource::Create(std::move(map)));
       return kProfileLoadSuccess;
     } else {
@@ -1211,11 +1209,11 @@
     const std::string& debug_stage,
     std::string* error) {
   if (IsMemMap()) {
-    if (mem_map_cur_ + byte_count > mem_map_->Size()) {
+    if (mem_map_cur_ + byte_count > mem_map_.Size()) {
       return kProfileLoadBadData;
     }
     for (size_t i = 0; i < byte_count; i++) {
-      buffer[i] = *(mem_map_->Begin() + mem_map_cur_);
+      buffer[i] = *(mem_map_.Begin() + mem_map_cur_);
       mem_map_cur_++;
     }
   } else {
@@ -1237,13 +1235,13 @@
 
 bool ProfileCompilationInfo::ProfileSource::HasConsumedAllData() const {
   return IsMemMap()
-      ? (mem_map_ == nullptr || mem_map_cur_ == mem_map_->Size())
+      ? (!mem_map_.IsValid() || mem_map_cur_ == mem_map_.Size())
       : (testEOF(fd_) == 0);
 }
 
 bool ProfileCompilationInfo::ProfileSource::HasEmptyContent() const {
   if (IsMemMap()) {
-    return mem_map_ == nullptr || mem_map_->Size() == 0;
+    return !mem_map_.IsValid() || mem_map_.Size() == 0;
   } else {
     struct stat stat_buffer;
     if (fstat(fd_, &stat_buffer) != 0) {
diff --git a/libprofile/profile/profile_compilation_info.h b/libprofile/profile/profile_compilation_info.h
index 3596f3e..0dbf490 100644
--- a/libprofile/profile/profile_compilation_info.h
+++ b/libprofile/profile/profile_compilation_info.h
@@ -637,14 +637,14 @@
      */
     static ProfileSource* Create(int32_t fd) {
       DCHECK_GT(fd, -1);
-      return new ProfileSource(fd, /*map*/ nullptr);
+      return new ProfileSource(fd, MemMap::Invalid());
     }
 
     /**
      * Create a profile source backed by a memory map. The map can be null in
      * which case it will the treated as an empty source.
      */
-    static ProfileSource* Create(std::unique_ptr<MemMap>&& mem_map) {
+    static ProfileSource* Create(MemMap&& mem_map) {
       return new ProfileSource(/*fd*/ -1, std::move(mem_map));
     }
 
@@ -664,13 +664,13 @@
     bool HasConsumedAllData() const;
 
    private:
-    ProfileSource(int32_t fd, std::unique_ptr<MemMap>&& mem_map)
+    ProfileSource(int32_t fd, MemMap&& mem_map)
         : fd_(fd), mem_map_(std::move(mem_map)), mem_map_cur_(0) {}
 
     bool IsMemMap() const { return fd_ == -1; }
 
     int32_t fd_;  // The fd is not owned by this class.
-    std::unique_ptr<MemMap> mem_map_;
+    MemMap mem_map_;
     size_t mem_map_cur_;  // Current position in the map to read from.
   };
 
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 9d73879..a5cc38b 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -708,7 +708,7 @@
       return nullptr;
     }
 
-    std::unique_ptr<MemMap> mmap(MemMap::MapFile(
+    MemMap mmap = MemMap::MapFile(
         file->GetLength(),
         PROT_READ | PROT_WRITE,
         MAP_PRIVATE,
@@ -716,13 +716,13 @@
         /* start offset */ 0,
         /* low_4gb */ false,
         vdex_filename.c_str(),
-        error_msg));
-    if (mmap == nullptr) {
+        error_msg);
+    if (!mmap.IsValid()) {
       *error_msg = "Failed to mmap file " + vdex_filename + ": " + *error_msg;
       return nullptr;
     }
 
-    std::unique_ptr<VdexFile> vdex_file(new VdexFile(mmap.release()));
+    std::unique_ptr<VdexFile> vdex_file(new VdexFile(std::move(mmap)));
     if (!vdex_file->IsValid()) {
       *error_msg = "Vdex file is not valid";
       return nullptr;
@@ -1578,7 +1578,7 @@
       // The optimizing compiler outputs its CodeInfo data in the vmap table.
       StackMapsHelper helper(oat_method.GetVmapTable(), instruction_set_);
       if (AddStatsObject(oat_method.GetVmapTable())) {
-        helper.GetCodeInfo().AddSizeStats(&stats_);
+        helper.GetCodeInfo().CollectSizeStats(oat_method.GetVmapTable(), &stats_);
       }
       const uint8_t* quick_native_pc = reinterpret_cast<const uint8_t*>(quick_code);
       size_t offset = 0;
diff --git a/oatdump/oatdump_test.h b/oatdump/oatdump_test.h
index 2c28f06..4ee5101 100644
--- a/oatdump/oatdump_test.h
+++ b/oatdump/oatdump_test.h
@@ -171,7 +171,7 @@
         // Code and dex code do not show up if list only.
         expected_prefixes.push_back("DEX CODE:");
         expected_prefixes.push_back("CODE:");
-        expected_prefixes.push_back("InlineInfos");
+        expected_prefixes.push_back("InlineInfo");
       }
       if (mode == kModeArt) {
         exec_argv.push_back("--image=" + core_art_location_);
diff --git a/openjdkjvm/OpenjdkJvm.cc b/openjdkjvm/OpenjdkJvm.cc
index df002b6..8d0200c 100644
--- a/openjdkjvm/OpenjdkJvm.cc
+++ b/openjdkjvm/OpenjdkJvm.cc
@@ -187,7 +187,7 @@
 }
 
 JNIEXPORT int jio_vfprintf(FILE* fp, const char* fmt, va_list args) {
-    assert(fp != NULL);
+    assert(fp != nullptr);
     return vfprintf(fp, fmt, args);
 }
 
@@ -203,7 +203,7 @@
 JNIEXPORT jlong JVM_CurrentTimeMillis(JNIEnv* env ATTRIBUTE_UNUSED,
                                       jclass clazz ATTRIBUTE_UNUSED) {
     struct timeval tv;
-    gettimeofday(&tv, (struct timezone *) NULL);
+    gettimeofday(&tv, (struct timezone *) nullptr);
     jlong when = tv.tv_sec * 1000LL + tv.tv_usec / 1000;
     return when;
 }
@@ -319,8 +319,8 @@
                                  jstring javaFilename,
                                  jobject javaLoader) {
   ScopedUtfChars filename(env, javaFilename);
-  if (filename.c_str() == NULL) {
-    return NULL;
+  if (filename.c_str() == nullptr) {
+    return nullptr;
   }
 
   std::string error_msg;
@@ -348,7 +348,7 @@
   art::ScopedObjectAccess soa(env);
   art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
   art::Thread* thread = art::Thread::FromManagedThread(soa, jthread);
-  if (thread != NULL) {
+  if (thread != nullptr) {
     thread->SetNativePriority(prio);
   }
 }
@@ -421,7 +421,7 @@
                                               art::SuspendReason::kInternal,
                                               &timed_out);
   }
-  if (thread != NULL) {
+  if (thread != nullptr) {
     {
       art::ScopedObjectAccess soa(env);
       thread->SetThreadName(name.c_str());
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc
index 2f24d7e..d20c756 100644
--- a/openjdkjvmti/deopt_manager.cc
+++ b/openjdkjvmti/deopt_manager.cc
@@ -40,6 +40,8 @@
 #include "dex/dex_file_annotations.h"
 #include "dex/modifiers.h"
 #include "events-inl.h"
+#include "gc/heap.h"
+#include "gc/scoped_gc_critical_section.h"
 #include "jit/jit.h"
 #include "jni/jni_internal.h"
 #include "mirror/class-inl.h"
@@ -266,29 +268,35 @@
   deoptimization_status_lock_.ExclusiveUnlock(self);
 }
 
+// Users should make sure that only gc-critical-section safe code is used while a
+// ScopedDeoptimizationContext exists.
 class ScopedDeoptimizationContext : public art::ValueObject {
  public:
   ScopedDeoptimizationContext(art::Thread* self, DeoptManager* deopt)
       RELEASE(deopt->deoptimization_status_lock_)
       ACQUIRE(art::Locks::mutator_lock_)
       ACQUIRE(art::Roles::uninterruptible_)
-      : self_(self), deopt_(deopt), uninterruptible_cause_(nullptr) {
+      : self_(self),
+        deopt_(deopt),
+        critical_section_(self_, "JVMTI Deoptimizing methods"),
+        uninterruptible_cause_(nullptr) {
     deopt_->WaitForDeoptimizationToFinishLocked(self_);
     DCHECK(!deopt->performing_deoptimization_)
         << "Already performing deoptimization on another thread!";
     // Use performing_deoptimization_ to keep track of the lock.
     deopt_->performing_deoptimization_ = true;
     deopt_->deoptimization_status_lock_.Unlock(self_);
+    uninterruptible_cause_ = critical_section_.Enter(art::gc::kGcCauseInstrumentation,
+                                                     art::gc::kCollectorTypeCriticalSection);
     art::Runtime::Current()->GetThreadList()->SuspendAll("JMVTI Deoptimizing methods",
                                                          /*long_suspend*/ false);
-    uninterruptible_cause_ = self_->StartAssertNoThreadSuspension("JVMTI deoptimizing methods");
   }
 
   ~ScopedDeoptimizationContext()
       RELEASE(art::Locks::mutator_lock_)
       RELEASE(art::Roles::uninterruptible_) {
     // Can be suspended again.
-    self_->EndAssertNoThreadSuspension(uninterruptible_cause_);
+    critical_section_.Exit(uninterruptible_cause_);
     // Release the mutator lock.
     art::Runtime::Current()->GetThreadList()->ResumeAll();
     // Let other threads know it's fine to proceed.
@@ -300,6 +308,7 @@
  private:
   art::Thread* self_;
   DeoptManager* deopt_;
+  art::gc::GCCriticalSection critical_section_;
   const char* uninterruptible_cause_;
 };
 
diff --git a/openjdkjvmti/fixed_up_dex_file.cc b/openjdkjvmti/fixed_up_dex_file.cc
index a660fb5..2ca87fd 100644
--- a/openjdkjvmti/fixed_up_dex_file.cc
+++ b/openjdkjvmti/fixed_up_dex_file.cc
@@ -79,11 +79,8 @@
   const art::VdexFile* vdex = GetVdex(original_dex_file);
   if (vdex != nullptr) {
     vdex->UnquickenDexFile(new_dex_file, original_dex_file, /* decompile_return_instruction */true);
-  } else {
-    // The dex file isn't quickened since it is being used directly. We might still have hiddenapis
-    // so we need to get rid of those.
-    UnhideApis(new_dex_file);
   }
+  UnhideApis(new_dex_file);
 }
 
 static void DCheckVerifyDexFile(const art::DexFile& dex) {
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index 9bea18a..209add3 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -91,10 +91,8 @@
   // Make the mmap
   std::string error_msg;
   art::ArrayRef<const unsigned char> final_data(final_dex_data, final_len);
-  std::unique_ptr<art::MemMap> map(Redefiner::MoveDataToMemMap(orig_location,
-                                                               final_data,
-                                                               &error_msg));
-  if (map.get() == nullptr) {
+  art::MemMap map = Redefiner::MoveDataToMemMap(orig_location, final_data, &error_msg);
+  if (!map.IsValid()) {
     LOG(WARNING) << "Unable to allocate mmap for redefined dex file! Error was: " << error_msg;
     self->ThrowOutOfMemoryError(StringPrintf(
         "Unable to allocate dex file for transformation of %s", descriptor).c_str());
@@ -102,15 +100,15 @@
   }
 
   // Make a dex-file
-  if (map->Size() < sizeof(art::DexFile::Header)) {
+  if (map.Size() < sizeof(art::DexFile::Header)) {
     LOG(WARNING) << "Could not read dex file header because dex_data was too short";
     art::ThrowClassFormatError(nullptr,
                                "Unable to read transformed dex file of %s",
                                descriptor);
     return nullptr;
   }
-  uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_;
-  std::string map_name = map->GetName();
+  uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map.Begin())->checksum_;
+  std::string map_name = map.GetName();
   const art::ArtDexFileLoader dex_file_loader;
   std::unique_ptr<const art::DexFile> dex_file(dex_file_loader.Open(map_name,
                                                                     checksum,
diff --git a/openjdkjvmti/ti_class_definition.cc b/openjdkjvmti/ti_class_definition.cc
index dce2733..895e734 100644
--- a/openjdkjvmti/ti_class_definition.cc
+++ b/openjdkjvmti/ti_class_definition.cc
@@ -49,26 +49,27 @@
 void ArtClassDefinition::InitializeMemory() const {
   DCHECK(art::MemMap::kCanReplaceMapping);
   VLOG(signals) << "Initializing de-quickened memory for dex file of " << name_;
-  CHECK(dex_data_mmap_ != nullptr);
-  CHECK(temp_mmap_ != nullptr);
-  CHECK_EQ(dex_data_mmap_->GetProtect(), PROT_NONE);
-  CHECK_EQ(temp_mmap_->GetProtect(), PROT_READ | PROT_WRITE);
+  CHECK(dex_data_mmap_.IsValid());
+  CHECK(temp_mmap_.IsValid());
+  CHECK_EQ(dex_data_mmap_.GetProtect(), PROT_NONE);
+  CHECK_EQ(temp_mmap_.GetProtect(), PROT_READ | PROT_WRITE);
 
   std::string desc = std::string("L") + name_ + ";";
   std::unique_ptr<FixedUpDexFile>
       fixed_dex_file(FixedUpDexFile::Create(*initial_dex_file_unquickened_, desc.c_str()));
   CHECK(fixed_dex_file.get() != nullptr);
-  CHECK_LE(fixed_dex_file->Size(), temp_mmap_->Size());
-  CHECK_EQ(temp_mmap_->Size(), dex_data_mmap_->Size());
+  CHECK_LE(fixed_dex_file->Size(), temp_mmap_.Size());
+  CHECK_EQ(temp_mmap_.Size(), dex_data_mmap_.Size());
   // Copy the data to the temp mmap.
-  memcpy(temp_mmap_->Begin(), fixed_dex_file->Begin(), fixed_dex_file->Size());
+  memcpy(temp_mmap_.Begin(), fixed_dex_file->Begin(), fixed_dex_file->Size());
 
   // Move the mmap atomically.
-  art::MemMap* source = temp_mmap_.release();
+  art::MemMap source;
+  source.swap(temp_mmap_);
   std::string error;
-  CHECK(dex_data_mmap_->ReplaceWith(&source, &error)) << "Failed to replace mmap for "
-                                                      << name_ << " because " << error;
-  CHECK(dex_data_mmap_->Protect(PROT_READ));
+  CHECK(dex_data_mmap_.ReplaceWith(&source, &error)) << "Failed to replace mmap for "
+                                                     << name_ << " because " << error;
+  CHECK(dex_data_mmap_.Protect(PROT_READ));
 }
 
 bool ArtClassDefinition::IsModified() const {
@@ -85,13 +86,13 @@
   }
 
   // The dex_data_ was never touched by the agents.
-  if (dex_data_mmap_ != nullptr && dex_data_mmap_->GetProtect() == PROT_NONE) {
-    if (current_dex_file_.data() == dex_data_mmap_->Begin()) {
+  if (dex_data_mmap_.IsValid() && dex_data_mmap_.GetProtect() == PROT_NONE) {
+    if (current_dex_file_.data() == dex_data_mmap_.Begin()) {
       // the dex_data_ looks like it changed (not equal to current_dex_file_) but we never
       // initialized the dex_data_mmap_. This means the new_dex_data was filled in without looking
       // at the initial dex_data_.
       return true;
-    } else if (dex_data_.data() == dex_data_mmap_->Begin()) {
+    } else if (dex_data_.data() == dex_data_mmap_.Begin()) {
       // The dex file used to have modifications but they were not added again.
       return true;
     } else {
@@ -244,26 +245,24 @@
     std::string mmap_name("anon-mmap-for-redefine: ");
     mmap_name += name_;
     std::string error;
-    dex_data_mmap_.reset(art::MemMap::MapAnonymous(mmap_name.c_str(),
-                                                   nullptr,
-                                                   dequick_size,
-                                                   PROT_NONE,
-                                                   /*low_4gb*/ false,
-                                                   /*reuse*/ false,
-                                                   &error));
-    mmap_name += "-TEMP";
-    temp_mmap_.reset(art::MemMap::MapAnonymous(mmap_name.c_str(),
-                                               nullptr,
+    dex_data_mmap_ = art::MemMap::MapAnonymous(mmap_name.c_str(),
+                                               /* addr */ nullptr,
                                                dequick_size,
-                                               PROT_READ | PROT_WRITE,
+                                               PROT_NONE,
                                                /*low_4gb*/ false,
-                                               /*reuse*/ false,
-                                               &error));
-    if (UNLIKELY(dex_data_mmap_ != nullptr && temp_mmap_ != nullptr)) {
+                                               &error);
+    mmap_name += "-TEMP";
+    temp_mmap_ = art::MemMap::MapAnonymous(mmap_name.c_str(),
+                                           /* addr */ nullptr,
+                                           dequick_size,
+                                           PROT_READ | PROT_WRITE,
+                                           /*low_4gb*/ false,
+                                           &error);
+    if (UNLIKELY(dex_data_mmap_.IsValid() && temp_mmap_.IsValid())) {
       // Need to save the initial dexfile so we don't need to search for it in the fault-handler.
       initial_dex_file_unquickened_ = quick_dex;
-      dex_data_ = art::ArrayRef<const unsigned char>(dex_data_mmap_->Begin(),
-                                                     dex_data_mmap_->Size());
+      dex_data_ = art::ArrayRef<const unsigned char>(dex_data_mmap_.Begin(),
+                                                     dex_data_mmap_.Size());
       if (from_class_ext_) {
         // We got initial from class_ext so the current one must have undergone redefinition so no
         // cdex or quickening stuff.
@@ -275,14 +274,14 @@
         // This class hasn't been redefined before. The dequickened current data is the same as the
         // dex_data_mmap_ when it's filled it. We don't need to copy anything because the mmap will
         // not be cleared until after everything is done.
-        current_dex_file_ = art::ArrayRef<const unsigned char>(dex_data_mmap_->Begin(),
+        current_dex_file_ = art::ArrayRef<const unsigned char>(dex_data_mmap_.Begin(),
                                                                dequick_size);
       }
       return;
     }
   }
-  dex_data_mmap_.reset(nullptr);
-  temp_mmap_.reset(nullptr);
+  dex_data_mmap_.Reset();
+  temp_mmap_.Reset();
   // Failed to mmap a large enough area (or on-demand dequickening was disabled). This is
   // unfortunate. Since currently the size is just a guess though we might as well try to do it
   // manually.
diff --git a/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h
index f888a74..224e664 100644
--- a/openjdkjvmti/ti_class_definition.h
+++ b/openjdkjvmti/ti_class_definition.h
@@ -56,8 +56,8 @@
         loader_(nullptr),
         name_(),
         protection_domain_(nullptr),
-        dex_data_mmap_(nullptr),
-        temp_mmap_(nullptr),
+        dex_data_mmap_(),
+        temp_mmap_(),
         dex_data_memory_(),
         initial_dex_file_unquickened_(nullptr),
         dex_data_(),
@@ -100,9 +100,9 @@
   }
 
   bool ContainsAddress(uintptr_t ptr) const {
-    return dex_data_mmap_ != nullptr &&
-        reinterpret_cast<uintptr_t>(dex_data_mmap_->Begin()) <= ptr &&
-        reinterpret_cast<uintptr_t>(dex_data_mmap_->End()) > ptr;
+    return dex_data_mmap_.IsValid() &&
+        reinterpret_cast<uintptr_t>(dex_data_mmap_.Begin()) <= ptr &&
+        reinterpret_cast<uintptr_t>(dex_data_mmap_.End()) > ptr;
   }
 
   bool IsModified() const REQUIRES_SHARED(art::Locks::mutator_lock_);
@@ -128,9 +128,9 @@
 
   bool IsLazyDefinition() const {
     DCHECK(IsInitialized());
-    return dex_data_mmap_ != nullptr &&
-        dex_data_.data() == dex_data_mmap_->Begin() &&
-        dex_data_mmap_->GetProtect() == PROT_NONE;
+    return dex_data_mmap_.IsValid() &&
+        dex_data_.data() == dex_data_mmap_.Begin() &&
+        dex_data_mmap_.GetProtect() == PROT_NONE;
   }
 
   jobject GetProtectionDomain() const {
@@ -159,9 +159,9 @@
 
   // Mmap that will be filled with the original-dex-file lazily if it needs to be de-quickened or
   // de-compact-dex'd
-  mutable std::unique_ptr<art::MemMap> dex_data_mmap_;
+  mutable art::MemMap dex_data_mmap_;
   // This is a temporary mmap we will use to be able to fill the dex file data atomically.
-  mutable std::unique_ptr<art::MemMap> temp_mmap_;
+  mutable art::MemMap temp_mmap_;
 
   // A unique_ptr to the current dex_data if it needs to be cleaned up.
   std::vector<unsigned char> dex_data_memory_;
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 1476880..8707e27 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -300,24 +300,22 @@
 }
 
 // Moves dex data to an anonymous, read-only mmap'd region.
-std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& original_location,
-                                                         art::ArrayRef<const unsigned char> data,
-                                                         std::string* error_msg) {
-  std::unique_ptr<art::MemMap> map(art::MemMap::MapAnonymous(
+art::MemMap Redefiner::MoveDataToMemMap(const std::string& original_location,
+                                        art::ArrayRef<const unsigned char> data,
+                                        std::string* error_msg) {
+  art::MemMap map = art::MemMap::MapAnonymous(
       StringPrintf("%s-transformed", original_location.c_str()).c_str(),
-      nullptr,
+      /* addr */ nullptr,
       data.size(),
       PROT_READ|PROT_WRITE,
-      /*low_4gb*/false,
-      /*reuse*/false,
-      error_msg));
-  if (map == nullptr) {
-    return map;
+      /*low_4gb*/ false,
+      error_msg);
+  if (LIKELY(map.IsValid())) {
+    memcpy(map.Begin(), data.data(), data.size());
+    // Make the dex files mmap read only. This matches how other DexFiles are mmaped and prevents
+    // programs from corrupting it.
+    map.Protect(PROT_READ);
   }
-  memcpy(map->Begin(), data.data(), data.size());
-  // Make the dex files mmap read only. This matches how other DexFiles are mmaped and prevents
-  // programs from corrupting it.
-  map->Protect(PROT_READ);
   return map;
 }
 
@@ -368,17 +366,6 @@
     if (res != OK) {
       return res;
     }
-    // We make a copy of the class_bytes to pass into the retransformation.
-    // This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we
-    // will need to keep the original bytes around unaltered for subsequent RetransformClasses calls
-    // to get the passed in bytes.
-    unsigned char* class_bytes_copy = nullptr;
-    res = env->Allocate(definitions[i].class_byte_count, &class_bytes_copy);
-    if (res != OK) {
-      return res;
-    }
-    memcpy(class_bytes_copy, definitions[i].class_bytes, definitions[i].class_byte_count);
-
     ArtClassDefinition def;
     res = def.Init(self, definitions[i]);
     if (res != OK) {
@@ -440,23 +427,22 @@
   }
   JvmtiUniquePtr<char> generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
   JvmtiUniquePtr<char> signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
-  std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location,
-                                                    def.GetDexData(),
-                                                    error_msg_));
+  art::MemMap map = MoveDataToMemMap(original_dex_location, def.GetDexData(), error_msg_);
   std::ostringstream os;
-  if (map.get() == nullptr) {
+  if (!map.IsValid()) {
     os << "Failed to create anonymous mmap for modified dex file of class " << def.GetName()
        << "in dex file " << original_dex_location << " because: " << *error_msg_;
     *error_msg_ = os.str();
     return ERR(OUT_OF_MEMORY);
   }
-  if (map->Size() < sizeof(art::DexFile::Header)) {
+  if (map.Size() < sizeof(art::DexFile::Header)) {
     *error_msg_ = "Could not read dex file header because dex_data was too short";
     return ERR(INVALID_CLASS_FORMAT);
   }
-  uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_;
+  std::string name = map.GetName();
+  uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map.Begin())->checksum_;
   const art::ArtDexFileLoader dex_file_loader;
-  std::unique_ptr<const art::DexFile> dex_file(dex_file_loader.Open(map->GetName(),
+  std::unique_ptr<const art::DexFile> dex_file(dex_file_loader.Open(name,
                                                                     checksum,
                                                                     std::move(map),
                                                                     /*verify*/true,
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 6d8f6bf..f4a4280 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -78,9 +78,9 @@
 
   static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
 
-  static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location,
-                                                       art::ArrayRef<const unsigned char> data,
-                                                       std::string* error_msg);
+  static art::MemMap MoveDataToMemMap(const std::string& original_location,
+                                      art::ArrayRef<const unsigned char> data,
+                                      std::string* error_msg);
 
   // Helper for checking if redefinition/retransformation is allowed.
   static jvmtiError GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg)
diff --git a/openjdkjvmti/ti_timers.cc b/openjdkjvmti/ti_timers.cc
index 24fb041..11b58c4 100644
--- a/openjdkjvmti/ti_timers.cc
+++ b/openjdkjvmti/ti_timers.cc
@@ -83,7 +83,7 @@
   // No CLOCK_MONOTONIC support on older Mac OS.
   struct timeval t;
   t.tv_sec = t.tv_usec = 0;
-  gettimeofday(&t, NULL);
+  gettimeofday(&t, nullptr);
   *nanos_ptr = static_cast<jlong>(t.tv_sec)*1000000000LL + static_cast<jlong>(t.tv_usec)*1000LL;
 #endif
 
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index a15f7b8..8169979 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -538,7 +538,7 @@
   ScopedObjectAccess soa(Thread::Current());
 
   std::vector<gc::space::ImageSpace*> spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();
-  std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>> space_to_memmap_map;
+  std::map<gc::space::ImageSpace*, MemMap> space_to_memmap_map;
 
   for (size_t i = 0; i < spaces.size(); ++i) {
     t.NewTiming("Image Patching setup");
@@ -567,15 +567,15 @@
 
     // Create the map where we will write the image patches to.
     std::string error_msg;
-    std::unique_ptr<MemMap> image(MemMap::MapFile(image_len,
-                                                  PROT_READ | PROT_WRITE,
-                                                  MAP_PRIVATE,
-                                                  input_image->Fd(),
-                                                  0,
-                                                  /*low_4gb*/false,
-                                                  input_image->GetPath().c_str(),
-                                                  &error_msg));
-    if (image.get() == nullptr) {
+    MemMap image = MemMap::MapFile(image_len,
+                                   PROT_READ | PROT_WRITE,
+                                   MAP_PRIVATE,
+                                   input_image->Fd(),
+                                   0,
+                                   /*low_4gb*/false,
+                                   input_image->GetPath().c_str(),
+                                   &error_msg);
+    if (!image.IsValid()) {
       LOG(ERROR) << "Unable to map image file " << input_image->GetPath() << " : " << error_msg;
       return false;
     }
@@ -583,7 +583,7 @@
 
     space_to_memmap_map.emplace(space, std::move(image));
     PatchOat p = PatchOat(isa,
-                          space_to_memmap_map[space].get(),
+                          &space_to_memmap_map[space],
                           space->GetLiveBitmap(),
                           space->GetMemMap(),
                           delta,
@@ -636,22 +636,22 @@
         LOG(ERROR) << "Error while getting input image size";
         return false;
       }
-      std::unique_ptr<MemMap> original(MemMap::MapFile(input_image_size,
-                                                       PROT_READ,
-                                                       MAP_PRIVATE,
-                                                       input_image->Fd(),
-                                                       0,
-                                                       /*low_4gb*/false,
-                                                       input_image->GetPath().c_str(),
-                                                       &error_msg));
-      if (original.get() == nullptr) {
+      MemMap original = MemMap::MapFile(input_image_size,
+                                        PROT_READ,
+                                        MAP_PRIVATE,
+                                        input_image->Fd(),
+                                        0,
+                                        /*low_4gb*/false,
+                                        input_image->GetPath().c_str(),
+                                        &error_msg);
+      if (!original.IsValid()) {
         LOG(ERROR) << "Unable to map image file " << input_image->GetPath() << " : " << error_msg;
         return false;
       }
 
       const MemMap* relocated = p.image_;
 
-      if (!WriteRelFile(*original, *relocated, image_relocation_filename, &error_msg)) {
+      if (!WriteRelFile(original, *relocated, image_relocation_filename, &error_msg)) {
         LOG(ERROR) << "Failed to create image relocation file " << image_relocation_filename
             << ": " << error_msg;
         return false;
diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h
index 2b1210b..ac2fdf5 100644
--- a/patchoat/patchoat.h
+++ b/patchoat/patchoat.h
@@ -74,7 +74,7 @@
   // All pointers are only borrowed.
   PatchOat(InstructionSet isa, MemMap* image,
            gc::accounting::ContinuousSpaceBitmap* bitmap, MemMap* heap, off_t delta,
-           std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>>* map, TimingLogger* timings)
+           std::map<gc::space::ImageSpace*, MemMap>* map, TimingLogger* timings)
       : image_(image), bitmap_(bitmap), heap_(heap),
         delta_(delta), isa_(isa), space_map_(map), timings_(timings) {}
 
@@ -139,7 +139,7 @@
       if (image_space->Contains(obj)) {
         uintptr_t heap_off = reinterpret_cast<uintptr_t>(obj) -
                              reinterpret_cast<uintptr_t>(image_space->GetMemMap()->Begin());
-        return reinterpret_cast<T*>(space_map_->find(image_space)->second->Begin() + heap_off);
+        return reinterpret_cast<T*>(space_map_->find(image_space)->second.Begin() + heap_off);
       }
     }
     LOG(FATAL) << "Did not find object in boot image space " << obj;
@@ -195,7 +195,7 @@
   // Active instruction set, used to know the entrypoint size.
   const InstructionSet isa_;
 
-  const std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>>* space_map_;
+  const std::map<gc::space::ImageSpace*, MemMap>* space_map_;
 
   TimingLogger* timings_;
 
diff --git a/runtime/arch/arm/asm_support_arm.h b/runtime/arch/arm/asm_support_arm.h
index 7123ae7..9a01656 100644
--- a/runtime/arch/arm/asm_support_arm.h
+++ b/runtime/arch/arm/asm_support_arm.h
@@ -37,6 +37,9 @@
 // The offset from art_quick_read_barrier_mark_introspection to the array switch cases,
 // i.e. art_quick_read_barrier_mark_introspection_arrays.
 #define BAKER_MARK_INTROSPECTION_ARRAY_SWITCH_OFFSET 0x100
+// The offset from art_quick_read_barrier_mark_introspection to the entrypoint for the
+// UnsafeCASObject intrinsic, i.e. art_quick_read_barrier_mark_introspection_unsafe_cas.
+#define BAKER_MARK_INTROSPECTION_UNSAFE_CAS_ENTRYPOINT_OFFSET 0x180
 
 // The offset of the reference load LDR from the return address in LR for field loads.
 #ifdef USE_HEAP_POISONING
@@ -55,5 +58,7 @@
 // The offset of the reference load LDR from the return address in LR for GC root loads.
 #define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_OFFSET -8
 #define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_OFFSET -6
+// The offset of the ADD from the return address in LR for UnsafeCASObject intrinsic.
+#define BAKER_MARK_INTROSPECTION_UNSAFE_CAS_ADD_OFFSET -8
 
 #endif  // ART_RUNTIME_ARCH_ARM_ASM_SUPPORT_ARM_H_
diff --git a/runtime/arch/arm/entrypoints_init_arm.cc b/runtime/arch/arm/entrypoints_init_arm.cc
index b4e9036..2c5465e 100644
--- a/runtime/arch/arm/entrypoints_init_arm.cc
+++ b/runtime/arch/arm/entrypoints_init_arm.cc
@@ -59,6 +59,7 @@
 extern "C" mirror::Object* art_quick_read_barrier_mark_introspection_gc_roots_wide(mirror::Object*);
 extern "C" mirror::Object* art_quick_read_barrier_mark_introspection_gc_roots_narrow(
     mirror::Object*);
+extern "C" mirror::Object* art_quick_read_barrier_mark_introspection_unsafe_cas(mirror::Object*);
 
 // Used by soft float.
 // Single-precision FP arithmetics.
@@ -113,6 +114,10 @@
         reinterpret_cast<intptr_t>(art_quick_read_barrier_mark_introspection_gc_roots_narrow) -
         reinterpret_cast<intptr_t>(art_quick_read_barrier_mark_introspection);
     DCHECK_EQ(BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_ENTRYPOINT_OFFSET, gc_roots_narrow_diff);
+    intptr_t unsafe_cas_diff =
+        reinterpret_cast<intptr_t>(art_quick_read_barrier_mark_introspection_unsafe_cas) -
+        reinterpret_cast<intptr_t>(art_quick_read_barrier_mark_introspection);
+    DCHECK_EQ(BAKER_MARK_INTROSPECTION_UNSAFE_CAS_ENTRYPOINT_OFFSET, unsafe_cas_diff);
     // The register 12, i.e. IP, is reserved, so there is no art_quick_read_barrier_mark_reg12.
     // We're using the entry to hold a pointer to the introspection entrypoint instead.
     qpoints->pReadBarrierMarkReg12 =
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index c86baa1..1153a77 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -656,18 +656,21 @@
 END art_quick_osr_stub
 
     /*
-     * On entry r0 is uint32_t* gprs_ and r1 is uint32_t* fprs_
+     * On entry r0 is uint32_t* gprs_ and r1 is uint32_t* fprs_.
+     * Both must reside on the stack, between current SP and target SP.
+     * The r12 (IP) shall be clobbered rather than retrieved from gprs_.
      */
 ARM_ENTRY art_quick_do_long_jump
-    vldm r1, {s0-s31}     @ load all fprs from argument fprs_
-    ldr  r2, [r0, #60]    @ r2 = r15 (PC from gprs_ 60=4*15)
-    ldr  r14, [r0, #56]   @ (LR from gprs_ 56=4*14)
-    add  r0, r0, #12      @ increment r0 to skip gprs_[0..2] 12=4*3
-    ldm  r0, {r3-r13}     @ load remaining gprs from argument gprs_
+    vldm r1, {s0-s31}     @ Load all fprs from argument fprs_.
+    mov  sp, r0           @ Make SP point to gprs_.
+                          @ Do not access fprs_ from now, they may be below SP.
+    ldm  sp, {r0-r11}     @ load r0-r11 from gprs_.
+    ldr  r12, [sp, #60]   @ Load the value of PC (r15) from gprs_ (60 = 4 * 15) into IP (r12).
+    ldr  lr, [sp, #56]    @ Load LR from gprs_, 56 = 4 * 14.
+    ldr  sp, [sp, #52]    @ Load SP from gprs_ 52 = 4 * 13.
+                          @ Do not access gprs_ from now, they are below SP.
     REFRESH_MARKING_REGISTER
-    ldr  r0, [r0, #-12]   @ load r0 value
-    mov  r1, #0           @ clear result register r1
-    bx   r2               @ do long jump
+    bx   r12              @ Do long jump.
 END art_quick_do_long_jump
 
     /*
@@ -2575,6 +2578,12 @@
      * The thunk also performs all the fast-path checks, so we need just the
      * slow path.
      *
+     * The UnsafeCASObject intrinsic is similar to the GC roots wide approach
+     * but using ADD (register, T3) instead of the LDR (immediate, T3), so the
+     * destination register is in bits 8-11 rather than 12-15. Therefore it has
+     * its own entrypoint, art_quick_read_barrier_mark_introspection_unsafe_cas
+     * at the offset BAKER_MARK_INTROSPECTION_UNSAFE_CAS_ENTRYPOINT_OFFSET.
+     *
      * The code structure is
      *   art_quick_read_barrier_mark_introspection:                   // @0x00
      *     Up to 32 bytes code for main entrypoint fast-path code for fields
@@ -2610,6 +2619,9 @@
      *     return switch.
      *   art_quick_read_barrier_mark_introspection_arrays:            // @0x100
      *     Exactly 128 bytes for array load switch cases (16x2 instructions).
+     *   art_quick_read_barrier_mark_introspection_unsafe_cas:        // @0x180
+     *     UnsafeCASObject intrinsic entrypoint for ADD (register) encoding T3
+     *     (6 bytes). Loads the return register and jumps to the runtime call.
      */
 #if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
     .balign 512
@@ -2669,7 +2681,6 @@
     BRBMI_RUNTIME_CALL
     b       .Lmark_introspection_return_switch
 
-
     .balign 256
     .thumb_func
     .type art_quick_read_barrier_mark_introspection_arrays, #function
@@ -2677,6 +2688,19 @@
     .global art_quick_read_barrier_mark_introspection_arrays
 art_quick_read_barrier_mark_introspection_arrays:
     BRBMI_FOR_REGISTERS BRBMI_ARRAY_LOAD, BRBMI_BKPT_FILL_8B
+
+    .balign 8
+    .thumb_func
+    .type art_quick_read_barrier_mark_introspection_unsafe_cas, #function
+    .hidden art_quick_read_barrier_mark_introspection_unsafe_cas
+    .global art_quick_read_barrier_mark_introspection_unsafe_cas
+art_quick_read_barrier_mark_introspection_unsafe_cas:
+    // Load the byte of the ADD instruction that contains Rd. Adjust for the thumb state in LR.
+    // The ADD (register, T3) is |11101011000|S|Rn|(0)imm3|Rd|imm2|type|Rm| and we're using
+    // no shift (type=0, imm2=0, imm3=0), so the byte we read here, i.e. |(0)imm3|Rd|,
+    // contains only the register number, the top 4 bits are 0.
+    ldrb    rMR, [lr, #(-1 + BAKER_MARK_INTROSPECTION_UNSAFE_CAS_ADD_OFFSET + 3)]
+    b .Lmark_introspection_runtime_call
 END art_quick_read_barrier_mark_introspection
 #else  // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
 ENTRY art_quick_read_barrier_mark_introspection
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 8b77453..2622d40 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -1091,56 +1091,59 @@
 END art_quick_osr_stub
 
     /*
-     * On entry x0 is uintptr_t* gprs_ and x1 is uint64_t* fprs_
+     * On entry x0 is uintptr_t* gprs_ and x1 is uint64_t* fprs_.
+     * Both must reside on the stack, between current SP and target SP.
+     * IP0 and IP1 shall be clobbered rather than retrieved from gprs_.
      */
 
 ENTRY art_quick_do_long_jump
     // Load FPRs
-    ldp d0, d1, [x1], #16
-    ldp d2, d3, [x1], #16
-    ldp d4, d5, [x1], #16
-    ldp d6, d7, [x1], #16
-    ldp d8, d9, [x1], #16
-    ldp d10, d11, [x1], #16
-    ldp d12, d13, [x1], #16
-    ldp d14, d15, [x1], #16
-    ldp d16, d17, [x1], #16
-    ldp d18, d19, [x1], #16
-    ldp d20, d21, [x1], #16
-    ldp d22, d23, [x1], #16
-    ldp d24, d25, [x1], #16
-    ldp d26, d27, [x1], #16
-    ldp d28, d29, [x1], #16
-    ldp d30, d31, [x1]
+    ldp d0, d1, [x1, #0]
+    ldp d2, d3, [x1, #16]
+    ldp d4, d5, [x1, #32]
+    ldp d6, d7, [x1, #48]
+    ldp d8, d9, [x1, #64]
+    ldp d10, d11, [x1, #80]
+    ldp d12, d13, [x1, #96]
+    ldp d14, d15, [x1, #112]
+    ldp d16, d17, [x1, #128]
+    ldp d18, d19, [x1, #144]
+    ldp d20, d21, [x1, #160]
+    ldp d22, d23, [x1, #176]
+    ldp d24, d25, [x1, #192]
+    ldp d26, d27, [x1, #208]
+    ldp d28, d29, [x1, #224]
+    ldp d30, d31, [x1, #240]
 
-    // Load GPRs
-    // TODO: lots of those are smashed, could optimize.
-    add x0, x0, #30*8
-    ldp x30, x1, [x0], #-16          // LR & SP
-    ldp x28, x29, [x0], #-16
-    ldp x26, x27, [x0], #-16
-    ldp x24, x25, [x0], #-16
-    ldp x22, x23, [x0], #-16
-    ldp x20, x21, [x0], #-16
-    ldp x18, x19, [x0], #-16         // X18 & xSELF
-    ldp x16, x17, [x0], #-16
-    ldp x14, x15, [x0], #-16
-    ldp x12, x13, [x0], #-16
-    ldp x10, x11, [x0], #-16
-    ldp x8, x9, [x0], #-16
-    ldp x6, x7, [x0], #-16
-    ldp x4, x5, [x0], #-16
-    ldp x2, x3, [x0], #-16
-    mov sp, x1
+    // Load GPRs. Delay loading x0, x1 because x0 is used as gprs_.
+    ldp x2, x3, [x0, #16]
+    ldp x4, x5, [x0, #32]
+    ldp x6, x7, [x0, #48]
+    ldp x8, x9, [x0, #64]
+    ldp x10, x11, [x0, #80]
+    ldp x12, x13, [x0, #96]
+    ldp x14, x15, [x0, #112]
+    // Do not load IP0 (x16) and IP1 (x17), these shall be clobbered below.
+    ldp x18, x19, [x0, #144]      // X18 and xSELF.
+    ldp x20, x21, [x0, #160]      // For Baker RB, wMR (w20) is reloaded below.
+    ldp x22, x23, [x0, #176]
+    ldp x24, x25, [x0, #192]
+    ldp x26, x27, [x0, #208]
+    ldp x28, x29, [x0, #224]
+    ldp x30, xIP0, [x0, #240]     // LR and SP, load SP to IP0.
+
+    // Load PC to IP1, it's at the end (after the space for the unused XZR).
+    ldr xIP1, [x0, #33*8]
+
+    // Load x0, x1.
+    ldp x0, x1, [x0, #0]
+
+    // Set SP. Do not access fprs_ and gprs_ from now, they are below SP.
+    mov sp, xIP0
 
     REFRESH_MARKING_REGISTER
 
-    // Need to load PC, it's at the end (after the space for the unused XZR). Use x1.
-    ldr x1, [x0, #33*8]
-    // And the value of x0.
-    ldr x0, [x0]
-
-    br  x1
+    br  xIP1
 END art_quick_do_long_jump
 
     /*
@@ -2785,6 +2788,8 @@
      * the root register to IP0 and jumps to the customized entrypoint,
      * art_quick_read_barrier_mark_introspection_gc_roots. The thunk also
      * performs all the fast-path checks, so we need just the slow path.
+     * The UnsafeCASObject intrinsic is also using the GC root entrypoint with
+     * MOV instead of LDR, the destination register is in the same bits.
      *
      * The code structure is
      *   art_quick_read_barrier_mark_introspection:
diff --git a/runtime/base/mem_map_arena_pool.cc b/runtime/base/mem_map_arena_pool.cc
index 702f0e4..a9fbafe 100644
--- a/runtime/base/mem_map_arena_pool.cc
+++ b/runtime/base/mem_map_arena_pool.cc
@@ -38,22 +38,33 @@
   void Release() OVERRIDE;
 
  private:
-  std::unique_ptr<MemMap> map_;
+  static MemMap Allocate(size_t size, bool low_4gb, const char* name);
+
+  MemMap map_;
 };
 
-MemMapArena::MemMapArena(size_t size, bool low_4gb, const char* name) {
+MemMapArena::MemMapArena(size_t size, bool low_4gb, const char* name)
+    : map_(Allocate(size, low_4gb, name)) {
+  memory_ = map_.Begin();
+  static_assert(ArenaAllocator::kArenaAlignment <= kPageSize,
+                "Arena should not need stronger alignment than kPageSize.");
+  DCHECK_ALIGNED(memory_, ArenaAllocator::kArenaAlignment);
+  size_ = map_.Size();
+}
+
+MemMap MemMapArena::Allocate(size_t size, bool low_4gb, const char* name) {
   // Round up to a full page as that's the smallest unit of allocation for mmap()
   // and we want to be able to use all memory that we actually allocate.
   size = RoundUp(size, kPageSize);
   std::string error_msg;
-  map_.reset(MemMap::MapAnonymous(
-      name, nullptr, size, PROT_READ | PROT_WRITE, low_4gb, false, &error_msg));
-  CHECK(map_.get() != nullptr) << error_msg;
-  memory_ = map_->Begin();
-  static_assert(ArenaAllocator::kArenaAlignment <= kPageSize,
-                "Arena should not need stronger alignment than kPageSize.");
-  DCHECK_ALIGNED(memory_, ArenaAllocator::kArenaAlignment);
-  size_ = map_->Size();
+  MemMap map = MemMap::MapAnonymous(name,
+                                    /* addr */ nullptr,
+                                    size,
+                                    PROT_READ | PROT_WRITE,
+                                    low_4gb,
+                                    &error_msg);
+  CHECK(map.IsValid()) << error_msg;
+  return map;
 }
 
 MemMapArena::~MemMapArena() {
@@ -62,7 +73,7 @@
 
 void MemMapArena::Release() {
   if (bytes_allocated_ > 0) {
-    map_->MadviseDontNeedAndZero();
+    map_.MadviseDontNeedAndZero();
     bytes_allocated_ = 0;
   }
 }
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index 7b888b1..044c4c2 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -1142,10 +1142,6 @@
     DCHECK(subtype_check_lock_ == nullptr);
     subtype_check_lock_ = new Mutex("SubtypeCheck lock", current_lock_level);
 
-    UPDATE_CURRENT_LOCK_LEVEL(kCHALock);
-    DCHECK(cha_lock_ == nullptr);
-    cha_lock_ = new Mutex("CHA lock", current_lock_level);
-
     UPDATE_CURRENT_LOCK_LEVEL(kClassLinkerClassesLock);
     DCHECK(classlinker_classes_lock_ == nullptr);
     classlinker_classes_lock_ = new ReaderWriterMutex("ClassLinker classes lock",
@@ -1226,6 +1222,10 @@
     DCHECK(custom_tls_lock_ == nullptr);
     custom_tls_lock_ = new Mutex("Thread::custom_tls_ lock", current_lock_level);
 
+    UPDATE_CURRENT_LOCK_LEVEL(kCHALock);
+    DCHECK(cha_lock_ == nullptr);
+    cha_lock_ = new Mutex("CHA lock", current_lock_level);
+
     UPDATE_CURRENT_LOCK_LEVEL(kNativeDebugInterfaceLock);
     DCHECK(native_debug_interface_lock_ == nullptr);
     native_debug_interface_lock_ = new Mutex("Native debug interface lock", current_lock_level);
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index af2e7b2..fba209a 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -72,6 +72,7 @@
   kJdwpSocketLock,
   kRegionSpaceRegionLock,
   kMarkSweepMarkStackLock,
+  kCHALock,
   kJitCodeCacheLock,
   kRosAllocGlobalLock,
   kRosAllocBracketLock,
@@ -109,7 +110,6 @@
   kMonitorPoolLock,
   kClassLinkerClassesLock,  // TODO rename.
   kDexToDexCompilerLock,
-  kCHALock,
   kSubtypeCheckLock,
   kBreakpointLock,
   kMonitorLock,
@@ -661,14 +661,11 @@
   // TODO: improve name, perhaps instrumentation_update_lock_.
   static Mutex* deoptimization_lock_ ACQUIRED_AFTER(alloc_tracker_lock_);
 
-  // Guards Class Hierarchy Analysis (CHA).
-  static Mutex* cha_lock_ ACQUIRED_AFTER(deoptimization_lock_);
-
   // Guard the update of the SubtypeCheck data stores in each Class::status_ field.
   // This lock is used in SubtypeCheck methods which are the interface for
   // any SubtypeCheck-mutating methods.
   // In Class::IsSubClass, the lock is not required since it does not update the SubtypeCheck data.
-  static Mutex* subtype_check_lock_ ACQUIRED_AFTER(cha_lock_);
+  static Mutex* subtype_check_lock_ ACQUIRED_AFTER(deoptimization_lock_);
 
   // The thread_list_lock_ guards ThreadList::list_. It is also commonly held to stop threads
   // attaching and detaching.
@@ -745,11 +742,14 @@
   // GetThreadLocalStorage.
   static Mutex* custom_tls_lock_ ACQUIRED_AFTER(jni_function_table_lock_);
 
+  // Guards Class Hierarchy Analysis (CHA).
+  static Mutex* cha_lock_ ACQUIRED_AFTER(custom_tls_lock_);
+
   // When declaring any Mutex add BOTTOM_MUTEX_ACQUIRED_AFTER to use annotalysis to check the code
   // doesn't try to acquire a higher level Mutex. NB Due to the way the annotalysis works this
   // actually only encodes the mutex being below jni_function_table_lock_ although having
   // kGenericBottomLock level is lower than this.
-  #define BOTTOM_MUTEX_ACQUIRED_AFTER ACQUIRED_AFTER(art::Locks::custom_tls_lock_)
+  #define BOTTOM_MUTEX_ACQUIRED_AFTER ACQUIRED_AFTER(art::Locks::cha_lock_)
 
   // Have an exclusive aborting thread.
   static Mutex* abort_lock_ ACQUIRED_AFTER(custom_tls_lock_);
diff --git a/runtime/cha.cc b/runtime/cha.cc
index ccbe066..ce84e8c 100644
--- a/runtime/cha.cc
+++ b/runtime/cha.cc
@@ -636,38 +636,54 @@
       // We do this under cha_lock_. Committing code also grabs this lock to
       // make sure the code is only committed when all single-implementation
       // assumptions are still true.
-      MutexLock cha_mu(self, *Locks::cha_lock_);
-      // Invalidate compiled methods that assume some virtual calls have only
-      // single implementations.
-      for (ArtMethod* invalidated : invalidated_single_impl_methods) {
-        if (!invalidated->HasSingleImplementation()) {
-          // It might have been invalidated already when other class linking is
-          // going on.
-          continue;
-        }
-        invalidated->SetHasSingleImplementation(false);
-        if (invalidated->IsAbstract()) {
-          // Clear the single implementation method.
-          invalidated->SetSingleImplementation(nullptr, image_pointer_size);
-        }
+      std::vector<std::pair<ArtMethod*, OatQuickMethodHeader*>> headers;
+      {
+        MutexLock cha_mu(self, *Locks::cha_lock_);
+        // Invalidate compiled methods that assume some virtual calls have only
+        // single implementations.
+        for (ArtMethod* invalidated : invalidated_single_impl_methods) {
+          if (!invalidated->HasSingleImplementation()) {
+            // It might have been invalidated already when other class linking is
+            // going on.
+            continue;
+          }
+          invalidated->SetHasSingleImplementation(false);
+          if (invalidated->IsAbstract()) {
+            // Clear the single implementation method.
+            invalidated->SetSingleImplementation(nullptr, image_pointer_size);
+          }
 
-        if (runtime->IsAotCompiler()) {
-          // No need to invalidate any compiled code as the AotCompiler doesn't
-          // run any code.
-          continue;
-        }
+          if (runtime->IsAotCompiler()) {
+            // No need to invalidate any compiled code as the AotCompiler doesn't
+            // run any code.
+            continue;
+          }
 
-        // Invalidate all dependents.
-        for (const auto& dependent : GetDependents(invalidated)) {
-          ArtMethod* method = dependent.first;;
-          OatQuickMethodHeader* method_header = dependent.second;
-          VLOG(class_linker) << "CHA invalidated compiled code for " << method->PrettyMethod();
-          DCHECK(runtime->UseJitCompilation());
-          runtime->GetJit()->GetCodeCache()->InvalidateCompiledCodeFor(
-              method, method_header);
-          dependent_method_headers.insert(method_header);
+          // Invalidate all dependents.
+          for (const auto& dependent : GetDependents(invalidated)) {
+            ArtMethod* method = dependent.first;;
+            OatQuickMethodHeader* method_header = dependent.second;
+            VLOG(class_linker) << "CHA invalidated compiled code for " << method->PrettyMethod();
+            DCHECK(runtime->UseJitCompilation());
+            // We need to call JitCodeCache::InvalidateCompiledCodeFor but we cannot do it here
+            // since it would run into problems with lock-ordering. We don't want to re-order the
+            // locks since that would make code-commit racy.
+            headers.push_back({method, method_header});
+            dependent_method_headers.insert(method_header);
+          }
+          RemoveAllDependenciesFor(invalidated);
         }
-        RemoveAllDependenciesFor(invalidated);
+      }
+      // Since we are still loading the class that invalidated the code it's fine we have this after
+      // getting rid of the dependency. Any calls would need to be with the old version (since the
+      // new one isn't loaded yet) which still works fine. We will deoptimize just after this to
+      // ensure everything gets the new state.
+      jit::Jit* jit = Runtime::Current()->GetJit();
+      if (jit != nullptr) {
+        jit::JitCodeCache* code_cache = jit->GetCodeCache();
+        for (const auto& pair : headers) {
+          code_cache->InvalidateCompiledCodeFor(pair.first, pair.second);
+        }
       }
     }
 
diff --git a/runtime/class_root.h b/runtime/class_root.h
index 4aa9801..19a78b1 100644
--- a/runtime/class_root.h
+++ b/runtime/class_root.h
@@ -165,18 +165,19 @@
 template <class MirrorType, ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
 inline ObjPtr<mirror::Class> GetClassRoot(ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return GetClassRoot<kWithReadBarrier>(detail::ClassRootSelector<MirrorType>::value, class_roots);
+  return GetClassRoot<kReadBarrierOption>(detail::ClassRootSelector<MirrorType>::value,
+                                          class_roots);
 }
 
 template <class MirrorType, ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
 inline ObjPtr<mirror::Class> GetClassRoot(ClassLinker* linker)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return GetClassRoot<kWithReadBarrier>(detail::ClassRootSelector<MirrorType>::value, linker);
+  return GetClassRoot<kReadBarrierOption>(detail::ClassRootSelector<MirrorType>::value, linker);
 }
 
 template <class MirrorType, ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
 inline ObjPtr<mirror::Class> GetClassRoot() REQUIRES_SHARED(Locks::mutator_lock_) {
-  return GetClassRoot<kWithReadBarrier>(detail::ClassRootSelector<MirrorType>::value);
+  return GetClassRoot<kReadBarrierOption>(detail::ClassRootSelector<MirrorType>::value);
 }
 
 }  // namespace art
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index f8388f3..9e3159d 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -249,14 +249,16 @@
 void DexoptTest::ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) {
   if (start < end) {
     std::string error_msg;
-    image_reservation_.push_back(std::unique_ptr<MemMap>(
-        MemMap::MapAnonymous("image reservation",
-            reinterpret_cast<uint8_t*>(start), end - start,
-            PROT_NONE, false, false, &error_msg)));
-    ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg;
+    image_reservation_.push_back(MemMap::MapAnonymous("image reservation",
+                                                      reinterpret_cast<uint8_t*>(start),
+                                                      end - start,
+                                                      PROT_NONE,
+                                                      /* low_4gb*/ false,
+                                                      &error_msg));
+    ASSERT_TRUE(image_reservation_.back().IsValid()) << error_msg;
     LOG(INFO) << "Reserved space for image " <<
-      reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" <<
-      reinterpret_cast<void*>(image_reservation_.back()->End());
+      reinterpret_cast<void*>(image_reservation_.back().Begin()) << "-" <<
+      reinterpret_cast<void*>(image_reservation_.back().End());
   }
 }
 
diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h
index 6e8dc09..3203ee5 100644
--- a/runtime/dexopt_test.h
+++ b/runtime/dexopt_test.h
@@ -91,7 +91,7 @@
   // before the image is loaded.
   void UnreserveImageSpace();
 
-  std::vector<std::unique_ptr<MemMap>> image_reservation_;
+  std::vector<MemMap> image_reservation_;
 };
 
 }  // namespace art
diff --git a/runtime/elf_file.cc b/runtime/elf_file.cc
index 026b5da..d45a689 100644
--- a/runtime/elf_file.cc
+++ b/runtime/elf_file.cc
@@ -283,7 +283,6 @@
 
 template <typename ElfTypes>
 ElfFileImpl<ElfTypes>::~ElfFileImpl() {
-  STLDeleteElements(&segments_);
   delete symtab_symbol_table_;
   delete dynsym_symbol_table_;
 }
@@ -418,17 +417,17 @@
 }
 
 template <typename ElfTypes>
-bool ElfFileImpl<ElfTypes>::SetMap(File* file, MemMap* map, std::string* error_msg) {
-  if (map == nullptr) {
+bool ElfFileImpl<ElfTypes>::SetMap(File* file, MemMap&& map, std::string* error_msg) {
+  if (!map.IsValid()) {
     // MemMap::Open should have already set an error.
     DCHECK(!error_msg->empty());
     return false;
   }
-  map_.reset(map);
-  CHECK(map_.get() != nullptr) << file->GetPath();
-  CHECK(map_->Begin() != nullptr) << file->GetPath();
+  map_ = std::move(map);
+  CHECK(map_.IsValid()) << file->GetPath();
+  CHECK(map_.Begin() != nullptr) << file->GetPath();
 
-  header_ = reinterpret_cast<Elf_Ehdr*>(map_->Begin());
+  header_ = reinterpret_cast<Elf_Ehdr*>(map_.Begin());
   if ((ELFMAG0 != header_->e_ident[EI_MAG0])
       || (ELFMAG1 != header_->e_ident[EI_MAG1])
       || (ELFMAG2 != header_->e_ident[EI_MAG2])
@@ -1164,14 +1163,13 @@
         DCHECK(!error_msg->empty());
         return false;
       }
-      std::unique_ptr<MemMap> reserve(MemMap::MapAnonymous(reservation_name.c_str(),
-                                                           reserve_base_override,
-                                                           loaded_size,
-                                                           PROT_NONE,
-                                                           low_4gb,
-                                                           false,
-                                                           error_msg));
-      if (reserve.get() == nullptr) {
+      MemMap reserve = MemMap::MapAnonymous(reservation_name.c_str(),
+                                            reserve_base_override,
+                                            loaded_size,
+                                            PROT_NONE,
+                                            low_4gb,
+                                            error_msg);
+      if (!reserve.IsValid()) {
         *error_msg = StringPrintf("Failed to allocate %s: %s",
                                   reservation_name.c_str(), error_msg->c_str());
         return false;
@@ -1179,14 +1177,14 @@
       reserved = true;
 
       // Base address is the difference of actual mapped location and the p_vaddr
-      base_address_ = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(reserve->Begin())
+      base_address_ = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(reserve.Begin())
                        - reinterpret_cast<uintptr_t>(reserve_base));
       // By adding the p_vaddr of a section/symbol to base_address_ we will always get the
       // dynamic memory address of where that object is actually mapped
       //
       // TODO: base_address_ needs to be calculated in ::Open, otherwise
       // FindDynamicSymbolAddress returns the wrong values until Load is called.
-      segments_.push_back(reserve.release());
+      segments_.push_back(std::move(reserve));
     }
     // empty segment, nothing to map
     if (program_header->p_memsz == 0) {
@@ -1234,7 +1232,7 @@
       return false;
     }
     if (program_header->p_filesz != 0u) {
-      std::unique_ptr<MemMap> segment(
+      MemMap segment =
           MemMap::MapFileAtAddress(p_vaddr,
                                    program_header->p_filesz,
                                    prot,
@@ -1244,40 +1242,42 @@
                                    /*low4_gb*/false,
                                    /*reuse*/true,  // implies MAP_FIXED
                                    file->GetPath().c_str(),
-                                   error_msg));
-      if (segment.get() == nullptr) {
+                                   error_msg);
+      if (!segment.IsValid()) {
         *error_msg = StringPrintf("Failed to map ELF file segment %d from %s: %s",
                                   i, file->GetPath().c_str(), error_msg->c_str());
         return false;
       }
-      if (segment->Begin() != p_vaddr) {
+      if (segment.Begin() != p_vaddr) {
         *error_msg = StringPrintf("Failed to map ELF file segment %d from %s at expected address %p, "
                                   "instead mapped to %p",
-                                  i, file->GetPath().c_str(), p_vaddr, segment->Begin());
+                                  i, file->GetPath().c_str(), p_vaddr, segment.Begin());
         return false;
       }
-      segments_.push_back(segment.release());
+      segments_.push_back(std::move(segment));
     }
     if (program_header->p_filesz < program_header->p_memsz) {
       std::string name = StringPrintf("Zero-initialized segment %" PRIu64 " of ELF file %s",
                                       static_cast<uint64_t>(i), file->GetPath().c_str());
-      std::unique_ptr<MemMap> segment(
-          MemMap::MapAnonymous(name.c_str(),
-                               p_vaddr + program_header->p_filesz,
-                               program_header->p_memsz - program_header->p_filesz,
-                               prot, false, true /* reuse */, error_msg));
-      if (segment == nullptr) {
+      MemMap segment = MemMap::MapAnonymous(name.c_str(),
+                                            p_vaddr + program_header->p_filesz,
+                                            program_header->p_memsz - program_header->p_filesz,
+                                            prot,
+                                            /* low_4gb */ false,
+                                            /* reuse */ true,
+                                            error_msg);
+      if (!segment.IsValid()) {
         *error_msg = StringPrintf("Failed to map zero-initialized ELF file segment %d from %s: %s",
                                   i, file->GetPath().c_str(), error_msg->c_str());
         return false;
       }
-      if (segment->Begin() != p_vaddr) {
+      if (segment.Begin() != p_vaddr) {
         *error_msg = StringPrintf("Failed to map zero-initialized ELF file segment %d from %s "
                                   "at expected address %p, instead mapped to %p",
-                                  i, file->GetPath().c_str(), p_vaddr, segment->Begin());
+                                  i, file->GetPath().c_str(), p_vaddr, segment.Begin());
         return false;
       }
-      segments_.push_back(segment.release());
+      segments_.push_back(std::move(segment));
     }
   }
 
@@ -1343,9 +1343,8 @@
 
 template <typename ElfTypes>
 bool ElfFileImpl<ElfTypes>::ValidPointer(const uint8_t* start) const {
-  for (size_t i = 0; i < segments_.size(); ++i) {
-    const MemMap* segment = segments_[i];
-    if (segment->Begin() <= start && start < segment->End()) {
+  for (const MemMap& segment : segments_) {
+    if (segment.Begin() <= start && start < segment.End()) {
       return true;
     }
   }
@@ -1712,18 +1711,18 @@
                               file->GetPath().c_str());
     return nullptr;
   }
-  std::unique_ptr<MemMap> map(MemMap::MapFile(EI_NIDENT,
-                                              PROT_READ,
-                                              MAP_PRIVATE,
-                                              file->Fd(),
-                                              0,
-                                              low_4gb,
-                                              file->GetPath().c_str(),
-                                              error_msg));
-  if (map == nullptr || map->Size() != EI_NIDENT) {
+  MemMap map = MemMap::MapFile(EI_NIDENT,
+                               PROT_READ,
+                               MAP_PRIVATE,
+                               file->Fd(),
+                               0,
+                               low_4gb,
+                               file->GetPath().c_str(),
+                               error_msg);
+  if (!map.IsValid() || map.Size() != EI_NIDENT) {
     return nullptr;
   }
-  uint8_t* header = map->Begin();
+  uint8_t* header = map.Begin();
   if (header[EI_CLASS] == ELFCLASS64) {
     ElfFileImpl64* elf_file_impl = ElfFileImpl64::Open(file,
                                                        writable,
@@ -1763,18 +1762,18 @@
                               file->GetPath().c_str());
     return nullptr;
   }
-  std::unique_ptr<MemMap> map(MemMap::MapFile(EI_NIDENT,
-                                              PROT_READ,
-                                              MAP_PRIVATE,
-                                              file->Fd(),
-                                              0,
-                                              low_4gb,
-                                              file->GetPath().c_str(),
-                                              error_msg));
-  if (map == nullptr || map->Size() != EI_NIDENT) {
+  MemMap map = MemMap::MapFile(EI_NIDENT,
+                               PROT_READ,
+                               MAP_PRIVATE,
+                               file->Fd(),
+                               /* start */ 0,
+                               low_4gb,
+                               file->GetPath().c_str(),
+                               error_msg);
+  if (!map.IsValid() || map.Size() != EI_NIDENT) {
     return nullptr;
   }
-  uint8_t* header = map->Begin();
+  uint8_t* header = map.Begin();
   if (header[EI_CLASS] == ELFCLASS64) {
     ElfFileImpl64* elf_file_impl = ElfFileImpl64::Open(file,
                                                        mmap_prot,
diff --git a/runtime/elf_file_impl.h b/runtime/elf_file_impl.h
index a5808e2..58c38a4 100644
--- a/runtime/elf_file_impl.h
+++ b/runtime/elf_file_impl.h
@@ -62,15 +62,15 @@
   }
 
   uint8_t* Begin() const {
-    return map_->Begin();
+    return map_.Begin();
   }
 
   uint8_t* End() const {
-    return map_->End();
+    return map_.End();
   }
 
   size_t Size() const {
-    return map_->Size();
+    return map_.Size();
   }
 
   Elf_Ehdr& GetHeader() const;
@@ -135,7 +135,7 @@
 
   bool Setup(File* file, int prot, int flags, bool low_4gb, std::string* error_msg);
 
-  bool SetMap(File* file, MemMap* map, std::string* error_msg);
+  bool SetMap(File* file, MemMap&& map, std::string* error_msg);
 
   uint8_t* GetProgramHeadersStart() const;
   uint8_t* GetSectionHeadersStart() const;
@@ -193,9 +193,9 @@
 
   // ELF header mapping. If program_header_only_ is false, will
   // actually point to the entire elf file.
-  std::unique_ptr<MemMap> map_;
+  MemMap map_;
   Elf_Ehdr* header_;
-  std::vector<MemMap*> segments_;
+  std::vector<MemMap> segments_;
 
   // Pointer to start of first PT_LOAD program segment after Load()
   // when program_header_only_ is true.
diff --git a/runtime/gc/accounting/atomic_stack.h b/runtime/gc/accounting/atomic_stack.h
index e30fef4..10af10d 100644
--- a/runtime/gc/accounting/atomic_stack.h
+++ b/runtime/gc/accounting/atomic_stack.h
@@ -72,12 +72,12 @@
   ~AtomicStack() {}
 
   void Reset() {
-    DCHECK(mem_map_.get() != nullptr);
+    DCHECK(mem_map_.IsValid());
     DCHECK(begin_ != nullptr);
     front_index_.store(0, std::memory_order_relaxed);
     back_index_.store(0, std::memory_order_relaxed);
     debug_is_sorted_ = true;
-    mem_map_->MadviseDontNeedAndZero();
+    mem_map_.MadviseDontNeedAndZero();
   }
 
   // Beware: Mixing atomic pushes and atomic pops will cause ABA problem.
@@ -252,10 +252,14 @@
   // Size in number of elements.
   void Init() {
     std::string error_msg;
-    mem_map_.reset(MemMap::MapAnonymous(name_.c_str(), nullptr, capacity_ * sizeof(begin_[0]),
-                                        PROT_READ | PROT_WRITE, false, false, &error_msg));
-    CHECK(mem_map_.get() != nullptr) << "couldn't allocate mark stack.\n" << error_msg;
-    uint8_t* addr = mem_map_->Begin();
+    mem_map_ = MemMap::MapAnonymous(name_.c_str(),
+                                    /* addr */ nullptr,
+                                    capacity_ * sizeof(begin_[0]),
+                                    PROT_READ | PROT_WRITE,
+                                    /* low_4gb */ false,
+                                    &error_msg);
+    CHECK(mem_map_.IsValid()) << "couldn't allocate mark stack.\n" << error_msg;
+    uint8_t* addr = mem_map_.Begin();
     CHECK(addr != nullptr);
     debug_is_sorted_ = true;
     begin_ = reinterpret_cast<StackReference<T>*>(addr);
@@ -265,7 +269,7 @@
   // Name of the mark stack.
   std::string name_;
   // Memory mapping of the atomic stack.
-  std::unique_ptr<MemMap> mem_map_;
+  MemMap mem_map_;
   // Back index (index after the last element pushed).
   AtomicInteger back_index_;
   // Front index, used for implementing PopFront.
diff --git a/runtime/gc/accounting/bitmap.cc b/runtime/gc/accounting/bitmap.cc
index d45a0cc..bb2beaa 100644
--- a/runtime/gc/accounting/bitmap.cc
+++ b/runtime/gc/accounting/bitmap.cc
@@ -27,47 +27,50 @@
 namespace gc {
 namespace accounting {
 
-Bitmap* Bitmap::CreateFromMemMap(MemMap* mem_map, size_t num_bits) {
-  CHECK(mem_map != nullptr);
-  return new Bitmap(mem_map, num_bits);
+Bitmap* Bitmap::CreateFromMemMap(MemMap&& mem_map, size_t num_bits) {
+  CHECK(mem_map.IsValid());
+  return new Bitmap(std::move(mem_map), num_bits);
 }
 
-Bitmap::Bitmap(MemMap* mem_map, size_t bitmap_size)
-    : mem_map_(mem_map), bitmap_begin_(reinterpret_cast<uintptr_t*>(mem_map->Begin())),
+Bitmap::Bitmap(MemMap&& mem_map, size_t bitmap_size)
+    : mem_map_(std::move(mem_map)),
+      bitmap_begin_(reinterpret_cast<uintptr_t*>(mem_map_.Begin())),
       bitmap_size_(bitmap_size) {
   CHECK(bitmap_begin_ != nullptr);
   CHECK_NE(bitmap_size, 0U);
 }
 
 Bitmap::~Bitmap() {
-  // Destroys MemMap via std::unique_ptr<>.
+  // Destroys member MemMap.
 }
 
-MemMap* Bitmap::AllocateMemMap(const std::string& name, size_t num_bits) {
+MemMap Bitmap::AllocateMemMap(const std::string& name, size_t num_bits) {
   const size_t bitmap_size = RoundUp(
       RoundUp(num_bits, kBitsPerBitmapWord) / kBitsPerBitmapWord * sizeof(uintptr_t), kPageSize);
   std::string error_msg;
-  std::unique_ptr<MemMap> mem_map(MemMap::MapAnonymous(name.c_str(), nullptr, bitmap_size,
-                                                       PROT_READ | PROT_WRITE, false, false,
-                                                       &error_msg));
-  if (UNLIKELY(mem_map.get() == nullptr)) {
+  MemMap mem_map = MemMap::MapAnonymous(name.c_str(),
+                                        /* addr */ nullptr,
+                                        bitmap_size,
+                                        PROT_READ | PROT_WRITE,
+                                        /* low_4gb */ false,
+                                        &error_msg);
+  if (UNLIKELY(!mem_map.IsValid())) {
     LOG(ERROR) << "Failed to allocate bitmap " << name << ": " << error_msg;
-    return nullptr;
   }
-  return mem_map.release();
+  return mem_map;
 }
 
 Bitmap* Bitmap::Create(const std::string& name, size_t num_bits) {
-  auto* const mem_map = AllocateMemMap(name, num_bits);
-  if (mem_map == nullptr) {
+  MemMap mem_map = AllocateMemMap(name, num_bits);
+  if (UNLIKELY(!mem_map.IsValid())) {
     return nullptr;
   }
-  return CreateFromMemMap(mem_map, num_bits);
+  return CreateFromMemMap(std::move(mem_map), num_bits);
 }
 
 void Bitmap::Clear() {
   if (bitmap_begin_ != nullptr) {
-    mem_map_->MadviseDontNeedAndZero();
+    mem_map_.MadviseDontNeedAndZero();
   }
 }
 
@@ -83,14 +86,15 @@
   CHECK_ALIGNED(cover_begin, kAlignment);
   CHECK_ALIGNED(cover_end, kAlignment);
   const size_t num_bits = (cover_end - cover_begin) / kAlignment;
-  auto* const mem_map = Bitmap::AllocateMemMap(name, num_bits);
-  return CreateFromMemMap(mem_map, cover_begin, num_bits);
+  MemMap mem_map = Bitmap::AllocateMemMap(name, num_bits);
+  CHECK(mem_map.IsValid());
+  return CreateFromMemMap(std::move(mem_map), cover_begin, num_bits);
 }
 
 template<size_t kAlignment>
 MemoryRangeBitmap<kAlignment>* MemoryRangeBitmap<kAlignment>::CreateFromMemMap(
-    MemMap* mem_map, uintptr_t begin, size_t num_bits) {
-  return new MemoryRangeBitmap(mem_map, begin, num_bits);
+    MemMap&& mem_map, uintptr_t begin, size_t num_bits) {
+  return new MemoryRangeBitmap(std::move(mem_map), begin, num_bits);
 }
 
 template class MemoryRangeBitmap<CardTable::kCardSize>;
diff --git a/runtime/gc/accounting/bitmap.h b/runtime/gc/accounting/bitmap.h
index 2d83a8a..ffef566 100644
--- a/runtime/gc/accounting/bitmap.h
+++ b/runtime/gc/accounting/bitmap.h
@@ -24,12 +24,11 @@
 #include <vector>
 
 #include "base/globals.h"
+#include "base/mem_map.h"
 #include "base/mutex.h"
 
 namespace art {
 
-class MemMap;
-
 namespace gc {
 namespace accounting {
 
@@ -42,7 +41,7 @@
   // Initialize a space bitmap using the provided mem_map as the live bits. Takes ownership of the
   // mem map. The address range covered starts at heap_begin and is of size equal to heap_capacity.
   // Objects are kAlignement-aligned.
-  static Bitmap* CreateFromMemMap(MemMap* mem_map, size_t num_bits);
+  static Bitmap* CreateFromMemMap(MemMap&& mem_map, size_t num_bits);
 
   // offset is the difference from base to a index.
   static ALWAYS_INLINE constexpr size_t BitIndexToWordIndex(uintptr_t offset) {
@@ -101,17 +100,17 @@
  protected:
   static constexpr size_t kBitsPerBitmapWord = sizeof(uintptr_t) * kBitsPerByte;
 
-  Bitmap(MemMap* mem_map, size_t bitmap_size);
+  Bitmap(MemMap&& mem_map, size_t bitmap_size);
   ~Bitmap();
 
   // Allocate the mem-map for a bitmap based on how many bits are required.
-  static MemMap* AllocateMemMap(const std::string& name, size_t num_bits);
+  static MemMap AllocateMemMap(const std::string& name, size_t num_bits);
 
   template<bool kSetBit>
   ALWAYS_INLINE bool ModifyBit(uintptr_t bit_index);
 
   // Backing storage for bitmap.
-  std::unique_ptr<MemMap> mem_map_;
+  MemMap mem_map_;
 
   // This bitmap itself, word sized for efficiency in scanning.
   uintptr_t* const bitmap_begin_;
@@ -127,10 +126,10 @@
 template<size_t kAlignment>
 class MemoryRangeBitmap : public Bitmap {
  public:
-  static MemoryRangeBitmap* Create(const std::string& name, uintptr_t cover_begin,
-                                   uintptr_t cover_end);
-  static MemoryRangeBitmap* CreateFromMemMap(MemMap* mem_map, uintptr_t cover_begin,
-                                             size_t num_bits);
+  static MemoryRangeBitmap* Create(
+      const std::string& name, uintptr_t cover_begin, uintptr_t cover_end);
+  static MemoryRangeBitmap* CreateFromMemMap(
+      MemMap&& mem_map, uintptr_t cover_begin, size_t num_bits);
 
   // Beginning of the memory range that the bitmap covers.
   ALWAYS_INLINE uintptr_t CoverBegin() const {
@@ -177,9 +176,10 @@
   }
 
  private:
-  MemoryRangeBitmap(MemMap* mem_map, uintptr_t begin, size_t num_bits)
-     : Bitmap(mem_map, num_bits), cover_begin_(begin), cover_end_(begin + kAlignment * num_bits) {
-  }
+  MemoryRangeBitmap(MemMap&& mem_map, uintptr_t begin, size_t num_bits)
+      : Bitmap(std::move(mem_map), num_bits),
+        cover_begin_(begin),
+        cover_end_(begin + kAlignment * num_bits) {}
 
   uintptr_t const cover_begin_;
   uintptr_t const cover_end_;
diff --git a/runtime/gc/accounting/card_table-inl.h b/runtime/gc/accounting/card_table-inl.h
index 357a498..1e7d76c 100644
--- a/runtime/gc/accounting/card_table-inl.h
+++ b/runtime/gc/accounting/card_table-inl.h
@@ -213,8 +213,8 @@
 inline void* CardTable::AddrFromCard(const uint8_t *card_addr) const {
   DCHECK(IsValidCard(card_addr))
     << " card_addr: " << reinterpret_cast<const void*>(card_addr)
-    << " begin: " << reinterpret_cast<void*>(mem_map_->Begin() + offset_)
-    << " end: " << reinterpret_cast<void*>(mem_map_->End());
+    << " begin: " << reinterpret_cast<void*>(mem_map_.Begin() + offset_)
+    << " end: " << reinterpret_cast<void*>(mem_map_.End());
   uintptr_t offset = card_addr - biased_begin_;
   return reinterpret_cast<void*>(offset << kCardShift);
 }
@@ -228,16 +228,16 @@
 }
 
 inline bool CardTable::IsValidCard(const uint8_t* card_addr) const {
-  uint8_t* begin = mem_map_->Begin() + offset_;
-  uint8_t* end = mem_map_->End();
+  uint8_t* begin = mem_map_.Begin() + offset_;
+  uint8_t* end = mem_map_.End();
   return card_addr >= begin && card_addr < end;
 }
 
 inline void CardTable::CheckCardValid(uint8_t* card) const {
   DCHECK(IsValidCard(card))
       << " card_addr: " << reinterpret_cast<const void*>(card)
-      << " begin: " << reinterpret_cast<void*>(mem_map_->Begin() + offset_)
-      << " end: " << reinterpret_cast<void*>(mem_map_->End());
+      << " begin: " << reinterpret_cast<void*>(mem_map_.Begin() + offset_)
+      << " end: " << reinterpret_cast<void*>(mem_map_.End());
 }
 
 }  // namespace accounting
diff --git a/runtime/gc/accounting/card_table.cc b/runtime/gc/accounting/card_table.cc
index c7f936f..7cddec6 100644
--- a/runtime/gc/accounting/card_table.cc
+++ b/runtime/gc/accounting/card_table.cc
@@ -41,10 +41,10 @@
  * non-null values to heap addresses should go through an entry in
  * WriteBarrier, and from there to here.
  *
- * The heap is divided into "cards" of GC_CARD_SIZE bytes, as
- * determined by GC_CARD_SHIFT. The card table contains one byte of
+ * The heap is divided into "cards" of `kCardSize` bytes, as
+ * determined by `kCardShift`. The card table contains one byte of
  * data per card, to be used by the GC. The value of the byte will be
- * one of GC_CARD_CLEAN or GC_CARD_DIRTY.
+ * one of `kCardClean` or `kCardDirty`.
  *
  * After any store of a non-null object pointer into a heap object,
  * code is obliged to mark the card dirty. The setters in
@@ -53,9 +53,9 @@
  *
  * The card table's base [the "biased card table"] gets set to a
  * rather strange value.  In order to keep the JIT from having to
- * fabricate or load GC_DIRTY_CARD to store into the card table,
+ * fabricate or load `kCardDirty` to store into the card table,
  * biased base is within the mmap allocation at a point where its low
- * byte is equal to GC_DIRTY_CARD. See CardTable::Create for details.
+ * byte is equal to `kCardDirty`. See CardTable::Create for details.
  */
 
 CardTable* CardTable::Create(const uint8_t* heap_begin, size_t heap_capacity) {
@@ -64,19 +64,22 @@
   size_t capacity = heap_capacity / kCardSize;
   /* Allocate an extra 256 bytes to allow fixed low-byte of base */
   std::string error_msg;
-  std::unique_ptr<MemMap> mem_map(
-      MemMap::MapAnonymous("card table", nullptr, capacity + 256, PROT_READ | PROT_WRITE,
-                           false, false, &error_msg));
-  CHECK(mem_map.get() != nullptr) << "couldn't allocate card table: " << error_msg;
+  MemMap mem_map = MemMap::MapAnonymous("card table",
+                                        /* addr */ nullptr,
+                                        capacity + 256,
+                                        PROT_READ | PROT_WRITE,
+                                        /* low_4gb */ false,
+                                        &error_msg);
+  CHECK(mem_map.IsValid()) << "couldn't allocate card table: " << error_msg;
   // All zeros is the correct initial value; all clean. Anonymous mmaps are initialized to zero, we
   // don't clear the card table to avoid unnecessary pages being allocated
   static_assert(kCardClean == 0, "kCardClean must be 0");
 
-  uint8_t* cardtable_begin = mem_map->Begin();
+  uint8_t* cardtable_begin = mem_map.Begin();
   CHECK(cardtable_begin != nullptr);
 
-  // We allocated up to a bytes worth of extra space to allow biased_begin's byte value to equal
-  // kCardDirty, compute a offset value to make this the case
+  // We allocated up to a bytes worth of extra space to allow `biased_begin`'s byte value to equal
+  // `kCardDirty`, compute a offset value to make this the case
   size_t offset = 0;
   uint8_t* biased_begin = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(cardtable_begin) -
       (reinterpret_cast<uintptr_t>(heap_begin) >> kCardShift));
@@ -87,11 +90,11 @@
     biased_begin += offset;
   }
   CHECK_EQ(reinterpret_cast<uintptr_t>(biased_begin) & 0xff, kCardDirty);
-  return new CardTable(mem_map.release(), biased_begin, offset);
+  return new CardTable(std::move(mem_map), biased_begin, offset);
 }
 
-CardTable::CardTable(MemMap* mem_map, uint8_t* biased_begin, size_t offset)
-    : mem_map_(mem_map), biased_begin_(biased_begin), offset_(offset) {
+CardTable::CardTable(MemMap&& mem_map, uint8_t* biased_begin, size_t offset)
+    : mem_map_(std::move(mem_map)), biased_begin_(biased_begin), offset_(offset) {
 }
 
 CardTable::~CardTable() {
@@ -100,7 +103,7 @@
 
 void CardTable::ClearCardTable() {
   static_assert(kCardClean == 0, "kCardClean must be 0");
-  mem_map_->MadviseDontNeedAndZero();
+  mem_map_.MadviseDontNeedAndZero();
 }
 
 void CardTable::ClearCardRange(uint8_t* start, uint8_t* end) {
@@ -118,8 +121,8 @@
 
 void CardTable::CheckAddrIsInCardTable(const uint8_t* addr) const {
   uint8_t* card_addr = biased_begin_ + ((uintptr_t)addr >> kCardShift);
-  uint8_t* begin = mem_map_->Begin() + offset_;
-  uint8_t* end = mem_map_->End();
+  uint8_t* begin = mem_map_.Begin() + offset_;
+  uint8_t* end = mem_map_.End();
   CHECK(AddrIsInCardTable(addr))
       << "Card table " << this
       << " begin: " << reinterpret_cast<void*>(begin)
diff --git a/runtime/gc/accounting/card_table.h b/runtime/gc/accounting/card_table.h
index f3548f7..f163898 100644
--- a/runtime/gc/accounting/card_table.h
+++ b/runtime/gc/accounting/card_table.h
@@ -20,12 +20,11 @@
 #include <memory>
 
 #include "base/globals.h"
+#include "base/mem_map.h"
 #include "base/mutex.h"
 
 namespace art {
 
-class MemMap;
-
 namespace mirror {
 class Object;
 }  // namespace mirror
@@ -56,7 +55,7 @@
   static CardTable* Create(const uint8_t* heap_begin, size_t heap_capacity);
   ~CardTable();
 
-  // Set the card associated with the given address to GC_CARD_DIRTY.
+  // Set the card associated with the given address to `kCardDirty`.
   ALWAYS_INLINE void MarkCard(const void *addr) {
     *CardFromAddr(addr) = kCardDirty;
   }
@@ -66,6 +65,11 @@
     return GetCard(obj) == kCardDirty;
   }
 
+  // Is the object on a clean card?
+  bool IsClean(const mirror::Object* obj) const {
+    return GetCard(obj) == kCardClean;
+  }
+
   // Return the state of the card at an address.
   uint8_t GetCard(const mirror::Object* obj) const {
     return *CardFromAddr(obj);
@@ -84,8 +88,8 @@
     }
   }
 
-  // Returns a value that when added to a heap address >> GC_CARD_SHIFT will address the appropriate
-  // card table byte. For convenience this value is cached in every Thread
+  // Returns a value that when added to a heap address >> `kCardShift` will address the appropriate
+  // card table byte. For convenience this value is cached in every Thread.
   uint8_t* GetBiasedBegin() const {
     return biased_begin_;
   }
@@ -133,7 +137,7 @@
   bool AddrIsInCardTable(const void* addr) const;
 
  private:
-  CardTable(MemMap* begin, uint8_t* biased_begin, size_t offset);
+  CardTable(MemMap&& mem_map, uint8_t* biased_begin, size_t offset);
 
   // Returns true iff the card table address is within the bounds of the card table.
   bool IsValidCard(const uint8_t* card_addr) const ALWAYS_INLINE;
@@ -144,11 +148,11 @@
   void VerifyCardTable();
 
   // Mmapped pages for the card table
-  std::unique_ptr<MemMap> mem_map_;
+  MemMap mem_map_;
   // Value used to compute card table addresses from object addresses, see GetBiasedBegin
   uint8_t* const biased_begin_;
   // Card table doesn't begin at the beginning of the mem_map_, instead it is displaced by offset
-  // to allow the byte value of biased_begin_ to equal GC_CARD_DIRTY
+  // to allow the byte value of `biased_begin_` to equal `kCardDirty`.
   const size_t offset_;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(CardTable);
diff --git a/runtime/gc/accounting/read_barrier_table.h b/runtime/gc/accounting/read_barrier_table.h
index 4b5a8c6..8bdf6da 100644
--- a/runtime/gc/accounting/read_barrier_table.h
+++ b/runtime/gc/accounting/read_barrier_table.h
@@ -39,11 +39,14 @@
     DCHECK_EQ(kHeapCapacity / kRegionSize,
               static_cast<uint64_t>(static_cast<size_t>(kHeapCapacity / kRegionSize)));
     std::string error_msg;
-    MemMap* mem_map = MemMap::MapAnonymous("read barrier table", nullptr, capacity,
-                                           PROT_READ | PROT_WRITE, false, false, &error_msg);
-    CHECK(mem_map != nullptr && mem_map->Begin() != nullptr)
+    mem_map_ = MemMap::MapAnonymous("read barrier table",
+                                    /* addr */ nullptr,
+                                    capacity,
+                                    PROT_READ | PROT_WRITE,
+                                    /* low_4gb */ false,
+                                    &error_msg);
+    CHECK(mem_map_.IsValid() && mem_map_.Begin() != nullptr)
         << "couldn't allocate read barrier table: " << error_msg;
-    mem_map_.reset(mem_map);
   }
   void ClearForSpace(space::ContinuousSpace* space) {
     uint8_t* entry_start = EntryFromAddr(space->Begin());
@@ -66,14 +69,14 @@
     return entry_value == kSetEntryValue;
   }
   void ClearAll() {
-    mem_map_->MadviseDontNeedAndZero();
+    mem_map_.MadviseDontNeedAndZero();
   }
   void SetAll() {
-    memset(mem_map_->Begin(), kSetEntryValue, mem_map_->Size());
+    memset(mem_map_.Begin(), kSetEntryValue, mem_map_.Size());
   }
   bool IsAllCleared() const {
-    for (uint32_t* p = reinterpret_cast<uint32_t*>(mem_map_->Begin());
-         p < reinterpret_cast<uint32_t*>(mem_map_->End()); ++p) {
+    for (uint32_t* p = reinterpret_cast<uint32_t*>(mem_map_.Begin());
+         p < reinterpret_cast<uint32_t*>(mem_map_.End()); ++p) {
       if (*p != 0) {
         return false;
       }
@@ -90,7 +93,7 @@
 
   uint8_t* EntryFromAddr(const void* heap_addr) const {
     DCHECK(IsValidHeapAddr(heap_addr)) << heap_addr;
-    uint8_t* entry_addr = mem_map_->Begin() + reinterpret_cast<uintptr_t>(heap_addr) / kRegionSize;
+    uint8_t* entry_addr = mem_map_.Begin() + reinterpret_cast<uintptr_t>(heap_addr) / kRegionSize;
     DCHECK(IsValidEntry(entry_addr)) << "heap_addr: " << heap_addr
                                      << " entry_addr: " << reinterpret_cast<void*>(entry_addr);
     return entry_addr;
@@ -106,12 +109,12 @@
   }
 
   bool IsValidEntry(const uint8_t* entry_addr) const {
-    uint8_t* begin = mem_map_->Begin();
-    uint8_t* end = mem_map_->End();
+    uint8_t* begin = mem_map_.Begin();
+    uint8_t* end = mem_map_.End();
     return entry_addr >= begin && entry_addr < end;
   }
 
-  std::unique_ptr<MemMap> mem_map_;
+  MemMap mem_map_;
 };
 
 }  // namespace accounting
diff --git a/runtime/gc/accounting/space_bitmap.cc b/runtime/gc/accounting/space_bitmap.cc
index ced62cd..2946486 100644
--- a/runtime/gc/accounting/space_bitmap.cc
+++ b/runtime/gc/accounting/space_bitmap.cc
@@ -49,21 +49,22 @@
 
 template<size_t kAlignment>
 SpaceBitmap<kAlignment>* SpaceBitmap<kAlignment>::CreateFromMemMap(
-    const std::string& name, MemMap* mem_map, uint8_t* heap_begin, size_t heap_capacity) {
-  CHECK(mem_map != nullptr);
-  uintptr_t* bitmap_begin = reinterpret_cast<uintptr_t*>(mem_map->Begin());
+    const std::string& name, MemMap&& mem_map, uint8_t* heap_begin, size_t heap_capacity) {
+  CHECK(mem_map.IsValid());
+  uintptr_t* bitmap_begin = reinterpret_cast<uintptr_t*>(mem_map.Begin());
   const size_t bitmap_size = ComputeBitmapSize(heap_capacity);
-  return new SpaceBitmap(name, mem_map, bitmap_begin, bitmap_size, heap_begin, heap_capacity);
+  return new SpaceBitmap(
+      name, std::move(mem_map), bitmap_begin, bitmap_size, heap_begin, heap_capacity);
 }
 
 template<size_t kAlignment>
 SpaceBitmap<kAlignment>::SpaceBitmap(const std::string& name,
-                                     MemMap* mem_map,
+                                     MemMap&& mem_map,
                                      uintptr_t* bitmap_begin,
                                      size_t bitmap_size,
                                      const void* heap_begin,
                                      size_t heap_capacity)
-    : mem_map_(mem_map),
+    : mem_map_(std::move(mem_map)),
       bitmap_begin_(reinterpret_cast<Atomic<uintptr_t>*>(bitmap_begin)),
       bitmap_size_(bitmap_size),
       heap_begin_(reinterpret_cast<uintptr_t>(heap_begin)),
@@ -83,14 +84,17 @@
   // (we represent one word as an `intptr_t`).
   const size_t bitmap_size = ComputeBitmapSize(heap_capacity);
   std::string error_msg;
-  std::unique_ptr<MemMap> mem_map(MemMap::MapAnonymous(name.c_str(), nullptr, bitmap_size,
-                                                       PROT_READ | PROT_WRITE, false, false,
-                                                       &error_msg));
-  if (UNLIKELY(mem_map.get() == nullptr)) {
+  MemMap mem_map = MemMap::MapAnonymous(name.c_str(),
+                                        /* addr */ nullptr,
+                                        bitmap_size,
+                                        PROT_READ | PROT_WRITE,
+                                        /* low_4gb */ false,
+                                        &error_msg);
+  if (UNLIKELY(!mem_map.IsValid())) {
     LOG(ERROR) << "Failed to allocate bitmap " << name << ": " << error_msg;
     return nullptr;
   }
-  return CreateFromMemMap(name, mem_map.release(), heap_begin, heap_capacity);
+  return CreateFromMemMap(name, std::move(mem_map), heap_begin, heap_capacity);
 }
 
 template<size_t kAlignment>
@@ -114,7 +118,7 @@
 template<size_t kAlignment>
 void SpaceBitmap<kAlignment>::Clear() {
   if (bitmap_begin_ != nullptr) {
-    mem_map_->MadviseDontNeedAndZero();
+    mem_map_.MadviseDontNeedAndZero();
   }
 }
 
diff --git a/runtime/gc/accounting/space_bitmap.h b/runtime/gc/accounting/space_bitmap.h
index 1237f6e..6a3faef 100644
--- a/runtime/gc/accounting/space_bitmap.h
+++ b/runtime/gc/accounting/space_bitmap.h
@@ -24,6 +24,7 @@
 #include <vector>
 
 #include "base/globals.h"
+#include "base/mem_map.h"
 #include "base/mutex.h"
 
 namespace art {
@@ -32,7 +33,6 @@
 class Class;
 class Object;
 }  // namespace mirror
-class MemMap;
 
 namespace gc {
 namespace accounting {
@@ -50,8 +50,10 @@
   // Initialize a space bitmap using the provided mem_map as the live bits. Takes ownership of the
   // mem map. The address range covered starts at heap_begin and is of size equal to heap_capacity.
   // Objects are kAlignement-aligned.
-  static SpaceBitmap* CreateFromMemMap(const std::string& name, MemMap* mem_map,
-                                       uint8_t* heap_begin, size_t heap_capacity);
+  static SpaceBitmap* CreateFromMemMap(const std::string& name,
+                                       MemMap&& mem_map,
+                                       uint8_t* heap_begin,
+                                       size_t heap_capacity);
 
   ~SpaceBitmap();
 
@@ -215,7 +217,7 @@
   // TODO: heap_end_ is initialized so that the heap bitmap is empty, this doesn't require the -1,
   // however, we document that this is expected on heap_end_
   SpaceBitmap(const std::string& name,
-              MemMap* mem_map,
+              MemMap&& mem_map,
               uintptr_t* bitmap_begin,
               size_t bitmap_size,
               const void* heap_begin,
@@ -227,7 +229,7 @@
   bool Modify(const mirror::Object* obj);
 
   // Backing storage for bitmap.
-  std::unique_ptr<MemMap> mem_map_;
+  MemMap mem_map_;
 
   // This bitmap itself, word sized for efficiency in scanning.
   Atomic<uintptr_t>* const bitmap_begin_;
diff --git a/runtime/gc/allocator/rosalloc.cc b/runtime/gc/allocator/rosalloc.cc
index a4095d8..0dbafde 100644
--- a/runtime/gc/allocator/rosalloc.cc
+++ b/runtime/gc/allocator/rosalloc.cc
@@ -91,11 +91,14 @@
   size_t num_of_pages = footprint_ / kPageSize;
   size_t max_num_of_pages = max_capacity_ / kPageSize;
   std::string error_msg;
-  page_map_mem_map_.reset(MemMap::MapAnonymous("rosalloc page map", nullptr,
-                                               RoundUp(max_num_of_pages, kPageSize),
-                                               PROT_READ | PROT_WRITE, false, false, &error_msg));
-  CHECK(page_map_mem_map_.get() != nullptr) << "Couldn't allocate the page map : " << error_msg;
-  page_map_ = page_map_mem_map_->Begin();
+  page_map_mem_map_ = MemMap::MapAnonymous("rosalloc page map",
+                                           /* addr */ nullptr,
+                                           RoundUp(max_num_of_pages, kPageSize),
+                                           PROT_READ | PROT_WRITE,
+                                           /* low_4gb */ false,
+                                           &error_msg);
+  CHECK(page_map_mem_map_.IsValid()) << "Couldn't allocate the page map : " << error_msg;
+  page_map_ = page_map_mem_map_.Begin();
   page_map_size_ = num_of_pages;
   max_page_map_size_ = max_num_of_pages;
   free_page_run_size_map_.resize(num_of_pages);
@@ -1364,8 +1367,8 @@
     // Zero out the tail of the page map.
     uint8_t* zero_begin = const_cast<uint8_t*>(page_map_) + new_num_of_pages;
     uint8_t* madvise_begin = AlignUp(zero_begin, kPageSize);
-    DCHECK_LE(madvise_begin, page_map_mem_map_->End());
-    size_t madvise_size = page_map_mem_map_->End() - madvise_begin;
+    DCHECK_LE(madvise_begin, page_map_mem_map_.End());
+    size_t madvise_size = page_map_mem_map_.End() - madvise_begin;
     if (madvise_size > 0) {
       DCHECK_ALIGNED(madvise_begin, kPageSize);
       DCHECK_EQ(RoundUp(madvise_size, kPageSize), madvise_size);
diff --git a/runtime/gc/allocator/rosalloc.h b/runtime/gc/allocator/rosalloc.h
index 30213d5..0562167 100644
--- a/runtime/gc/allocator/rosalloc.h
+++ b/runtime/gc/allocator/rosalloc.h
@@ -31,13 +31,12 @@
 #include "base/allocator.h"
 #include "base/bit_utils.h"
 #include "base/globals.h"
+#include "base/mem_map.h"
 #include "base/mutex.h"
 #include "thread.h"
 
 namespace art {
 
-class MemMap;
-
 namespace gc {
 namespace allocator {
 
@@ -746,7 +745,7 @@
   volatile uint8_t* page_map_;  // No GUARDED_BY(lock_) for kReadPageMapEntryWithoutLockInBulkFree.
   size_t page_map_size_;
   size_t max_page_map_size_;
-  std::unique_ptr<MemMap> page_map_mem_map_;
+  MemMap page_map_mem_map_;
 
   // The table that indicates the size of free page runs. These sizes
   // are stored here to avoid storing in the free page header and
diff --git a/runtime/gc/collector/concurrent_copying-inl.h b/runtime/gc/collector/concurrent_copying-inl.h
index 36fefbd..3095f9f 100644
--- a/runtime/gc/collector/concurrent_copying-inl.h
+++ b/runtime/gc/collector/concurrent_copying-inl.h
@@ -25,6 +25,7 @@
 #include "gc/space/region_space.h"
 #include "gc/verification.h"
 #include "lock_word.h"
+#include "mirror/class.h"
 #include "mirror/object-readbarrier-inl.h"
 
 namespace art {
@@ -35,12 +36,32 @@
     Thread* const self,
     mirror::Object* ref,
     accounting::ContinuousSpaceBitmap* bitmap) {
-  // For the Baker-style RB, in a rare case, we could incorrectly change the object from white
-  // to gray even though the object has already been marked through. This happens if a mutator
-  // thread gets preempted before the AtomicSetReadBarrierState below, GC marks through the
-  // object (changes it from white to gray and back to white), and the thread runs and
-  // incorrectly changes it from white to gray. If this happens, the object will get added to the
-  // mark stack again and get changed back to white after it is processed.
+  if (kEnableGenerationalConcurrentCopyingCollection
+      && young_gen_
+      && !done_scanning_.load(std::memory_order_acquire)) {
+    // Everything in the unevac space should be marked for generational CC except for large objects.
+    DCHECK(region_space_bitmap_->Test(ref) || region_space_->IsLargeObject(ref)) << ref << " "
+        << ref->GetClass<kVerifyNone, kWithoutReadBarrier>()->PrettyClass();
+    // Since the mark bitmap is still filled in from last GC, we can not use that or else the
+    // mutator may see references to the from space. Instead, use the baker pointer itself as
+    // the mark bit.
+    if (ref->AtomicSetReadBarrierState(ReadBarrier::NonGrayState(), ReadBarrier::GrayState())) {
+      // TODO: We don't actually need to scan this object later, we just need to clear the gray
+      // bit.
+      // TODO: We could also set the mark bit here for "free" since this case comes from the
+      // read barrier.
+      PushOntoMarkStack(self, ref);
+    }
+    DCHECK_EQ(ref->GetReadBarrierState(), ReadBarrier::GrayState());
+    return ref;
+  }
+  // For the Baker-style RB, in a rare case, we could incorrectly change the object from non-gray
+  // (black) to gray even though the object has already been marked through. This happens if a
+  // mutator thread gets preempted before the AtomicSetReadBarrierState below, GC marks through the
+  // object (changes it from non-gray (white) to gray and back to non-gray (black)), and the thread
+  // runs and incorrectly changes it from non-gray (black) to gray. If this happens, the object
+  // will get added to the mark stack again and get changed back to non-gray (black) after it is
+  // processed.
   if (kUseBakerReadBarrier) {
     // Test the bitmap first to avoid graying an object that has already been marked through most
     // of the time.
@@ -55,7 +76,7 @@
     // we can avoid an expensive CAS.
     // For the baker case, an object is marked if either the mark bit marked or the bitmap bit is
     // set.
-    success = ref->AtomicSetReadBarrierState(/* expected_rb_state */ ReadBarrier::WhiteState(),
+    success = ref->AtomicSetReadBarrierState(/* expected_rb_state */ ReadBarrier::NonGrayState(),
                                              /* rb_state */ ReadBarrier::GrayState());
   } else {
     success = !bitmap->AtomicTestAndSet(ref);
@@ -91,8 +112,9 @@
       return ref;
     }
     // This may or may not succeed, which is ok because the object may already be gray.
-    bool success = ref->AtomicSetReadBarrierState(/* expected_rb_state */ ReadBarrier::WhiteState(),
-                                                  /* rb_state */ ReadBarrier::GrayState());
+    bool success =
+        ref->AtomicSetReadBarrierState(/* expected_rb_state */ ReadBarrier::NonGrayState(),
+                                       /* rb_state */ ReadBarrier::GrayState());
     if (success) {
       MutexLock mu(self, immune_gray_stack_lock_);
       immune_gray_stack_.push_back(ref);
@@ -101,11 +123,13 @@
   return ref;
 }
 
-template<bool kGrayImmuneObject, bool kFromGCThread>
+template<bool kGrayImmuneObject, bool kNoUnEvac, bool kFromGCThread>
 inline mirror::Object* ConcurrentCopying::Mark(Thread* const self,
                                                mirror::Object* from_ref,
                                                mirror::Object* holder,
                                                MemberOffset offset) {
+  // Cannot have `kNoUnEvac` when Generational CC collection is disabled.
+  DCHECK(kEnableGenerationalConcurrentCopyingCollection || !kNoUnEvac);
   if (from_ref == nullptr) {
     return nullptr;
   }
@@ -147,9 +171,19 @@
         return to_ref;
       }
       case space::RegionSpace::RegionType::kRegionTypeUnevacFromSpace:
+        if (kEnableGenerationalConcurrentCopyingCollection
+            && kNoUnEvac
+            && !region_space_->IsLargeObject(from_ref)) {
+          if (!kFromGCThread) {
+            DCHECK(IsMarkedInUnevacFromSpace(from_ref)) << "Returning unmarked object to mutator";
+          }
+          return from_ref;
+        }
         return MarkUnevacFromSpaceRegion(self, from_ref, region_space_bitmap_);
       default:
-        // The reference is in an unused region.
+        // The reference is in an unused region. Remove memory protection from
+        // the region space and log debugging information.
+        region_space_->Unprotect();
         LOG(FATAL_WITHOUT_ABORT) << DumpHeapReference(holder, offset, from_ref);
         region_space_->DumpNonFreeRegions(LOG_STREAM(FATAL_WITHOUT_ABORT));
         heap_->GetVerification()->LogHeapCorruption(holder, offset, from_ref, /* fatal */ true);
@@ -175,7 +209,8 @@
   if (UNLIKELY(mark_from_read_barrier_measurements_)) {
     ret = MarkFromReadBarrierWithMeasurements(self, from_ref);
   } else {
-    ret = Mark(self, from_ref);
+    ret = Mark</*kGrayImmuneObject*/true, /*kNoUnEvac*/false, /*kFromGCThread*/false>(self,
+                                                                                      from_ref);
   }
   // Only set the mark bit for baker barrier.
   if (kUseBakerReadBarrier && LIKELY(!rb_mark_bit_stack_full_ && ret->AtomicSetMarkBit(0, 1))) {
@@ -204,9 +239,14 @@
 }
 
 inline bool ConcurrentCopying::IsMarkedInUnevacFromSpace(mirror::Object* from_ref) {
-  // Use load acquire on the read barrier pointer to ensure that we never see a white read barrier
-  // state with an unmarked bit due to reordering.
+  // Use load-acquire on the read barrier pointer to ensure that we never see a black (non-gray)
+  // read barrier state with an unmarked bit due to reordering.
   DCHECK(region_space_->IsInUnevacFromSpace(from_ref));
+  if (kEnableGenerationalConcurrentCopyingCollection
+      && young_gen_
+      && !done_scanning_.load(std::memory_order_acquire)) {
+    return from_ref->GetReadBarrierStateAcquire() == ReadBarrier::GrayState();
+  }
   if (kUseBakerReadBarrier && from_ref->GetReadBarrierStateAcquire() == ReadBarrier::GrayState()) {
     return true;
   }
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index edaa043..fdd0b62 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -53,17 +53,20 @@
 // If kFilterModUnionCards then we attempt to filter cards that don't need to be dirty in the mod
 // union table. Disabled since it does not seem to help the pause much.
 static constexpr bool kFilterModUnionCards = kIsDebugBuild;
-// If kDisallowReadBarrierDuringScan is true then the GC aborts if there are any that occur during
-// ConcurrentCopying::Scan. May be used to diagnose possibly unnecessary read barriers.
-// Only enabled for kIsDebugBuild to avoid performance hit.
+// If kDisallowReadBarrierDuringScan is true then the GC aborts if there are any read barrier that
+// occur during ConcurrentCopying::Scan in GC thread. May be used to diagnose possibly unnecessary
+// read barriers. Only enabled for kIsDebugBuild to avoid performance hit.
 static constexpr bool kDisallowReadBarrierDuringScan = kIsDebugBuild;
 // Slow path mark stack size, increase this if the stack is getting full and it is causing
 // performance problems.
 static constexpr size_t kReadBarrierMarkStackSize = 512 * KB;
+// Size (in the number of objects) of the sweep array free buffer.
+static constexpr size_t kSweepArrayChunkFreeSize = 1024;
 // Verify that there are no missing card marks.
 static constexpr bool kVerifyNoMissingCardMarks = kIsDebugBuild;
 
 ConcurrentCopying::ConcurrentCopying(Heap* heap,
+                                     bool young_gen,
                                      const std::string& name_prefix,
                                      bool measure_read_barrier_slow_path)
     : GarbageCollector(heap,
@@ -90,6 +93,7 @@
       from_space_num_bytes_at_first_pause_(0),
       mark_stack_mode_(kMarkStackModeOff),
       weak_ref_access_enabled_(true),
+      young_gen_(young_gen),
       skipped_blocks_lock_("concurrent copying bytes blocks lock", kMarkSweepMarkStackLock),
       measure_read_barrier_slow_path_(measure_read_barrier_slow_path),
       mark_from_read_barrier_measurements_(false),
@@ -107,6 +111,7 @@
                               kMarkSweepMarkStackLock) {
   static_assert(space::RegionSpace::kRegionSize == accounting::ReadBarrierTable::kRegionSize,
                 "The region space size and the read barrier table region size must match");
+  CHECK(kEnableGenerationalConcurrentCopyingCollection || !young_gen_);
   Thread* self = Thread::Current();
   {
     ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
@@ -125,6 +130,19 @@
       pooled_mark_stacks_.push_back(mark_stack);
     }
   }
+  if (kEnableGenerationalConcurrentCopyingCollection) {
+    // Allocate sweep array free buffer.
+    std::string error_msg;
+    sweep_array_free_buffer_mem_map_ = MemMap::MapAnonymous(
+        "concurrent copying sweep array free buffer",
+        /* addr */ nullptr,
+        RoundUp(kSweepArrayChunkFreeSize * sizeof(mirror::Object*), kPageSize),
+        PROT_READ | PROT_WRITE,
+        /* low_4gb */ false,
+        &error_msg);
+    CHECK(sweep_array_free_buffer_mem_map_.IsValid())
+        << "Couldn't allocate sweep array free buffer: " << error_msg;
+  }
 }
 
 void ConcurrentCopying::MarkHeapReference(mirror::HeapReference<mirror::Object>* field,
@@ -268,11 +286,36 @@
         space->GetGcRetentionPolicy() == space::kGcRetentionPolicyFullCollect) {
       CHECK(space->IsZygoteSpace() || space->IsImageSpace());
       immune_spaces_.AddSpace(space);
-    } else if (space == region_space_) {
-      // It is OK to clear the bitmap with mutators running since the only place it is read is
-      // VisitObjects which has exclusion with CC.
-      region_space_bitmap_ = region_space_->GetMarkBitmap();
-      region_space_bitmap_->Clear();
+    } else {
+      CHECK(!space->IsZygoteSpace());
+      CHECK(!space->IsImageSpace());
+      if (kEnableGenerationalConcurrentCopyingCollection) {
+        if (space == region_space_) {
+          region_space_bitmap_ = region_space_->GetMarkBitmap();
+        } else if (young_gen_ && space->IsContinuousMemMapAllocSpace()) {
+          DCHECK_EQ(space->GetGcRetentionPolicy(), space::kGcRetentionPolicyAlwaysCollect);
+          space->AsContinuousMemMapAllocSpace()->BindLiveToMarkBitmap();
+        }
+        // Age all of the cards for the region space so that we know which evac regions to scan.
+        Runtime::Current()->GetHeap()->GetCardTable()->ModifyCardsAtomic(
+            space->Begin(),
+            space->End(),
+            AgeCardVisitor(),
+            VoidFunctor());
+      } else {
+        if (space == region_space_) {
+          // It is OK to clear the bitmap with mutators running since the only place it is read is
+          // VisitObjects which has exclusion with CC.
+          region_space_bitmap_ = region_space_->GetMarkBitmap();
+          region_space_bitmap_->Clear();
+        }
+      }
+    }
+  }
+  if (kEnableGenerationalConcurrentCopyingCollection && young_gen_) {
+    for (const auto& space : GetHeap()->GetDiscontinuousSpaces()) {
+      CHECK(space->IsLargeObjectSpace());
+      space->AsLargeObjectSpace()->CopyLiveToMarked();
     }
   }
 }
@@ -304,12 +347,14 @@
   bytes_moved_gc_thread_ = 0;
   objects_moved_gc_thread_ = 0;
   GcCause gc_cause = GetCurrentIteration()->GetGcCause();
-  if (gc_cause == kGcCauseExplicit ||
-      gc_cause == kGcCauseCollectorTransition ||
-      GetCurrentIteration()->GetClearSoftReferences()) {
-    force_evacuate_all_ = true;
-  } else {
-    force_evacuate_all_ = false;
+
+  force_evacuate_all_ = false;
+  if (!kEnableGenerationalConcurrentCopyingCollection || !young_gen_) {
+    if (gc_cause == kGcCauseExplicit ||
+        gc_cause == kGcCauseCollectorTransition ||
+        GetCurrentIteration()->GetClearSoftReferences()) {
+      force_evacuate_all_ = true;
+    }
   }
   if (kUseBakerReadBarrier) {
     updated_all_immune_objects_.store(false, std::memory_order_relaxed);
@@ -320,9 +365,13 @@
       DCHECK(immune_gray_stack_.empty());
     }
   }
+  if (kEnableGenerationalConcurrentCopyingCollection) {
+    done_scanning_.store(false, std::memory_order_release);
+  }
   BindBitmaps();
   if (kVerboseMode) {
-    LOG(INFO) << "force_evacuate_all=" << force_evacuate_all_;
+    LOG(INFO) << "young_gen=" << std::boolalpha << young_gen_ << std::noboolalpha;
+    LOG(INFO) << "force_evacuate_all=" << std::boolalpha << force_evacuate_all_ << std::noboolalpha;
     LOG(INFO) << "Largest immune region: " << immune_spaces_.GetLargestImmuneRegion().Begin()
               << "-" << immune_spaces_.GetLargestImmuneRegion().End();
     for (space::ContinuousSpace* space : immune_spaces_.GetSpaces()) {
@@ -330,6 +379,9 @@
     }
     LOG(INFO) << "GC end of InitializePhase";
   }
+  if (kEnableGenerationalConcurrentCopyingCollection && !young_gen_) {
+    region_space_bitmap_->Clear();
+  }
   // Mark all of the zygote large objects without graying them.
   MarkZygoteLargeObjects();
 }
@@ -425,9 +477,18 @@
     }
     CHECK_EQ(thread, self);
     Locks::mutator_lock_->AssertExclusiveHeld(self);
+    space::RegionSpace::EvacMode evac_mode = space::RegionSpace::kEvacModeLivePercentNewlyAllocated;
+    if (cc->young_gen_) {
+      CHECK(!cc->force_evacuate_all_);
+      evac_mode = space::RegionSpace::kEvacModeNewlyAllocated;
+    } else if (cc->force_evacuate_all_) {
+      evac_mode = space::RegionSpace::kEvacModeForceAll;
+    }
     {
       TimingLogger::ScopedTiming split2("(Paused)SetFromSpace", cc->GetTimings());
-      cc->region_space_->SetFromSpace(cc->rb_table_, cc->force_evacuate_all_);
+      // Only change live bytes for full CC.
+      cc->region_space_->SetFromSpace(
+          cc->rb_table_, evac_mode, /*clear_live_bytes*/ !cc->young_gen_);
     }
     cc->SwapStacks();
     if (ConcurrentCopying::kEnableFromSpaceAccountingCheck) {
@@ -438,7 +499,7 @@
     cc->is_marking_ = true;
     cc->mark_stack_mode_.store(ConcurrentCopying::kMarkStackModeThreadLocal,
                                std::memory_order_relaxed);
-    if (kIsDebugBuild) {
+    if (kIsDebugBuild && !cc->young_gen_) {
       cc->region_space_->AssertAllRegionLiveBytesZeroOrCleared();
     }
     if (UNLIKELY(Runtime::Current()->IsActiveTransaction())) {
@@ -596,7 +657,8 @@
   auto visitor = [&](mirror::Object* obj)
       REQUIRES(Locks::mutator_lock_)
       REQUIRES(!mark_stack_lock_) {
-    // Objects not on dirty or aged cards should never have references to newly allocated regions.
+    // Objects on clean cards should never have references to newly allocated regions. Note
+    // that aged cards are also not clean.
     if (heap_->GetCardTable()->GetCard(obj) == gc::accounting::CardTable::kCardClean) {
       VerifyNoMissingCardMarkVisitor internal_visitor(this, /*holder*/ obj);
       obj->VisitReferences</*kVisitNativeRoots*/true, kVerifyNone, kWithoutReadBarrier>(
@@ -646,10 +708,10 @@
   explicit GrayImmuneObjectVisitor(Thread* self) : self_(self) {}
 
   ALWAYS_INLINE void operator()(mirror::Object* obj) const REQUIRES_SHARED(Locks::mutator_lock_) {
-    if (kUseBakerReadBarrier && obj->GetReadBarrierState() == ReadBarrier::WhiteState()) {
+    if (kUseBakerReadBarrier && obj->GetReadBarrierState() == ReadBarrier::NonGrayState()) {
       if (kConcurrent) {
         Locks::mutator_lock_->AssertSharedHeld(self_);
-        obj->AtomicSetReadBarrierState(ReadBarrier::WhiteState(), ReadBarrier::GrayState());
+        obj->AtomicSetReadBarrierState(ReadBarrier::NonGrayState(), ReadBarrier::GrayState());
         // Mod union table VisitObjects may visit the same object multiple times so we can't check
         // the result of the atomic set.
       } else {
@@ -752,7 +814,13 @@
   DCHECK(obj != nullptr);
   DCHECK(immune_spaces_.ContainsObject(obj));
   // Update the fields without graying it or pushing it onto the mark stack.
-  Scan(obj);
+  if (kEnableGenerationalConcurrentCopyingCollection && young_gen_) {
+    // Young GC does not care about references to unevac space. It is safe to not gray these as
+    // long as scan immune objects happens after scanning the dirty cards.
+    Scan<true>(obj);
+  } else {
+    Scan<false>(obj);
+  }
 }
 
 class ConcurrentCopying::ImmuneSpaceScanObjVisitor {
@@ -765,9 +833,9 @@
       // Only need to scan gray objects.
       if (obj->GetReadBarrierState() == ReadBarrier::GrayState()) {
         collector_->ScanImmuneObject(obj);
-        // Done scanning the object, go back to white.
+        // Done scanning the object, go back to black (non-gray).
         bool success = obj->AtomicSetReadBarrierState(ReadBarrier::GrayState(),
-                                                      ReadBarrier::WhiteState());
+                                                      ReadBarrier::NonGrayState());
         CHECK(success)
             << Runtime::Current()->GetHeap()->GetVerification()->DumpObjectInfo(obj, "failed CAS");
       }
@@ -803,7 +871,55 @@
   if (kUseBakerReadBarrier) {
     gc_grays_immune_objects_ = false;
   }
+  if (kEnableGenerationalConcurrentCopyingCollection && young_gen_) {
+    if (kVerboseMode) {
+      LOG(INFO) << "GC ScanCardsForSpace";
+    }
+    TimingLogger::ScopedTiming split2("ScanCardsForSpace", GetTimings());
+    WriterMutexLock rmu(Thread::Current(), *Locks::heap_bitmap_lock_);
+    CHECK(!done_scanning_.load(std::memory_order_relaxed));
+    if (kIsDebugBuild) {
+      // Leave some time for mutators to race ahead to try and find races between the GC card
+      // scanning and mutators reading references.
+      usleep(10 * 1000);
+    }
+    for (space::ContinuousSpace* space : GetHeap()->GetContinuousSpaces()) {
+      if (space->IsImageSpace() || space->IsZygoteSpace()) {
+        // Image and zygote spaces are already handled since we gray the objects in the pause.
+        continue;
+      }
+      // Scan all of the objects on dirty cards in unevac from space, and non moving space. These
+      // are from previous GCs and may reference things in the from space.
+      Runtime::Current()->GetHeap()->GetCardTable()->Scan<false>(
+          space->GetMarkBitmap(),
+          space->Begin(),
+          space->End(),
+          [this, space](mirror::Object* obj)
+              REQUIRES(Locks::heap_bitmap_lock_)
+              REQUIRES_SHARED(Locks::mutator_lock_) {
+            // Don't push or gray unevac refs.
+            if (kIsDebugBuild && space == region_space_) {
+              // We may get unevac large objects.
+              if (!region_space_->IsInUnevacFromSpace(obj)) {
+                CHECK(region_space_bitmap_->Test(obj));
+                region_space_->DumpRegionForObject(LOG_STREAM(FATAL_WITHOUT_ABORT), obj);
+                LOG(FATAL) << "Scanning " << obj << " not in unevac space";
+              }
+            }
+            Scan<true>(obj);
+          },
+          accounting::CardTable::kCardDirty - 1);
+    }
+    // Done scanning unevac space.
+    done_scanning_.store(true, std::memory_order_release);
+    if (kVerboseMode) {
+      LOG(INFO) << "GC end of ScanCardsForSpace";
+    }
+  }
   {
+    // For a sticky-bit collection, this phase needs to be after the card scanning since the
+    // mutator may read an unevac space object out of an image object. If the image object is no
+    // longer gray it will trigger a read barrier for the unevac space object.
     TimingLogger::ScopedTiming split2("ScanImmuneSpaces", GetTimings());
     for (auto& space : immune_spaces_.GetSpaces()) {
       DCHECK(space->IsImageSpace() || space->IsZygoteSpace());
@@ -824,21 +940,23 @@
     // This release fence makes the field updates in the above loop visible before allowing mutator
     // getting access to immune objects without graying it first.
     updated_all_immune_objects_.store(true, std::memory_order_release);
-    // Now whiten immune objects concurrently accessed and grayed by mutators. We can't do this in
-    // the above loop because we would incorrectly disable the read barrier by whitening an object
-    // which may point to an unscanned, white object, breaking the to-space invariant.
+    // Now "un-gray" (conceptually blacken) immune objects concurrently accessed and grayed by
+    // mutators. We can't do this in the above loop because we would incorrectly disable the read
+    // barrier by un-graying (conceptually blackening) an object which may point to an unscanned,
+    // white object, breaking the to-space invariant (a mutator shall never observe a from-space
+    // (white) object).
     //
-    // Make sure no mutators are in the middle of marking an immune object before whitening immune
-    // objects.
+    // Make sure no mutators are in the middle of marking an immune object before un-graying
+    // (blackening) immune objects.
     IssueEmptyCheckpoint();
     MutexLock mu(Thread::Current(), immune_gray_stack_lock_);
     if (kVerboseMode) {
       LOG(INFO) << "immune gray stack size=" << immune_gray_stack_.size();
     }
     for (mirror::Object* obj : immune_gray_stack_) {
-      DCHECK(obj->GetReadBarrierState() == ReadBarrier::GrayState());
+      DCHECK_EQ(obj->GetReadBarrierState(), ReadBarrier::GrayState());
       bool success = obj->AtomicSetReadBarrierState(ReadBarrier::GrayState(),
-                                                    ReadBarrier::WhiteState());
+                                                    ReadBarrier::NonGrayState());
       DCHECK(success);
     }
     immune_gray_stack_.clear();
@@ -1038,16 +1156,17 @@
 
 void ConcurrentCopying::ProcessFalseGrayStack() {
   CHECK(kUseBakerReadBarrier);
-  // Change the objects on the false gray stack from gray to white.
+  // Change the objects on the false gray stack from gray to non-gray (conceptually black).
   MutexLock mu(Thread::Current(), mark_stack_lock_);
   for (mirror::Object* obj : false_gray_stack_) {
     DCHECK(IsMarked(obj));
-    // The object could be white here if a thread got preempted after a success at the
-    // AtomicSetReadBarrierState in Mark(), GC started marking through it (but not finished so
-    // still gray), and the thread ran to register it onto the false gray stack.
+    // The object could be non-gray (conceptually black) here if a thread got preempted after a
+    // success at the AtomicSetReadBarrierState in MarkNonMoving(), GC started marking through it
+    // (but not finished so still gray), the thread ran to register it onto the false gray stack,
+    // and then GC eventually marked it black (non-gray) after it finished scanning it.
     if (obj->GetReadBarrierState() == ReadBarrier::GrayState()) {
       bool success = obj->AtomicSetReadBarrierState(ReadBarrier::GrayState(),
-                                                    ReadBarrier::WhiteState());
+                                                    ReadBarrier::NonGrayState());
       DCHECK(success);
     }
   }
@@ -1166,9 +1285,8 @@
     }
     collector_->AssertToSpaceInvariant(holder, offset, ref);
     if (kUseBakerReadBarrier) {
-      CHECK_EQ(ref->GetReadBarrierState(), ReadBarrier::WhiteState())
-          << "Ref " << ref << " " << ref->PrettyTypeOf()
-          << " has non-white rb_state ";
+      CHECK_EQ(ref->GetReadBarrierState(), ReadBarrier::NonGrayState())
+          << "Ref " << ref << " " << ref->PrettyTypeOf() << " has gray rb_state";
     }
   }
 
@@ -1200,7 +1318,7 @@
                   ObjPtr<mirror::Reference> ref) const
       REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE {
     CHECK(klass->IsTypeOfReferenceClass());
-    this->operator()(ref, mirror::Reference::ReferentOffset(), false);
+    this->operator()(ObjPtr<mirror::Object>(ref), mirror::Reference::ReferentOffset(), false);
   }
 
   void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
@@ -1243,8 +1361,8 @@
         visitor,
         visitor);
     if (kUseBakerReadBarrier) {
-      CHECK_EQ(obj->GetReadBarrierState(), ReadBarrier::WhiteState())
-          << "obj=" << obj << " non-white rb_state " << obj->GetReadBarrierState();
+      CHECK_EQ(obj->GetReadBarrierState(), ReadBarrier::NonGrayState())
+          << "obj=" << obj << " has gray rb_state " << obj->GetReadBarrierState();
     }
   };
   // Roots.
@@ -1518,18 +1636,40 @@
         << " " << to_ref << " " << to_ref->GetReadBarrierState()
         << " is_marked=" << IsMarked(to_ref);
   }
+  space::RegionSpace::RegionType rtype = region_space_->GetRegionType(to_ref);
   bool add_to_live_bytes = false;
-  if (region_space_->IsInUnevacFromSpace(to_ref)) {
+  // Invariant: There should be no object from a newly-allocated
+  // region (either large or non-large) on the mark stack.
+  DCHECK(!region_space_->IsInNewlyAllocatedRegion(to_ref)) << to_ref;
+  if (rtype == space::RegionSpace::RegionType::kRegionTypeUnevacFromSpace) {
     // Mark the bitmap only in the GC thread here so that we don't need a CAS.
-    if (!kUseBakerReadBarrier || !region_space_bitmap_->Set(to_ref)) {
+    if (!kUseBakerReadBarrier ||
+        !region_space_bitmap_->Set(to_ref)) {
       // It may be already marked if we accidentally pushed the same object twice due to the racy
       // bitmap read in MarkUnevacFromSpaceRegion.
-      Scan(to_ref);
-      // Only add to the live bytes if the object was not already marked.
+      if (kEnableGenerationalConcurrentCopyingCollection && young_gen_) {
+        CHECK(region_space_->IsLargeObject(to_ref));
+        region_space_->ZeroLiveBytesForLargeObject(to_ref);
+        Scan<true>(to_ref);
+      } else {
+        Scan<false>(to_ref);
+      }
+      // Only add to the live bytes if the object was not already marked and we are not the young
+      // GC.
       add_to_live_bytes = true;
     }
   } else {
-    Scan(to_ref);
+    if (kEnableGenerationalConcurrentCopyingCollection) {
+      if (rtype == space::RegionSpace::RegionType::kRegionTypeToSpace) {
+        // Copied to to-space, set the bit so that the next GC can scan objects.
+        region_space_bitmap_->Set(to_ref);
+      }
+    }
+    if (kEnableGenerationalConcurrentCopyingCollection && young_gen_) {
+      Scan<true>(to_ref);
+    } else {
+      Scan<false>(to_ref);
+    }
   }
   if (kUseBakerReadBarrier) {
     DCHECK(to_ref->GetReadBarrierState() == ReadBarrier::GrayState())
@@ -1542,18 +1682,18 @@
                 (referent = to_ref->AsReference()->GetReferent<kWithoutReadBarrier>()) != nullptr &&
                 !IsInToSpace(referent)))) {
     // Leave this reference gray in the queue so that GetReferent() will trigger a read barrier. We
-    // will change it to white later in ReferenceQueue::DequeuePendingReference().
+    // will change it to non-gray later in ReferenceQueue::DisableReadBarrierForReference.
     DCHECK(to_ref->AsReference()->GetPendingNext() != nullptr)
         << "Left unenqueued ref gray " << to_ref;
   } else {
-    // We may occasionally leave a reference white in the queue if its referent happens to be
+    // We may occasionally leave a reference non-gray in the queue if its referent happens to be
     // concurrently marked after the Scan() call above has enqueued the Reference, in which case the
-    // above IsInToSpace() evaluates to true and we change the color from gray to white here in this
-    // else block.
+    // above IsInToSpace() evaluates to true and we change the color from gray to non-gray here in
+    // this else block.
     if (kUseBakerReadBarrier) {
       bool success = to_ref->AtomicSetReadBarrierState<std::memory_order_release>(
           ReadBarrier::GrayState(),
-          ReadBarrier::WhiteState());
+          ReadBarrier::NonGrayState());
       DCHECK(success) << "Must succeed as we won the race.";
     }
   }
@@ -1672,29 +1812,126 @@
 }
 
 void ConcurrentCopying::Sweep(bool swap_bitmaps) {
-  {
-    TimingLogger::ScopedTiming t("MarkStackAsLive", GetTimings());
-    accounting::ObjectStack* live_stack = heap_->GetLiveStack();
-    if (kEnableFromSpaceAccountingCheck) {
-      CHECK_GE(live_stack_freeze_size_, live_stack->Size());
+  if (kEnableGenerationalConcurrentCopyingCollection && young_gen_) {
+    // Only sweep objects on the live stack.
+    SweepArray(heap_->GetLiveStack(), /* swap_bitmaps */ false);
+  } else {
+    {
+      TimingLogger::ScopedTiming t("MarkStackAsLive", GetTimings());
+      accounting::ObjectStack* live_stack = heap_->GetLiveStack();
+      if (kEnableFromSpaceAccountingCheck) {
+        // Ensure that nobody inserted items in the live stack after we swapped the stacks.
+        CHECK_GE(live_stack_freeze_size_, live_stack->Size());
+      }
+      heap_->MarkAllocStackAsLive(live_stack);
+      live_stack->Reset();
     }
-    heap_->MarkAllocStackAsLive(live_stack);
-    live_stack->Reset();
+    CheckEmptyMarkStack();
+    TimingLogger::ScopedTiming split("Sweep", GetTimings());
+    for (const auto& space : GetHeap()->GetContinuousSpaces()) {
+      if (space->IsContinuousMemMapAllocSpace()) {
+        space::ContinuousMemMapAllocSpace* alloc_space = space->AsContinuousMemMapAllocSpace();
+        if (space == region_space_ || immune_spaces_.ContainsSpace(space)) {
+          continue;
+        }
+        TimingLogger::ScopedTiming split2(
+            alloc_space->IsZygoteSpace() ? "SweepZygoteSpace" : "SweepAllocSpace", GetTimings());
+        RecordFree(alloc_space->Sweep(swap_bitmaps));
+      }
+    }
+    SweepLargeObjects(swap_bitmaps);
   }
+}
+
+// Copied and adapted from MarkSweep::SweepArray.
+void ConcurrentCopying::SweepArray(accounting::ObjectStack* allocations, bool swap_bitmaps) {
+  // This method is only used when Generational CC collection is enabled.
+  DCHECK(kEnableGenerationalConcurrentCopyingCollection);
   CheckEmptyMarkStack();
-  TimingLogger::ScopedTiming split("Sweep", GetTimings());
-  for (const auto& space : GetHeap()->GetContinuousSpaces()) {
-    if (space->IsContinuousMemMapAllocSpace()) {
-      space::ContinuousMemMapAllocSpace* alloc_space = space->AsContinuousMemMapAllocSpace();
-      if (space == region_space_ || immune_spaces_.ContainsSpace(space)) {
+  TimingLogger::ScopedTiming t("SweepArray", GetTimings());
+  Thread* self = Thread::Current();
+  mirror::Object** chunk_free_buffer = reinterpret_cast<mirror::Object**>(
+      sweep_array_free_buffer_mem_map_.BaseBegin());
+  size_t chunk_free_pos = 0;
+  ObjectBytePair freed;
+  ObjectBytePair freed_los;
+  // How many objects are left in the array, modified after each space is swept.
+  StackReference<mirror::Object>* objects = allocations->Begin();
+  size_t count = allocations->Size();
+  // Start by sweeping the continuous spaces.
+  for (space::ContinuousSpace* space : heap_->GetContinuousSpaces()) {
+    if (!space->IsAllocSpace() ||
+        space == region_space_ ||
+        immune_spaces_.ContainsSpace(space) ||
+        space->GetLiveBitmap() == nullptr) {
+      continue;
+    }
+    space::AllocSpace* alloc_space = space->AsAllocSpace();
+    accounting::ContinuousSpaceBitmap* live_bitmap = space->GetLiveBitmap();
+    accounting::ContinuousSpaceBitmap* mark_bitmap = space->GetMarkBitmap();
+    if (swap_bitmaps) {
+      std::swap(live_bitmap, mark_bitmap);
+    }
+    StackReference<mirror::Object>* out = objects;
+    for (size_t i = 0; i < count; ++i) {
+      mirror::Object* const obj = objects[i].AsMirrorPtr();
+      if (kUseThreadLocalAllocationStack && obj == nullptr) {
         continue;
       }
-      TimingLogger::ScopedTiming split2(
-          alloc_space->IsZygoteSpace() ? "SweepZygoteSpace" : "SweepAllocSpace", GetTimings());
-      RecordFree(alloc_space->Sweep(swap_bitmaps));
+      if (space->HasAddress(obj)) {
+        // This object is in the space, remove it from the array and add it to the sweep buffer
+        // if needed.
+        if (!mark_bitmap->Test(obj)) {
+          if (chunk_free_pos >= kSweepArrayChunkFreeSize) {
+            TimingLogger::ScopedTiming t2("FreeList", GetTimings());
+            freed.objects += chunk_free_pos;
+            freed.bytes += alloc_space->FreeList(self, chunk_free_pos, chunk_free_buffer);
+            chunk_free_pos = 0;
+          }
+          chunk_free_buffer[chunk_free_pos++] = obj;
+        }
+      } else {
+        (out++)->Assign(obj);
+      }
+    }
+    if (chunk_free_pos > 0) {
+      TimingLogger::ScopedTiming t2("FreeList", GetTimings());
+      freed.objects += chunk_free_pos;
+      freed.bytes += alloc_space->FreeList(self, chunk_free_pos, chunk_free_buffer);
+      chunk_free_pos = 0;
+    }
+    // All of the references which space contained are no longer in the allocation stack, update
+    // the count.
+    count = out - objects;
+  }
+  // Handle the large object space.
+  space::LargeObjectSpace* large_object_space = GetHeap()->GetLargeObjectsSpace();
+  if (large_object_space != nullptr) {
+    accounting::LargeObjectBitmap* large_live_objects = large_object_space->GetLiveBitmap();
+    accounting::LargeObjectBitmap* large_mark_objects = large_object_space->GetMarkBitmap();
+    if (swap_bitmaps) {
+      std::swap(large_live_objects, large_mark_objects);
+    }
+    for (size_t i = 0; i < count; ++i) {
+      mirror::Object* const obj = objects[i].AsMirrorPtr();
+      // Handle large objects.
+      if (kUseThreadLocalAllocationStack && obj == nullptr) {
+        continue;
+      }
+      if (!large_mark_objects->Test(obj)) {
+        ++freed_los.objects;
+        freed_los.bytes += large_object_space->Free(self, obj);
+      }
     }
   }
-  SweepLargeObjects(swap_bitmaps);
+  {
+    TimingLogger::ScopedTiming t2("RecordFree", GetTimings());
+    RecordFree(freed);
+    RecordFreeLOS(freed_los);
+    t2.NewTiming("ResetStack");
+    allocations->Reset();
+  }
+  sweep_array_free_buffer_mem_map_.MadviseDontNeedAndZero();
 }
 
 void ConcurrentCopying::MarkZygoteLargeObjects() {
@@ -1804,7 +2041,7 @@
 
   {
     WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
-    Sweep(false);
+    Sweep(/* swap_bitmaps */ false);
     SwapBitmaps();
     heap_->UnBindBitmaps();
 
@@ -1871,20 +2108,45 @@
       } else if (type == RegionType::kRegionTypeUnevacFromSpace) {
         if (!IsMarkedInUnevacFromSpace(ref)) {
           LOG(FATAL_WITHOUT_ABORT) << "Found unmarked reference in unevac from-space:";
+          // Remove memory protection from the region space and log debugging information.
+          region_space_->Unprotect();
           LOG(FATAL_WITHOUT_ABORT) << DumpHeapReference(obj, offset, ref);
+          Thread::Current()->DumpJavaStack(LOG_STREAM(FATAL_WITHOUT_ABORT));
         }
         CHECK(IsMarkedInUnevacFromSpace(ref)) << ref;
      } else {
         // Not OK: either a from-space ref or a reference in an unused region.
-        // Do extra logging.
         if (type == RegionType::kRegionTypeFromSpace) {
           LOG(FATAL_WITHOUT_ABORT) << "Found from-space reference:";
         } else {
           LOG(FATAL_WITHOUT_ABORT) << "Found reference in region with type " << type << ":";
         }
+        // Remove memory protection from the region space and log debugging information.
+        region_space_->Unprotect();
         LOG(FATAL_WITHOUT_ABORT) << DumpHeapReference(obj, offset, ref);
         if (obj != nullptr) {
           LogFromSpaceRefHolder(obj, offset);
+          LOG(FATAL_WITHOUT_ABORT) << "UNEVAC " << region_space_->IsInUnevacFromSpace(obj) << " "
+                                   << obj << " " << obj->GetMarkBit();
+          if (region_space_->HasAddress(obj)) {
+            region_space_->DumpRegionForObject(LOG_STREAM(FATAL_WITHOUT_ABORT), obj);
+          }
+          LOG(FATAL_WITHOUT_ABORT) << "CARD " << static_cast<size_t>(
+              *Runtime::Current()->GetHeap()->GetCardTable()->CardFromAddr(
+                  reinterpret_cast<uint8_t*>(obj)));
+          if (region_space_->HasAddress(obj)) {
+            LOG(FATAL_WITHOUT_ABORT) << "BITMAP " << region_space_bitmap_->Test(obj);
+          } else {
+            accounting::ContinuousSpaceBitmap* mark_bitmap =
+                heap_mark_bitmap_->GetContinuousSpaceBitmap(obj);
+            if (mark_bitmap != nullptr) {
+              LOG(FATAL_WITHOUT_ABORT) << "BITMAP " << mark_bitmap->Test(obj);
+            } else {
+              accounting::LargeObjectBitmap* los_bitmap =
+                  heap_mark_bitmap_->GetLargeObjectBitmap(obj);
+              LOG(FATAL_WITHOUT_ABORT) << "BITMAP " << los_bitmap->Test(obj);
+            }
+          }
         }
         ref->GetLockWord(false).Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
         LOG(FATAL_WITHOUT_ABORT) << "Non-free regions:";
@@ -1949,17 +2211,20 @@
       } else if (type == RegionType::kRegionTypeUnevacFromSpace) {
         if (!IsMarkedInUnevacFromSpace(ref)) {
           LOG(FATAL_WITHOUT_ABORT) << "Found unmarked reference in unevac from-space:";
+          // Remove memory protection from the region space and log debugging information.
+          region_space_->Unprotect();
           LOG(FATAL_WITHOUT_ABORT) << DumpGcRoot(ref);
         }
         CHECK(IsMarkedInUnevacFromSpace(ref)) << ref;
       } else {
         // Not OK: either a from-space ref or a reference in an unused region.
-        // Do extra logging.
         if (type == RegionType::kRegionTypeFromSpace) {
           LOG(FATAL_WITHOUT_ABORT) << "Found from-space reference:";
         } else {
           LOG(FATAL_WITHOUT_ABORT) << "Found reference in region with type " << type << ":";
         }
+        // Remove memory protection from the region space and log debugging information.
+        region_space_->Unprotect();
         LOG(FATAL_WITHOUT_ABORT) << DumpGcRoot(ref);
         if (gc_root_source == nullptr) {
           // No info.
@@ -2077,15 +2342,19 @@
 }
 
 // Used to scan ref fields of an object.
+template <bool kNoUnEvac>
 class ConcurrentCopying::RefFieldsVisitor {
  public:
   explicit RefFieldsVisitor(ConcurrentCopying* collector, Thread* const thread)
-      : collector_(collector), thread_(thread) {}
+      : collector_(collector), thread_(thread) {
+    // Cannot have `kNoUnEvac` when Generational CC collection is disabled.
+    DCHECK(kEnableGenerationalConcurrentCopyingCollection || !kNoUnEvac);
+  }
 
   void operator()(mirror::Object* obj, MemberOffset offset, bool /* is_static */)
       const ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES_SHARED(Locks::heap_bitmap_lock_) {
-    collector_->Process(obj, offset);
+    collector_->Process<kNoUnEvac>(obj, offset);
   }
 
   void operator()(ObjPtr<mirror::Class> klass, ObjPtr<mirror::Reference> ref) const
@@ -2113,7 +2382,10 @@
   Thread* const thread_;
 };
 
+template <bool kNoUnEvac>
 inline void ConcurrentCopying::Scan(mirror::Object* to_ref) {
+  // Cannot have `kNoUnEvac` when Generational CC collection is disabled.
+  DCHECK(kEnableGenerationalConcurrentCopyingCollection || !kNoUnEvac);
   if (kDisallowReadBarrierDuringScan && !Runtime::Current()->IsActiveTransaction()) {
     // Avoid all read barriers during visit references to help performance.
     // Don't do this in transaction mode because we may read the old value of an field which may
@@ -2122,7 +2394,7 @@
   }
   DCHECK(!region_space_->IsInFromSpace(to_ref));
   DCHECK_EQ(Thread::Current(), thread_running_gc_);
-  RefFieldsVisitor visitor(this, thread_running_gc_);
+  RefFieldsVisitor<kNoUnEvac> visitor(this, thread_running_gc_);
   // Disable the read barrier for a performance reason.
   to_ref->VisitReferences</*kVisitNativeRoots*/true, kDefaultVerifyFlags, kWithoutReadBarrier>(
       visitor, visitor);
@@ -2131,11 +2403,14 @@
   }
 }
 
+template <bool kNoUnEvac>
 inline void ConcurrentCopying::Process(mirror::Object* obj, MemberOffset offset) {
+  // Cannot have `kNoUnEvac` when Generational CC collection is disabled.
+  DCHECK(kEnableGenerationalConcurrentCopyingCollection || !kNoUnEvac);
   DCHECK_EQ(Thread::Current(), thread_running_gc_);
   mirror::Object* ref = obj->GetFieldObject<
       mirror::Object, kVerifyNone, kWithoutReadBarrier, false>(offset);
-  mirror::Object* to_ref = Mark</*kGrayImmuneObject*/false, /*kFromGCThread*/true>(
+  mirror::Object* to_ref = Mark</*kGrayImmuneObject*/false, kNoUnEvac, /*kFromGCThread*/true>(
       thread_running_gc_,
       ref,
       /*holder*/ obj,
@@ -2359,6 +2634,8 @@
   // from a previous GC that is either inside or outside the allocated region.
   mirror::Class* klass = from_ref->GetClass<kVerifyNone, kWithoutReadBarrier>();
   if (UNLIKELY(klass == nullptr)) {
+    // Remove memory protection from the region space and log debugging information.
+    region_space_->Unprotect();
     heap_->GetVerification()->LogHeapCorruption(holder, offset, from_ref, /* fatal */ true);
   }
   // There must not be a read barrier to avoid nested RB that might violate the to-space invariant.
@@ -2413,7 +2690,8 @@
       accounting::ContinuousSpaceBitmap* mark_bitmap =
           heap_mark_bitmap_->GetContinuousSpaceBitmap(to_ref);
       CHECK(mark_bitmap != nullptr);
-      CHECK(!mark_bitmap->AtomicTestAndSet(to_ref));
+      bool previously_marked_in_bitmap = mark_bitmap->AtomicTestAndSet(to_ref);
+      CHECK(!previously_marked_in_bitmap);
     }
   }
   DCHECK(to_ref != nullptr);
@@ -2610,6 +2888,28 @@
   accounting::LargeObjectBitmap* los_bitmap =
       heap_mark_bitmap_->GetLargeObjectBitmap(ref);
   bool is_los = mark_bitmap == nullptr;
+  if (kEnableGenerationalConcurrentCopyingCollection && young_gen_) {
+    // The sticky-bit CC collector is only compatible with Baker-style read barriers.
+    DCHECK(kUseBakerReadBarrier);
+    // Not done scanning, use AtomicSetReadBarrierPointer.
+    if (!done_scanning_.load(std::memory_order_acquire)) {
+      // Since the mark bitmap is still filled in from last GC, we can not use that or else the
+      // mutator may see references to the from space. Instead, use the Baker pointer itself as
+      // the mark bit.
+      if (ref->AtomicSetReadBarrierState(ReadBarrier::NonGrayState(), ReadBarrier::GrayState())) {
+        // TODO: We don't actually need to scan this object later, we just need to clear the gray
+        // bit.
+        // Also make sure the object is marked.
+        if (is_los) {
+          los_bitmap->AtomicTestAndSet(ref);
+        } else {
+          mark_bitmap->AtomicTestAndSet(ref);
+        }
+        PushOntoMarkStack(self, ref);
+      }
+      return ref;
+    }
+  }
   if (!is_los && mark_bitmap->Test(ref)) {
     // Already marked.
   } else if (is_los && los_bitmap->Test(ref)) {
@@ -2617,7 +2917,7 @@
   } else {
     // Not marked.
     if (IsOnAllocStack(ref)) {
-      // If it's on the allocation stack, it's considered marked. Keep it white.
+      // If it's on the allocation stack, it's considered marked. Keep it white (non-gray).
       // Objects on the allocation stack need not be marked.
       if (!is_los) {
         DCHECK(!mark_bitmap->Test(ref));
@@ -2625,7 +2925,7 @@
         DCHECK(!los_bitmap->Test(ref));
       }
       if (kUseBakerReadBarrier) {
-        DCHECK_EQ(ref->GetReadBarrierState(), ReadBarrier::WhiteState());
+        DCHECK_EQ(ref->GetReadBarrierState(), ReadBarrier::NonGrayState());
       }
     } else {
       // For the baker-style RB, we need to handle 'false-gray' cases. See the
@@ -2638,26 +2938,31 @@
         }
       }
       if (is_los && !IsAligned<kPageSize>(ref)) {
-        // Ref is a large object that is not aligned, it must be heap corruption. Dump data before
-        // AtomicSetReadBarrierState since it will fault if the address is not valid.
+        // Ref is a large object that is not aligned, it must be heap
+        // corruption. Remove memory protection and dump data before
+        // AtomicSetReadBarrierState since it will fault if the address is not
+        // valid.
+        region_space_->Unprotect();
         heap_->GetVerification()->LogHeapCorruption(holder, offset, ref, /* fatal */ true);
       }
-      // Not marked or on the allocation stack. Try to mark it.
+      // Not marked nor on the allocation stack. Try to mark it.
       // This may or may not succeed, which is ok.
       bool cas_success = false;
       if (kUseBakerReadBarrier) {
-        cas_success = ref->AtomicSetReadBarrierState(ReadBarrier::WhiteState(),
+        cas_success = ref->AtomicSetReadBarrierState(ReadBarrier::NonGrayState(),
                                                      ReadBarrier::GrayState());
       }
       if (!is_los && mark_bitmap->AtomicTestAndSet(ref)) {
         // Already marked.
-        if (kUseBakerReadBarrier && cas_success &&
+        if (kUseBakerReadBarrier &&
+            cas_success &&
             ref->GetReadBarrierState() == ReadBarrier::GrayState()) {
           PushOntoFalseGrayStack(self, ref);
         }
       } else if (is_los && los_bitmap->AtomicTestAndSet(ref)) {
         // Already marked in LOS.
-        if (kUseBakerReadBarrier && cas_success &&
+        if (kUseBakerReadBarrier &&
+            cas_success &&
             ref->GetReadBarrierState() == ReadBarrier::GrayState()) {
           PushOntoFalseGrayStack(self, ref);
         }
@@ -2681,7 +2986,7 @@
   }
   // kVerifyNoMissingCardMarks relies on the region space cards not being cleared to avoid false
   // positives.
-  if (!kVerifyNoMissingCardMarks) {
+  if (!kEnableGenerationalConcurrentCopyingCollection && !kVerifyNoMissingCardMarks) {
     TimingLogger::ScopedTiming split("ClearRegionSpaceCards", GetTimings());
     // We do not currently use the region space cards at all, madvise them away to save ram.
     heap_->GetCardTable()->ClearCardRange(region_space_->Begin(), region_space_->Limit());
@@ -2789,7 +3094,8 @@
   }
   ScopedTrace tr(__FUNCTION__);
   const uint64_t start_time = measure_read_barrier_slow_path_ ? NanoTime() : 0u;
-  mirror::Object* ret = Mark(self, from_ref);
+  mirror::Object* ret =
+      Mark</*kGrayImmuneObject*/true, /*kNoUnEvac*/false, /*kFromGCThread*/false>(self, from_ref);
   if (measure_read_barrier_slow_path_) {
     rb_slow_path_ns_.fetch_add(NanoTime() - start_time, std::memory_order_relaxed);
   }
diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h
index 448525d..0ebe6f0 100644
--- a/runtime/gc/collector/concurrent_copying.h
+++ b/runtime/gc/collector/concurrent_copying.h
@@ -66,6 +66,7 @@
   static constexpr bool kGrayDirtyImmuneObjects = true;
 
   explicit ConcurrentCopying(Heap* heap,
+                             bool young_gen,
                              const std::string& name_prefix = "",
                              bool measure_read_barrier_slow_path = false);
   ~ConcurrentCopying();
@@ -87,7 +88,9 @@
   void BindBitmaps() REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::heap_bitmap_lock_);
   virtual GcType GetGcType() const OVERRIDE {
-    return kGcTypePartial;
+    return (kEnableGenerationalConcurrentCopyingCollection && young_gen_)
+        ? kGcTypeSticky
+        : kGcTypePartial;
   }
   virtual CollectorType GetCollectorType() const OVERRIDE {
     return kCollectorTypeCC;
@@ -110,8 +113,8 @@
     DCHECK(ref != nullptr);
     return IsMarked(ref) == ref;
   }
-  template<bool kGrayImmuneObject = true, bool kFromGCThread = false>
   // Mark object `from_ref`, copying it to the to-space if needed.
+  template<bool kGrayImmuneObject = true, bool kNoUnEvac = false, bool kFromGCThread = false>
   ALWAYS_INLINE mirror::Object* Mark(Thread* const self,
                                      mirror::Object* from_ref,
                                      mirror::Object* holder = nullptr,
@@ -155,9 +158,11 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_, !immune_gray_stack_lock_);
   // Scan the reference fields of object `to_ref`.
+  template <bool kNoUnEvac>
   void Scan(mirror::Object* to_ref) REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!mark_stack_lock_);
   // Process a field.
+  template <bool kNoUnEvac>
   void Process(mirror::Object* obj, MemberOffset offset)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!mark_stack_lock_ , !skipped_blocks_lock_, !immune_gray_stack_lock_);
@@ -217,8 +222,14 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   void SweepSystemWeaks(Thread* self)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::heap_bitmap_lock_);
+  // Sweep unmarked objects to complete the garbage collection. Full GCs sweep
+  // all allocation spaces (except the region space). Sticky-bit GCs just sweep
+  // a subset of the heap.
   void Sweep(bool swap_bitmaps)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_, !mark_stack_lock_);
+  // Sweep only pointers within an array.
+  void SweepArray(accounting::ObjectStack* allocation_stack_, bool swap_bitmaps)
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_, !mark_stack_lock_);
   void SweepLargeObjects(bool swap_bitmaps)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
   void MarkZygoteLargeObjects()
@@ -347,6 +358,12 @@
   Atomic<uint64_t> cumulative_bytes_moved_;
   Atomic<uint64_t> cumulative_objects_moved_;
 
+  // Generational "sticky", only trace through dirty objects in region space.
+  const bool young_gen_;
+  // If true, the GC thread is done scanning marked objects on dirty and aged
+  // card (see ConcurrentCopying::MarkingPhase).
+  Atomic<bool> done_scanning_;
+
   // The skipped blocks are memory blocks/chucks that were copies of
   // objects that were unused due to lost races (cas failures) at
   // object copy/forward pointer install. They are reused.
@@ -381,6 +398,11 @@
   // ObjPtr since the GC may transition to suspended and runnable between phases.
   mirror::Class* java_lang_Object_;
 
+  // Sweep array free buffer, used to sweep the spaces based on an array more
+  // efficiently, by recording dead objects to be freed in batches (see
+  // ConcurrentCopying::SweepArray).
+  MemMap sweep_array_free_buffer_mem_map_;
+
   class ActivateReadBarrierEntrypointsCallback;
   class ActivateReadBarrierEntrypointsCheckpoint;
   class AssertToSpaceInvariantFieldVisitor;
@@ -394,7 +416,7 @@
   template <bool kConcurrent> class GrayImmuneObjectVisitor;
   class ImmuneSpaceScanObjVisitor;
   class LostCopyVisitor;
-  class RefFieldsVisitor;
+  template <bool kNoUnEvac> class RefFieldsVisitor;
   class RevokeThreadLocalMarkStackCheckpoint;
   class ScopedGcGraysImmuneObjects;
   class ThreadFlipVisitor;
diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc
index 9767807..145bd02 100644
--- a/runtime/gc/collector/immune_spaces_test.cc
+++ b/runtime/gc/collector/immune_spaces_test.cc
@@ -40,22 +40,22 @@
 
 class DummyImageSpace : public space::ImageSpace {
  public:
-  DummyImageSpace(MemMap* map,
+  DummyImageSpace(MemMap&& map,
                   accounting::ContinuousSpaceBitmap* live_bitmap,
                   std::unique_ptr<DummyOatFile>&& oat_file,
-                  std::unique_ptr<MemMap>&& oat_map)
+                  MemMap&& oat_map)
       : ImageSpace("DummyImageSpace",
                    /*image_location*/"",
-                   map,
+                   std::move(map),
                    live_bitmap,
-                   map->End()),
+                   map.End()),
         oat_map_(std::move(oat_map)) {
     oat_file_ = std::move(oat_file);
     oat_file_non_owned_ = oat_file_.get();
   }
 
  private:
-  std::unique_ptr<MemMap> oat_map_;
+  MemMap oat_map_;
 };
 
 class ImmuneSpacesTest : public CommonRuntimeTest {
@@ -83,39 +83,37 @@
                                     uint8_t* oat_begin,
                                     size_t oat_size) {
     std::string error_str;
-    std::unique_ptr<MemMap> map(MemMap::MapAnonymous("DummyImageSpace",
-                                                     image_begin,
-                                                     image_size,
-                                                     PROT_READ | PROT_WRITE,
-                                                     /*low_4gb*/true,
-                                                     /*reuse*/false,
-                                                     &error_str));
-    if (map == nullptr) {
+    MemMap map = MemMap::MapAnonymous("DummyImageSpace",
+                                      image_begin,
+                                      image_size,
+                                      PROT_READ | PROT_WRITE,
+                                      /*low_4gb*/true,
+                                      &error_str);
+    if (!map.IsValid()) {
       LOG(ERROR) << error_str;
       return nullptr;
     }
     CHECK(!live_bitmaps_.empty());
     std::unique_ptr<accounting::ContinuousSpaceBitmap> live_bitmap(std::move(live_bitmaps_.back()));
     live_bitmaps_.pop_back();
-    std::unique_ptr<MemMap> oat_map(MemMap::MapAnonymous("OatMap",
-                                                         oat_begin,
-                                                         oat_size,
-                                                         PROT_READ | PROT_WRITE,
-                                                         /*low_4gb*/true,
-                                                         /*reuse*/false,
-                                                         &error_str));
-    if (oat_map == nullptr) {
+    MemMap oat_map = MemMap::MapAnonymous("OatMap",
+                                          oat_begin,
+                                          oat_size,
+                                          PROT_READ | PROT_WRITE,
+                                          /*low_4gb*/true,
+                                          &error_str);
+    if (!oat_map.IsValid()) {
       LOG(ERROR) << error_str;
       return nullptr;
     }
-    std::unique_ptr<DummyOatFile> oat_file(new DummyOatFile(oat_map->Begin(), oat_map->End()));
+    std::unique_ptr<DummyOatFile> oat_file(new DummyOatFile(oat_map.Begin(), oat_map.End()));
     // Create image header.
     ImageSection sections[ImageHeader::kSectionCount];
-    new (map->Begin()) ImageHeader(
-        /*image_begin*/PointerToLowMemUInt32(map->Begin()),
-        /*image_size*/map->Size(),
+    new (map.Begin()) ImageHeader(
+        /*image_begin*/PointerToLowMemUInt32(map.Begin()),
+        /*image_size*/map.Size(),
         sections,
-        /*image_roots*/PointerToLowMemUInt32(map->Begin()) + 1,
+        /*image_roots*/PointerToLowMemUInt32(map.Begin()) + 1,
         /*oat_checksum*/0u,
         // The oat file data in the header is always right after the image space.
         /*oat_file_begin*/PointerToLowMemUInt32(oat_begin),
@@ -131,7 +129,7 @@
         /*is_pic*/false,
         ImageHeader::kStorageModeUncompressed,
         /*storage_size*/0u);
-    return new DummyImageSpace(map.release(),
+    return new DummyImageSpace(std::move(map),
                                live_bitmap.release(),
                                std::move(oat_file),
                                std::move(oat_map));
@@ -141,18 +139,17 @@
   // returned address.
   static uint8_t* GetContinuousMemoryRegion(size_t size) {
     std::string error_str;
-    std::unique_ptr<MemMap> map(MemMap::MapAnonymous("reserve",
-                                                     nullptr,
-                                                     size,
-                                                     PROT_READ | PROT_WRITE,
-                                                     /*low_4gb*/true,
-                                                     /*reuse*/false,
-                                                     &error_str));
-    if (map == nullptr) {
+    MemMap map = MemMap::MapAnonymous("reserve",
+                                      /* addr */ nullptr,
+                                      size,
+                                      PROT_READ | PROT_WRITE,
+                                      /*low_4gb*/ true,
+                                      &error_str);
+    if (!map.IsValid()) {
       LOG(ERROR) << "Failed to allocate memory region " << error_str;
       return nullptr;
     }
-    return map->Begin();
+    return map.Begin();
   }
 
  private:
diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc
index 2335964..997d3b6 100644
--- a/runtime/gc/collector/mark_sweep.cc
+++ b/runtime/gc/collector/mark_sweep.cc
@@ -103,12 +103,15 @@
       is_concurrent_(is_concurrent),
       live_stack_freeze_size_(0) {
   std::string error_msg;
-  MemMap* mem_map = MemMap::MapAnonymous(
-      "mark sweep sweep array free buffer", nullptr,
+  sweep_array_free_buffer_mem_map_ = MemMap::MapAnonymous(
+      "mark sweep sweep array free buffer",
+      /* addr */ nullptr,
       RoundUp(kSweepArrayChunkFreeSize * sizeof(mirror::Object*), kPageSize),
-      PROT_READ | PROT_WRITE, false, false, &error_msg);
-  CHECK(mem_map != nullptr) << "Couldn't allocate sweep array free buffer: " << error_msg;
-  sweep_array_free_buffer_mem_map_.reset(mem_map);
+      PROT_READ | PROT_WRITE,
+      /* low_4gb */ false,
+      &error_msg);
+  CHECK(sweep_array_free_buffer_mem_map_.IsValid())
+      << "Couldn't allocate sweep array free buffer: " << error_msg;
 }
 
 void MarkSweep::InitializePhase() {
@@ -1207,7 +1210,7 @@
   TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
   Thread* self = Thread::Current();
   mirror::Object** chunk_free_buffer = reinterpret_cast<mirror::Object**>(
-      sweep_array_free_buffer_mem_map_->BaseBegin());
+      sweep_array_free_buffer_mem_map_.BaseBegin());
   size_t chunk_free_pos = 0;
   ObjectBytePair freed;
   ObjectBytePair freed_los;
@@ -1300,7 +1303,7 @@
     t2.NewTiming("ResetStack");
     allocations->Reset();
   }
-  sweep_array_free_buffer_mem_map_->MadviseDontNeedAndZero();
+  sweep_array_free_buffer_mem_map_.MadviseDontNeedAndZero();
 }
 
 void MarkSweep::Sweep(bool swap_bitmaps) {
diff --git a/runtime/gc/collector/mark_sweep.h b/runtime/gc/collector/mark_sweep.h
index 5e0fe06..af2bb97 100644
--- a/runtime/gc/collector/mark_sweep.h
+++ b/runtime/gc/collector/mark_sweep.h
@@ -351,7 +351,10 @@
   // Verification.
   size_t live_stack_freeze_size_;
 
-  std::unique_ptr<MemMap> sweep_array_free_buffer_mem_map_;
+  // Sweep array free buffer, used to sweep the spaces based on an array more
+  // efficiently, by recording dead objects to be freed in batches (see
+  // MarkSweep::SweepArray).
+  MemMap sweep_array_free_buffer_mem_map_;
 
  private:
   class CardScanTask;
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 58becb1..7913354 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -102,7 +102,8 @@
 // Sticky GC throughput adjustment, divided by 4. Increasing this causes sticky GC to occur more
 // relative to partial/full GC. This may be desirable since sticky GCs interfere less with mutator
 // threads (lower pauses, use less memory bandwidth).
-static constexpr double kStickyGcThroughputAdjustment = 1.0;
+static constexpr double kStickyGcThroughputAdjustment =
+    kEnableGenerationalConcurrentCopyingCollection ? 0.5 : 1.0;
 // Whether or not we compact the zygote in PreZygoteFork.
 static constexpr bool kCompactZygote = kMovingCollector;
 // How many reserve entries are at the end of the allocation stack, these are only needed if the
@@ -260,6 +261,8 @@
       verify_object_mode_(kVerifyObjectModeDisabled),
       disable_moving_gc_count_(0),
       semi_space_collector_(nullptr),
+      active_concurrent_copying_collector_(nullptr),
+      young_concurrent_copying_collector_(nullptr),
       concurrent_copying_collector_(nullptr),
       is_running_on_memory_tool_(Runtime::Current()->IsRunningOnMemoryTool()),
       use_tlab_(use_tlab),
@@ -318,12 +321,14 @@
   }
 
   // Load image space(s).
+  std::vector<std::unique_ptr<space::ImageSpace>> boot_image_spaces;
   if (space::ImageSpace::LoadBootImage(image_file_name,
                                        image_instruction_set,
-                                       &boot_image_spaces_,
+                                       &boot_image_spaces,
                                        &requested_alloc_space_begin)) {
-    for (auto space : boot_image_spaces_) {
-      AddSpace(space);
+    for (std::unique_ptr<space::ImageSpace>& space : boot_image_spaces) {
+      boot_image_spaces_.push_back(space.get());
+      AddSpace(space.release());
     }
   }
 
@@ -358,8 +363,8 @@
   if (foreground_collector_type_ == kCollectorTypeGSS) {
     separate_non_moving_space = false;
   }
-  std::unique_ptr<MemMap> main_mem_map_1;
-  std::unique_ptr<MemMap> main_mem_map_2;
+  MemMap main_mem_map_1;
+  MemMap main_mem_map_2;
 
   // Gross hack to make dex2oat deterministic.
   if (foreground_collector_type_ == kCollectorTypeMS &&
@@ -374,7 +379,7 @@
     request_begin += non_moving_space_capacity;
   }
   std::string error_str;
-  std::unique_ptr<MemMap> non_moving_space_mem_map;
+  MemMap non_moving_space_mem_map;
   if (separate_non_moving_space) {
     ScopedTrace trace2("Create separate non moving space");
     // If we are the zygote, the non moving space becomes the zygote space when we run
@@ -383,11 +388,9 @@
     const char* space_name = is_zygote ? kZygoteSpaceName : kNonMovingSpaceName;
     // Reserve the non moving mem map before the other two since it needs to be at a specific
     // address.
-    non_moving_space_mem_map.reset(MapAnonymousPreferredAddress(space_name,
-                                                                requested_alloc_space_begin,
-                                                                non_moving_space_capacity,
-                                                                &error_str));
-    CHECK(non_moving_space_mem_map != nullptr) << error_str;
+    non_moving_space_mem_map = MapAnonymousPreferredAddress(
+        space_name, requested_alloc_space_begin, non_moving_space_capacity, &error_str);
+    CHECK(non_moving_space_mem_map.IsValid()) << error_str;
     // Try to reserve virtual memory at a lower address if we have a separate non moving space.
     request_begin = kPreferredAllocSpaceBegin + non_moving_space_capacity;
   }
@@ -395,27 +398,28 @@
   if (foreground_collector_type_ != kCollectorTypeCC) {
     ScopedTrace trace2("Create main mem map");
     if (separate_non_moving_space || !is_zygote) {
-      main_mem_map_1.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[0],
-                                                        request_begin,
-                                                        capacity_,
-                                                        &error_str));
+      main_mem_map_1 = MapAnonymousPreferredAddress(
+          kMemMapSpaceName[0], request_begin, capacity_, &error_str);
     } else {
       // If no separate non-moving space and we are the zygote, the main space must come right
       // after the image space to avoid a gap. This is required since we want the zygote space to
       // be adjacent to the image space.
-      main_mem_map_1.reset(MemMap::MapAnonymous(kMemMapSpaceName[0], request_begin, capacity_,
-                                                PROT_READ | PROT_WRITE, true, false,
-                                                &error_str));
+      main_mem_map_1 = MemMap::MapAnonymous(kMemMapSpaceName[0],
+                                            request_begin,
+                                            capacity_,
+                                            PROT_READ | PROT_WRITE,
+                                            /* low_4gb */ true,
+                                            &error_str);
     }
-    CHECK(main_mem_map_1.get() != nullptr) << error_str;
+    CHECK(main_mem_map_1.IsValid()) << error_str;
   }
   if (support_homogeneous_space_compaction ||
       background_collector_type_ == kCollectorTypeSS ||
       foreground_collector_type_ == kCollectorTypeSS) {
     ScopedTrace trace2("Create main mem map 2");
-    main_mem_map_2.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[1], main_mem_map_1->End(),
-                                                      capacity_, &error_str));
-    CHECK(main_mem_map_2.get() != nullptr) << error_str;
+    main_mem_map_2 = MapAnonymousPreferredAddress(
+        kMemMapSpaceName[1], main_mem_map_1.End(), capacity_, &error_str);
+    CHECK(main_mem_map_2.IsValid()) << error_str;
   }
 
   // Create the non moving space first so that bitmaps don't take up the address range.
@@ -423,10 +427,14 @@
     ScopedTrace trace2("Add non moving space");
     // Non moving space is always dlmalloc since we currently don't have support for multiple
     // active rosalloc spaces.
-    const size_t size = non_moving_space_mem_map->Size();
-    non_moving_space_ = space::DlMallocSpace::CreateFromMemMap(
-        non_moving_space_mem_map.release(), "zygote / non moving space", kDefaultStartingSize,
-        initial_size, size, size, false);
+    const size_t size = non_moving_space_mem_map.Size();
+    non_moving_space_ = space::DlMallocSpace::CreateFromMemMap(std::move(non_moving_space_mem_map),
+                                                               "zygote / non moving space",
+                                                               kDefaultStartingSize,
+                                                               initial_size,
+                                                               size,
+                                                               size,
+                                                               /* can_move_objects */ false);
     non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
     CHECK(non_moving_space_ != nullptr) << "Failed creating non moving space "
         << requested_alloc_space_begin;
@@ -436,11 +444,10 @@
   if (foreground_collector_type_ == kCollectorTypeCC) {
     CHECK(separate_non_moving_space);
     // Reserve twice the capacity, to allow evacuating every region for explicit GCs.
-    MemMap* region_space_mem_map = space::RegionSpace::CreateMemMap(kRegionSpaceName,
-                                                                    capacity_ * 2,
-                                                                    request_begin);
-    CHECK(region_space_mem_map != nullptr) << "No region space mem map";
-    region_space_ = space::RegionSpace::Create(kRegionSpaceName, region_space_mem_map);
+    MemMap region_space_mem_map =
+        space::RegionSpace::CreateMemMap(kRegionSpaceName, capacity_ * 2, request_begin);
+    CHECK(region_space_mem_map.IsValid()) << "No region space mem map";
+    region_space_ = space::RegionSpace::Create(kRegionSpaceName, std::move(region_space_mem_map));
     AddSpace(region_space_);
   } else if (IsMovingGc(foreground_collector_type_) &&
       foreground_collector_type_ != kCollectorTypeGSS) {
@@ -448,16 +455,16 @@
     // We only to create the bump pointer if the foreground collector is a compacting GC.
     // TODO: Place bump-pointer spaces somewhere to minimize size of card table.
     bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 1",
-                                                                    main_mem_map_1.release());
+                                                                    std::move(main_mem_map_1));
     CHECK(bump_pointer_space_ != nullptr) << "Failed to create bump pointer space";
     AddSpace(bump_pointer_space_);
     temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
-                                                            main_mem_map_2.release());
+                                                            std::move(main_mem_map_2));
     CHECK(temp_space_ != nullptr) << "Failed to create bump pointer space";
     AddSpace(temp_space_);
     CHECK(separate_non_moving_space);
   } else {
-    CreateMainMallocSpace(main_mem_map_1.release(), initial_size, growth_limit_, capacity_);
+    CreateMainMallocSpace(std::move(main_mem_map_1), initial_size, growth_limit_, capacity_);
     CHECK(main_space_ != nullptr);
     AddSpace(main_space_);
     if (!separate_non_moving_space) {
@@ -467,19 +474,23 @@
     if (foreground_collector_type_ == kCollectorTypeGSS) {
       CHECK_EQ(foreground_collector_type_, background_collector_type_);
       // Create bump pointer spaces instead of a backup space.
-      main_mem_map_2.release();
-      bump_pointer_space_ = space::BumpPointerSpace::Create("Bump pointer space 1",
-                                                            kGSSBumpPointerSpaceCapacity, nullptr);
+      main_mem_map_2.Reset();
+      bump_pointer_space_ = space::BumpPointerSpace::Create(
+          "Bump pointer space 1", kGSSBumpPointerSpaceCapacity, /* requested_begin */ nullptr);
       CHECK(bump_pointer_space_ != nullptr);
       AddSpace(bump_pointer_space_);
-      temp_space_ = space::BumpPointerSpace::Create("Bump pointer space 2",
-                                                    kGSSBumpPointerSpaceCapacity, nullptr);
+      temp_space_ = space::BumpPointerSpace::Create(
+          "Bump pointer space 2", kGSSBumpPointerSpaceCapacity, /* requested_begin */ nullptr);
       CHECK(temp_space_ != nullptr);
       AddSpace(temp_space_);
-    } else if (main_mem_map_2.get() != nullptr) {
+    } else if (main_mem_map_2.IsValid()) {
       const char* name = kUseRosAlloc ? kRosAllocSpaceName[1] : kDlMallocSpaceName[1];
-      main_space_backup_.reset(CreateMallocSpaceFromMemMap(main_mem_map_2.release(), initial_size,
-                                                           growth_limit_, capacity_, name, true));
+      main_space_backup_.reset(CreateMallocSpaceFromMemMap(std::move(main_mem_map_2),
+                                                           initial_size,
+                                                           growth_limit_,
+                                                           capacity_,
+                                                           name,
+                                                           /* can_move_objects */ true));
       CHECK(main_space_backup_.get() != nullptr);
       // Add the space so its accounted for in the heap_begin and heap_end.
       AddSpace(main_space_backup_.get());
@@ -594,11 +605,26 @@
     }
     if (MayUseCollector(kCollectorTypeCC)) {
       concurrent_copying_collector_ = new collector::ConcurrentCopying(this,
+                                                                       /*young_gen*/false,
                                                                        "",
                                                                        measure_gc_performance);
+      if (kEnableGenerationalConcurrentCopyingCollection) {
+        young_concurrent_copying_collector_ = new collector::ConcurrentCopying(
+            this,
+            /*young_gen*/true,
+            "young",
+            measure_gc_performance);
+      }
+      active_concurrent_copying_collector_ = concurrent_copying_collector_;
       DCHECK(region_space_ != nullptr);
       concurrent_copying_collector_->SetRegionSpace(region_space_);
+      if (kEnableGenerationalConcurrentCopyingCollection) {
+        young_concurrent_copying_collector_->SetRegionSpace(region_space_);
+      }
       garbage_collectors_.push_back(concurrent_copying_collector_);
+      if (kEnableGenerationalConcurrentCopyingCollection) {
+        garbage_collectors_.push_back(young_concurrent_copying_collector_);
+      }
     }
   }
   if (!GetBootImageSpaces().empty() && non_moving_space_ != nullptr &&
@@ -613,7 +639,7 @@
         first_space = space;
       }
     }
-    bool no_gap = MemMap::CheckNoGaps(first_space->GetMemMap(), non_moving_space_->GetMemMap());
+    bool no_gap = MemMap::CheckNoGaps(*first_space->GetMemMap(), *non_moving_space_->GetMemMap());
     if (!no_gap) {
       PrintFileToLog("/proc/self/maps", LogSeverity::ERROR);
       MemMap::DumpMaps(LOG_STREAM(ERROR), true);
@@ -632,14 +658,18 @@
   }
 }
 
-MemMap* Heap::MapAnonymousPreferredAddress(const char* name,
-                                           uint8_t* request_begin,
-                                           size_t capacity,
-                                           std::string* out_error_str) {
+MemMap Heap::MapAnonymousPreferredAddress(const char* name,
+                                          uint8_t* request_begin,
+                                          size_t capacity,
+                                          std::string* out_error_str) {
   while (true) {
-    MemMap* map = MemMap::MapAnonymous(name, request_begin, capacity,
-                                       PROT_READ | PROT_WRITE, true, false, out_error_str);
-    if (map != nullptr || request_begin == nullptr) {
+    MemMap map = MemMap::MapAnonymous(name,
+                                      request_begin,
+                                      capacity,
+                                      PROT_READ | PROT_WRITE,
+                                      /* low_4gb*/ true,
+                                      out_error_str);
+    if (map.IsValid() || request_begin == nullptr) {
       return map;
     }
     // Retry a  second time with no specified request begin.
@@ -651,7 +681,7 @@
   return foreground_collector_type_ == type || background_collector_type_ == type;
 }
 
-space::MallocSpace* Heap::CreateMallocSpaceFromMemMap(MemMap* mem_map,
+space::MallocSpace* Heap::CreateMallocSpaceFromMemMap(MemMap&& mem_map,
                                                       size_t initial_size,
                                                       size_t growth_limit,
                                                       size_t capacity,
@@ -660,12 +690,21 @@
   space::MallocSpace* malloc_space = nullptr;
   if (kUseRosAlloc) {
     // Create rosalloc space.
-    malloc_space = space::RosAllocSpace::CreateFromMemMap(mem_map, name, kDefaultStartingSize,
-                                                          initial_size, growth_limit, capacity,
-                                                          low_memory_mode_, can_move_objects);
+    malloc_space = space::RosAllocSpace::CreateFromMemMap(std::move(mem_map),
+                                                          name,
+                                                          kDefaultStartingSize,
+                                                          initial_size,
+                                                          growth_limit,
+                                                          capacity,
+                                                          low_memory_mode_,
+                                                          can_move_objects);
   } else {
-    malloc_space = space::DlMallocSpace::CreateFromMemMap(mem_map, name, kDefaultStartingSize,
-                                                          initial_size, growth_limit, capacity,
+    malloc_space = space::DlMallocSpace::CreateFromMemMap(std::move(mem_map),
+                                                          name,
+                                                          kDefaultStartingSize,
+                                                          initial_size,
+                                                          growth_limit,
+                                                          capacity,
                                                           can_move_objects);
   }
   if (collector::SemiSpace::kUseRememberedSet) {
@@ -679,7 +718,9 @@
   return malloc_space;
 }
 
-void Heap::CreateMainMallocSpace(MemMap* mem_map, size_t initial_size, size_t growth_limit,
+void Heap::CreateMainMallocSpace(MemMap&& mem_map,
+                                 size_t initial_size,
+                                 size_t growth_limit,
                                  size_t capacity) {
   // Is background compaction is enabled?
   bool can_move_objects = IsMovingGc(background_collector_type_) !=
@@ -698,7 +739,10 @@
     RemoveRememberedSet(main_space_);
   }
   const char* name = kUseRosAlloc ? kRosAllocSpaceName[0] : kDlMallocSpaceName[0];
-  main_space_ = CreateMallocSpaceFromMemMap(mem_map, initial_size, growth_limit, capacity, name,
+  main_space_ = CreateMallocSpaceFromMemMap(std::move(mem_map),
+                                            initial_size,
+                                            growth_limit,
+                                            capacity, name,
                                             can_move_objects);
   SetSpaceAsDefault(main_space_);
   VLOG(heap) << "Created main space " << main_space_;
@@ -2012,17 +2056,17 @@
         if (!IsMovingGc(collector_type_)) {
           // Create the bump pointer space from the backup space.
           CHECK(main_space_backup_ != nullptr);
-          std::unique_ptr<MemMap> mem_map(main_space_backup_->ReleaseMemMap());
+          MemMap mem_map = main_space_backup_->ReleaseMemMap();
           // We are transitioning from non moving GC -> moving GC, since we copied from the bump
           // pointer space last transition it will be protected.
-          CHECK(mem_map != nullptr);
-          mem_map->Protect(PROT_READ | PROT_WRITE);
+          CHECK(mem_map.IsValid());
+          mem_map.Protect(PROT_READ | PROT_WRITE);
           bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space",
-                                                                          mem_map.release());
+                                                                          std::move(mem_map));
           AddSpace(bump_pointer_space_);
           collector = Compact(bump_pointer_space_, main_space_, kGcCauseCollectorTransition);
           // Use the now empty main space mem map for the bump pointer temp space.
-          mem_map.reset(main_space_->ReleaseMemMap());
+          mem_map = main_space_->ReleaseMemMap();
           // Unset the pointers just in case.
           if (dlmalloc_space_ == main_space_) {
             dlmalloc_space_ = nullptr;
@@ -2038,7 +2082,7 @@
           RemoveRememberedSet(main_space_backup_.get());
           main_space_backup_.reset(nullptr);  // Deletes the space.
           temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
-                                                                  mem_map.release());
+                                                                  std::move(mem_map));
           AddSpace(temp_space_);
         }
         break;
@@ -2048,37 +2092,35 @@
       case kCollectorTypeCMS: {
         if (IsMovingGc(collector_type_)) {
           CHECK(temp_space_ != nullptr);
-          std::unique_ptr<MemMap> mem_map(temp_space_->ReleaseMemMap());
+          MemMap mem_map = temp_space_->ReleaseMemMap();
           RemoveSpace(temp_space_);
           temp_space_ = nullptr;
-          mem_map->Protect(PROT_READ | PROT_WRITE);
-          CreateMainMallocSpace(mem_map.get(),
+          mem_map.Protect(PROT_READ | PROT_WRITE);
+          CreateMainMallocSpace(std::move(mem_map),
                                 kDefaultInitialSize,
-                                std::min(mem_map->Size(), growth_limit_),
-                                mem_map->Size());
-          mem_map.release();
+                                std::min(mem_map.Size(), growth_limit_),
+                                mem_map.Size());
           // Compact to the main space from the bump pointer space, don't need to swap semispaces.
           AddSpace(main_space_);
           collector = Compact(main_space_, bump_pointer_space_, kGcCauseCollectorTransition);
-          mem_map.reset(bump_pointer_space_->ReleaseMemMap());
+          mem_map = bump_pointer_space_->ReleaseMemMap();
           RemoveSpace(bump_pointer_space_);
           bump_pointer_space_ = nullptr;
           const char* name = kUseRosAlloc ? kRosAllocSpaceName[1] : kDlMallocSpaceName[1];
           // Temporarily unprotect the backup mem map so rosalloc can write the debug magic number.
           if (kIsDebugBuild && kUseRosAlloc) {
-            mem_map->Protect(PROT_READ | PROT_WRITE);
+            mem_map.Protect(PROT_READ | PROT_WRITE);
           }
           main_space_backup_.reset(CreateMallocSpaceFromMemMap(
-              mem_map.get(),
+              std::move(mem_map),
               kDefaultInitialSize,
-              std::min(mem_map->Size(), growth_limit_),
-              mem_map->Size(),
+              std::min(mem_map.Size(), growth_limit_),
+              mem_map.Size(),
               name,
               true));
           if (kIsDebugBuild && kUseRosAlloc) {
-            mem_map->Protect(PROT_NONE);
+            main_space_backup_->GetMemMap()->Protect(PROT_NONE);
           }
-          mem_map.release();
         }
         break;
       }
@@ -2120,6 +2162,9 @@
     gc_plan_.clear();
     switch (collector_type_) {
       case kCollectorTypeCC: {
+        if (kEnableGenerationalConcurrentCopyingCollection) {
+          gc_plan_.push_back(collector::kGcTypeSticky);
+        }
         gc_plan_.push_back(collector::kGcTypeFull);
         if (use_tlab_) {
           ChangeAllocator(kAllocatorTypeRegionTLAB);
@@ -2159,7 +2204,8 @@
     }
     if (IsGcConcurrent()) {
       concurrent_start_bytes_ =
-          std::max(max_allowed_footprint_, kMinConcurrentRemainingBytes) - kMinConcurrentRemainingBytes;
+          std::max(max_allowed_footprint_, kMinConcurrentRemainingBytes) -
+          kMinConcurrentRemainingBytes;
     } else {
       concurrent_start_bytes_ = std::numeric_limits<size_t>::max();
     }
@@ -2323,11 +2369,13 @@
     if (reset_main_space) {
       main_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
       madvise(main_space_->Begin(), main_space_->Capacity(), MADV_DONTNEED);
-      MemMap* mem_map = main_space_->ReleaseMemMap();
+      MemMap mem_map = main_space_->ReleaseMemMap();
       RemoveSpace(main_space_);
       space::Space* old_main_space = main_space_;
-      CreateMainMallocSpace(mem_map, kDefaultInitialSize, std::min(mem_map->Size(), growth_limit_),
-                            mem_map->Size());
+      CreateMainMallocSpace(std::move(mem_map),
+                            kDefaultInitialSize,
+                            std::min(mem_map.Size(), growth_limit_),
+                            mem_map.Size());
       delete old_main_space;
       AddSpace(main_space_);
     } else {
@@ -2567,12 +2615,19 @@
         collector = semi_space_collector_;
         break;
       case kCollectorTypeCC:
-        collector = concurrent_copying_collector_;
+        if (kEnableGenerationalConcurrentCopyingCollection) {
+          // TODO: Other threads must do the flip checkpoint before they start poking at
+          // active_concurrent_copying_collector_. So we should not concurrency here.
+          active_concurrent_copying_collector_ = (gc_type == collector::kGcTypeSticky) ?
+              young_concurrent_copying_collector_ : concurrent_copying_collector_;
+          active_concurrent_copying_collector_->SetRegionSpace(region_space_);
+        }
+        collector = active_concurrent_copying_collector_;
         break;
       default:
         LOG(FATAL) << "Invalid collector type " << static_cast<size_t>(collector_type_);
     }
-    if (collector != concurrent_copying_collector_) {
+    if (collector != active_concurrent_copying_collector_) {
       temp_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
       if (kIsDebugBuild) {
         // Try to read each page of the memory map in case mprotect didn't work properly b/19894268.
@@ -3435,7 +3490,8 @@
   uint64_t target_size;
   collector::GcType gc_type = collector_ran->GetGcType();
   // Use the multiplier to grow more for foreground.
-  const double multiplier = HeapGrowthMultiplier();
+  const double multiplier = HeapGrowthMultiplier();  // Use the multiplier to grow more for
+  // foreground.
   const uint64_t adjusted_min_free = static_cast<uint64_t>(min_free_ * multiplier);
   const uint64_t adjusted_max_free = static_cast<uint64_t>(max_free_ * multiplier);
   if (gc_type != collector::kGcTypeSticky) {
@@ -3451,6 +3507,12 @@
     collector::GcType non_sticky_gc_type = NonStickyGcType();
     // Find what the next non sticky collector will be.
     collector::GarbageCollector* non_sticky_collector = FindCollectorByGcType(non_sticky_gc_type);
+    if (kEnableGenerationalConcurrentCopyingCollection) {
+      if (non_sticky_collector == nullptr) {
+        non_sticky_collector = FindCollectorByGcType(collector::kGcTypePartial);
+      }
+      CHECK(non_sticky_collector != nullptr);
+    }
     // If the throughput of the current sticky GC >= throughput of the non sticky collector, then
     // do another sticky collection next.
     // We also check that the bytes allocated aren't over the footprint limit in order to prevent a
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index d014372..90bac20 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -709,8 +709,15 @@
     return zygote_space_ != nullptr;
   }
 
+  // Returns the active concurrent copying collector.
   collector::ConcurrentCopying* ConcurrentCopyingCollector() {
-    return concurrent_copying_collector_;
+    if (kEnableGenerationalConcurrentCopyingCollection) {
+      DCHECK((active_concurrent_copying_collector_ == concurrent_copying_collector_) ||
+             (active_concurrent_copying_collector_ == young_concurrent_copying_collector_));
+    } else {
+      DCHECK_EQ(active_concurrent_copying_collector_, concurrent_copying_collector_);
+    }
+    return active_concurrent_copying_collector_;
   }
 
   CollectorType CurrentCollectorType() {
@@ -835,8 +842,10 @@
   void FinishGC(Thread* self, collector::GcType gc_type) REQUIRES(!*gc_complete_lock_);
 
   // Create a mem map with a preferred base address.
-  static MemMap* MapAnonymousPreferredAddress(const char* name, uint8_t* request_begin,
-                                              size_t capacity, std::string* out_error_str);
+  static MemMap MapAnonymousPreferredAddress(const char* name,
+                                             uint8_t* request_begin,
+                                             size_t capacity,
+                                             std::string* out_error_str);
 
   bool SupportHSpaceCompaction() const {
     // Returns true if we can do hspace compaction
@@ -979,13 +988,13 @@
   collector::GarbageCollector* FindCollectorByGcType(collector::GcType gc_type);
 
   // Create the main free list malloc space, either a RosAlloc space or DlMalloc space.
-  void CreateMainMallocSpace(MemMap* mem_map,
+  void CreateMainMallocSpace(MemMap&& mem_map,
                              size_t initial_size,
                              size_t growth_limit,
                              size_t capacity);
 
   // Create a malloc space based on a mem map. Does not set the space as default.
-  space::MallocSpace* CreateMallocSpaceFromMemMap(MemMap* mem_map,
+  space::MallocSpace* CreateMallocSpaceFromMemMap(MemMap&& mem_map,
                                                   size_t initial_size,
                                                   size_t growth_limit,
                                                   size_t capacity,
@@ -1335,6 +1344,8 @@
 
   std::vector<collector::GarbageCollector*> garbage_collectors_;
   collector::SemiSpace* semi_space_collector_;
+  collector::ConcurrentCopying* active_concurrent_copying_collector_;
+  collector::ConcurrentCopying* young_concurrent_copying_collector_;
   collector::ConcurrentCopying* concurrent_copying_collector_;
 
   const bool is_running_on_memory_tool_;
@@ -1425,6 +1436,7 @@
   friend class collector::ConcurrentCopying;
   friend class collector::MarkSweep;
   friend class collector::SemiSpace;
+  friend class GCCriticalSection;
   friend class ReferenceQueue;
   friend class ScopedGCCriticalSection;
   friend class VerifyReferenceCardVisitor;
diff --git a/runtime/gc/heap_test.cc b/runtime/gc/heap_test.cc
index c6b2120..8720a3e 100644
--- a/runtime/gc/heap_test.cc
+++ b/runtime/gc/heap_test.cc
@@ -33,19 +33,18 @@
     MemMap::Init();
     std::string error_msg;
     // Reserve the preferred address to force the heap to use another one for testing.
-    reserved_.reset(MemMap::MapAnonymous("ReserveMap",
-                                         gc::Heap::kPreferredAllocSpaceBegin,
-                                         16 * KB,
-                                         PROT_READ,
-                                         /*low_4gb*/ true,
-                                         /*reuse*/ false,
-                                         &error_msg));
-    ASSERT_TRUE(reserved_ != nullptr) << error_msg;
+    reserved_ = MemMap::MapAnonymous("ReserveMap",
+                                     gc::Heap::kPreferredAllocSpaceBegin,
+                                     16 * KB,
+                                     PROT_READ,
+                                     /*low_4gb*/ true,
+                                     &error_msg);
+    ASSERT_TRUE(reserved_.IsValid()) << error_msg;
     CommonRuntimeTest::SetUp();
   }
 
  private:
-  std::unique_ptr<MemMap> reserved_;
+  MemMap reserved_;
 };
 
 TEST_F(HeapTest, ClearGrowthLimit) {
diff --git a/runtime/gc/reference_queue.cc b/runtime/gc/reference_queue.cc
index 321d22a..e25e279 100644
--- a/runtime/gc/reference_queue.cc
+++ b/runtime/gc/reference_queue.cc
@@ -76,19 +76,19 @@
   Heap* heap = Runtime::Current()->GetHeap();
   if (kUseBakerOrBrooksReadBarrier && heap->CurrentCollectorType() == kCollectorTypeCC &&
       heap->ConcurrentCopyingCollector()->IsActive()) {
-    // Change the gray ptr we left in ConcurrentCopying::ProcessMarkStackRef() to white.
+    // Change the gray ptr we left in ConcurrentCopying::ProcessMarkStackRef() to non-gray.
     // We check IsActive() above because we don't want to do this when the zygote compaction
     // collector (SemiSpace) is running.
     CHECK(ref != nullptr);
     collector::ConcurrentCopying* concurrent_copying = heap->ConcurrentCopyingCollector();
     uint32_t rb_state = ref->GetReadBarrierState();
     if (rb_state == ReadBarrier::GrayState()) {
-      ref->AtomicSetReadBarrierState(ReadBarrier::GrayState(), ReadBarrier::WhiteState());
-      CHECK_EQ(ref->GetReadBarrierState(), ReadBarrier::WhiteState());
+      ref->AtomicSetReadBarrierState(ReadBarrier::GrayState(), ReadBarrier::NonGrayState());
+      CHECK_EQ(ref->GetReadBarrierState(), ReadBarrier::NonGrayState());
     } else {
-      // In ConcurrentCopying::ProcessMarkStackRef() we may leave a white reference in the queue and
-      // find it here, which is OK.
-      CHECK_EQ(rb_state, ReadBarrier::WhiteState()) << "ref=" << ref << " rb_state=" << rb_state;
+      // In ConcurrentCopying::ProcessMarkStackRef() we may leave a non-gray reference in the queue
+      // and find it here, which is OK.
+      CHECK_EQ(rb_state, ReadBarrier::NonGrayState()) << "ref=" << ref << " rb_state=" << rb_state;
       ObjPtr<mirror::Object> referent = ref->GetReferent<kWithoutReadBarrier>();
       // The referent could be null if it's cleared by a mutator (Reference.clear()).
       if (referent != nullptr) {
diff --git a/runtime/gc/scoped_gc_critical_section.cc b/runtime/gc/scoped_gc_critical_section.cc
index 2976dd0..7a0a6e8 100644
--- a/runtime/gc/scoped_gc_critical_section.cc
+++ b/runtime/gc/scoped_gc_critical_section.cc
@@ -24,20 +24,38 @@
 namespace art {
 namespace gc {
 
+const char* GCCriticalSection::Enter(GcCause cause, CollectorType type) {
+  Runtime::Current()->GetHeap()->StartGC(self_, cause, type);
+  if (self_ != nullptr) {
+    return self_->StartAssertNoThreadSuspension(section_name_);
+  } else {
+    // Workaround to avoid having to mark the whole function as NO_THREAD_SAFETY_ANALYSIS.
+    auto kludge = []() ACQUIRE(Roles::uninterruptible_) NO_THREAD_SAFETY_ANALYSIS {};
+    kludge();
+    return nullptr;
+  }
+}
+
+void GCCriticalSection::Exit(const char* old_cause) {
+  if (self_ != nullptr) {
+    self_->EndAssertNoThreadSuspension(old_cause);
+  } else {
+    // Workaround to avoid having to mark the whole function as NO_THREAD_SAFETY_ANALYSIS.
+    auto kludge = []() RELEASE(Roles::uninterruptible_) NO_THREAD_SAFETY_ANALYSIS {};
+    kludge();
+  }
+  Runtime::Current()->GetHeap()->FinishGC(self_, collector::kGcTypeNone);
+}
+
 ScopedGCCriticalSection::ScopedGCCriticalSection(Thread* self,
                                                  GcCause cause,
                                                  CollectorType collector_type)
-    : self_(self) {
-  Runtime::Current()->GetHeap()->StartGC(self, cause, collector_type);
-  if (self != nullptr) {
-    old_cause_ = self->StartAssertNoThreadSuspension("ScopedGCCriticalSection");
-  }
+    : critical_section_(self, "ScopedGCCriticalSection") {
+  old_no_suspend_reason_ = critical_section_.Enter(cause, collector_type);
 }
+
 ScopedGCCriticalSection::~ScopedGCCriticalSection() {
-  if (self_ != nullptr) {
-    self_->EndAssertNoThreadSuspension(old_cause_);
-  }
-  Runtime::Current()->GetHeap()->FinishGC(self_, collector::kGcTypeNone);
+  critical_section_.Exit(old_no_suspend_reason_);
 }
 
 }  // namespace gc
diff --git a/runtime/gc/scoped_gc_critical_section.h b/runtime/gc/scoped_gc_critical_section.h
index 1271ff7..864bf87 100644
--- a/runtime/gc/scoped_gc_critical_section.h
+++ b/runtime/gc/scoped_gc_critical_section.h
@@ -27,6 +27,24 @@
 
 namespace gc {
 
+// The use of ScopedGCCriticalSection should be preferred whenever possible.
+class GCCriticalSection {
+ public:
+  GCCriticalSection(Thread* self, const char* name)
+      : self_(self), section_name_(name) {}
+  ~GCCriticalSection() {}
+
+  // Starts a GCCriticalSection. Returns the previous no-suspension reason.
+  const char* Enter(GcCause cause, CollectorType type) ACQUIRE(Roles::uninterruptible_);
+
+  // Ends a GCCriticalSection. Takes the old no-suspension reason.
+  void Exit(const char* old_reason) RELEASE(Roles::uninterruptible_);
+
+ private:
+  Thread* const self_;
+  const char* section_name_;
+};
+
 // Wait until the GC is finished and then prevent the GC from starting until the destructor. Used
 // to prevent deadlocks in places where we call ClassLinker::VisitClass with all the threads
 // suspended.
@@ -37,8 +55,8 @@
   ~ScopedGCCriticalSection() RELEASE(Roles::uninterruptible_);
 
  private:
-  Thread* const self_;
-  const char* old_cause_;
+  GCCriticalSection critical_section_;
+  const char* old_no_suspend_reason_;
 };
 
 }  // namespace gc
diff --git a/runtime/gc/space/bump_pointer_space.cc b/runtime/gc/space/bump_pointer_space.cc
index e95da01..42453f5 100644
--- a/runtime/gc/space/bump_pointer_space.cc
+++ b/runtime/gc/space/bump_pointer_space.cc
@@ -28,23 +28,30 @@
                                            uint8_t* requested_begin) {
   capacity = RoundUp(capacity, kPageSize);
   std::string error_msg;
-  std::unique_ptr<MemMap> mem_map(MemMap::MapAnonymous(name.c_str(), requested_begin, capacity,
-                                                       PROT_READ | PROT_WRITE, true, false,
-                                                       &error_msg));
-  if (mem_map.get() == nullptr) {
+  MemMap mem_map = MemMap::MapAnonymous(name.c_str(),
+                                        requested_begin,
+                                        capacity,
+                                        PROT_READ | PROT_WRITE,
+                                        /* low_4gb */ true,
+                                        &error_msg);
+  if (!mem_map.IsValid()) {
     LOG(ERROR) << "Failed to allocate pages for alloc space (" << name << ") of size "
         << PrettySize(capacity) << " with message " << error_msg;
     return nullptr;
   }
-  return new BumpPointerSpace(name, mem_map.release());
+  return new BumpPointerSpace(name, std::move(mem_map));
 }
 
-BumpPointerSpace* BumpPointerSpace::CreateFromMemMap(const std::string& name, MemMap* mem_map) {
-  return new BumpPointerSpace(name, mem_map);
+BumpPointerSpace* BumpPointerSpace::CreateFromMemMap(const std::string& name, MemMap&& mem_map) {
+  return new BumpPointerSpace(name, std::move(mem_map));
 }
 
 BumpPointerSpace::BumpPointerSpace(const std::string& name, uint8_t* begin, uint8_t* limit)
-    : ContinuousMemMapAllocSpace(name, nullptr, begin, begin, limit,
+    : ContinuousMemMapAllocSpace(name,
+                                 MemMap::Invalid(),
+                                 begin,
+                                 begin,
+                                 limit,
                                  kGcRetentionPolicyAlwaysCollect),
       growth_end_(limit),
       objects_allocated_(0), bytes_allocated_(0),
@@ -53,10 +60,14 @@
       num_blocks_(0) {
 }
 
-BumpPointerSpace::BumpPointerSpace(const std::string& name, MemMap* mem_map)
-    : ContinuousMemMapAllocSpace(name, mem_map, mem_map->Begin(), mem_map->Begin(), mem_map->End(),
+BumpPointerSpace::BumpPointerSpace(const std::string& name, MemMap&& mem_map)
+    : ContinuousMemMapAllocSpace(name,
+                                 std::move(mem_map),
+                                 mem_map.Begin(),
+                                 mem_map.Begin(),
+                                 mem_map.End(),
                                  kGcRetentionPolicyAlwaysCollect),
-      growth_end_(mem_map->End()),
+      growth_end_(mem_map_.End()),
       objects_allocated_(0), bytes_allocated_(0),
       block_lock_("Block lock", kBumpPointerSpaceBlockLock),
       main_block_size_(0),
diff --git a/runtime/gc/space/bump_pointer_space.h b/runtime/gc/space/bump_pointer_space.h
index 5ba13ca..9b31558 100644
--- a/runtime/gc/space/bump_pointer_space.h
+++ b/runtime/gc/space/bump_pointer_space.h
@@ -47,7 +47,7 @@
   // guaranteed to be granted, if it is required, the caller should call Begin on the returned
   // space to confirm the request was granted.
   static BumpPointerSpace* Create(const std::string& name, size_t capacity, uint8_t* requested_begin);
-  static BumpPointerSpace* CreateFromMemMap(const std::string& name, MemMap* mem_map);
+  static BumpPointerSpace* CreateFromMemMap(const std::string& name, MemMap&& mem_map);
 
   // Allocate num_bytes, returns null if the space is full.
   mirror::Object* Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated,
@@ -166,7 +166,7 @@
   static constexpr size_t kAlignment = 8;
 
  protected:
-  BumpPointerSpace(const std::string& name, MemMap* mem_map);
+  BumpPointerSpace(const std::string& name, MemMap&& mem_map);
 
   // Allocate a raw block of bytes.
   uint8_t* AllocBlock(size_t bytes) REQUIRES(block_lock_);
diff --git a/runtime/gc/space/dlmalloc_space.cc b/runtime/gc/space/dlmalloc_space.cc
index 025c3f0..36d2161 100644
--- a/runtime/gc/space/dlmalloc_space.cc
+++ b/runtime/gc/space/dlmalloc_space.cc
@@ -38,41 +38,73 @@
 
 static constexpr bool kPrefetchDuringDlMallocFreeList = true;
 
-DlMallocSpace::DlMallocSpace(MemMap* mem_map, size_t initial_size, const std::string& name,
-                             void* mspace, uint8_t* begin, uint8_t* end, uint8_t* limit,
-                             size_t growth_limit, bool can_move_objects, size_t starting_size)
-    : MallocSpace(name, mem_map, begin, end, limit, growth_limit, true, can_move_objects,
+DlMallocSpace::DlMallocSpace(MemMap&& mem_map,
+                             size_t initial_size,
+                             const std::string& name,
+                             void* mspace,
+                             uint8_t* begin,
+                             uint8_t* end,
+                             uint8_t* limit,
+                             size_t growth_limit,
+                             bool can_move_objects,
+                             size_t starting_size)
+    : MallocSpace(name,
+                  std::move(mem_map),
+                  begin,
+                  end,
+                  limit,
+                  growth_limit,
+                  /* create_bitmaps */ true,
+                  can_move_objects,
                   starting_size, initial_size),
       mspace_(mspace) {
   CHECK(mspace != nullptr);
 }
 
-DlMallocSpace* DlMallocSpace::CreateFromMemMap(MemMap* mem_map, const std::string& name,
-                                               size_t starting_size, size_t initial_size,
-                                               size_t growth_limit, size_t capacity,
+DlMallocSpace* DlMallocSpace::CreateFromMemMap(MemMap&& mem_map,
+                                               const std::string& name,
+                                               size_t starting_size,
+                                               size_t initial_size,
+                                               size_t growth_limit,
+                                               size_t capacity,
                                                bool can_move_objects) {
-  DCHECK(mem_map != nullptr);
-  void* mspace = CreateMspace(mem_map->Begin(), starting_size, initial_size);
+  DCHECK(mem_map.IsValid());
+  void* mspace = CreateMspace(mem_map.Begin(), starting_size, initial_size);
   if (mspace == nullptr) {
     LOG(ERROR) << "Failed to initialize mspace for alloc space (" << name << ")";
     return nullptr;
   }
 
   // Protect memory beyond the starting size. morecore will add r/w permissions when necessory
-  uint8_t* end = mem_map->Begin() + starting_size;
+  uint8_t* end = mem_map.Begin() + starting_size;
   if (capacity - starting_size > 0) {
     CheckedCall(mprotect, name.c_str(), end, capacity - starting_size, PROT_NONE);
   }
 
   // Everything is set so record in immutable structure and leave
-  uint8_t* begin = mem_map->Begin();
+  uint8_t* begin = mem_map.Begin();
   if (Runtime::Current()->IsRunningOnMemoryTool()) {
     return new MemoryToolMallocSpace<DlMallocSpace, kDefaultMemoryToolRedZoneBytes, true, false>(
-        mem_map, initial_size, name, mspace, begin, end, begin + capacity, growth_limit,
-        can_move_objects, starting_size);
+        std::move(mem_map),
+        initial_size,
+        name,
+        mspace,
+        begin,
+        end,
+        begin + capacity, growth_limit,
+        can_move_objects,
+        starting_size);
   } else {
-    return new DlMallocSpace(mem_map, initial_size, name, mspace, begin, end, begin + capacity,
-                             growth_limit, can_move_objects, starting_size);
+    return new DlMallocSpace(std::move(mem_map),
+                             initial_size,
+                             name,
+                             mspace,
+                             begin,
+                             end,
+                             begin + capacity,
+                             growth_limit,
+                             can_move_objects,
+                             starting_size);
   }
 }
 
@@ -94,15 +126,20 @@
   // will ask for this memory from sys_alloc which will fail as the footprint (this value plus the
   // size of the large allocation) will be greater than the footprint limit.
   size_t starting_size = kPageSize;
-  MemMap* mem_map = CreateMemMap(name, starting_size, &initial_size, &growth_limit, &capacity,
-                                 requested_begin);
-  if (mem_map == nullptr) {
+  MemMap mem_map =
+      CreateMemMap(name, starting_size, &initial_size, &growth_limit, &capacity, requested_begin);
+  if (!mem_map.IsValid()) {
     LOG(ERROR) << "Failed to create mem map for alloc space (" << name << ") of size "
                << PrettySize(capacity);
     return nullptr;
   }
-  DlMallocSpace* space = CreateFromMemMap(mem_map, name, starting_size, initial_size,
-                                          growth_limit, capacity, can_move_objects);
+  DlMallocSpace* space = CreateFromMemMap(std::move(mem_map),
+                                          name,
+                                          starting_size,
+                                          initial_size,
+                                          growth_limit,
+                                          capacity,
+                                          can_move_objects);
   // We start out with only the initial size possibly containing objects.
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
     LOG(INFO) << "DlMallocSpace::Create exiting (" << PrettyDuration(NanoTime() - start_time)
@@ -152,17 +189,37 @@
   return result;
 }
 
-MallocSpace* DlMallocSpace::CreateInstance(MemMap* mem_map, const std::string& name,
-                                           void* allocator, uint8_t* begin, uint8_t* end,
-                                           uint8_t* limit, size_t growth_limit,
+MallocSpace* DlMallocSpace::CreateInstance(MemMap&& mem_map,
+                                           const std::string& name,
+                                           void* allocator,
+                                           uint8_t* begin,
+                                           uint8_t* end,
+                                           uint8_t* limit,
+                                           size_t growth_limit,
                                            bool can_move_objects) {
   if (Runtime::Current()->IsRunningOnMemoryTool()) {
     return new MemoryToolMallocSpace<DlMallocSpace, kDefaultMemoryToolRedZoneBytes, true, false>(
-        mem_map, initial_size_, name, allocator, begin, end, limit, growth_limit,
-        can_move_objects, starting_size_);
+        std::move(mem_map),
+        initial_size_,
+        name,
+        allocator,
+        begin,
+        end,
+        limit,
+        growth_limit,
+        can_move_objects,
+        starting_size_);
   } else {
-    return new DlMallocSpace(mem_map, initial_size_, name, allocator, begin, end, limit,
-                             growth_limit, can_move_objects, starting_size_);
+    return new DlMallocSpace(std::move(mem_map),
+                             initial_size_,
+                             name,
+                             allocator,
+                             begin,
+                             end,
+                             limit,
+                             growth_limit,
+                             can_move_objects,
+                             starting_size_);
   }
 }
 
@@ -283,7 +340,7 @@
   live_bitmap_->Clear();
   mark_bitmap_->Clear();
   SetEnd(Begin() + starting_size_);
-  mspace_ = CreateMspace(mem_map_->Begin(), starting_size_, initial_size_);
+  mspace_ = CreateMspace(mem_map_.Begin(), starting_size_, initial_size_);
   SetFootprintLimit(footprint_limit);
 }
 
diff --git a/runtime/gc/space/dlmalloc_space.h b/runtime/gc/space/dlmalloc_space.h
index 4c7fcfd..66537d5 100644
--- a/runtime/gc/space/dlmalloc_space.h
+++ b/runtime/gc/space/dlmalloc_space.h
@@ -34,9 +34,12 @@
 class DlMallocSpace : public MallocSpace {
  public:
   // Create a DlMallocSpace from an existing mem_map.
-  static DlMallocSpace* CreateFromMemMap(MemMap* mem_map, const std::string& name,
-                                         size_t starting_size, size_t initial_size,
-                                         size_t growth_limit, size_t capacity,
+  static DlMallocSpace* CreateFromMemMap(MemMap&& mem_map,
+                                         const std::string& name,
+                                         size_t starting_size,
+                                         size_t initial_size,
+                                         size_t growth_limit,
+                                         size_t capacity,
                                          bool can_move_objects);
 
   // Create a DlMallocSpace with the requested sizes. The requested
@@ -118,9 +121,14 @@
   // allocations fail we GC before increasing the footprint limit and allowing the mspace to grow.
   void SetFootprintLimit(size_t limit) OVERRIDE;
 
-  MallocSpace* CreateInstance(MemMap* mem_map, const std::string& name, void* allocator,
-                              uint8_t* begin, uint8_t* end, uint8_t* limit, size_t growth_limit,
-                              bool can_move_objects);
+  MallocSpace* CreateInstance(MemMap&& mem_map,
+                              const std::string& name,
+                              void* allocator,
+                              uint8_t* begin,
+                              uint8_t* end,
+                              uint8_t* limit,
+                              size_t growth_limit,
+                              bool can_move_objects) OVERRIDE;
 
   uint64_t GetBytesAllocated() OVERRIDE;
   uint64_t GetObjectsAllocated() OVERRIDE;
@@ -139,9 +147,16 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
  protected:
-  DlMallocSpace(MemMap* mem_map, size_t initial_size, const std::string& name, void* mspace,
-                uint8_t* begin, uint8_t* end, uint8_t* limit, size_t growth_limit,
-                bool can_move_objects, size_t starting_size);
+  DlMallocSpace(MemMap&& mem_map,
+                size_t initial_size,
+                const std::string& name,
+                void* mspace,
+                uint8_t* begin,
+                uint8_t* end,
+                uint8_t* limit,
+                size_t growth_limit,
+                bool can_move_objects,
+                size_t starting_size);
 
  private:
   mirror::Object* AllocWithoutGrowthLocked(Thread* self, size_t num_bytes, size_t* bytes_allocated,
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 826f382..7178627 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -62,12 +62,12 @@
 
 ImageSpace::ImageSpace(const std::string& image_filename,
                        const char* image_location,
-                       MemMap* mem_map,
+                       MemMap&& mem_map,
                        accounting::ContinuousSpaceBitmap* live_bitmap,
                        uint8_t* end)
     : MemMapSpace(image_filename,
-                  mem_map,
-                  mem_map->Begin(),
+                  std::move(mem_map),
+                  mem_map.Begin(),
                   end,
                   end,
                   kGcRetentionPolicyNeverCollect),
@@ -181,18 +181,19 @@
   bool have_android_data = false;
   *dalvik_cache_exists = false;
   GetDalvikCache(GetInstructionSetString(image_isa),
-                 true,
+                 /* create_if_absent */ true,
                  dalvik_cache,
                  &have_android_data,
                  dalvik_cache_exists,
                  is_global_cache);
 
-  if (have_android_data && *dalvik_cache_exists) {
+  if (*dalvik_cache_exists) {
+    DCHECK(have_android_data);
     // Always set output location even if it does not exist,
     // so that the caller knows where to create the image.
     //
     // image_location = /system/framework/boot.art
-    // *image_filename = /data/dalvik-cache/<image_isa>/boot.art
+    // *image_filename = /data/dalvik-cache/<image_isa>/system@framework@boot.art
     std::string error_msg;
     if (!GetDalvikCacheFilename(image_location,
                                 dalvik_cache->c_str(),
@@ -381,33 +382,6 @@
   return nullptr;
 }
 
-static bool ChecksumsMatch(const char* image_a, const char* image_b, std::string* error_msg) {
-  DCHECK(error_msg != nullptr);
-
-  ImageHeader hdr_a;
-  ImageHeader hdr_b;
-
-  if (!ReadSpecificImageHeader(image_a, &hdr_a)) {
-    *error_msg = StringPrintf("Cannot read header of %s", image_a);
-    return false;
-  }
-  if (!ReadSpecificImageHeader(image_b, &hdr_b)) {
-    *error_msg = StringPrintf("Cannot read header of %s", image_b);
-    return false;
-  }
-
-  if (hdr_a.GetOatChecksum() != hdr_b.GetOatChecksum()) {
-    *error_msg = StringPrintf("Checksum mismatch: %u(%s) vs %u(%s)",
-                              hdr_a.GetOatChecksum(),
-                              image_a,
-                              hdr_b.GetOatChecksum(),
-                              image_b);
-    return false;
-  }
-
-  return true;
-}
-
 static bool CanWriteToDalvikCache(const InstructionSet isa) {
   const std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(isa));
   if (access(dalvik_cache.c_str(), O_RDWR) == 0) {
@@ -507,9 +481,9 @@
 
 // Helper class encapsulating loading, so we can access private ImageSpace members (this is a
 // friend class), but not declare functions in the header.
-class ImageSpaceLoader {
+class ImageSpace::Loader {
  public:
-  static std::unique_ptr<ImageSpace> Load(const char* image_location,
+  static std::unique_ptr<ImageSpace> Load(const std::string& image_location,
                                           const std::string& image_filename,
                                           bool is_zygote,
                                           bool is_global_cache,
@@ -541,7 +515,7 @@
     // Since we are the boot image, pass null since we load the oat file from the boot image oat
     // file name.
     return Init(image_filename.c_str(),
-                image_location,
+                image_location.c_str(),
                 validate_oat_file,
                 /* oat_file */nullptr,
                 error_msg);
@@ -636,53 +610,53 @@
       return nullptr;
     }
 
-    std::unique_ptr<MemMap> map;
+    MemMap map;
 
     // GetImageBegin is the preferred address to map the image. If we manage to map the
     // image at the image begin, the amount of fixup work required is minimized.
     // If it is pic we will retry with error_msg for the failure case. Pass a null error_msg to
     // avoid reading proc maps for a mapping failure and slowing everything down.
-    map.reset(LoadImageFile(image_filename,
-                            image_location,
-                            *image_header,
-                            image_header->GetImageBegin(),
-                            file->Fd(),
-                            logger,
-                            image_header->IsPic() ? nullptr : error_msg));
+    map = LoadImageFile(image_filename,
+                        image_location,
+                        *image_header,
+                        image_header->GetImageBegin(),
+                        file->Fd(),
+                        logger,
+                        image_header->IsPic() ? nullptr : error_msg);
     // If the header specifies PIC mode, we can also map at a random low_4gb address since we can
     // relocate in-place.
-    if (map == nullptr && image_header->IsPic()) {
-      map.reset(LoadImageFile(image_filename,
-                              image_location,
-                              *image_header,
-                              /* address */ nullptr,
-                              file->Fd(),
-                              logger,
-                              error_msg));
+    if (!map.IsValid() && image_header->IsPic()) {
+      map = LoadImageFile(image_filename,
+                          image_location,
+                          *image_header,
+                          /* address */ nullptr,
+                          file->Fd(),
+                          logger,
+                          error_msg);
     }
     // Were we able to load something and continue?
-    if (map == nullptr) {
+    if (!map.IsValid()) {
       DCHECK(!error_msg->empty());
       return nullptr;
     }
-    DCHECK_EQ(0, memcmp(image_header, map->Begin(), sizeof(ImageHeader)));
+    DCHECK_EQ(0, memcmp(image_header, map.Begin(), sizeof(ImageHeader)));
 
-    std::unique_ptr<MemMap> image_bitmap_map(MemMap::MapFileAtAddress(nullptr,
-                                                                      bitmap_section.Size(),
-                                                                      PROT_READ, MAP_PRIVATE,
-                                                                      file->Fd(),
-                                                                      image_bitmap_offset,
-                                                                      /*low_4gb*/false,
-                                                                      /*reuse*/false,
-                                                                      image_filename,
-                                                                      error_msg));
-    if (image_bitmap_map == nullptr) {
+    MemMap image_bitmap_map = MemMap::MapFileAtAddress(nullptr,
+                                                       bitmap_section.Size(),
+                                                       PROT_READ, MAP_PRIVATE,
+                                                       file->Fd(),
+                                                       image_bitmap_offset,
+                                                       /*low_4gb*/false,
+                                                       /*reuse*/false,
+                                                       image_filename,
+                                                       error_msg);
+    if (!image_bitmap_map.IsValid()) {
       *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
       return nullptr;
     }
     // Loaded the map, use the image header from the file now in case we patch it with
     // RelocateInPlace.
-    image_header = reinterpret_cast<ImageHeader*>(map->Begin());
+    image_header = reinterpret_cast<ImageHeader*>(map.Begin());
     const uint32_t bitmap_index = ImageSpace::bitmap_index_.fetch_add(1, std::memory_order_seq_cst);
     std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u",
                                          image_filename,
@@ -690,15 +664,15 @@
     // Bitmap only needs to cover until the end of the mirror objects section.
     const ImageSection& image_objects = image_header->GetObjectsSection();
     // We only want the mirror object, not the ArtFields and ArtMethods.
-    uint8_t* const image_end = map->Begin() + image_objects.End();
+    uint8_t* const image_end = map.Begin() + image_objects.End();
     std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap;
     {
       TimingLogger::ScopedTiming timing("CreateImageBitmap", &logger);
       bitmap.reset(
           accounting::ContinuousSpaceBitmap::CreateFromMemMap(
               bitmap_name,
-              image_bitmap_map.release(),
-              reinterpret_cast<uint8_t*>(map->Begin()),
+              std::move(image_bitmap_map),
+              reinterpret_cast<uint8_t*>(map.Begin()),
               // Make sure the bitmap is aligned to card size instead of just bitmap word size.
               RoundUp(image_objects.End(), gc::accounting::CardTable::kCardSize)));
       if (bitmap == nullptr) {
@@ -709,7 +683,7 @@
     {
       TimingLogger::ScopedTiming timing("RelocateImage", &logger);
       if (!RelocateInPlace(*image_header,
-                           map->Begin(),
+                           map.Begin(),
                            bitmap.get(),
                            oat_file,
                            error_msg)) {
@@ -719,7 +693,7 @@
     // We only want the mirror object, not the ArtFields and ArtMethods.
     std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename,
                                                      image_location,
-                                                     map.release(),
+                                                     std::move(map),
                                                      bitmap.release(),
                                                      image_end));
 
@@ -807,13 +781,13 @@
   }
 
  private:
-  static MemMap* LoadImageFile(const char* image_filename,
-                               const char* image_location,
-                               const ImageHeader& image_header,
-                               uint8_t* address,
-                               int fd,
-                               TimingLogger& logger,
-                               std::string* error_msg) {
+  static MemMap LoadImageFile(const char* image_filename,
+                              const char* image_location,
+                              const ImageHeader& image_header,
+                              uint8_t* address,
+                              int fd,
+                              TimingLogger& logger,
+                              std::string* error_msg) {
     TimingLogger::ScopedTiming timing("MapImageFile", &logger);
     const ImageHeader::StorageMode storage_mode = image_header.GetStorageMode();
     if (storage_mode == ImageHeader::kStorageModeUncompressed) {
@@ -835,45 +809,44 @@
         *error_msg = StringPrintf("Invalid storage mode in image header %d",
                                   static_cast<int>(storage_mode));
       }
-      return nullptr;
+      return MemMap::Invalid();
     }
 
     // Reserve output and decompress into it.
-    std::unique_ptr<MemMap> map(MemMap::MapAnonymous(image_location,
-                                                     address,
-                                                     image_header.GetImageSize(),
-                                                     PROT_READ | PROT_WRITE,
-                                                     /*low_4gb*/true,
-                                                     /*reuse*/false,
-                                                     error_msg));
-    if (map != nullptr) {
+    MemMap map = MemMap::MapAnonymous(image_location,
+                                      address,
+                                      image_header.GetImageSize(),
+                                      PROT_READ | PROT_WRITE,
+                                      /*low_4gb*/ true,
+                                      error_msg);
+    if (map.IsValid()) {
       const size_t stored_size = image_header.GetDataSize();
       const size_t decompress_offset = sizeof(ImageHeader);  // Skip the header.
-      std::unique_ptr<MemMap> temp_map(MemMap::MapFile(sizeof(ImageHeader) + stored_size,
-                                                       PROT_READ,
-                                                       MAP_PRIVATE,
-                                                       fd,
-                                                       /*offset*/0,
-                                                       /*low_4gb*/false,
-                                                       image_filename,
-                                                       error_msg));
-      if (temp_map == nullptr) {
+      MemMap temp_map = MemMap::MapFile(sizeof(ImageHeader) + stored_size,
+                                        PROT_READ,
+                                        MAP_PRIVATE,
+                                        fd,
+                                        /*offset*/0,
+                                        /*low_4gb*/false,
+                                        image_filename,
+                                        error_msg);
+      if (!temp_map.IsValid()) {
         DCHECK(error_msg == nullptr || !error_msg->empty());
-        return nullptr;
+        return MemMap::Invalid();
       }
-      memcpy(map->Begin(), &image_header, sizeof(ImageHeader));
+      memcpy(map.Begin(), &image_header, sizeof(ImageHeader));
       const uint64_t start = NanoTime();
       // LZ4HC and LZ4 have same internal format, both use LZ4_decompress.
       TimingLogger::ScopedTiming timing2("LZ4 decompress image", &logger);
       const size_t decompressed_size = LZ4_decompress_safe(
-          reinterpret_cast<char*>(temp_map->Begin()) + sizeof(ImageHeader),
-          reinterpret_cast<char*>(map->Begin()) + decompress_offset,
+          reinterpret_cast<char*>(temp_map.Begin()) + sizeof(ImageHeader),
+          reinterpret_cast<char*>(map.Begin()) + decompress_offset,
           stored_size,
-          map->Size() - decompress_offset);
+          map.Size() - decompress_offset);
       const uint64_t time = NanoTime() - start;
       // Add one 1 ns to prevent possible divide by 0.
       VLOG(image) << "Decompressing image took " << PrettyDuration(time) << " ("
-                  << PrettySize(static_cast<uint64_t>(map->Size()) * MsToNs(1000) / (time + 1))
+                  << PrettySize(static_cast<uint64_t>(map.Size()) * MsToNs(1000) / (time + 1))
                   << "/s)";
       if (decompressed_size + sizeof(ImageHeader) != image_header.GetImageSize()) {
         if (error_msg != nullptr) {
@@ -882,11 +855,11 @@
               decompressed_size + sizeof(ImageHeader),
               image_header.GetImageSize());
         }
-        return nullptr;
+        return MemMap::Invalid();
       }
     }
 
-    return map.release();
+    return map;
   }
 
   class FixupVisitor : public ValueObject {
@@ -1471,6 +1444,181 @@
   }
 };
 
+class ImageSpace::BootImageLoader {
+ public:
+  BootImageLoader(const std::string& image_location, InstructionSet image_isa)
+      : image_location_(image_location),
+        image_isa_(image_isa),
+        is_zygote_(Runtime::Current()->IsZygote()),
+        has_system_(false),
+        has_cache_(false),
+        is_global_cache_(true),
+        dalvik_cache_exists_(false),
+        dalvik_cache_(),
+        cache_filename_() {
+  }
+
+  bool IsZygote() const { return is_zygote_; }
+
+  void FindImageFiles() {
+    std::string system_filename;
+    bool found_image = FindImageFilenameImpl(image_location_.c_str(),
+                                             image_isa_,
+                                             &has_system_,
+                                             &system_filename,
+                                             &dalvik_cache_exists_,
+                                             &dalvik_cache_,
+                                             &is_global_cache_,
+                                             &has_cache_,
+                                             &cache_filename_);
+    DCHECK(!dalvik_cache_exists_ || !dalvik_cache_.empty());
+    DCHECK_EQ(found_image, has_system_ || has_cache_);
+  }
+
+  bool HasSystem() const { return has_system_; }
+  bool HasCache() const { return has_cache_; }
+
+  bool DalvikCacheExists() const { return dalvik_cache_exists_; }
+  bool IsGlobalCache() const { return is_global_cache_; }
+
+  const std::string& GetDalvikCache() const {
+    return dalvik_cache_;
+  }
+
+  const std::string& GetCacheFilename() const {
+    return cache_filename_;
+  }
+
+  bool LoadFromSystem(/*out*/ std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces,
+                      /*out*/ uint8_t** oat_file_end,
+                      /*out*/ std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) {
+    std::string filename = GetSystemImageFilename(image_location_.c_str(), image_isa_);
+    std::vector<std::string> locations;
+    if (!GetBootClassPathImageLocations(image_location_, filename, &locations, error_msg)) {
+      return false;
+    }
+    std::vector<std::unique_ptr<ImageSpace>> spaces;
+    spaces.reserve(locations.size());
+    for (const std::string& location : locations) {
+      filename = GetSystemImageFilename(location.c_str(), image_isa_);
+      spaces.push_back(Loader::Load(location,
+                                    filename,
+                                    is_zygote_,
+                                    is_global_cache_,
+                                    /* validate_oat_file */ false,
+                                    error_msg));
+      if (spaces.back() == nullptr) {
+        return false;
+      }
+    }
+    *oat_file_end = GetOatFileEnd(spaces);
+    boot_image_spaces->swap(spaces);
+    return true;
+  }
+
+  bool LoadFromDalvikCache(
+      bool validate_system_checksums,
+      bool validate_oat_file,
+      /*out*/ std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces,
+      /*out*/ uint8_t** oat_file_end,
+      /*out*/ std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK(DalvikCacheExists());
+    std::vector<std::string> locations;
+    if (!GetBootClassPathImageLocations(image_location_, cache_filename_, &locations, error_msg)) {
+      return false;
+    }
+    std::vector<std::unique_ptr<ImageSpace>> spaces;
+    spaces.reserve(locations.size());
+    for (const std::string& location : locations) {
+      std::string filename;
+      if (!GetDalvikCacheFilename(location.c_str(), dalvik_cache_.c_str(), &filename, error_msg)) {
+        return false;
+      }
+      spaces.push_back(Loader::Load(location,
+                                    filename,
+                                    is_zygote_,
+                                    is_global_cache_,
+                                    validate_oat_file,
+                                    error_msg));
+      if (spaces.back() == nullptr) {
+        return false;
+      }
+      if (validate_system_checksums) {
+        ImageHeader system_hdr;
+        std::string system_filename = GetSystemImageFilename(location.c_str(), image_isa_);
+        if (!ReadSpecificImageHeader(system_filename.c_str(), &system_hdr)) {
+          *error_msg = StringPrintf("Cannot read header of %s", system_filename.c_str());
+          return false;
+        }
+        if (spaces.back()->GetImageHeader().GetOatChecksum() != system_hdr.GetOatChecksum()) {
+          *error_msg = StringPrintf("Checksum mismatch: %u(%s) vs %u(%s)",
+                                    spaces.back()->GetImageHeader().GetOatChecksum(),
+                                    filename.c_str(),
+                                    system_hdr.GetOatChecksum(),
+                                    system_filename.c_str());
+          return false;
+        }
+      }
+    }
+    *oat_file_end = GetOatFileEnd(spaces);
+    boot_image_spaces->swap(spaces);
+    return true;
+  }
+
+ private:
+  // Extract boot class path from oat file associated with `image_filename`
+  // and list all associated image locations.
+  static bool GetBootClassPathImageLocations(const std::string& image_location,
+                                             const std::string& image_filename,
+                                             /*out*/ std::vector<std::string>* all_locations,
+                                             /*out*/ std::string* error_msg) {
+    std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_filename);
+    std::unique_ptr<OatFile> oat_file(OatFile::Open(/* zip_fd */ -1,
+                                                    oat_filename,
+                                                    oat_filename,
+                                                    /* requested_base */ nullptr,
+                                                    /* oat_file_begin */ nullptr,
+                                                    /* executable */ false,
+                                                    /* low_4gb */ false,
+                                                    /* abs_dex_location */ nullptr,
+                                                    error_msg));
+    if (oat_file == nullptr) {
+      *error_msg = StringPrintf("Failed to open oat file '%s' for image file %s: %s",
+                                oat_filename.c_str(),
+                                image_filename.c_str(),
+                                error_msg->c_str());
+      return false;
+    }
+    const OatHeader& oat_header = oat_file->GetOatHeader();
+    const char* boot_classpath = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey);
+    all_locations->push_back(image_location);
+    if (boot_classpath != nullptr && boot_classpath[0] != 0) {
+      ExtractMultiImageLocations(image_location, boot_classpath, all_locations);
+    }
+    return true;
+  }
+
+  uint8_t* GetOatFileEnd(const std::vector<std::unique_ptr<ImageSpace>>& spaces) {
+    DCHECK(std::is_sorted(
+        spaces.begin(),
+        spaces.end(),
+        [](const std::unique_ptr<ImageSpace>& lhs, const std::unique_ptr<ImageSpace>& rhs) {
+          return lhs->GetOatFileEnd() < rhs->GetOatFileEnd();
+        }));
+    return AlignUp(spaces.back()->GetOatFileEnd(), kPageSize);
+  }
+
+  const std::string& image_location_;
+  InstructionSet image_isa_;
+  bool is_zygote_;
+  bool has_system_;
+  bool has_cache_;
+  bool is_global_cache_;
+  bool dalvik_cache_exists_;
+  std::string dalvik_cache_;
+  std::string cache_filename_;
+};
+
 static constexpr uint64_t kLowSpaceValue = 50 * MB;
 static constexpr uint64_t kTmpFsSentinelValue = 384 * MB;
 
@@ -1506,70 +1654,56 @@
   return true;
 }
 
-std::unique_ptr<ImageSpace> ImageSpace::CreateBootImage(const char* image_location,
-                                                        const InstructionSet image_isa,
-                                                        bool secondary_image,
-                                                        std::string* error_msg) {
+bool ImageSpace::LoadBootImage(
+    const std::string& image_location,
+    const InstructionSet image_isa,
+    /*out*/ std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces,
+    /*out*/ uint8_t** oat_file_end) {
   ScopedTrace trace(__FUNCTION__);
 
+  DCHECK(boot_image_spaces != nullptr);
+  DCHECK(boot_image_spaces->empty());
+  DCHECK(oat_file_end != nullptr);
+  DCHECK_NE(image_isa, InstructionSet::kNone);
+
+  if (image_location.empty()) {
+    return false;
+  }
+
+  BootImageLoader loader(image_location, image_isa);
+
   // Step 0: Extra zygote work.
 
   // Step 0.a: If we're the zygote, mark boot.
-  const bool is_zygote = Runtime::Current()->IsZygote();
-  if (is_zygote && !secondary_image && CanWriteToDalvikCache(image_isa)) {
+  if (loader.IsZygote() && CanWriteToDalvikCache(image_isa)) {
     MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots());
   }
 
+  loader.FindImageFiles();
+
   // Step 0.b: If we're the zygote, check for free space, and prune the cache preemptively,
   //           if necessary. While the runtime may be fine (it is pretty tolerant to
   //           out-of-disk-space situations), other parts of the platform are not.
   //
   //           The advantage of doing this proactively is that the later steps are simplified,
   //           i.e., we do not need to code retries.
-  std::string system_filename;
-  bool has_system = false;
-  std::string cache_filename;
-  bool has_cache = false;
-  bool dalvik_cache_exists = false;
-  bool is_global_cache = true;
-  std::string dalvik_cache;
-  bool found_image = FindImageFilenameImpl(image_location,
-                                           image_isa,
-                                           &has_system,
-                                           &system_filename,
-                                           &dalvik_cache_exists,
-                                           &dalvik_cache,
-                                           &is_global_cache,
-                                           &has_cache,
-                                           &cache_filename);
-
   bool dex2oat_enabled = Runtime::Current()->IsImageDex2OatEnabled();
 
-  if (is_zygote && dalvik_cache_exists && !secondary_image) {
+  if (loader.IsZygote() && loader.DalvikCacheExists()) {
     // Extra checks for the zygote. These only apply when loading the first image, explained below.
+    const std::string& dalvik_cache = loader.GetDalvikCache();
     DCHECK(!dalvik_cache.empty());
     std::string local_error_msg;
     // All secondary images are verified when the primary image is verified.
-    bool verified = VerifyImage(image_location, dalvik_cache.c_str(), image_isa, &local_error_msg);
-    // If we prune for space at a secondary image, we may end up in a crash loop with the _exit
-    // path.
+    bool verified =
+        VerifyImage(image_location.c_str(), dalvik_cache.c_str(), image_isa, &local_error_msg);
     bool check_space = CheckSpace(dalvik_cache, &local_error_msg);
     if (!verified || !check_space) {
-      // Note: it is important to only prune for space on the primary image, or we will hit the
-      //       restart path.
       LOG(WARNING) << local_error_msg << " Preemptively pruning the dalvik cache.";
       PruneDalvikCache(image_isa);
 
       // Re-evaluate the image.
-      found_image = FindImageFilenameImpl(image_location,
-                                          image_isa,
-                                          &has_system,
-                                          &system_filename,
-                                          &dalvik_cache_exists,
-                                          &dalvik_cache,
-                                          &is_global_cache,
-                                          &has_cache,
-                                          &cache_filename);
+      loader.FindImageFiles();
     }
     if (!check_space) {
       // Disable compilation/patching - we do not want to fill up the space again.
@@ -1580,39 +1714,16 @@
   // Collect all the errors.
   std::vector<std::string> error_msgs;
 
-  // Step 1: Check if we have an existing and relocated image.
-
-  // Step 1.a: Have files in system and cache. Then they need to match.
-  if (found_image && has_system && has_cache) {
+  // Step 1: Check if we have an existing image in the dalvik cache.
+  if (loader.HasCache()) {
     std::string local_error_msg;
-    // Check that the files are matching.
-    if (ChecksumsMatch(system_filename.c_str(), cache_filename.c_str(), &local_error_msg)) {
-      std::unique_ptr<ImageSpace> relocated_space =
-          ImageSpaceLoader::Load(image_location,
-                                 cache_filename,
-                                 is_zygote,
-                                 is_global_cache,
-                                 /* validate_oat_file */ false,
-                                 &local_error_msg);
-      if (relocated_space != nullptr) {
-        return relocated_space;
-      }
-    }
-    error_msgs.push_back(local_error_msg);
-  }
-
-  // Step 1.b: Only have a cache file.
-  if (found_image && !has_system && has_cache) {
-    std::string local_error_msg;
-    std::unique_ptr<ImageSpace> cache_space =
-        ImageSpaceLoader::Load(image_location,
-                               cache_filename,
-                               is_zygote,
-                               is_global_cache,
-                               /* validate_oat_file */ true,
-                               &local_error_msg);
-    if (cache_space != nullptr) {
-      return cache_space;
+    // If we have system image, validate system image checksums, otherwise validate the oat file.
+    if (loader.LoadFromDalvikCache(/* validate_system_checksums */ loader.HasSystem(),
+                                   /* validate_oat_file */ !loader.HasSystem(),
+                                   boot_image_spaces,
+                                   oat_file_end,
+                                   &local_error_msg)) {
+      return true;
     }
     error_msgs.push_back(local_error_msg);
   }
@@ -1622,83 +1733,64 @@
   // Step 2.a: We are not required to relocate it. Then we can use it directly.
   bool relocate = Runtime::Current()->ShouldRelocate();
 
-  if (found_image && has_system && !relocate) {
+  if (loader.HasSystem() && !relocate) {
     std::string local_error_msg;
-    std::unique_ptr<ImageSpace> system_space =
-        ImageSpaceLoader::Load(image_location,
-                               system_filename,
-                               is_zygote,
-                               is_global_cache,
-                               /* validate_oat_file */ false,
-                               &local_error_msg);
-    if (system_space != nullptr) {
-      return system_space;
+    if (loader.LoadFromSystem(boot_image_spaces, oat_file_end, &local_error_msg)) {
+      return true;
     }
     error_msgs.push_back(local_error_msg);
   }
 
-  // Step 2.b: We require a relocated image. Then we must patch it. This step fails if this is a
-  //           secondary image.
-  if (found_image && has_system && relocate) {
+  // Step 2.b: We require a relocated image. Then we must patch it.
+  if (loader.HasSystem() && relocate) {
     std::string local_error_msg;
     if (!dex2oat_enabled) {
       local_error_msg = "Patching disabled.";
-    } else if (secondary_image) {
-      // We really want a working image. Prune and restart.
-      PruneDalvikCache(image_isa);
-      _exit(1);
-    } else if (ImageCreationAllowed(is_global_cache, image_isa, &local_error_msg)) {
-      bool patch_success =
-          RelocateImage(image_location, dalvik_cache.c_str(), image_isa, &local_error_msg);
+    } else if (ImageCreationAllowed(loader.IsGlobalCache(), image_isa, &local_error_msg)) {
+      bool patch_success = RelocateImage(
+          image_location.c_str(), loader.GetDalvikCache().c_str(), image_isa, &local_error_msg);
       if (patch_success) {
-        std::unique_ptr<ImageSpace> patched_space =
-            ImageSpaceLoader::Load(image_location,
-                                   cache_filename,
-                                   is_zygote,
-                                   is_global_cache,
-                                   /* validate_oat_file */ false,
-                                   &local_error_msg);
-        if (patched_space != nullptr) {
-          return patched_space;
+        if (loader.LoadFromDalvikCache(/* validate_system_checksums */ false,
+                                       /* validate_oat_file */ false,
+                                       boot_image_spaces,
+                                       oat_file_end,
+                                       &local_error_msg)) {
+          return true;
         }
       }
     }
     error_msgs.push_back(StringPrintf("Cannot relocate image %s to %s: %s",
-                                      image_location,
-                                      cache_filename.c_str(),
+                                      image_location.c_str(),
+                                      loader.GetCacheFilename().c_str(),
                                       local_error_msg.c_str()));
   }
 
-  // Step 3: We do not have an existing image in /system, so generate an image into the dalvik
-  //         cache. This step fails if this is a secondary image.
-  if (!has_system) {
+  // Step 3: We do not have an existing image in /system,
+  //         so generate an image into the dalvik cache.
+  if (!loader.HasSystem() && loader.DalvikCacheExists()) {
     std::string local_error_msg;
     if (!dex2oat_enabled) {
       local_error_msg = "Image compilation disabled.";
-    } else if (secondary_image) {
-      local_error_msg = "Cannot compile a secondary image.";
-    } else if (ImageCreationAllowed(is_global_cache, image_isa, &local_error_msg)) {
-      bool compilation_success = GenerateImage(cache_filename, image_isa, &local_error_msg);
+    } else if (ImageCreationAllowed(loader.IsGlobalCache(), image_isa, &local_error_msg)) {
+      bool compilation_success =
+          GenerateImage(loader.GetCacheFilename(), image_isa, &local_error_msg);
       if (compilation_success) {
-        std::unique_ptr<ImageSpace> compiled_space =
-            ImageSpaceLoader::Load(image_location,
-                                   cache_filename,
-                                   is_zygote,
-                                   is_global_cache,
-                                   /* validate_oat_file */ false,
-                                   &local_error_msg);
-        if (compiled_space != nullptr) {
-          return compiled_space;
+        if (loader.LoadFromDalvikCache(/* validate_system_checksums */ false,
+                                       /* validate_oat_file */ false,
+                                       boot_image_spaces,
+                                       oat_file_end,
+                                       &local_error_msg)) {
+          return true;
         }
       }
     }
     error_msgs.push_back(StringPrintf("Cannot compile image to %s: %s",
-                                      cache_filename.c_str(),
+                                      loader.GetCacheFilename().c_str(),
                                       local_error_msg.c_str()));
   }
 
-  // We failed. Prune the cache the free up space, create a compound error message and return no
-  // image.
+  // We failed. Prune the cache the free up space, create a compound error message
+  // and return false.
   PruneDalvikCache(image_isa);
 
   std::ostringstream oss;
@@ -1709,84 +1801,11 @@
     }
     oss << msg;
   }
-  *error_msg = oss.str();
 
-  return nullptr;
-}
+  LOG(ERROR) << "Could not create image space with image file '" << image_location << "'. "
+      << "Attempting to fall back to imageless running. Error was: " << oss.str();
 
-bool ImageSpace::LoadBootImage(const std::string& image_file_name,
-                               const InstructionSet image_instruction_set,
-                               std::vector<space::ImageSpace*>* boot_image_spaces,
-                               uint8_t** oat_file_end) {
-  DCHECK(boot_image_spaces != nullptr);
-  DCHECK(boot_image_spaces->empty());
-  DCHECK(oat_file_end != nullptr);
-  DCHECK_NE(image_instruction_set, InstructionSet::kNone);
-
-  if (image_file_name.empty()) {
-    return false;
-  }
-
-  // For code reuse, handle this like a work queue.
-  std::vector<std::string> image_file_names;
-  image_file_names.push_back(image_file_name);
-
-  bool error = false;
-  uint8_t* oat_file_end_tmp = *oat_file_end;
-
-  for (size_t index = 0; index < image_file_names.size(); ++index) {
-    std::string& image_name = image_file_names[index];
-    std::string error_msg;
-    std::unique_ptr<space::ImageSpace> boot_image_space_uptr = CreateBootImage(
-        image_name.c_str(),
-        image_instruction_set,
-        index > 0,
-        &error_msg);
-    if (boot_image_space_uptr != nullptr) {
-      space::ImageSpace* boot_image_space = boot_image_space_uptr.release();
-      boot_image_spaces->push_back(boot_image_space);
-      // Oat files referenced by image files immediately follow them in memory, ensure alloc space
-      // isn't going to get in the middle
-      uint8_t* oat_file_end_addr = boot_image_space->GetImageHeader().GetOatFileEnd();
-      CHECK_GT(oat_file_end_addr, boot_image_space->End());
-      oat_file_end_tmp = AlignUp(oat_file_end_addr, kPageSize);
-
-      if (index == 0) {
-        // If this was the first space, check whether there are more images to load.
-        const OatFile* boot_oat_file = boot_image_space->GetOatFile();
-        if (boot_oat_file == nullptr) {
-          continue;
-        }
-
-        const OatHeader& boot_oat_header = boot_oat_file->GetOatHeader();
-        const char* boot_classpath =
-            boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey);
-        if (boot_classpath == nullptr) {
-          continue;
-        }
-
-        ExtractMultiImageLocations(image_file_name, boot_classpath, &image_file_names);
-      }
-    } else {
-      error = true;
-      LOG(ERROR) << "Could not create image space with image file '" << image_file_name << "'. "
-          << "Attempting to fall back to imageless running. Error was: " << error_msg
-          << "\nAttempted image: " << image_name;
-      break;
-    }
-  }
-
-  if (error) {
-    // Remove already loaded spaces.
-    for (space::Space* loaded_space : *boot_image_spaces) {
-      delete loaded_space;
-    }
-    boot_image_spaces->clear();
-    return false;
-  }
-
-  *oat_file_end = oat_file_end_tmp;
-  return true;
+  return false;
 }
 
 ImageSpace::~ImageSpace() {
@@ -1815,11 +1834,7 @@
 std::unique_ptr<ImageSpace> ImageSpace::CreateFromAppImage(const char* image,
                                                            const OatFile* oat_file,
                                                            std::string* error_msg) {
-  return ImageSpaceLoader::Init(image,
-                                image,
-                                /*validate_oat_file*/false,
-                                oat_file,
-                                /*out*/error_msg);
+  return Loader::Init(image, image, /*validate_oat_file*/false, oat_file, /*out*/error_msg);
 }
 
 const OatFile* ImageSpace::GetOatFile() const {
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index 3383d6b..20bce66 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -41,11 +41,11 @@
   // On successful return, the loaded spaces are added to boot_image_spaces (which must be
   // empty on entry) and oat_file_end is updated with the (page-aligned) end of the last
   // oat file.
-  static bool LoadBootImage(const std::string& image_file_name,
-                            const InstructionSet image_instruction_set,
-                            std::vector<space::ImageSpace*>* boot_image_spaces,
-                            uint8_t** oat_file_end)
-      REQUIRES_SHARED(Locks::mutator_lock_);
+  static bool LoadBootImage(
+      const std::string& image_location,
+      const InstructionSet image_isa,
+      /*out*/ std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces,
+      /*out*/ uint8_t** oat_file_end) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Try to open an existing app image space.
   static std::unique_ptr<ImageSpace> CreateFromAppImage(const char* image,
@@ -182,7 +182,7 @@
 
   ImageSpace(const std::string& name,
              const char* image_location,
-             MemMap* mem_map,
+             MemMap&& mem_map,
              accounting::ContinuousSpaceBitmap* live_bitmap,
              uint8_t* end);
 
@@ -197,23 +197,11 @@
 
   const std::string image_location_;
 
-  friend class ImageSpaceLoader;
   friend class Space;
 
  private:
-  // Create a boot image space from an image file for a specified instruction
-  // set. Cannot be used for future allocation or collected.
-  //
-  // Create also opens the OatFile associated with the image file so
-  // that it be contiguously allocated with the image before the
-  // creation of the alloc space. The ReleaseOatFile will later be
-  // used to transfer ownership of the OatFile to the ClassLinker when
-  // it is initialized.
-  static std::unique_ptr<ImageSpace> CreateBootImage(const char* image,
-                                     InstructionSet image_isa,
-                                     bool secondary_image,
-                                     std::string* error_msg)
-      REQUIRES_SHARED(Locks::mutator_lock_);
+  class Loader;
+  class BootImageLoader;
 
   DISALLOW_COPY_AND_ASSIGN(ImageSpace);
 };
diff --git a/runtime/gc/space/image_space_test.cc b/runtime/gc/space/image_space_test.cc
index f202a43..a1ffa06 100644
--- a/runtime/gc/space/image_space_test.cc
+++ b/runtime/gc/space/image_space_test.cc
@@ -150,6 +150,48 @@
   EXPECT_FALSE(Runtime::Current()->GetHeap()->GetBootImageSpaces().empty());
 }
 
+class NoAccessAndroidDataTest : public ImageSpaceLoadingTest<false, true, false, true> {
+ protected:
+  void SetUpRuntimeOptions(RuntimeOptions* options) OVERRIDE {
+    const char* android_data = getenv("ANDROID_DATA");
+    CHECK(android_data != nullptr);
+    old_android_data_ = android_data;
+    bad_android_data_ = old_android_data_ + "/no-android-data";
+    int result = setenv("ANDROID_DATA", bad_android_data_.c_str(), /* replace */ 1);
+    CHECK_EQ(result, 0) << strerror(errno);
+    result = mkdir(bad_android_data_.c_str(), /* mode */ 0700);
+    CHECK_EQ(result, 0) << strerror(errno);
+    // Create a regular file "dalvik_cache". GetDalvikCache() shall get EEXIST
+    // when trying to create a directory with the same name and creating a
+    // subdirectory for a particular architecture shall fail.
+    bad_dalvik_cache_ = bad_android_data_ + "/dalvik-cache";
+    int fd = creat(bad_dalvik_cache_.c_str(), /* mode */ 0);
+    CHECK_NE(fd, -1) << strerror(errno);
+    result = close(fd);
+    CHECK_EQ(result, 0) << strerror(errno);
+    ImageSpaceLoadingTest<false, true, false, true>::SetUpRuntimeOptions(options);
+  }
+
+  void TearDown() OVERRIDE {
+    int result = unlink(bad_dalvik_cache_.c_str());
+    CHECK_EQ(result, 0) << strerror(errno);
+    result = rmdir(bad_android_data_.c_str());
+    CHECK_EQ(result, 0) << strerror(errno);
+    result = setenv("ANDROID_DATA", old_android_data_.c_str(), /* replace */ 1);
+    CHECK_EQ(result, 0) << strerror(errno);
+    ImageSpaceLoadingTest<false, true, false, true>::TearDown();
+  }
+
+ private:
+  std::string old_android_data_;
+  std::string bad_android_data_;
+  std::string bad_dalvik_cache_;
+};
+
+TEST_F(NoAccessAndroidDataTest, Test) {
+  EXPECT_TRUE(Runtime::Current()->GetHeap()->GetBootImageSpaces().empty());
+}
+
 }  // namespace space
 }  // namespace gc
 }  // namespace art
diff --git a/runtime/gc/space/large_object_space.cc b/runtime/gc/space/large_object_space.cc
index a24ca32..76ea9fd 100644
--- a/runtime/gc/space/large_object_space.cc
+++ b/runtime/gc/space/large_object_space.cc
@@ -48,10 +48,6 @@
     // Historical note: We were deleting large objects to keep Valgrind happy if there were
     // any large objects such as Dex cache arrays which aren't freed since they are held live
     // by the class linker.
-    MutexLock mu(Thread::Current(), lock_);
-    for (auto& m : large_objects_) {
-      delete m.second.mem_map;
-    }
   }
 
   mirror::Object* Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated,
@@ -139,16 +135,20 @@
                                            size_t* bytes_allocated, size_t* usable_size,
                                            size_t* bytes_tl_bulk_allocated) {
   std::string error_msg;
-  MemMap* mem_map = MemMap::MapAnonymous("large object space allocation", nullptr, num_bytes,
-                                         PROT_READ | PROT_WRITE, true, false, &error_msg);
-  if (UNLIKELY(mem_map == nullptr)) {
+  MemMap mem_map = MemMap::MapAnonymous("large object space allocation",
+                                        /* addr */ nullptr,
+                                        num_bytes,
+                                        PROT_READ | PROT_WRITE,
+                                        /* low_4gb */ true,
+                                        &error_msg);
+  if (UNLIKELY(!mem_map.IsValid())) {
     LOG(WARNING) << "Large object allocation failed: " << error_msg;
     return nullptr;
   }
-  mirror::Object* const obj = reinterpret_cast<mirror::Object*>(mem_map->Begin());
+  mirror::Object* const obj = reinterpret_cast<mirror::Object*>(mem_map.Begin());
+  const size_t allocation_size = mem_map.BaseSize();
   MutexLock mu(self, lock_);
-  large_objects_.Put(obj, LargeObject {mem_map, false /* not zygote */});
-  const size_t allocation_size = mem_map->BaseSize();
+  large_objects_.Put(obj, LargeObject {std::move(mem_map), false /* not zygote */});
   DCHECK(bytes_allocated != nullptr);
 
   if (begin_ == nullptr || begin_ > reinterpret_cast<uint8_t*>(obj)) {
@@ -191,13 +191,11 @@
     Runtime::Current()->GetHeap()->DumpSpaces(LOG_STREAM(FATAL_WITHOUT_ABORT));
     LOG(FATAL) << "Attempted to free large object " << ptr << " which was not live";
   }
-  MemMap* mem_map = it->second.mem_map;
-  const size_t map_size = mem_map->BaseSize();
+  const size_t map_size = it->second.mem_map.BaseSize();
   DCHECK_GE(num_bytes_allocated_, map_size);
   size_t allocation_size = map_size;
   num_bytes_allocated_ -= allocation_size;
   --num_objects_allocated_;
-  delete mem_map;
   large_objects_.erase(it);
   return allocation_size;
 }
@@ -206,7 +204,7 @@
   MutexLock mu(Thread::Current(), lock_);
   auto it = large_objects_.find(obj);
   CHECK(it != large_objects_.end()) << "Attempted to get size of a large object which is not live";
-  size_t alloc_size = it->second.mem_map->BaseSize();
+  size_t alloc_size = it->second.mem_map.BaseSize();
   if (usable_size != nullptr) {
     *usable_size = alloc_size;
   }
@@ -227,7 +225,7 @@
 void LargeObjectMapSpace::Walk(DlMallocSpace::WalkCallback callback, void* arg) {
   MutexLock mu(Thread::Current(), lock_);
   for (auto& pair : large_objects_) {
-    MemMap* mem_map = pair.second.mem_map;
+    MemMap* mem_map = &pair.second.mem_map;
     callback(mem_map->Begin(), mem_map->End(), mem_map->Size(), arg);
     callback(nullptr, nullptr, 0, arg);
   }
@@ -326,7 +324,7 @@
 
 size_t FreeListSpace::GetSlotIndexForAllocationInfo(const AllocationInfo* info) const {
   DCHECK_GE(info, allocation_info_);
-  DCHECK_LT(info, reinterpret_cast<AllocationInfo*>(allocation_info_map_->End()));
+  DCHECK_LT(info, reinterpret_cast<AllocationInfo*>(allocation_info_map_.End()));
   return info - allocation_info_;
 }
 
@@ -350,28 +348,37 @@
 FreeListSpace* FreeListSpace::Create(const std::string& name, uint8_t* requested_begin, size_t size) {
   CHECK_EQ(size % kAlignment, 0U);
   std::string error_msg;
-  MemMap* mem_map = MemMap::MapAnonymous(name.c_str(), requested_begin, size,
-                                         PROT_READ | PROT_WRITE, true, false, &error_msg);
-  CHECK(mem_map != nullptr) << "Failed to allocate large object space mem map: " << error_msg;
-  return new FreeListSpace(name, mem_map, mem_map->Begin(), mem_map->End());
+  MemMap mem_map = MemMap::MapAnonymous(name.c_str(),
+                                        requested_begin,
+                                        size,
+                                        PROT_READ | PROT_WRITE,
+                                        /* low_4gb */ true,
+                                        &error_msg);
+  CHECK(mem_map.IsValid()) << "Failed to allocate large object space mem map: " << error_msg;
+  return new FreeListSpace(name, std::move(mem_map), mem_map.Begin(), mem_map.End());
 }
 
-FreeListSpace::FreeListSpace(const std::string& name, MemMap* mem_map, uint8_t* begin, uint8_t* end)
+FreeListSpace::FreeListSpace(const std::string& name,
+                             MemMap&& mem_map,
+                             uint8_t* begin,
+                             uint8_t* end)
     : LargeObjectSpace(name, begin, end),
-      mem_map_(mem_map),
+      mem_map_(std::move(mem_map)),
       lock_("free list space lock", kAllocSpaceLock) {
   const size_t space_capacity = end - begin;
   free_end_ = space_capacity;
   CHECK_ALIGNED(space_capacity, kAlignment);
   const size_t alloc_info_size = sizeof(AllocationInfo) * (space_capacity / kAlignment);
   std::string error_msg;
-  allocation_info_map_.reset(
+  allocation_info_map_ =
       MemMap::MapAnonymous("large object free list space allocation info map",
-                           nullptr, alloc_info_size, PROT_READ | PROT_WRITE,
-                           false, false, &error_msg));
-  CHECK(allocation_info_map_.get() != nullptr) << "Failed to allocate allocation info map"
-      << error_msg;
-  allocation_info_ = reinterpret_cast<AllocationInfo*>(allocation_info_map_->Begin());
+                           /* addr */ nullptr,
+                           alloc_info_size,
+                           PROT_READ | PROT_WRITE,
+                           /* low_4gb */ false,
+                           &error_msg);
+  CHECK(allocation_info_map_.IsValid()) << "Failed to allocate allocation info map" << error_msg;
+  allocation_info_ = reinterpret_cast<AllocationInfo*>(allocation_info_map_.Begin());
 }
 
 FreeListSpace::~FreeListSpace() {}
diff --git a/runtime/gc/space/large_object_space.h b/runtime/gc/space/large_object_space.h
index f37d814..b69bd91 100644
--- a/runtime/gc/space/large_object_space.h
+++ b/runtime/gc/space/large_object_space.h
@@ -148,7 +148,7 @@
 
  protected:
   struct LargeObject {
-    MemMap* mem_map;
+    MemMap mem_map;
     bool is_zygote;
   };
   explicit LargeObjectMapSpace(const std::string& name);
@@ -182,7 +182,7 @@
   std::pair<uint8_t*, uint8_t*> GetBeginEndAtomic() const OVERRIDE REQUIRES(!lock_);
 
  protected:
-  FreeListSpace(const std::string& name, MemMap* mem_map, uint8_t* begin, uint8_t* end);
+  FreeListSpace(const std::string& name, MemMap&& mem_map, uint8_t* begin, uint8_t* end);
   size_t GetSlotIndexForAddress(uintptr_t address) const {
     DCHECK(Contains(reinterpret_cast<mirror::Object*>(address)));
     return (address - reinterpret_cast<uintptr_t>(Begin())) / kAlignment;
@@ -210,9 +210,9 @@
 
   // There is not footer for any allocations at the end of the space, so we keep track of how much
   // free space there is at the end manually.
-  std::unique_ptr<MemMap> mem_map_;
+  MemMap mem_map_;
   // Side table for allocation info, one per page.
-  std::unique_ptr<MemMap> allocation_info_map_;
+  MemMap allocation_info_map_;
   AllocationInfo* allocation_info_;
 
   mutable Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
diff --git a/runtime/gc/space/malloc_space.cc b/runtime/gc/space/malloc_space.cc
index 0965560..445560a 100644
--- a/runtime/gc/space/malloc_space.cc
+++ b/runtime/gc/space/malloc_space.cc
@@ -40,19 +40,26 @@
 
 size_t MallocSpace::bitmap_index_ = 0;
 
-MallocSpace::MallocSpace(const std::string& name, MemMap* mem_map,
-                         uint8_t* begin, uint8_t* end, uint8_t* limit, size_t growth_limit,
-                         bool create_bitmaps, bool can_move_objects, size_t starting_size,
+MallocSpace::MallocSpace(const std::string& name,
+                         MemMap&& mem_map,
+                         uint8_t* begin,
+                         uint8_t* end,
+                         uint8_t* limit,
+                         size_t growth_limit,
+                         bool create_bitmaps,
+                         bool can_move_objects,
+                         size_t starting_size,
                          size_t initial_size)
-    : ContinuousMemMapAllocSpace(name, mem_map, begin, end, limit, kGcRetentionPolicyAlwaysCollect),
+    : ContinuousMemMapAllocSpace(
+        name, std::move(mem_map), begin, end, limit, kGcRetentionPolicyAlwaysCollect),
       recent_free_pos_(0), lock_("allocation space lock", kAllocSpaceLock),
       growth_limit_(growth_limit), can_move_objects_(can_move_objects),
       starting_size_(starting_size), initial_size_(initial_size) {
   if (create_bitmaps) {
     size_t bitmap_index = bitmap_index_++;
     static const uintptr_t kGcCardSize = static_cast<uintptr_t>(accounting::CardTable::kCardSize);
-    CHECK_ALIGNED(reinterpret_cast<uintptr_t>(mem_map->Begin()), kGcCardSize);
-    CHECK_ALIGNED(reinterpret_cast<uintptr_t>(mem_map->End()), kGcCardSize);
+    CHECK_ALIGNED(reinterpret_cast<uintptr_t>(mem_map_.Begin()), kGcCardSize);
+    CHECK_ALIGNED(reinterpret_cast<uintptr_t>(mem_map_.End()), kGcCardSize);
     live_bitmap_.reset(accounting::ContinuousSpaceBitmap::Create(
         StringPrintf("allocspace %s live-bitmap %d", name.c_str(), static_cast<int>(bitmap_index)),
         Begin(), NonGrowthLimitCapacity()));
@@ -70,8 +77,12 @@
   }
 }
 
-MemMap* MallocSpace::CreateMemMap(const std::string& name, size_t starting_size, size_t* initial_size,
-                                  size_t* growth_limit, size_t* capacity, uint8_t* requested_begin) {
+MemMap MallocSpace::CreateMemMap(const std::string& name,
+                                 size_t starting_size,
+                                 size_t* initial_size,
+                                 size_t* growth_limit,
+                                 size_t* capacity,
+                                 uint8_t* requested_begin) {
   // Sanity check arguments
   if (starting_size > *initial_size) {
     *initial_size = starting_size;
@@ -80,13 +91,13 @@
     LOG(ERROR) << "Failed to create alloc space (" << name << ") where the initial size ("
         << PrettySize(*initial_size) << ") is larger than its capacity ("
         << PrettySize(*growth_limit) << ")";
-    return nullptr;
+    return MemMap::Invalid();
   }
   if (*growth_limit > *capacity) {
     LOG(ERROR) << "Failed to create alloc space (" << name << ") where the growth limit capacity ("
         << PrettySize(*growth_limit) << ") is larger than the capacity ("
         << PrettySize(*capacity) << ")";
-    return nullptr;
+    return MemMap::Invalid();
   }
 
   // Page align growth limit and capacity which will be used to manage mmapped storage
@@ -94,9 +105,13 @@
   *capacity = RoundUp(*capacity, kPageSize);
 
   std::string error_msg;
-  MemMap* mem_map = MemMap::MapAnonymous(name.c_str(), requested_begin, *capacity,
-                                         PROT_READ | PROT_WRITE, true, false, &error_msg);
-  if (mem_map == nullptr) {
+  MemMap mem_map = MemMap::MapAnonymous(name.c_str(),
+                                        requested_begin,
+                                        *capacity,
+                                        PROT_READ | PROT_WRITE,
+                                        /* low_4gb */ true,
+                                        &error_msg);
+  if (!mem_map.IsValid()) {
     LOG(ERROR) << "Failed to allocate pages for alloc space (" << name << ") of size "
                << PrettySize(*capacity) << ": " << error_msg;
   }
@@ -194,18 +209,24 @@
   VLOG(heap) << "Capacity " << PrettySize(capacity);
   // Remap the tail.
   std::string error_msg;
-  std::unique_ptr<MemMap> mem_map(GetMemMap()->RemapAtEnd(End(), alloc_space_name,
-                                                          PROT_READ | PROT_WRITE, &error_msg));
-  CHECK(mem_map.get() != nullptr) << error_msg;
-  void* allocator = CreateAllocator(End(), starting_size_, initial_size_, capacity,
-                                    low_memory_mode);
+  MemMap mem_map = GetMemMap()->RemapAtEnd(
+      End(), alloc_space_name, PROT_READ | PROT_WRITE, &error_msg);
+  CHECK(mem_map.IsValid()) << error_msg;
+  void* allocator =
+      CreateAllocator(End(), starting_size_, initial_size_, capacity, low_memory_mode);
   // Protect memory beyond the initial size.
-  uint8_t* end = mem_map->Begin() + starting_size_;
+  uint8_t* end = mem_map.Begin() + starting_size_;
   if (capacity > initial_size_) {
     CheckedCall(mprotect, alloc_space_name, end, capacity - initial_size_, PROT_NONE);
   }
-  *out_malloc_space = CreateInstance(mem_map.release(), alloc_space_name, allocator, End(), end,
-                                     limit_, growth_limit, CanMoveObjects());
+  *out_malloc_space = CreateInstance(std::move(mem_map),
+                                     alloc_space_name,
+                                     allocator,
+                                     End(),
+                                     end,
+                                     limit_,
+                                     growth_limit,
+                                     CanMoveObjects());
   SetLimit(End());
   live_bitmap_->SetHeapLimit(reinterpret_cast<uintptr_t>(End()));
   CHECK_EQ(live_bitmap_->HeapLimit(), reinterpret_cast<uintptr_t>(End()));
@@ -247,7 +268,7 @@
     }
   }
   // Use a bulk free, that merges consecutive objects before freeing or free per object?
-  // Documentation suggests better free performance with merging, but this may be at the expensive
+  // Documentation suggests better free performance with merging, but this may be at the expense
   // of allocation.
   context->freed.objects += num_ptrs;
   context->freed.bytes += space->FreeList(self, num_ptrs, ptrs);
diff --git a/runtime/gc/space/malloc_space.h b/runtime/gc/space/malloc_space.h
index c1f4841..e4a6f15 100644
--- a/runtime/gc/space/malloc_space.h
+++ b/runtime/gc/space/malloc_space.h
@@ -113,9 +113,14 @@
 
   void SetGrowthLimit(size_t growth_limit);
 
-  virtual MallocSpace* CreateInstance(MemMap* mem_map, const std::string& name, void* allocator,
-                                      uint8_t* begin, uint8_t* end, uint8_t* limit,
-                                      size_t growth_limit, bool can_move_objects) = 0;
+  virtual MallocSpace* CreateInstance(MemMap&& mem_map,
+                                      const std::string& name,
+                                      void* allocator,
+                                      uint8_t* begin,
+                                      uint8_t* end,
+                                      uint8_t* limit,
+                                      size_t growth_limit,
+                                      bool can_move_objects) = 0;
 
   // Splits ourself into a zygote space and new malloc space which has our unused memory. When true,
   // the low memory mode argument specifies that the heap wishes the created space to be more
@@ -137,12 +142,23 @@
   }
 
  protected:
-  MallocSpace(const std::string& name, MemMap* mem_map, uint8_t* begin, uint8_t* end,
-              uint8_t* limit, size_t growth_limit, bool create_bitmaps, bool can_move_objects,
-              size_t starting_size, size_t initial_size);
+  MallocSpace(const std::string& name,
+              MemMap&& mem_map,
+              uint8_t* begin,
+              uint8_t* end,
+              uint8_t* limit,
+              size_t growth_limit,
+              bool create_bitmaps,
+              bool can_move_objects,
+              size_t starting_size,
+              size_t initial_size);
 
-  static MemMap* CreateMemMap(const std::string& name, size_t starting_size, size_t* initial_size,
-                              size_t* growth_limit, size_t* capacity, uint8_t* requested_begin);
+  static MemMap CreateMemMap(const std::string& name,
+                             size_t starting_size,
+                             size_t* initial_size,
+                             size_t* growth_limit,
+                             size_t* capacity,
+                             uint8_t* requested_begin);
 
   // When true the low memory mode argument specifies that the heap wishes the created allocator to
   // be more aggressive in releasing unused pages.
diff --git a/runtime/gc/space/memory_tool_malloc_space-inl.h b/runtime/gc/space/memory_tool_malloc_space-inl.h
index c022171..f1c1cb8 100644
--- a/runtime/gc/space/memory_tool_malloc_space-inl.h
+++ b/runtime/gc/space/memory_tool_malloc_space-inl.h
@@ -267,8 +267,8 @@
                       kMemoryToolRedZoneBytes,
                       kAdjustForRedzoneInAllocSize,
                       kUseObjSizeForUsable>::MemoryToolMallocSpace(
-                          MemMap* mem_map, size_t initial_size, Params... params)
-                          : S(mem_map, initial_size, params...) {
+                          MemMap&& mem_map, size_t initial_size, Params... params)
+                          : S(std::move(mem_map), initial_size, params...) {
   // Don't want to change the memory tool states of the mem map here as the allocator is already
   // initialized at this point and that may interfere with what the allocator does internally. Note
   // that the tail beyond the initial size is mprotected.
diff --git a/runtime/gc/space/memory_tool_malloc_space.h b/runtime/gc/space/memory_tool_malloc_space.h
index e53f009..32bd204 100644
--- a/runtime/gc/space/memory_tool_malloc_space.h
+++ b/runtime/gc/space/memory_tool_malloc_space.h
@@ -53,7 +53,7 @@
   size_t MaxBytesBulkAllocatedFor(size_t num_bytes) OVERRIDE;
 
   template <typename... Params>
-  MemoryToolMallocSpace(MemMap* mem_map, size_t initial_size, Params... params);
+  MemoryToolMallocSpace(MemMap&& mem_map, size_t initial_size, Params... params);
   virtual ~MemoryToolMallocSpace() {}
 
  private:
diff --git a/runtime/gc/space/region_space-inl.h b/runtime/gc/space/region_space-inl.h
index c6ec174..e048515 100644
--- a/runtime/gc/space/region_space-inl.h
+++ b/runtime/gc/space/region_space-inl.h
@@ -247,14 +247,14 @@
                                                /* out */ size_t* bytes_tl_bulk_allocated) {
   DCHECK_ALIGNED(num_bytes, kAlignment);
   DCHECK_GT(num_bytes, kRegionSize);
-  size_t num_regs = RoundUp(num_bytes, kRegionSize) / kRegionSize;
-  DCHECK_GT(num_regs, 0U);
-  DCHECK_LT((num_regs - 1) * kRegionSize, num_bytes);
-  DCHECK_LE(num_bytes, num_regs * kRegionSize);
+  size_t num_regs_in_large_region = RoundUp(num_bytes, kRegionSize) / kRegionSize;
+  DCHECK_GT(num_regs_in_large_region, 0U);
+  DCHECK_LT((num_regs_in_large_region - 1) * kRegionSize, num_bytes);
+  DCHECK_LE(num_bytes, num_regs_in_large_region * kRegionSize);
   MutexLock mu(Thread::Current(), region_lock_);
   if (!kForEvac) {
     // Retain sufficient free regions for full evacuation.
-    if ((num_non_free_regions_ + num_regs) * 2 > num_regions_) {
+    if ((num_non_free_regions_ + num_regs_in_large_region) * 2 > num_regions_) {
       return nullptr;
     }
   }
@@ -265,7 +265,7 @@
     size_t next_region1 = -1;
     mirror::Object* region1 = AllocLargeInRange<kForEvac>(cyclic_alloc_region_index_,
                                                           num_regions_,
-                                                          num_regs,
+                                                          num_regs_in_large_region,
                                                           bytes_allocated,
                                                           usable_size,
                                                           bytes_tl_bulk_allocated,
@@ -280,16 +280,16 @@
     }
 
     // If the previous attempt failed, try to find a range of free regions within
-    // [0, cyclic_alloc_region_index_ + num_regions_ - 1).
+    // [0, min(cyclic_alloc_region_index_ + num_regs_in_large_region - 1, num_regions_)).
     size_t next_region2 = -1;
-    mirror::Object* region2 =
-        AllocLargeInRange<kForEvac>(0,
-                                    cyclic_alloc_region_index_ + num_regions_ - 1,
-                                    num_regs,
-                                    bytes_allocated,
-                                    usable_size,
-                                    bytes_tl_bulk_allocated,
-                                    &next_region2);
+    mirror::Object* region2 = AllocLargeInRange<kForEvac>(
+            0,
+            std::min(cyclic_alloc_region_index_ + num_regs_in_large_region - 1, num_regions_),
+            num_regs_in_large_region,
+            bytes_allocated,
+            usable_size,
+            bytes_tl_bulk_allocated,
+            &next_region2);
     if (region2 != nullptr) {
       DCHECK_LT(0u, next_region2);
       DCHECK_LE(next_region2, num_regions_);
@@ -302,7 +302,7 @@
     // Try to find a range of free regions within [0, num_regions_).
     mirror::Object* region = AllocLargeInRange<kForEvac>(0,
                                                          num_regions_,
-                                                         num_regs,
+                                                         num_regs_in_large_region,
                                                          bytes_allocated,
                                                          usable_size,
                                                          bytes_tl_bulk_allocated);
@@ -316,17 +316,21 @@
 template<bool kForEvac>
 inline mirror::Object* RegionSpace::AllocLargeInRange(size_t begin,
                                                       size_t end,
-                                                      size_t num_regs,
+                                                      size_t num_regs_in_large_region,
                                                       /* out */ size_t* bytes_allocated,
                                                       /* out */ size_t* usable_size,
                                                       /* out */ size_t* bytes_tl_bulk_allocated,
                                                       /* out */ size_t* next_region) {
+  DCHECK_LE(0u, begin);
+  DCHECK_LT(begin, end);
+  DCHECK_LE(end, num_regions_);
   size_t left = begin;
-  while (left + num_regs - 1 < end) {
+  while (left + num_regs_in_large_region - 1 < end) {
     bool found = true;
     size_t right = left;
-    DCHECK_LT(right, left + num_regs) << "The inner loop should iterate at least once";
-    while (right < left + num_regs) {
+    DCHECK_LT(right, left + num_regs_in_large_region)
+        << "The inner loop should iterate at least once";
+    while (right < left + num_regs_in_large_region) {
       if (regions_[right].IsFree()) {
         ++right;
         // Ensure `right` is not going beyond the past-the-end index of the region space.
@@ -338,7 +342,7 @@
     }
     if (found) {
       // `right` points to the one region past the last free region.
-      DCHECK_EQ(left + num_regs, right);
+      DCHECK_EQ(left + num_regs_in_large_region, right);
       Region* first_reg = &regions_[left];
       DCHECK(first_reg->IsFree());
       first_reg->UnfreeLarge(this, time_);
@@ -347,10 +351,14 @@
       } else {
         ++num_non_free_regions_;
       }
-      size_t allocated = num_regs * kRegionSize;
+      size_t allocated = num_regs_in_large_region * kRegionSize;
       // We make 'top' all usable bytes, as the caller of this
       // allocation may use all of 'usable_size' (see mirror::Array::Alloc).
       first_reg->SetTop(first_reg->Begin() + allocated);
+      if (!kForEvac) {
+        // Evac doesn't count as newly allocated.
+        first_reg->SetNewlyAllocated();
+      }
       for (size_t p = left + 1; p < right; ++p) {
         DCHECK_LT(p, num_regions_);
         DCHECK(regions_[p].IsFree());
@@ -360,6 +368,10 @@
         } else {
           ++num_non_free_regions_;
         }
+        if (!kForEvac) {
+          // Evac doesn't count as newly allocated.
+          regions_[p].SetNewlyAllocated();
+        }
       }
       *bytes_allocated = allocated;
       if (usable_size != nullptr) {
@@ -403,7 +415,7 @@
       --num_non_free_regions_;
     }
   }
-  if (end_addr < Limit()) {
+  if (kIsDebugBuild && end_addr < Limit()) {
     // If we aren't at the end of the space, check that the next region is not a large tail.
     Region* following_reg = RefToRegionLocked(reinterpret_cast<mirror::Object*>(end_addr));
     DCHECK(!following_reg->IsLargeTail());
diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc
index 0701330..f74fa86 100644
--- a/runtime/gc/space/region_space.cc
+++ b/runtime/gc/space/region_space.cc
@@ -16,6 +16,7 @@
 
 #include "bump_pointer_space-inl.h"
 #include "bump_pointer_space.h"
+#include "base/dumpable.h"
 #include "gc/accounting/read_barrier_table.h"
 #include "mirror/class-inl.h"
 #include "mirror/object-inl.h"
@@ -29,9 +30,8 @@
 // value of the region size, evaculate the region.
 static constexpr uint kEvacuateLivePercentThreshold = 75U;
 
-// Whether we protect the cleared regions.
-// Only protect for target builds to prevent flaky test failures (b/63131961).
-static constexpr bool kProtectClearedRegions = kIsTargetBuild;
+// Whether we protect the unused and cleared regions.
+static constexpr bool kProtectClearedRegions = true;
 
 // Wether we poison memory areas occupied by dead objects in unevacuated regions.
 static constexpr bool kPoisonDeadObjectsInUnevacuatedRegions = true;
@@ -42,60 +42,67 @@
 // points to a valid, non-protected memory area.
 static constexpr uint32_t kPoisonDeadObject = 0xBADDB01D;  // "BADDROID"
 
-MemMap* RegionSpace::CreateMemMap(const std::string& name, size_t capacity,
-                                  uint8_t* requested_begin) {
+// Whether we check a region's live bytes count against the region bitmap.
+static constexpr bool kCheckLiveBytesAgainstRegionBitmap = kIsDebugBuild;
+
+MemMap RegionSpace::CreateMemMap(const std::string& name,
+                                 size_t capacity,
+                                 uint8_t* requested_begin) {
   CHECK_ALIGNED(capacity, kRegionSize);
   std::string error_msg;
   // Ask for the capacity of an additional kRegionSize so that we can align the map by kRegionSize
   // even if we get unaligned base address. This is necessary for the ReadBarrierTable to work.
-  std::unique_ptr<MemMap> mem_map;
+  MemMap mem_map;
   while (true) {
-    mem_map.reset(MemMap::MapAnonymous(name.c_str(),
-                                       requested_begin,
-                                       capacity + kRegionSize,
-                                       PROT_READ | PROT_WRITE,
-                                       true,
-                                       false,
-                                       &error_msg));
-    if (mem_map.get() != nullptr || requested_begin == nullptr) {
+    mem_map = MemMap::MapAnonymous(name.c_str(),
+                                   requested_begin,
+                                   capacity + kRegionSize,
+                                   PROT_READ | PROT_WRITE,
+                                   /* low_4gb */ true,
+                                   &error_msg);
+    if (mem_map.IsValid() || requested_begin == nullptr) {
       break;
     }
     // Retry with no specified request begin.
     requested_begin = nullptr;
   }
-  if (mem_map.get() == nullptr) {
+  if (!mem_map.IsValid()) {
     LOG(ERROR) << "Failed to allocate pages for alloc space (" << name << ") of size "
         << PrettySize(capacity) << " with message " << error_msg;
     MemMap::DumpMaps(LOG_STREAM(ERROR));
-    return nullptr;
+    return MemMap::Invalid();
   }
-  CHECK_EQ(mem_map->Size(), capacity + kRegionSize);
-  CHECK_EQ(mem_map->Begin(), mem_map->BaseBegin());
-  CHECK_EQ(mem_map->Size(), mem_map->BaseSize());
-  if (IsAlignedParam(mem_map->Begin(), kRegionSize)) {
+  CHECK_EQ(mem_map.Size(), capacity + kRegionSize);
+  CHECK_EQ(mem_map.Begin(), mem_map.BaseBegin());
+  CHECK_EQ(mem_map.Size(), mem_map.BaseSize());
+  if (IsAlignedParam(mem_map.Begin(), kRegionSize)) {
     // Got an aligned map. Since we requested a map that's kRegionSize larger. Shrink by
     // kRegionSize at the end.
-    mem_map->SetSize(capacity);
+    mem_map.SetSize(capacity);
   } else {
     // Got an unaligned map. Align the both ends.
-    mem_map->AlignBy(kRegionSize);
+    mem_map.AlignBy(kRegionSize);
   }
-  CHECK_ALIGNED(mem_map->Begin(), kRegionSize);
-  CHECK_ALIGNED(mem_map->End(), kRegionSize);
-  CHECK_EQ(mem_map->Size(), capacity);
-  return mem_map.release();
+  CHECK_ALIGNED(mem_map.Begin(), kRegionSize);
+  CHECK_ALIGNED(mem_map.End(), kRegionSize);
+  CHECK_EQ(mem_map.Size(), capacity);
+  return mem_map;
 }
 
-RegionSpace* RegionSpace::Create(const std::string& name, MemMap* mem_map) {
-  return new RegionSpace(name, mem_map);
+RegionSpace* RegionSpace::Create(const std::string& name, MemMap&& mem_map) {
+  return new RegionSpace(name, std::move(mem_map));
 }
 
-RegionSpace::RegionSpace(const std::string& name, MemMap* mem_map)
-    : ContinuousMemMapAllocSpace(name, mem_map, mem_map->Begin(), mem_map->End(), mem_map->End(),
+RegionSpace::RegionSpace(const std::string& name, MemMap&& mem_map)
+    : ContinuousMemMapAllocSpace(name,
+                                 std::move(mem_map),
+                                 mem_map.Begin(),
+                                 mem_map.End(),
+                                 mem_map.End(),
                                  kGcRetentionPolicyAlwaysCollect),
       region_lock_("Region lock", kRegionSpaceRegionLock),
       time_(1U),
-      num_regions_(mem_map->Size() / kRegionSize),
+      num_regions_(mem_map_.Size() / kRegionSize),
       num_non_free_regions_(0U),
       num_evac_regions_(0U),
       max_peak_num_non_free_regions_(0U),
@@ -103,11 +110,11 @@
       current_region_(&full_region_),
       evac_region_(nullptr),
       cyclic_alloc_region_index_(0U) {
-  CHECK_ALIGNED(mem_map->Size(), kRegionSize);
-  CHECK_ALIGNED(mem_map->Begin(), kRegionSize);
+  CHECK_ALIGNED(mem_map_.Size(), kRegionSize);
+  CHECK_ALIGNED(mem_map_.Begin(), kRegionSize);
   DCHECK_GT(num_regions_, 0U);
   regions_.reset(new Region[num_regions_]);
-  uint8_t* region_addr = mem_map->Begin();
+  uint8_t* region_addr = mem_map_.Begin();
   for (size_t i = 0; i < num_regions_; ++i, region_addr += kRegionSize) {
     regions_[i].Init(i, region_addr, region_addr + kRegionSize);
   }
@@ -128,6 +135,8 @@
   DCHECK(full_region_.IsAllocated());
   size_t ignored;
   DCHECK(full_region_.Alloc(kAlignment, &ignored, nullptr, &ignored) == nullptr);
+  // Protect the whole region space from the start.
+  Protect();
 }
 
 size_t RegionSpace::FromSpaceSize() {
@@ -166,15 +175,54 @@
   return num_regions * kRegionSize;
 }
 
-inline bool RegionSpace::Region::ShouldBeEvacuated() {
+inline bool RegionSpace::Region::ShouldBeEvacuated(EvacMode evac_mode) {
+  // Evacuation mode `kEvacModeNewlyAllocated` is only used during sticky-bit CC collections.
+  DCHECK(kEnableGenerationalConcurrentCopyingCollection || (evac_mode != kEvacModeNewlyAllocated));
   DCHECK((IsAllocated() || IsLarge()) && IsInToSpace());
   // The region should be evacuated if:
+  // - the evacuation is forced (`evac_mode == kEvacModeForceAll`); or
   // - the region was allocated after the start of the previous GC (newly allocated region); or
   // - the live ratio is below threshold (`kEvacuateLivePercentThreshold`).
-  bool result;
+  if (UNLIKELY(evac_mode == kEvacModeForceAll)) {
+    return true;
+  }
+  bool result = false;
   if (is_newly_allocated_) {
-    result = true;
-  } else {
+    // Invariant: newly allocated regions have an undefined live bytes count.
+    DCHECK_EQ(live_bytes_, static_cast<size_t>(-1));
+    if (IsAllocated()) {
+      // We always evacuate newly-allocated non-large regions as we
+      // believe they contain many dead objects (a very simple form of
+      // the generational hypothesis, even before the Sticky-Bit CC
+      // approach).
+      //
+      // TODO: Verify that assertion by collecting statistics on the
+      // number/proportion of live objects in newly allocated regions
+      // in RegionSpace::ClearFromSpace.
+      //
+      // Note that a side effect of evacuating a newly-allocated
+      // non-large region is that the "newly allocated" status will
+      // later be removed, as its live objects will be copied to an
+      // evacuation region, which won't be marked as "newly
+      // allocated" (see RegionSpace::AllocateRegion).
+      result = true;
+    } else {
+      DCHECK(IsLarge());
+      // We never want to evacuate a large region (and the associated
+      // tail regions), except if:
+      // - we are forced to do so (see the `kEvacModeForceAll` case
+      //   above); or
+      // - we know that the (sole) object contained in this region is
+      //   dead (see the corresponding logic below, in the
+      //   `kEvacModeLivePercentNewlyAllocated` case).
+      // For a newly allocated region (i.e. allocated since the
+      // previous GC started), we don't have any liveness information
+      // (the live bytes count is -1 -- also note this region has been
+      // a to-space one between the time of its allocation and now),
+      // so we prefer not to evacuate it.
+      result = false;
+    }
+  } else if (evac_mode == kEvacModeLivePercentNewlyAllocated) {
     bool is_live_percent_valid = (live_bytes_ != static_cast<size_t>(-1));
     if (is_live_percent_valid) {
       DCHECK(IsInToSpace());
@@ -199,9 +247,48 @@
   return result;
 }
 
+void RegionSpace::ZeroLiveBytesForLargeObject(mirror::Object* obj) {
+  // This method is only used when Generational CC collection is enabled.
+  DCHECK(kEnableGenerationalConcurrentCopyingCollection);
+
+  // This code uses a logic similar to the one used in RegionSpace::FreeLarge
+  // to traverse the regions supporting `obj`.
+  // TODO: Refactor.
+  DCHECK(IsLargeObject(obj));
+  DCHECK_ALIGNED(obj, kRegionSize);
+  size_t obj_size = obj->SizeOf<kDefaultVerifyFlags>();
+  DCHECK_GT(obj_size, space::RegionSpace::kRegionSize);
+  // Size of the memory area allocated for `obj`.
+  size_t obj_alloc_size = RoundUp(obj_size, space::RegionSpace::kRegionSize);
+  uint8_t* begin_addr = reinterpret_cast<uint8_t*>(obj);
+  uint8_t* end_addr = begin_addr + obj_alloc_size;
+  DCHECK_ALIGNED(end_addr, kRegionSize);
+
+  // Zero the live bytes of the large region and large tail regions containing the object.
+  MutexLock mu(Thread::Current(), region_lock_);
+  for (uint8_t* addr = begin_addr; addr < end_addr; addr += kRegionSize) {
+    Region* region = RefToRegionLocked(reinterpret_cast<mirror::Object*>(addr));
+    if (addr == begin_addr) {
+      DCHECK(region->IsLarge());
+    } else {
+      DCHECK(region->IsLargeTail());
+    }
+    region->ZeroLiveBytes();
+  }
+  if (kIsDebugBuild && end_addr < Limit()) {
+    // If we aren't at the end of the space, check that the next region is not a large tail.
+    Region* following_region = RefToRegionLocked(reinterpret_cast<mirror::Object*>(end_addr));
+    DCHECK(!following_region->IsLargeTail());
+  }
+}
+
 // Determine which regions to evacuate and mark them as
 // from-space. Mark the rest as unevacuated from-space.
-void RegionSpace::SetFromSpace(accounting::ReadBarrierTable* rb_table, bool force_evacuate_all) {
+void RegionSpace::SetFromSpace(accounting::ReadBarrierTable* rb_table,
+                               EvacMode evac_mode,
+                               bool clear_live_bytes) {
+  // Live bytes are only preserved (i.e. not cleared) during sticky-bit CC collections.
+  DCHECK(kEnableGenerationalConcurrentCopyingCollection || clear_live_bytes);
   ++time_;
   if (kUseTableLookupReadBarrier) {
     DCHECK(rb_table->IsAllCleared());
@@ -227,12 +314,12 @@
         DCHECK((state == RegionState::kRegionStateAllocated ||
                 state == RegionState::kRegionStateLarge) &&
                type == RegionType::kRegionTypeToSpace);
-        bool should_evacuate = force_evacuate_all || r->ShouldBeEvacuated();
+        bool should_evacuate = r->ShouldBeEvacuated(evac_mode);
         if (should_evacuate) {
           r->SetAsFromSpace();
           DCHECK(r->IsInFromSpace());
         } else {
-          r->SetAsUnevacFromSpace();
+          r->SetAsUnevacFromSpace(clear_live_bytes);
           DCHECK(r->IsInUnevacFromSpace());
         }
         if (UNLIKELY(state == RegionState::kRegionStateLarge &&
@@ -248,7 +335,7 @@
           r->SetAsFromSpace();
           DCHECK(r->IsInFromSpace());
         } else {
-          r->SetAsUnevacFromSpace();
+          r->SetAsUnevacFromSpace(clear_live_bytes);
           DCHECK(r->IsInUnevacFromSpace());
         }
         --num_expected_large_tails;
@@ -260,6 +347,8 @@
         rb_table->Clear(r->Begin(), r->End());
       }
     }
+    // Invariant: There should be no newly-allocated region in the from-space.
+    DCHECK(!r->is_newly_allocated_);
   }
   DCHECK_EQ(num_expected_large_tails, 0U);
   current_region_ = &full_region_;
@@ -316,6 +405,9 @@
   };
   for (size_t i = 0; i < std::min(num_regions_, non_free_region_index_limit_); ++i) {
     Region* r = &regions_[i];
+    if (kCheckLiveBytesAgainstRegionBitmap) {
+      CheckLiveBytesAgainstRegionBitmap(r);
+    }
     if (r->IsInFromSpace()) {
       *cleared_bytes += r->BytesAllocated();
       *cleared_objects += r->ObjectsAllocated();
@@ -362,8 +454,9 @@
           ++regions_to_clear_bitmap;
         }
 
-        // Optimization: If the live bytes are *all* live in a region
-        // then the live-bit information for these objects is superfluous:
+        // Optimization (for full CC only): If the live bytes are *all* live
+        // in a region then the live-bit information for these objects is
+        // superfluous:
         // - We can determine that these objects are all live by using
         //   Region::AllAllocatedBytesAreLive (which just checks whether
         //   `LiveBytes() == static_cast<size_t>(Top() - Begin())`.
@@ -372,19 +465,44 @@
         //   live bits (see RegionSpace::WalkInternal).
         // Therefore, we can clear the bits for these objects in the
         // (live) region space bitmap (and release the corresponding pages).
-        GetLiveBitmap()->ClearRange(
-            reinterpret_cast<mirror::Object*>(r->Begin()),
-            reinterpret_cast<mirror::Object*>(r->Begin() + regions_to_clear_bitmap * kRegionSize));
+        //
+        // This optimization is incompatible with Generational CC, because:
+        // - minor (young-generation) collections need to know which objects
+        //   where marked during the previous GC cycle, meaning all mark bitmaps
+        //   (this includes the region space bitmap) need to be preserved
+        //   between a (minor or major) collection N and a following minor
+        //   collection N+1;
+        // - at this stage (in the current GC cycle), we cannot determine
+        //   whether the next collection will be a minor or a major one;
+        // This means that we need to be conservative and always preserve the
+        // region space bitmap when using Generational CC.
+        // Note that major collections do not require the previous mark bitmaps
+        // to be preserved, and as matter of fact they do clear the region space
+        // bitmap. But they cannot do so before we know the next GC cycle will
+        // be a major one, so this operation happens at the beginning of such a
+        // major collection, before marking starts.
+        if (!kEnableGenerationalConcurrentCopyingCollection) {
+          GetLiveBitmap()->ClearRange(
+              reinterpret_cast<mirror::Object*>(r->Begin()),
+              reinterpret_cast<mirror::Object*>(r->Begin() + regions_to_clear_bitmap * kRegionSize));
+        }
         // Skip over extra regions for which we cleared the bitmaps: we shall not clear them,
         // as they are unevac regions that are live.
         // Subtract one for the for-loop.
         i += regions_to_clear_bitmap - 1;
       } else {
-        // Only some allocated bytes are live in this unevac region.
-        // This should only happen for an allocated non-large region.
-        DCHECK(r->IsAllocated()) << r->State();
-        if (kPoisonDeadObjectsInUnevacuatedRegions) {
-          PoisonDeadObjectsInUnevacuatedRegion(r);
+        // TODO: Explain why we do not poison dead objects in region
+        // `r` when it has an undefined live bytes count (i.e. when
+        // `r->LiveBytes() == static_cast<size_t>(-1)`) with
+        // Generational CC.
+        if (!kEnableGenerationalConcurrentCopyingCollection ||
+            (r->LiveBytes() != static_cast<size_t>(-1))) {
+          // Only some allocated bytes are live in this unevac region.
+          // This should only happen for an allocated non-large region.
+          DCHECK(r->IsAllocated()) << r->State();
+          if (kPoisonDeadObjectsInUnevacuatedRegions) {
+            PoisonDeadObjectsInUnevacuatedRegion(r);
+          }
         }
       }
     }
@@ -404,6 +522,42 @@
   num_evac_regions_ = 0;
 }
 
+void RegionSpace::CheckLiveBytesAgainstRegionBitmap(Region* r) {
+  if (r->LiveBytes() == static_cast<size_t>(-1)) {
+    // Live bytes count is undefined for `r`; nothing to check here.
+    return;
+  }
+
+  // Functor walking the region space bitmap for the range corresponding
+  // to region `r` and calculating the sum of live bytes.
+  size_t live_bytes_recount = 0u;
+  auto recount_live_bytes =
+      [&r, &live_bytes_recount](mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK_ALIGNED(obj, kAlignment);
+    if (r->IsLarge()) {
+      // If `r` is a large region, then it contains at most one
+      // object, which must start at the beginning of the
+      // region. The live byte count in that case is equal to the
+      // allocated regions (large region + large tails regions).
+      DCHECK_EQ(reinterpret_cast<uint8_t*>(obj), r->Begin());
+      DCHECK_EQ(live_bytes_recount, 0u);
+      live_bytes_recount = r->Top() - r->Begin();
+    } else {
+      DCHECK(r->IsAllocated())
+          << "r->State()=" << r->State() << " r->LiveBytes()=" << r->LiveBytes();
+      size_t obj_size = obj->SizeOf<kDefaultVerifyFlags>();
+      size_t alloc_size = RoundUp(obj_size, space::RegionSpace::kAlignment);
+      live_bytes_recount += alloc_size;
+    }
+  };
+  // Visit live objects in `r` and recount the live bytes.
+  GetLiveBitmap()->VisitMarkedRange(reinterpret_cast<uintptr_t>(r->Begin()),
+                                    reinterpret_cast<uintptr_t>(r->Top()),
+                                    recount_live_bytes);
+  // Check that this recount matches the region's current live bytes count.
+  DCHECK_EQ(live_bytes_recount, r->LiveBytes());
+}
+
 // Poison the memory area in range [`begin`, `end`) with value `kPoisonDeadObject`.
 static void PoisonUnevacuatedRange(uint8_t* begin, uint8_t* end) {
   static constexpr size_t kPoisonDeadObjectSize = sizeof(kPoisonDeadObject);
@@ -423,7 +577,8 @@
   // The live byte count of `r` should be different from -1, as this
   // region should neither be a newly allocated region nor an
   // evacuated region.
-  DCHECK_NE(r->LiveBytes(), static_cast<size_t>(-1));
+  DCHECK_NE(r->LiveBytes(), static_cast<size_t>(-1))
+      << "Unexpected live bytes count of -1 in " << Dumpable<Region>(*r);
 
   // Past-the-end address of the previously visited (live) object (or
   // the beginning of the region, if `maybe_poison` has not run yet).
@@ -508,6 +663,18 @@
   evac_region_ = &full_region_;
 }
 
+void RegionSpace::Protect() {
+  if (kProtectClearedRegions) {
+    CheckedCall(mprotect, __FUNCTION__, Begin(), Size(), PROT_NONE);
+  }
+}
+
+void RegionSpace::Unprotect() {
+  if (kProtectClearedRegions) {
+    CheckedCall(mprotect, __FUNCTION__, Begin(), Size(), PROT_READ | PROT_WRITE);
+  }
+}
+
 void RegionSpace::ClampGrowthLimit(size_t new_capacity) {
   MutexLock mu(Thread::Current(), region_lock_);
   CHECK_LE(new_capacity, NonGrowthLimitCapacity());
@@ -688,6 +855,10 @@
     Region* r = &regions_[region_index];
     if (r->IsFree()) {
       r->Unfree(this, time_);
+      if (kEnableGenerationalConcurrentCopyingCollection) {
+        // TODO: Add an explanation for this assertion.
+        DCHECK(!for_evac || !r->is_newly_allocated_);
+      }
       if (for_evac) {
         ++num_evac_regions_;
         // Evac doesn't count as newly allocated.
diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h
index a129171..8ad26ba 100644
--- a/runtime/gc/space/region_space.h
+++ b/runtime/gc/space/region_space.h
@@ -43,6 +43,12 @@
  public:
   typedef void(*WalkCallback)(void *start, void *end, size_t num_bytes, void* callback_arg);
 
+  enum EvacMode {
+    kEvacModeNewlyAllocated,
+    kEvacModeLivePercentNewlyAllocated,
+    kEvacModeForceAll,
+  };
+
   SpaceType GetType() const OVERRIDE {
     return kSpaceTypeRegionSpace;
   }
@@ -50,8 +56,8 @@
   // Create a region space mem map with the requested sizes. The requested base address is not
   // guaranteed to be granted, if it is required, the caller should call Begin on the returned
   // space to confirm the request was granted.
-  static MemMap* CreateMemMap(const std::string& name, size_t capacity, uint8_t* requested_begin);
-  static RegionSpace* Create(const std::string& name, MemMap* mem_map);
+  static MemMap CreateMemMap(const std::string& name, size_t capacity, uint8_t* requested_begin);
+  static RegionSpace* Create(const std::string& name, MemMap&& mem_map);
 
   // Allocate `num_bytes`, returns null if the space is full.
   mirror::Object* Alloc(Thread* self,
@@ -108,6 +114,17 @@
 
   void Clear() OVERRIDE REQUIRES(!region_lock_);
 
+  // Remove read and write memory protection from the whole region space,
+  // i.e. make memory pages backing the region area not readable and not
+  // writable.
+  void Protect();
+
+  // Remove memory protection from the whole region space, i.e. make memory
+  // pages backing the region area readable and writable. This method is useful
+  // to avoid page protection faults when dumping information about an invalid
+  // reference.
+  void Unprotect();
+
   // Change the non growth limit capacity to new capacity by shrinking or expanding the map.
   // Currently, only shrinking is supported.
   // Unlike implementations of this function in other spaces, we need to pass
@@ -230,6 +247,14 @@
     return false;
   }
 
+  bool IsLargeObject(mirror::Object* ref) {
+    if (HasAddress(ref)) {
+      Region* r = RefToRegionUnlocked(ref);
+      return r->IsLarge();
+    }
+    return false;
+  }
+
   bool IsInToSpace(mirror::Object* ref) {
     if (HasAddress(ref)) {
       Region* r = RefToRegionUnlocked(ref);
@@ -255,9 +280,15 @@
     return r->Type();
   }
 
+  // Zero live bytes for a large object, used by young gen CC for marking newly allocated large
+  // objects.
+  void ZeroLiveBytesForLargeObject(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Determine which regions to evacuate and tag them as
   // from-space. Tag the rest as unevacuated from-space.
-  void SetFromSpace(accounting::ReadBarrierTable* rb_table, bool force_evacuate_all)
+  void SetFromSpace(accounting::ReadBarrierTable* rb_table,
+                    EvacMode evac_mode,
+                    bool clear_live_bytes)
       REQUIRES(!region_lock_);
 
   size_t FromSpaceSize() REQUIRES(!region_lock_);
@@ -290,7 +321,7 @@
   }
 
  private:
-  RegionSpace(const std::string& name, MemMap* mem_map);
+  RegionSpace(const std::string& name, MemMap&& mem_map);
 
   template<bool kToSpaceOnly, typename Visitor>
   ALWAYS_INLINE void WalkInternal(Visitor&& visitor) NO_THREAD_SAFETY_ANALYSIS;
@@ -386,6 +417,10 @@
       return is_large;
     }
 
+    void ZeroLiveBytes() {
+      live_bytes_ = 0;
+    }
+
     // Large-tail allocated.
     bool IsLargeTail() const {
       bool is_large_tail = (state_ == RegionState::kRegionStateLargeTail);
@@ -425,6 +460,18 @@
     void SetAsFromSpace() {
       DCHECK(!IsFree() && IsInToSpace());
       type_ = RegionType::kRegionTypeFromSpace;
+      if (IsNewlyAllocated()) {
+        // Clear the "newly allocated" status here, as we do not want the
+        // GC to see it when encountering references in the from-space.
+        //
+        // Invariant: There should be no newly-allocated region in the
+        // from-space (when the from-space exists, which is between the calls
+        // to RegionSpace::SetFromSpace and RegionSpace::ClearFromSpace).
+        is_newly_allocated_ = false;
+      }
+      // Set live bytes to an invalid value, as we have made an
+      // evacuation decision (possibly based on the percentage of live
+      // bytes).
       live_bytes_ = static_cast<size_t>(-1);
     }
 
@@ -432,10 +479,32 @@
     // collection, RegionSpace::ClearFromSpace will preserve the space
     // used by this region, and tag it as to-space (see
     // Region::SetUnevacFromSpaceAsToSpace below).
-    void SetAsUnevacFromSpace() {
+    void SetAsUnevacFromSpace(bool clear_live_bytes) {
+      // Live bytes are only preserved (i.e. not cleared) during sticky-bit CC collections.
+      DCHECK(kEnableGenerationalConcurrentCopyingCollection || clear_live_bytes);
       DCHECK(!IsFree() && IsInToSpace());
       type_ = RegionType::kRegionTypeUnevacFromSpace;
-      live_bytes_ = 0U;
+      if (IsNewlyAllocated()) {
+        // A newly allocated region set as unevac from-space must be
+        // a large or large tail region.
+        DCHECK(IsLarge() || IsLargeTail()) << static_cast<uint>(state_);
+        // Always clear the live bytes of a newly allocated (large or
+        // large tail) region.
+        clear_live_bytes = true;
+        // Clear the "newly allocated" status here, as we do not want the
+        // GC to see it when encountering (and processing) references in the
+        // from-space.
+        //
+        // Invariant: There should be no newly-allocated region in the
+        // from-space (when the from-space exists, which is between the calls
+        // to RegionSpace::SetFromSpace and RegionSpace::ClearFromSpace).
+        is_newly_allocated_ = false;
+      }
+      if (clear_live_bytes) {
+        // Reset the live bytes, as we have made a non-evacuation
+        // decision (possibly based on the percentage of live bytes).
+        live_bytes_ = 0;
+      }
     }
 
     // Set this region as to-space. Used by RegionSpace::ClearFromSpace.
@@ -446,7 +515,7 @@
     }
 
     // Return whether this region should be evacuated. Used by RegionSpace::SetFromSpace.
-    ALWAYS_INLINE bool ShouldBeEvacuated();
+    ALWAYS_INLINE bool ShouldBeEvacuated(EvacMode evac_mode);
 
     void AddLiveBytes(size_t live_bytes) {
       DCHECK(IsInUnevacFromSpace());
@@ -586,22 +655,27 @@
   Region* AllocateRegion(bool for_evac) REQUIRES(region_lock_);
 
   // Scan region range [`begin`, `end`) in increasing order to try to
-  // allocate a large region having a size of `num_regs` regions. If
-  // there is no space in the region space to allocate this large
-  // region, return null.
+  // allocate a large region having a size of `num_regs_in_large_region`
+  // regions. If there is no space in the region space to allocate this
+  // large region, return null.
   //
   // If argument `next_region` is not null, use `*next_region` to
   // return the index to the region next to the allocated large region
   // returned by this method.
   template<bool kForEvac>
-  mirror::Object* AllocLargeInRange(size_t num_regs,
-                                    size_t begin,
+  mirror::Object* AllocLargeInRange(size_t begin,
                                     size_t end,
+                                    size_t num_regs_in_large_region,
                                     /* out */ size_t* bytes_allocated,
                                     /* out */ size_t* usable_size,
                                     /* out */ size_t* bytes_tl_bulk_allocated,
                                     /* out */ size_t* next_region = nullptr) REQUIRES(region_lock_);
 
+  // Check that the value of `r->LiveBytes()` matches the number of
+  // (allocated) bytes used by live objects according to the live bits
+  // in the region space bitmap range corresponding to region `r`.
+  void CheckLiveBytesAgainstRegionBitmap(Region* r);
+
   // Poison memory areas used by dead objects within unevacuated
   // region `r`. This is meant to detect dangling references to dead
   // objects earlier in debug mode.
diff --git a/runtime/gc/space/rosalloc_space.cc b/runtime/gc/space/rosalloc_space.cc
index b0402e4..10ff1c1 100644
--- a/runtime/gc/space/rosalloc_space.cc
+++ b/runtime/gc/space/rosalloc_space.cc
@@ -44,48 +44,88 @@
 // TODO: Fix
 // template class MemoryToolMallocSpace<RosAllocSpace, allocator::RosAlloc*>;
 
-RosAllocSpace::RosAllocSpace(MemMap* mem_map, size_t initial_size, const std::string& name,
-                             art::gc::allocator::RosAlloc* rosalloc, uint8_t* begin, uint8_t* end,
-                             uint8_t* limit, size_t growth_limit, bool can_move_objects,
-                             size_t starting_size, bool low_memory_mode)
-    : MallocSpace(name, mem_map, begin, end, limit, growth_limit, true, can_move_objects,
+RosAllocSpace::RosAllocSpace(MemMap&& mem_map,
+                             size_t initial_size,
+                             const std::string& name,
+                             art::gc::allocator::RosAlloc* rosalloc,
+                             uint8_t* begin,
+                             uint8_t* end,
+                             uint8_t* limit,
+                             size_t growth_limit,
+                             bool can_move_objects,
+                             size_t starting_size,
+                             bool low_memory_mode)
+    : MallocSpace(name,
+                  std::move(mem_map),
+                  begin,
+                  end,
+                  limit,
+                  growth_limit,
+                  true,
+                  can_move_objects,
                   starting_size, initial_size),
       rosalloc_(rosalloc), low_memory_mode_(low_memory_mode) {
   CHECK(rosalloc != nullptr);
 }
 
-RosAllocSpace* RosAllocSpace::CreateFromMemMap(MemMap* mem_map, const std::string& name,
-                                               size_t starting_size, size_t initial_size,
-                                               size_t growth_limit, size_t capacity,
-                                               bool low_memory_mode, bool can_move_objects) {
-  DCHECK(mem_map != nullptr);
+RosAllocSpace* RosAllocSpace::CreateFromMemMap(MemMap&& mem_map,
+                                               const std::string& name,
+                                               size_t starting_size,
+                                               size_t initial_size,
+                                               size_t growth_limit,
+                                               size_t capacity,
+                                               bool low_memory_mode,
+                                               bool can_move_objects) {
+  DCHECK(mem_map.IsValid());
 
   bool running_on_memory_tool = Runtime::Current()->IsRunningOnMemoryTool();
 
-  allocator::RosAlloc* rosalloc = CreateRosAlloc(mem_map->Begin(), starting_size, initial_size,
-                                                 capacity, low_memory_mode, running_on_memory_tool);
+  allocator::RosAlloc* rosalloc = CreateRosAlloc(mem_map.Begin(),
+                                                 starting_size,
+                                                 initial_size,
+                                                 capacity,
+                                                 low_memory_mode,
+                                                 running_on_memory_tool);
   if (rosalloc == nullptr) {
     LOG(ERROR) << "Failed to initialize rosalloc for alloc space (" << name << ")";
     return nullptr;
   }
 
   // Protect memory beyond the starting size. MoreCore will add r/w permissions when necessory
-  uint8_t* end = mem_map->Begin() + starting_size;
+  uint8_t* end = mem_map.Begin() + starting_size;
   if (capacity - starting_size > 0) {
     CheckedCall(mprotect, name.c_str(), end, capacity - starting_size, PROT_NONE);
   }
 
   // Everything is set so record in immutable structure and leave
-  uint8_t* begin = mem_map->Begin();
+  uint8_t* begin = mem_map.Begin();
   // TODO: Fix RosAllocSpace to support ASan. There is currently some issues with
   // AllocationSize caused by redzones. b/12944686
   if (running_on_memory_tool) {
     return new MemoryToolMallocSpace<RosAllocSpace, kDefaultMemoryToolRedZoneBytes, false, true>(
-        mem_map, initial_size, name, rosalloc, begin, end, begin + capacity, growth_limit,
-        can_move_objects, starting_size, low_memory_mode);
+        std::move(mem_map),
+        initial_size,
+        name,
+        rosalloc,
+        begin,
+        end,
+        begin + capacity,
+        growth_limit,
+        can_move_objects,
+        starting_size,
+        low_memory_mode);
   } else {
-    return new RosAllocSpace(mem_map, initial_size, name, rosalloc, begin, end, begin + capacity,
-                             growth_limit, can_move_objects, starting_size, low_memory_mode);
+    return new RosAllocSpace(std::move(mem_map),
+                             initial_size,
+                             name,
+                             rosalloc,
+                             begin,
+                             end,
+                             begin + capacity,
+                             growth_limit,
+                             can_move_objects,
+                             starting_size,
+                             low_memory_mode);
   }
 }
 
@@ -111,16 +151,21 @@
   // will ask for this memory from sys_alloc which will fail as the footprint (this value plus the
   // size of the large allocation) will be greater than the footprint limit.
   size_t starting_size = Heap::kDefaultStartingSize;
-  MemMap* mem_map = CreateMemMap(name, starting_size, &initial_size, &growth_limit, &capacity,
-                                 requested_begin);
-  if (mem_map == nullptr) {
+  MemMap mem_map =
+      CreateMemMap(name, starting_size, &initial_size, &growth_limit, &capacity, requested_begin);
+  if (!mem_map.IsValid()) {
     LOG(ERROR) << "Failed to create mem map for alloc space (" << name << ") of size "
                << PrettySize(capacity);
     return nullptr;
   }
 
-  RosAllocSpace* space = CreateFromMemMap(mem_map, name, starting_size, initial_size,
-                                          growth_limit, capacity, low_memory_mode,
+  RosAllocSpace* space = CreateFromMemMap(std::move(mem_map),
+                                          name,
+                                          starting_size,
+                                          initial_size,
+                                          growth_limit,
+                                          capacity,
+                                          low_memory_mode,
                                           can_move_objects);
   // We start out with only the initial size possibly containing objects.
   if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
@@ -175,18 +220,39 @@
   return result;
 }
 
-MallocSpace* RosAllocSpace::CreateInstance(MemMap* mem_map, const std::string& name,
-                                           void* allocator, uint8_t* begin, uint8_t* end,
-                                           uint8_t* limit, size_t growth_limit,
+MallocSpace* RosAllocSpace::CreateInstance(MemMap&& mem_map,
+                                           const std::string& name,
+                                           void* allocator,
+                                           uint8_t* begin,
+                                           uint8_t* end,
+                                           uint8_t* limit,
+                                           size_t growth_limit,
                                            bool can_move_objects) {
   if (Runtime::Current()->IsRunningOnMemoryTool()) {
     return new MemoryToolMallocSpace<RosAllocSpace, kDefaultMemoryToolRedZoneBytes, false, true>(
-        mem_map, initial_size_, name, reinterpret_cast<allocator::RosAlloc*>(allocator), begin, end,
-        limit, growth_limit, can_move_objects, starting_size_, low_memory_mode_);
+        std::move(mem_map),
+        initial_size_,
+        name,
+        reinterpret_cast<allocator::RosAlloc*>(allocator),
+        begin,
+        end,
+        limit,
+        growth_limit,
+        can_move_objects,
+        starting_size_,
+        low_memory_mode_);
   } else {
-    return new RosAllocSpace(mem_map, initial_size_, name,
-                             reinterpret_cast<allocator::RosAlloc*>(allocator), begin, end, limit,
-                             growth_limit, can_move_objects, starting_size_, low_memory_mode_);
+    return new RosAllocSpace(std::move(mem_map),
+                             initial_size_,
+                             name,
+                             reinterpret_cast<allocator::RosAlloc*>(allocator),
+                             begin,
+                             end,
+                             limit,
+                             growth_limit,
+                             can_move_objects,
+                             starting_size_,
+                             low_memory_mode_);
   }
 }
 
@@ -364,8 +430,11 @@
   mark_bitmap_->Clear();
   SetEnd(begin_ + starting_size_);
   delete rosalloc_;
-  rosalloc_ = CreateRosAlloc(mem_map_->Begin(), starting_size_, initial_size_,
-                             NonGrowthLimitCapacity(), low_memory_mode_,
+  rosalloc_ = CreateRosAlloc(mem_map_.Begin(),
+                             starting_size_,
+                             initial_size_,
+                             NonGrowthLimitCapacity(),
+                             low_memory_mode_,
                              Runtime::Current()->IsRunningOnMemoryTool());
   SetFootprintLimit(footprint_limit);
 }
diff --git a/runtime/gc/space/rosalloc_space.h b/runtime/gc/space/rosalloc_space.h
index 4c17233..c630826 100644
--- a/runtime/gc/space/rosalloc_space.h
+++ b/runtime/gc/space/rosalloc_space.h
@@ -41,10 +41,14 @@
   static RosAllocSpace* Create(const std::string& name, size_t initial_size, size_t growth_limit,
                                size_t capacity, uint8_t* requested_begin, bool low_memory_mode,
                                bool can_move_objects);
-  static RosAllocSpace* CreateFromMemMap(MemMap* mem_map, const std::string& name,
-                                         size_t starting_size, size_t initial_size,
-                                         size_t growth_limit, size_t capacity,
-                                         bool low_memory_mode, bool can_move_objects);
+  static RosAllocSpace* CreateFromMemMap(MemMap&& mem_map,
+                                         const std::string& name,
+                                         size_t starting_size,
+                                         size_t initial_size,
+                                         size_t growth_limit,
+                                         size_t capacity,
+                                         bool low_memory_mode,
+                                         bool can_move_objects);
 
   mirror::Object* AllocWithGrowth(Thread* self, size_t num_bytes, size_t* bytes_allocated,
                                   size_t* usable_size, size_t* bytes_tl_bulk_allocated)
@@ -111,8 +115,13 @@
 
   void Clear() OVERRIDE;
 
-  MallocSpace* CreateInstance(MemMap* mem_map, const std::string& name, void* allocator,
-                              uint8_t* begin, uint8_t* end, uint8_t* limit, size_t growth_limit,
+  MallocSpace* CreateInstance(MemMap&& mem_map,
+                              const std::string& name,
+                              void* allocator,
+                              uint8_t* begin,
+                              uint8_t* end,
+                              uint8_t* limit,
+                              size_t growth_limit,
                               bool can_move_objects) OVERRIDE;
 
   uint64_t GetBytesAllocated() OVERRIDE;
@@ -147,9 +156,16 @@
   void DumpStats(std::ostream& os);
 
  protected:
-  RosAllocSpace(MemMap* mem_map, size_t initial_size, const std::string& name,
-                allocator::RosAlloc* rosalloc, uint8_t* begin, uint8_t* end, uint8_t* limit,
-                size_t growth_limit, bool can_move_objects, size_t starting_size,
+  RosAllocSpace(MemMap&& mem_map,
+                size_t initial_size,
+                const std::string& name,
+                allocator::RosAlloc* rosalloc,
+                uint8_t* begin,
+                uint8_t* end,
+                uint8_t* limit,
+                size_t growth_limit,
+                bool can_move_objects,
+                size_t starting_size,
                 bool low_memory_mode);
 
  private:
diff --git a/runtime/gc/space/space.h b/runtime/gc/space/space.h
index 4f43d9f..4e173a8 100644
--- a/runtime/gc/space/space.h
+++ b/runtime/gc/space/space.h
@@ -377,30 +377,30 @@
   }
 
   MemMap* GetMemMap() {
-    return mem_map_.get();
+    return &mem_map_;
   }
 
   const MemMap* GetMemMap() const {
-    return mem_map_.get();
+    return &mem_map_;
   }
 
-  MemMap* ReleaseMemMap() {
-    return mem_map_.release();
+  MemMap ReleaseMemMap() {
+    return std::move(mem_map_);
   }
 
  protected:
   MemMapSpace(const std::string& name,
-              MemMap* mem_map,
+              MemMap&& mem_map,
               uint8_t* begin,
               uint8_t* end,
               uint8_t* limit,
               GcRetentionPolicy gc_retention_policy)
       : ContinuousSpace(name, gc_retention_policy, begin, end, limit),
-        mem_map_(mem_map) {
+        mem_map_(std::move(mem_map)) {
   }
 
   // Underlying storage of the space
-  std::unique_ptr<MemMap> mem_map_;
+  MemMap mem_map_;
 
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(MemMapSpace);
@@ -451,9 +451,13 @@
   std::unique_ptr<accounting::ContinuousSpaceBitmap> mark_bitmap_;
   std::unique_ptr<accounting::ContinuousSpaceBitmap> temp_bitmap_;
 
-  ContinuousMemMapAllocSpace(const std::string& name, MemMap* mem_map, uint8_t* begin,
-                             uint8_t* end, uint8_t* limit, GcRetentionPolicy gc_retention_policy)
-      : MemMapSpace(name, mem_map, begin, end, limit, gc_retention_policy) {
+  ContinuousMemMapAllocSpace(const std::string& name,
+                             MemMap&& mem_map,
+                             uint8_t* begin,
+                             uint8_t* end,
+                             uint8_t* limit,
+                             GcRetentionPolicy gc_retention_policy)
+      : MemMapSpace(name, std::move(mem_map), begin, end, limit, gc_retention_policy) {
   }
 
  private:
diff --git a/runtime/gc/space/zygote_space.cc b/runtime/gc/space/zygote_space.cc
index 8c73ef9..ed85b06 100644
--- a/runtime/gc/space/zygote_space.cc
+++ b/runtime/gc/space/zygote_space.cc
@@ -41,7 +41,8 @@
   size_t* const objects_allocated_;
 };
 
-ZygoteSpace* ZygoteSpace::Create(const std::string& name, MemMap* mem_map,
+ZygoteSpace* ZygoteSpace::Create(const std::string& name,
+                                 MemMap&& mem_map,
                                  accounting::ContinuousSpaceBitmap* live_bitmap,
                                  accounting::ContinuousSpaceBitmap* mark_bitmap) {
   DCHECK(live_bitmap != nullptr);
@@ -49,9 +50,9 @@
   size_t objects_allocated = 0;
   CountObjectsAllocated visitor(&objects_allocated);
   ReaderMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
-  live_bitmap->VisitMarkedRange(reinterpret_cast<uintptr_t>(mem_map->Begin()),
-                                reinterpret_cast<uintptr_t>(mem_map->End()), visitor);
-  ZygoteSpace* zygote_space = new ZygoteSpace(name, mem_map, objects_allocated);
+  live_bitmap->VisitMarkedRange(reinterpret_cast<uintptr_t>(mem_map.Begin()),
+                                reinterpret_cast<uintptr_t>(mem_map.End()), visitor);
+  ZygoteSpace* zygote_space = new ZygoteSpace(name, std::move(mem_map), objects_allocated);
   CHECK(zygote_space->live_bitmap_.get() == nullptr);
   CHECK(zygote_space->mark_bitmap_.get() == nullptr);
   zygote_space->live_bitmap_.reset(live_bitmap);
@@ -64,8 +65,12 @@
   UNREACHABLE();
 }
 
-ZygoteSpace::ZygoteSpace(const std::string& name, MemMap* mem_map, size_t objects_allocated)
-    : ContinuousMemMapAllocSpace(name, mem_map, mem_map->Begin(), mem_map->End(), mem_map->End(),
+ZygoteSpace::ZygoteSpace(const std::string& name, MemMap&& mem_map, size_t objects_allocated)
+    : ContinuousMemMapAllocSpace(name,
+                                 std::move(mem_map),
+                                 mem_map.Begin(),
+                                 mem_map.End(),
+                                 mem_map.End(),
                                  kGcRetentionPolicyFullCollect),
       objects_allocated_(objects_allocated) {
 }
diff --git a/runtime/gc/space/zygote_space.h b/runtime/gc/space/zygote_space.h
index 6fe21d9..200c79f 100644
--- a/runtime/gc/space/zygote_space.h
+++ b/runtime/gc/space/zygote_space.h
@@ -30,7 +30,8 @@
 class ZygoteSpace FINAL : public ContinuousMemMapAllocSpace {
  public:
   // Returns the remaining storage in the out_map field.
-  static ZygoteSpace* Create(const std::string& name, MemMap* mem_map,
+  static ZygoteSpace* Create(const std::string& name,
+                             MemMap&& mem_map,
                              accounting::ContinuousSpaceBitmap* live_bitmap,
                              accounting::ContinuousSpaceBitmap* mark_bitmap)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -85,7 +86,7 @@
   }
 
  private:
-  ZygoteSpace(const std::string& name, MemMap* mem_map, size_t objects_allocated);
+  ZygoteSpace(const std::string& name, MemMap&& mem_map, size_t objects_allocated);
   static void SweepCallback(size_t num_ptrs, mirror::Object** ptrs, void* arg);
 
   AtomicInteger objects_allocated_;
diff --git a/runtime/gc/system_weak_test.cc b/runtime/gc/system_weak_test.cc
index 21f5117..897ab01 100644
--- a/runtime/gc/system_weak_test.cc
+++ b/runtime/gc/system_weak_test.cc
@@ -26,6 +26,7 @@
 #include "gc_root-inl.h"
 #include "handle_scope-inl.h"
 #include "heap.h"
+#include "mirror/object-inl.h"
 #include "mirror/string.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread_list.h"
diff --git a/runtime/generated/asm_support_gen.h b/runtime/generated/asm_support_gen.h
index 464c2b7..a9230e0 100644
--- a/runtime/generated/asm_support_gen.h
+++ b/runtime/generated/asm_support_gen.h
@@ -80,9 +80,9 @@
 DEFINE_CHECK_EQ(static_cast<int32_t>(STRING_DEX_CACHE_HASH_BITS), (static_cast<int32_t>(art::LeastSignificantBit(art::mirror::DexCache::kDexCacheStringCacheSize))))
 #define STRING_DEX_CACHE_ELEMENT_SIZE 8
 DEFINE_CHECK_EQ(static_cast<int32_t>(STRING_DEX_CACHE_ELEMENT_SIZE), (static_cast<int32_t>(sizeof(art::mirror::StringDexCachePair))))
-#define METHOD_DEX_CACHE_SIZE_MINUS_ONE 1023
+#define METHOD_DEX_CACHE_SIZE_MINUS_ONE 511
 DEFINE_CHECK_EQ(static_cast<int32_t>(METHOD_DEX_CACHE_SIZE_MINUS_ONE), (static_cast<int32_t>(art::mirror::DexCache::kDexCacheMethodCacheSize - 1)))
-#define METHOD_DEX_CACHE_HASH_BITS 10
+#define METHOD_DEX_CACHE_HASH_BITS 9
 DEFINE_CHECK_EQ(static_cast<int32_t>(METHOD_DEX_CACHE_HASH_BITS), (static_cast<int32_t>(art::LeastSignificantBit(art::mirror::DexCache::kDexCacheMethodCacheSize))))
 #define CARD_TABLE_CARD_SHIFT 0xa
 DEFINE_CHECK_EQ(static_cast<size_t>(CARD_TABLE_CARD_SHIFT), (static_cast<size_t>(art::gc::accounting::CardTable::kCardShift)))
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index e41d1d3..5729800 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -174,6 +174,10 @@
   if (action_taken == kDeny) {
     log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_DENIED, 1);
   }
+  const std::string& package_name = Runtime::Current()->GetProcessPackageName();
+  if (!package_name.empty()) {
+    log_maker.SetPackageName(package_name);
+  }
   std::ostringstream signature_str;
   Dump(signature_str);
   log_maker.AddTaggedData(FIELD_HIDDEN_API_SIGNATURE, signature_str.str());
diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc
index dc42cfa..3f44928 100644
--- a/runtime/hprof/hprof.cc
+++ b/runtime/hprof/hprof.cc
@@ -1590,7 +1590,7 @@
   if (obj == nullptr) {
     return;
   }
-  MarkRootObject(obj, 0, xlate[info.GetType()], info.GetThreadId());
+  MarkRootObject(obj, nullptr, xlate[info.GetType()], info.GetThreadId());
 }
 
 // If "direct_to_ddms" is true, the other arguments are ignored, and data is
diff --git a/runtime/image.cc b/runtime/image.cc
index 028c515..b7a872c 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -26,7 +26,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '6', '3', '\0' };  // Image relocations.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '6', '4', '\0' };  // Half DexCache F&M arrays.
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
diff --git a/runtime/indirect_reference_table.cc b/runtime/indirect_reference_table.cc
index 950a54d..8ab4a9b 100644
--- a/runtime/indirect_reference_table.cc
+++ b/runtime/indirect_reference_table.cc
@@ -78,14 +78,18 @@
   CHECK_LE(max_count, kMaxTableSizeInBytes / sizeof(IrtEntry));
 
   const size_t table_bytes = max_count * sizeof(IrtEntry);
-  table_mem_map_.reset(MemMap::MapAnonymous("indirect ref table", nullptr, table_bytes,
-                                            PROT_READ | PROT_WRITE, false, false, error_msg));
-  if (table_mem_map_.get() == nullptr && error_msg->empty()) {
+  table_mem_map_ = MemMap::MapAnonymous("indirect ref table",
+                                        /* addr */ nullptr,
+                                        table_bytes,
+                                        PROT_READ | PROT_WRITE,
+                                        /* low_4gb */ false,
+                                        error_msg);
+  if (!table_mem_map_.IsValid() && error_msg->empty()) {
     *error_msg = "Unable to map memory for indirect ref table";
   }
 
-  if (table_mem_map_.get() != nullptr) {
-    table_ = reinterpret_cast<IrtEntry*>(table_mem_map_->Begin());
+  if (table_mem_map_.IsValid()) {
+    table_ = reinterpret_cast<IrtEntry*>(table_mem_map_.Begin());
   } else {
     table_ = nullptr;
   }
@@ -125,7 +129,7 @@
 }
 
 bool IndirectReferenceTable::IsValid() const {
-  return table_mem_map_.get() != nullptr;
+  return table_mem_map_.IsValid();
 }
 
 // Holes:
@@ -217,20 +221,19 @@
   // Note: the above check also ensures that there is no overflow below.
 
   const size_t table_bytes = new_size * sizeof(IrtEntry);
-  std::unique_ptr<MemMap> new_map(MemMap::MapAnonymous("indirect ref table",
-                                                       nullptr,
-                                                       table_bytes,
-                                                       PROT_READ | PROT_WRITE,
-                                                       false,
-                                                       false,
-                                                       error_msg));
-  if (new_map == nullptr) {
+  MemMap new_map = MemMap::MapAnonymous("indirect ref table",
+                                        /* addr */ nullptr,
+                                        table_bytes,
+                                        PROT_READ | PROT_WRITE,
+                                        /* is_low_4gb */ false,
+                                        error_msg);
+  if (!new_map.IsValid()) {
     return false;
   }
 
-  memcpy(new_map->Begin(), table_mem_map_->Begin(), table_mem_map_->Size());
+  memcpy(new_map.Begin(), table_mem_map_.Begin(), table_mem_map_.Size());
   table_mem_map_ = std::move(new_map);
-  table_ = reinterpret_cast<IrtEntry*>(table_mem_map_->Begin());
+  table_ = reinterpret_cast<IrtEntry*>(table_mem_map_.Begin());
   max_entries_ = new_size;
 
   return true;
@@ -444,7 +447,7 @@
   ScopedTrace trace(__PRETTY_FUNCTION__);
   const size_t top_index = Capacity();
   auto* release_start = AlignUp(reinterpret_cast<uint8_t*>(&table_[top_index]), kPageSize);
-  uint8_t* release_end = table_mem_map_->End();
+  uint8_t* release_end = table_mem_map_.End();
   madvise(release_start, release_end - release_start, MADV_DONTNEED);
 }
 
diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h
index d2093f2..8c63c00 100644
--- a/runtime/indirect_reference_table.h
+++ b/runtime/indirect_reference_table.h
@@ -27,6 +27,7 @@
 
 #include "base/bit_utils.h"
 #include "base/macros.h"
+#include "base/mem_map.h"
 #include "base/mutex.h"
 #include "gc_root.h"
 #include "obj_ptr.h"
@@ -41,8 +42,6 @@
 class Object;
 }  // namespace mirror
 
-class MemMap;
-
 // Maintain a table of indirect references.  Used for local/global JNI references.
 //
 // The table contains object references, where the strong (local/global) references are part of the
@@ -398,7 +397,7 @@
   IRTSegmentState segment_state_;
 
   // Mem map where we store the indirect refs.
-  std::unique_ptr<MemMap> table_mem_map_;
+  MemMap table_mem_map_;
   // bottom of the stack. Do not directly access the object references
   // in this as they are roots. Use Get() that has a read barrier.
   IrtEntry* table_;
diff --git a/runtime/interpreter/mterp/arm/op_iget.S b/runtime/interpreter/mterp/arm/op_iget.S
index c7f777b..1684a76 100644
--- a/runtime/interpreter/mterp/arm/op_iget.S
+++ b/runtime/interpreter/mterp/arm/op_iget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"artGet32InstanceFromCode"}
+%default { "is_object":"0", "helper":"MterpIGetU32"}
     /*
      * General instance field get.
      *
diff --git a/runtime/interpreter/mterp/arm/op_iget_boolean.S b/runtime/interpreter/mterp/arm/op_iget_boolean.S
index 628f40a..f23cb3a 100644
--- a/runtime/interpreter/mterp/arm/op_iget_boolean.S
+++ b/runtime/interpreter/mterp/arm/op_iget_boolean.S
@@ -1 +1 @@
-%include "arm/op_iget.S" { "helper":"artGetBooleanInstanceFromCode" }
+%include "arm/op_iget.S" { "helper":"MterpIGetU8" }
diff --git a/runtime/interpreter/mterp/arm/op_iget_byte.S b/runtime/interpreter/mterp/arm/op_iget_byte.S
index c4e08e2..9c4f37c 100644
--- a/runtime/interpreter/mterp/arm/op_iget_byte.S
+++ b/runtime/interpreter/mterp/arm/op_iget_byte.S
@@ -1 +1 @@
-%include "arm/op_iget.S" { "helper":"artGetByteInstanceFromCode" }
+%include "arm/op_iget.S" { "helper":"MterpIGetI8" }
diff --git a/runtime/interpreter/mterp/arm/op_iget_char.S b/runtime/interpreter/mterp/arm/op_iget_char.S
index 5e8da66..80c4227 100644
--- a/runtime/interpreter/mterp/arm/op_iget_char.S
+++ b/runtime/interpreter/mterp/arm/op_iget_char.S
@@ -1 +1 @@
-%include "arm/op_iget.S" { "helper":"artGetCharInstanceFromCode" }
+%include "arm/op_iget.S" { "helper":"MterpIGetU16" }
diff --git a/runtime/interpreter/mterp/arm/op_iget_object.S b/runtime/interpreter/mterp/arm/op_iget_object.S
index 1cf2e3c..e30b129 100644
--- a/runtime/interpreter/mterp/arm/op_iget_object.S
+++ b/runtime/interpreter/mterp/arm/op_iget_object.S
@@ -1 +1 @@
-%include "arm/op_iget.S" { "is_object":"1", "helper":"artGetObjInstanceFromCode" }
+%include "arm/op_iget.S" { "is_object":"1", "helper":"MterpIGetObj" }
diff --git a/runtime/interpreter/mterp/arm/op_iget_short.S b/runtime/interpreter/mterp/arm/op_iget_short.S
index 460f045..dd6bc99 100644
--- a/runtime/interpreter/mterp/arm/op_iget_short.S
+++ b/runtime/interpreter/mterp/arm/op_iget_short.S
@@ -1 +1 @@
-%include "arm/op_iget.S" { "helper":"artGetShortInstanceFromCode" }
+%include "arm/op_iget.S" { "helper":"MterpIGetI16" }
diff --git a/runtime/interpreter/mterp/arm/op_iget_wide.S b/runtime/interpreter/mterp/arm/op_iget_wide.S
index e287d51..46e9ec8 100644
--- a/runtime/interpreter/mterp/arm/op_iget_wide.S
+++ b/runtime/interpreter/mterp/arm/op_iget_wide.S
@@ -9,7 +9,7 @@
     GET_VREG r1, r1                        @ r1<- fp[B], the object pointer
     ldr      r2, [rFP, #OFF_FP_METHOD]     @ r2<- referrer
     mov      r3, rSELF                     @ r3<- self
-    bl       artGet64InstanceFromCode
+    bl       MterpIGetU64
     ldr      r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     r2, rINST, #8, #4             @ r2<- A
     PREFETCH_INST 2
diff --git a/runtime/interpreter/mterp/arm/op_iput.S b/runtime/interpreter/mterp/arm/op_iput.S
index d224cd8..a16795d 100644
--- a/runtime/interpreter/mterp/arm/op_iput.S
+++ b/runtime/interpreter/mterp/arm/op_iput.S
@@ -1,11 +1,11 @@
-%default { "is_object":"0", "handler":"artSet32InstanceFromMterp" }
+%default { "is_object":"0", "helper":"MterpIPutU32" }
     /*
      * General 32-bit instance field put.
      *
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern $handler
+    .extern $helper
     EXPORT_PC
     FETCH    r0, 1                      @ r0<- field ref CCCC
     mov      r1, rINST, lsr #12         @ r1<- B
@@ -14,7 +14,7 @@
     GET_VREG r2, r2                     @ r2<- fp[A]
     ldr      r3, [rFP, #OFF_FP_METHOD]  @ r3<- referrer
     PREFETCH_INST 2
-    bl       $handler
+    bl       $helper
     cmp      r0, #0
     bne      MterpPossibleException
     ADVANCE  2                          @ advance rPC
diff --git a/runtime/interpreter/mterp/arm/op_iput_boolean.S b/runtime/interpreter/mterp/arm/op_iput_boolean.S
index c9e8589..57edadd 100644
--- a/runtime/interpreter/mterp/arm/op_iput_boolean.S
+++ b/runtime/interpreter/mterp/arm/op_iput_boolean.S
@@ -1 +1 @@
-%include "arm/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "arm/op_iput.S" { "helper":"MterpIPutU8" }
diff --git a/runtime/interpreter/mterp/arm/op_iput_byte.S b/runtime/interpreter/mterp/arm/op_iput_byte.S
index c9e8589..ab283b9 100644
--- a/runtime/interpreter/mterp/arm/op_iput_byte.S
+++ b/runtime/interpreter/mterp/arm/op_iput_byte.S
@@ -1 +1 @@
-%include "arm/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "arm/op_iput.S" { "helper":"MterpIPutI8" }
diff --git a/runtime/interpreter/mterp/arm/op_iput_char.S b/runtime/interpreter/mterp/arm/op_iput_char.S
index 5046f6b..0fe5d96 100644
--- a/runtime/interpreter/mterp/arm/op_iput_char.S
+++ b/runtime/interpreter/mterp/arm/op_iput_char.S
@@ -1 +1 @@
-%include "arm/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "arm/op_iput.S" { "helper":"MterpIPutU16" }
diff --git a/runtime/interpreter/mterp/arm/op_iput_object.S b/runtime/interpreter/mterp/arm/op_iput_object.S
index d942e84..4f401eb 100644
--- a/runtime/interpreter/mterp/arm/op_iput_object.S
+++ b/runtime/interpreter/mterp/arm/op_iput_object.S
@@ -3,7 +3,7 @@
     mov     r1, rPC
     mov     r2, rINST
     mov     r3, rSELF
-    bl      MterpIputObject
+    bl      MterpIPutObj
     cmp     r0, #0
     beq     MterpException
     FETCH_ADVANCE_INST 2                @ advance rPC, load rINST
diff --git a/runtime/interpreter/mterp/arm/op_iput_short.S b/runtime/interpreter/mterp/arm/op_iput_short.S
index 5046f6b..cc98363 100644
--- a/runtime/interpreter/mterp/arm/op_iput_short.S
+++ b/runtime/interpreter/mterp/arm/op_iput_short.S
@@ -1 +1 @@
-%include "arm/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "arm/op_iput.S" { "helper":"MterpIPutI16" }
diff --git a/runtime/interpreter/mterp/arm/op_iput_wide.S b/runtime/interpreter/mterp/arm/op_iput_wide.S
index 3dda187..6a41473 100644
--- a/runtime/interpreter/mterp/arm/op_iput_wide.S
+++ b/runtime/interpreter/mterp/arm/op_iput_wide.S
@@ -1,5 +1,5 @@
     /* iput-wide vA, vB, field@CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     FETCH    r0, 1                      @ r0<- field ref CCCC
     mov      r1, rINST, lsr #12         @ r1<- B
@@ -8,7 +8,7 @@
     VREG_INDEX_TO_ADDR r2, r2           @ r2<- &fp[A]
     ldr      r3, [rFP, #OFF_FP_METHOD]  @ r3<- referrer
     PREFETCH_INST 2
-    bl       artSet64InstanceFromMterp
+    bl       MterpIPutU64
     cmp      r0, #0
     bne      MterpPossibleException
     ADVANCE  2                          @ advance rPC
diff --git a/runtime/interpreter/mterp/arm/op_sget.S b/runtime/interpreter/mterp/arm/op_sget.S
index 3c813ef..575a8c0 100644
--- a/runtime/interpreter/mterp/arm/op_sget.S
+++ b/runtime/interpreter/mterp/arm/op_sget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"MterpGet32Static" }
+%default { "is_object":"0", "helper":"MterpSGetU32" }
     /*
      * General SGET handler wrapper.
      *
diff --git a/runtime/interpreter/mterp/arm/op_sget_boolean.S b/runtime/interpreter/mterp/arm/op_sget_boolean.S
index eb06aa8..df1a024 100644
--- a/runtime/interpreter/mterp/arm/op_sget_boolean.S
+++ b/runtime/interpreter/mterp/arm/op_sget_boolean.S
@@ -1 +1 @@
-%include "arm/op_sget.S" {"helper":"MterpGetBooleanStatic"}
+%include "arm/op_sget.S" {"helper":"MterpSGetU8"}
diff --git a/runtime/interpreter/mterp/arm/op_sget_byte.S b/runtime/interpreter/mterp/arm/op_sget_byte.S
index 9f4c904..8ad3ff0 100644
--- a/runtime/interpreter/mterp/arm/op_sget_byte.S
+++ b/runtime/interpreter/mterp/arm/op_sget_byte.S
@@ -1 +1 @@
-%include "arm/op_sget.S" {"helper":"MterpGetByteStatic"}
+%include "arm/op_sget.S" {"helper":"MterpSGetI8"}
diff --git a/runtime/interpreter/mterp/arm/op_sget_char.S b/runtime/interpreter/mterp/arm/op_sget_char.S
index dd8c991..5239514 100644
--- a/runtime/interpreter/mterp/arm/op_sget_char.S
+++ b/runtime/interpreter/mterp/arm/op_sget_char.S
@@ -1 +1 @@
-%include "arm/op_sget.S" {"helper":"MterpGetCharStatic"}
+%include "arm/op_sget.S" {"helper":"MterpSGetU16"}
diff --git a/runtime/interpreter/mterp/arm/op_sget_object.S b/runtime/interpreter/mterp/arm/op_sget_object.S
index e1d9eae..e61a5a7 100644
--- a/runtime/interpreter/mterp/arm/op_sget_object.S
+++ b/runtime/interpreter/mterp/arm/op_sget_object.S
@@ -1 +1 @@
-%include "arm/op_sget.S" {"is_object":"1", "helper":"MterpGetObjStatic"}
+%include "arm/op_sget.S" {"is_object":"1", "helper":"MterpSGetObj"}
diff --git a/runtime/interpreter/mterp/arm/op_sget_short.S b/runtime/interpreter/mterp/arm/op_sget_short.S
index c0d61c4..49493eb 100644
--- a/runtime/interpreter/mterp/arm/op_sget_short.S
+++ b/runtime/interpreter/mterp/arm/op_sget_short.S
@@ -1 +1 @@
-%include "arm/op_sget.S" {"helper":"MterpGetShortStatic"}
+%include "arm/op_sget.S" {"helper":"MterpSGetI16"}
diff --git a/runtime/interpreter/mterp/arm/op_sget_wide.S b/runtime/interpreter/mterp/arm/op_sget_wide.S
index aeee016..5981ec4 100644
--- a/runtime/interpreter/mterp/arm/op_sget_wide.S
+++ b/runtime/interpreter/mterp/arm/op_sget_wide.S
@@ -4,12 +4,12 @@
      */
     /* sget-wide vAA, field@BBBB */
 
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC
     FETCH r0, 1                         @ r0<- field ref BBBB
     ldr   r1, [rFP, #OFF_FP_METHOD]
     mov   r2, rSELF
-    bl    MterpGet64Static
+    bl    MterpSGetU64
     ldr   r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     mov   r9, rINST, lsr #8             @ r9<- AA
     VREG_INDEX_TO_ADDR lr, r9           @ r9<- &fp[AA]
diff --git a/runtime/interpreter/mterp/arm/op_sput.S b/runtime/interpreter/mterp/arm/op_sput.S
index 494df8a..c4a8978 100644
--- a/runtime/interpreter/mterp/arm/op_sput.S
+++ b/runtime/interpreter/mterp/arm/op_sput.S
@@ -1,4 +1,4 @@
-%default { "helper":"MterpSet32Static"}
+%default { "helper":"MterpSPutU32"}
     /*
      * General SPUT handler wrapper.
      *
diff --git a/runtime/interpreter/mterp/arm/op_sput_boolean.S b/runtime/interpreter/mterp/arm/op_sput_boolean.S
index 47bed0a..0c37623 100644
--- a/runtime/interpreter/mterp/arm/op_sput_boolean.S
+++ b/runtime/interpreter/mterp/arm/op_sput_boolean.S
@@ -1 +1 @@
-%include "arm/op_sput.S" {"helper":"MterpSetBooleanStatic"}
+%include "arm/op_sput.S" {"helper":"MterpSPutU8"}
diff --git a/runtime/interpreter/mterp/arm/op_sput_byte.S b/runtime/interpreter/mterp/arm/op_sput_byte.S
index b4d22b4..8d4e754 100644
--- a/runtime/interpreter/mterp/arm/op_sput_byte.S
+++ b/runtime/interpreter/mterp/arm/op_sput_byte.S
@@ -1 +1 @@
-%include "arm/op_sput.S" {"helper":"MterpSetByteStatic"}
+%include "arm/op_sput.S" {"helper":"MterpSPutI8"}
diff --git a/runtime/interpreter/mterp/arm/op_sput_char.S b/runtime/interpreter/mterp/arm/op_sput_char.S
index 58a957d..442b56f 100644
--- a/runtime/interpreter/mterp/arm/op_sput_char.S
+++ b/runtime/interpreter/mterp/arm/op_sput_char.S
@@ -1 +1 @@
-%include "arm/op_sput.S" {"helper":"MterpSetCharStatic"}
+%include "arm/op_sput.S" {"helper":"MterpSPutU16"}
diff --git a/runtime/interpreter/mterp/arm/op_sput_object.S b/runtime/interpreter/mterp/arm/op_sput_object.S
index 6d3a9a7..c58918f 100644
--- a/runtime/interpreter/mterp/arm/op_sput_object.S
+++ b/runtime/interpreter/mterp/arm/op_sput_object.S
@@ -3,7 +3,7 @@
     mov     r1, rPC
     mov     r2, rINST
     mov     r3, rSELF
-    bl      MterpSputObject
+    bl      MterpSPutObj
     cmp     r0, #0
     beq     MterpException
     FETCH_ADVANCE_INST 2                @ advance rPC, load rINST
diff --git a/runtime/interpreter/mterp/arm/op_sput_short.S b/runtime/interpreter/mterp/arm/op_sput_short.S
index 88c3211..0eb533f 100644
--- a/runtime/interpreter/mterp/arm/op_sput_short.S
+++ b/runtime/interpreter/mterp/arm/op_sput_short.S
@@ -1 +1 @@
-%include "arm/op_sput.S" {"helper":"MterpSetShortStatic"}
+%include "arm/op_sput.S" {"helper":"MterpSPutI16"}
diff --git a/runtime/interpreter/mterp/arm/op_sput_wide.S b/runtime/interpreter/mterp/arm/op_sput_wide.S
index 1e8fcc9..0ed4017 100644
--- a/runtime/interpreter/mterp/arm/op_sput_wide.S
+++ b/runtime/interpreter/mterp/arm/op_sput_wide.S
@@ -3,7 +3,7 @@
      *
      */
     /* sput-wide vAA, field@BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     FETCH   r0, 1                       @ r0<- field ref BBBB
     mov     r1, rINST, lsr #8           @ r1<- AA
@@ -11,7 +11,7 @@
     ldr     r2, [rFP, #OFF_FP_METHOD]
     mov     r3, rSELF
     PREFETCH_INST 2                     @ Get next inst, but don't advance rPC
-    bl      MterpSet64Static
+    bl      MterpSPutU64
     cmp     r0, #0                      @ 0 on success, -1 on failure
     bne     MterpException
     ADVANCE 2                           @ Past exception point - now advance rPC
diff --git a/runtime/interpreter/mterp/arm64/op_iget.S b/runtime/interpreter/mterp/arm64/op_iget.S
index 88533bd..cb453ac 100644
--- a/runtime/interpreter/mterp/arm64/op_iget.S
+++ b/runtime/interpreter/mterp/arm64/op_iget.S
@@ -1,4 +1,4 @@
-%default { "extend":"", "is_object":"0", "helper":"artGet32InstanceFromCode"}
+%default { "extend":"", "is_object":"0", "helper":"MterpIGetU32"}
     /*
      * General instance field get.
      *
diff --git a/runtime/interpreter/mterp/arm64/op_iget_boolean.S b/runtime/interpreter/mterp/arm64/op_iget_boolean.S
index 36a9b6b..3b17144 100644
--- a/runtime/interpreter/mterp/arm64/op_iget_boolean.S
+++ b/runtime/interpreter/mterp/arm64/op_iget_boolean.S
@@ -1 +1 @@
-%include "arm64/op_iget.S" { "helper":"artGetBooleanInstanceFromCode", "extend":"uxtb w0, w0" }
+%include "arm64/op_iget.S" { "helper":"MterpIGetU8", "extend":"uxtb w0, w0" }
diff --git a/runtime/interpreter/mterp/arm64/op_iget_byte.S b/runtime/interpreter/mterp/arm64/op_iget_byte.S
index fd3f164..d5ef1d3 100644
--- a/runtime/interpreter/mterp/arm64/op_iget_byte.S
+++ b/runtime/interpreter/mterp/arm64/op_iget_byte.S
@@ -1 +1 @@
-%include "arm64/op_iget.S" { "helper":"artGetByteInstanceFromCode", "extend":"sxtb w0, w0" }
+%include "arm64/op_iget.S" { "helper":"MterpIGetI8", "extend":"sxtb w0, w0" }
diff --git a/runtime/interpreter/mterp/arm64/op_iget_char.S b/runtime/interpreter/mterp/arm64/op_iget_char.S
index ea23275..68e1435 100644
--- a/runtime/interpreter/mterp/arm64/op_iget_char.S
+++ b/runtime/interpreter/mterp/arm64/op_iget_char.S
@@ -1 +1 @@
-%include "arm64/op_iget.S" { "helper":"artGetCharInstanceFromCode", "extend":"uxth w0, w0" }
+%include "arm64/op_iget.S" { "helper":"MterpIGetU16", "extend":"uxth w0, w0" }
diff --git a/runtime/interpreter/mterp/arm64/op_iget_object.S b/runtime/interpreter/mterp/arm64/op_iget_object.S
index 03be78d..40ddadd 100644
--- a/runtime/interpreter/mterp/arm64/op_iget_object.S
+++ b/runtime/interpreter/mterp/arm64/op_iget_object.S
@@ -1 +1 @@
-%include "arm64/op_iget.S" { "is_object":"1", "helper":"artGetObjInstanceFromCode" }
+%include "arm64/op_iget.S" { "is_object":"1", "helper":"MterpIGetObj" }
diff --git a/runtime/interpreter/mterp/arm64/op_iget_short.S b/runtime/interpreter/mterp/arm64/op_iget_short.S
index c347542..714f4b9 100644
--- a/runtime/interpreter/mterp/arm64/op_iget_short.S
+++ b/runtime/interpreter/mterp/arm64/op_iget_short.S
@@ -1 +1 @@
-%include "arm64/op_iget.S" { "helper":"artGetShortInstanceFromCode", "extend":"sxth w0, w0" }
+%include "arm64/op_iget.S" { "helper":"MterpIGetI16", "extend":"sxth w0, w0" }
diff --git a/runtime/interpreter/mterp/arm64/op_iget_wide.S b/runtime/interpreter/mterp/arm64/op_iget_wide.S
index 9718390..4fc735c 100644
--- a/runtime/interpreter/mterp/arm64/op_iget_wide.S
+++ b/runtime/interpreter/mterp/arm64/op_iget_wide.S
@@ -9,7 +9,7 @@
     GET_VREG w1, w1                        // w1<- fp[B], the object pointer
     ldr      x2, [xFP, #OFF_FP_METHOD]     // w2<- referrer
     mov      x3, xSELF                     // w3<- self
-    bl       artGet64InstanceFromCode
+    bl       MterpIGetU64
     ldr      x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     w2, wINST, #8, #4             // w2<- A
     PREFETCH_INST 2
diff --git a/runtime/interpreter/mterp/arm64/op_iput.S b/runtime/interpreter/mterp/arm64/op_iput.S
index a8c0e61..5e21d5c 100644
--- a/runtime/interpreter/mterp/arm64/op_iput.S
+++ b/runtime/interpreter/mterp/arm64/op_iput.S
@@ -1,11 +1,11 @@
-%default { "is_object":"0", "handler":"artSet32InstanceFromMterp" }
+%default { "is_object":"0", "helper":"MterpIPutU32" }
     /*
      * General 32-bit instance field put.
      *
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern $handler
+    .extern $helper
     EXPORT_PC
     FETCH    w0, 1                      // w0<- field ref CCCC
     lsr      w1, wINST, #12             // w1<- B
@@ -14,7 +14,7 @@
     GET_VREG w2, w2                     // w2<- fp[A]
     ldr      x3, [xFP, #OFF_FP_METHOD]  // w3<- referrer
     PREFETCH_INST 2
-    bl       $handler
+    bl       $helper
     cbnz     w0, MterpPossibleException
     ADVANCE  2                          // advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
diff --git a/runtime/interpreter/mterp/arm64/op_iput_boolean.S b/runtime/interpreter/mterp/arm64/op_iput_boolean.S
index bbf5319..12a278c 100644
--- a/runtime/interpreter/mterp/arm64/op_iput_boolean.S
+++ b/runtime/interpreter/mterp/arm64/op_iput_boolean.S
@@ -1 +1 @@
-%include "arm64/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "arm64/op_iput.S" { "helper":"MterpIPutU8" }
diff --git a/runtime/interpreter/mterp/arm64/op_iput_byte.S b/runtime/interpreter/mterp/arm64/op_iput_byte.S
index bbf5319..82b99e9 100644
--- a/runtime/interpreter/mterp/arm64/op_iput_byte.S
+++ b/runtime/interpreter/mterp/arm64/op_iput_byte.S
@@ -1 +1 @@
-%include "arm64/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "arm64/op_iput.S" { "helper":"MterpIPutI8" }
diff --git a/runtime/interpreter/mterp/arm64/op_iput_char.S b/runtime/interpreter/mterp/arm64/op_iput_char.S
index 150d879..427d92d 100644
--- a/runtime/interpreter/mterp/arm64/op_iput_char.S
+++ b/runtime/interpreter/mterp/arm64/op_iput_char.S
@@ -1 +1 @@
-%include "arm64/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "arm64/op_iput.S" { "helper":"MterpIPutU16" }
diff --git a/runtime/interpreter/mterp/arm64/op_iput_object.S b/runtime/interpreter/mterp/arm64/op_iput_object.S
index 37a649b..0c0441a 100644
--- a/runtime/interpreter/mterp/arm64/op_iput_object.S
+++ b/runtime/interpreter/mterp/arm64/op_iput_object.S
@@ -3,7 +3,7 @@
     mov     x1, xPC
     mov     w2, wINST
     mov     x3, xSELF
-    bl      MterpIputObject
+    bl      MterpIPutObj
     cbz     w0, MterpException
     FETCH_ADVANCE_INST 2                // advance rPC, load rINST
     GET_INST_OPCODE ip                  // extract opcode from rINST
diff --git a/runtime/interpreter/mterp/arm64/op_iput_short.S b/runtime/interpreter/mterp/arm64/op_iput_short.S
index 150d879..67f1ace 100644
--- a/runtime/interpreter/mterp/arm64/op_iput_short.S
+++ b/runtime/interpreter/mterp/arm64/op_iput_short.S
@@ -1 +1 @@
-%include "arm64/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "arm64/op_iput.S" { "helper":"MterpIPutI16" }
diff --git a/runtime/interpreter/mterp/arm64/op_iput_wide.S b/runtime/interpreter/mterp/arm64/op_iput_wide.S
index e1ab127..be6aeb0 100644
--- a/runtime/interpreter/mterp/arm64/op_iput_wide.S
+++ b/runtime/interpreter/mterp/arm64/op_iput_wide.S
@@ -1,5 +1,5 @@
     /* iput-wide vA, vB, field//CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     FETCH    w0, 1                      // w0<- field ref CCCC
     lsr      w1, wINST, #12             // w1<- B
@@ -8,7 +8,7 @@
     VREG_INDEX_TO_ADDR x2, x2           // w2<- &fp[A]
     ldr      x3, [xFP, #OFF_FP_METHOD]  // w3<- referrer
     PREFETCH_INST 2
-    bl       artSet64InstanceFromMterp
+    bl       MterpIPutU64
     cbnz     w0, MterpPossibleException
     ADVANCE  2                          // advance rPC
     GET_INST_OPCODE ip                  // extract opcode from wINST
diff --git a/runtime/interpreter/mterp/arm64/op_sget.S b/runtime/interpreter/mterp/arm64/op_sget.S
index 84e71ac..00b07fa 100644
--- a/runtime/interpreter/mterp/arm64/op_sget.S
+++ b/runtime/interpreter/mterp/arm64/op_sget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"MterpGet32Static", "extend":"" }
+%default { "is_object":"0", "helper":"MterpSGetU32", "extend":"" }
     /*
      * General SGET handler wrapper.
      *
diff --git a/runtime/interpreter/mterp/arm64/op_sget_boolean.S b/runtime/interpreter/mterp/arm64/op_sget_boolean.S
index 868f41c..73f3a10 100644
--- a/runtime/interpreter/mterp/arm64/op_sget_boolean.S
+++ b/runtime/interpreter/mterp/arm64/op_sget_boolean.S
@@ -1 +1 @@
-%include "arm64/op_sget.S" {"helper":"MterpGetBooleanStatic", "extend":"uxtb w0, w0"}
+%include "arm64/op_sget.S" {"helper":"MterpSGetU8", "extend":"uxtb w0, w0"}
diff --git a/runtime/interpreter/mterp/arm64/op_sget_byte.S b/runtime/interpreter/mterp/arm64/op_sget_byte.S
index e135aa7..38c0da6 100644
--- a/runtime/interpreter/mterp/arm64/op_sget_byte.S
+++ b/runtime/interpreter/mterp/arm64/op_sget_byte.S
@@ -1 +1 @@
-%include "arm64/op_sget.S" {"helper":"MterpGetByteStatic", "extend":"sxtb w0, w0"}
+%include "arm64/op_sget.S" {"helper":"MterpSGetI8", "extend":"sxtb w0, w0"}
diff --git a/runtime/interpreter/mterp/arm64/op_sget_char.S b/runtime/interpreter/mterp/arm64/op_sget_char.S
index 05d57ac..c0801bf 100644
--- a/runtime/interpreter/mterp/arm64/op_sget_char.S
+++ b/runtime/interpreter/mterp/arm64/op_sget_char.S
@@ -1 +1 @@
-%include "arm64/op_sget.S" {"helper":"MterpGetCharStatic", "extend":"uxth w0, w0"}
+%include "arm64/op_sget.S" {"helper":"MterpSGetU16", "extend":"uxth w0, w0"}
diff --git a/runtime/interpreter/mterp/arm64/op_sget_object.S b/runtime/interpreter/mterp/arm64/op_sget_object.S
index 1faaf6e..69d6adb 100644
--- a/runtime/interpreter/mterp/arm64/op_sget_object.S
+++ b/runtime/interpreter/mterp/arm64/op_sget_object.S
@@ -1 +1 @@
-%include "arm64/op_sget.S" {"is_object":"1", "helper":"MterpGetObjStatic"}
+%include "arm64/op_sget.S" {"is_object":"1", "helper":"MterpSGetObj"}
diff --git a/runtime/interpreter/mterp/arm64/op_sget_short.S b/runtime/interpreter/mterp/arm64/op_sget_short.S
index 5900231..81e0434 100644
--- a/runtime/interpreter/mterp/arm64/op_sget_short.S
+++ b/runtime/interpreter/mterp/arm64/op_sget_short.S
@@ -1 +1 @@
-%include "arm64/op_sget.S" {"helper":"MterpGetShortStatic", "extend":"sxth w0, w0"}
+%include "arm64/op_sget.S" {"helper":"MterpSGetI16", "extend":"sxth w0, w0"}
diff --git a/runtime/interpreter/mterp/arm64/op_sget_wide.S b/runtime/interpreter/mterp/arm64/op_sget_wide.S
index 92f3f7d..546ab94 100644
--- a/runtime/interpreter/mterp/arm64/op_sget_wide.S
+++ b/runtime/interpreter/mterp/arm64/op_sget_wide.S
@@ -4,12 +4,12 @@
      */
     /* sget-wide vAA, field//BBBB */
 
-    .extern MterpGet64StaticFromCode
+    .extern MterpSGetU64
     EXPORT_PC
     FETCH w0, 1                         // w0<- field ref BBBB
     ldr   x1, [xFP, #OFF_FP_METHOD]
     mov   x2, xSELF
-    bl    MterpGet64Static
+    bl    MterpSGetU64
     ldr   x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     lsr   w4, wINST, #8                 // w4<- AA
     cbnz  x3, MterpException            // bail out
diff --git a/runtime/interpreter/mterp/arm64/op_sput.S b/runtime/interpreter/mterp/arm64/op_sput.S
index e322af0..7a0dc30 100644
--- a/runtime/interpreter/mterp/arm64/op_sput.S
+++ b/runtime/interpreter/mterp/arm64/op_sput.S
@@ -1,4 +1,4 @@
-%default { "helper":"MterpSet32Static"}
+%default { "helper":"MterpSPutU32"}
     /*
      * General SPUT handler wrapper.
      *
diff --git a/runtime/interpreter/mterp/arm64/op_sput_boolean.S b/runtime/interpreter/mterp/arm64/op_sput_boolean.S
index 9928f31..3d0c7c0 100644
--- a/runtime/interpreter/mterp/arm64/op_sput_boolean.S
+++ b/runtime/interpreter/mterp/arm64/op_sput_boolean.S
@@ -1 +1 @@
-%include "arm64/op_sput.S" {"helper":"MterpSetBooleanStatic"}
+%include "arm64/op_sput.S" {"helper":"MterpSPutU8"}
diff --git a/runtime/interpreter/mterp/arm64/op_sput_byte.S b/runtime/interpreter/mterp/arm64/op_sput_byte.S
index 16d6ba9..489cf92 100644
--- a/runtime/interpreter/mterp/arm64/op_sput_byte.S
+++ b/runtime/interpreter/mterp/arm64/op_sput_byte.S
@@ -1 +1 @@
-%include "arm64/op_sput.S" {"helper":"MterpSetByteStatic"}
+%include "arm64/op_sput.S" {"helper":"MterpSPutI8"}
diff --git a/runtime/interpreter/mterp/arm64/op_sput_char.S b/runtime/interpreter/mterp/arm64/op_sput_char.S
index ab5e815..f79d311 100644
--- a/runtime/interpreter/mterp/arm64/op_sput_char.S
+++ b/runtime/interpreter/mterp/arm64/op_sput_char.S
@@ -1 +1 @@
-%include "arm64/op_sput.S" {"helper":"MterpSetCharStatic"}
+%include "arm64/op_sput.S" {"helper":"MterpSPutU16"}
diff --git a/runtime/interpreter/mterp/arm64/op_sput_object.S b/runtime/interpreter/mterp/arm64/op_sput_object.S
index c176da2..a649656 100644
--- a/runtime/interpreter/mterp/arm64/op_sput_object.S
+++ b/runtime/interpreter/mterp/arm64/op_sput_object.S
@@ -3,7 +3,7 @@
     mov     x1, xPC
     mov     x2, xINST
     mov     x3, xSELF
-    bl      MterpSputObject
+    bl      MterpSPutObj
     cbz     w0, MterpException
     FETCH_ADVANCE_INST 2                // advance rPC, load rINST
     GET_INST_OPCODE ip                  // extract opcode from rINST
diff --git a/runtime/interpreter/mterp/arm64/op_sput_short.S b/runtime/interpreter/mterp/arm64/op_sput_short.S
index b54f88a..06482cd 100644
--- a/runtime/interpreter/mterp/arm64/op_sput_short.S
+++ b/runtime/interpreter/mterp/arm64/op_sput_short.S
@@ -1 +1 @@
-%include "arm64/op_sput.S" {"helper":"MterpSetShortStatic"}
+%include "arm64/op_sput.S" {"helper":"MterpSPutI16"}
diff --git a/runtime/interpreter/mterp/arm64/op_sput_wide.S b/runtime/interpreter/mterp/arm64/op_sput_wide.S
index 4aeb8ff..58b3c42 100644
--- a/runtime/interpreter/mterp/arm64/op_sput_wide.S
+++ b/runtime/interpreter/mterp/arm64/op_sput_wide.S
@@ -3,7 +3,7 @@
      *
      */
     /* sput-wide vAA, field//BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     FETCH   w0, 1                       // w0<- field ref BBBB
     lsr     w1, wINST, #8               // w1<- AA
@@ -11,7 +11,7 @@
     ldr     x2, [xFP, #OFF_FP_METHOD]
     mov     x3, xSELF
     PREFETCH_INST 2                     // Get next inst, but don't advance rPC
-    bl      MterpSet64Static
+    bl      MterpSPutU64
     cbnz    w0, MterpException          // 0 on success, -1 on failure
     ADVANCE 2                           // Past exception point - now advance rPC
     GET_INST_OPCODE ip                  // extract opcode from wINST
diff --git a/runtime/interpreter/mterp/mips/op_iget.S b/runtime/interpreter/mterp/mips/op_iget.S
index 01f42d9..33717de 100644
--- a/runtime/interpreter/mterp/mips/op_iget.S
+++ b/runtime/interpreter/mterp/mips/op_iget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"artGet32InstanceFromCode"}
+%default { "is_object":"0", "helper":"MterpIGetU32"}
     /*
      * General instance field get.
      *
diff --git a/runtime/interpreter/mterp/mips/op_iget_boolean.S b/runtime/interpreter/mterp/mips/op_iget_boolean.S
index e03364e..f2ef68d 100644
--- a/runtime/interpreter/mterp/mips/op_iget_boolean.S
+++ b/runtime/interpreter/mterp/mips/op_iget_boolean.S
@@ -1 +1 @@
-%include "mips/op_iget.S" { "helper":"artGetBooleanInstanceFromCode" }
+%include "mips/op_iget.S" { "helper":"MterpIGetU8" }
diff --git a/runtime/interpreter/mterp/mips/op_iget_byte.S b/runtime/interpreter/mterp/mips/op_iget_byte.S
index dc87cfe..0c8fb7c 100644
--- a/runtime/interpreter/mterp/mips/op_iget_byte.S
+++ b/runtime/interpreter/mterp/mips/op_iget_byte.S
@@ -1 +1 @@
-%include "mips/op_iget.S" { "helper":"artGetByteInstanceFromCode" }
+%include "mips/op_iget.S" { "helper":"MterpIGetI8" }
diff --git a/runtime/interpreter/mterp/mips/op_iget_char.S b/runtime/interpreter/mterp/mips/op_iget_char.S
index 55f8a93..69d04c4 100644
--- a/runtime/interpreter/mterp/mips/op_iget_char.S
+++ b/runtime/interpreter/mterp/mips/op_iget_char.S
@@ -1 +1 @@
-%include "mips/op_iget.S" { "helper":"artGetCharInstanceFromCode" }
+%include "mips/op_iget.S" { "helper":"MterpIGetU16" }
diff --git a/runtime/interpreter/mterp/mips/op_iget_object.S b/runtime/interpreter/mterp/mips/op_iget_object.S
index 11d93a4..bea330a 100644
--- a/runtime/interpreter/mterp/mips/op_iget_object.S
+++ b/runtime/interpreter/mterp/mips/op_iget_object.S
@@ -1 +1 @@
-%include "mips/op_iget.S" { "is_object":"1", "helper":"artGetObjInstanceFromCode" }
+%include "mips/op_iget.S" { "is_object":"1", "helper":"MterpIGetObj" }
diff --git a/runtime/interpreter/mterp/mips/op_iget_short.S b/runtime/interpreter/mterp/mips/op_iget_short.S
index 9086246..357c791 100644
--- a/runtime/interpreter/mterp/mips/op_iget_short.S
+++ b/runtime/interpreter/mterp/mips/op_iget_short.S
@@ -1 +1 @@
-%include "mips/op_iget.S" { "helper":"artGetShortInstanceFromCode" }
+%include "mips/op_iget.S" { "helper":"MterpIGetI16" }
diff --git a/runtime/interpreter/mterp/mips/op_iget_wide.S b/runtime/interpreter/mterp/mips/op_iget_wide.S
index cf5019e..858a889 100644
--- a/runtime/interpreter/mterp/mips/op_iget_wide.S
+++ b/runtime/interpreter/mterp/mips/op_iget_wide.S
@@ -10,7 +10,7 @@
     GET_VREG(a1, a1)                       # a1 <- fp[B], the object pointer
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- referrer
     move  a3, rSELF                        # a3 <- self
-    JAL(artGet64InstanceFromCode)
+    JAL(MterpIGetU64)
     lw   a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA4(a2)                           # a2<- A+
     PREFETCH_INST(2)                       # load rINST
diff --git a/runtime/interpreter/mterp/mips/op_iput.S b/runtime/interpreter/mterp/mips/op_iput.S
index 9133d60..4dd4075 100644
--- a/runtime/interpreter/mterp/mips/op_iput.S
+++ b/runtime/interpreter/mterp/mips/op_iput.S
@@ -1,11 +1,11 @@
-%default { "handler":"artSet32InstanceFromMterp" }
+%default { "helper":"MterpIPutU32" }
     /*
      * General 32-bit instance field put.
      *
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern $handler
+    .extern $helper
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPB(a1)                            # a1 <- B
@@ -14,7 +14,7 @@
     GET_VREG(a2, a2)                       # a2 <- fp[A]
     lw    a3, OFF_FP_METHOD(rFP)           # a3 <- referrer
     PREFETCH_INST(2)                       # load rINST
-    JAL($handler)
+    JAL($helper)
     bnez  v0, MterpPossibleException       # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mips/op_iput_boolean.S b/runtime/interpreter/mterp/mips/op_iput_boolean.S
index da28c97..55ac4ce 100644
--- a/runtime/interpreter/mterp/mips/op_iput_boolean.S
+++ b/runtime/interpreter/mterp/mips/op_iput_boolean.S
@@ -1 +1 @@
-%include "mips/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "mips/op_iput.S" { "helper":"MterpIPutU8" }
diff --git a/runtime/interpreter/mterp/mips/op_iput_byte.S b/runtime/interpreter/mterp/mips/op_iput_byte.S
index da28c97..61e489b 100644
--- a/runtime/interpreter/mterp/mips/op_iput_byte.S
+++ b/runtime/interpreter/mterp/mips/op_iput_byte.S
@@ -1 +1 @@
-%include "mips/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "mips/op_iput.S" { "helper":"MterpIPutI8" }
diff --git a/runtime/interpreter/mterp/mips/op_iput_char.S b/runtime/interpreter/mterp/mips/op_iput_char.S
index 389b0bf..2caad1e 100644
--- a/runtime/interpreter/mterp/mips/op_iput_char.S
+++ b/runtime/interpreter/mterp/mips/op_iput_char.S
@@ -1 +1 @@
-%include "mips/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "mips/op_iput.S" { "helper":"MterpIPutU16" }
diff --git a/runtime/interpreter/mterp/mips/op_iput_object.S b/runtime/interpreter/mterp/mips/op_iput_object.S
index cfa56ec..c96a4d4 100644
--- a/runtime/interpreter/mterp/mips/op_iput_object.S
+++ b/runtime/interpreter/mterp/mips/op_iput_object.S
@@ -9,7 +9,7 @@
     move   a1, rPC
     move   a2, rINST
     move   a3, rSELF
-    JAL(MterpIputObject)
+    JAL(MterpIPutObj)
     beqz   v0, MterpException
     FETCH_ADVANCE_INST(2)               # advance rPC, load rINST
     GET_INST_OPCODE(t0)                 # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mips/op_iput_short.S b/runtime/interpreter/mterp/mips/op_iput_short.S
index 389b0bf..414a15b 100644
--- a/runtime/interpreter/mterp/mips/op_iput_short.S
+++ b/runtime/interpreter/mterp/mips/op_iput_short.S
@@ -1 +1 @@
-%include "mips/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "mips/op_iput.S" { "helper":"MterpIPutI16" }
diff --git a/runtime/interpreter/mterp/mips/op_iput_wide.S b/runtime/interpreter/mterp/mips/op_iput_wide.S
index bc3d758..dccb6b7 100644
--- a/runtime/interpreter/mterp/mips/op_iput_wide.S
+++ b/runtime/interpreter/mterp/mips/op_iput_wide.S
@@ -1,5 +1,5 @@
     /* iput-wide vA, vB, field@CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPB(a1)                            # a1 <- B
@@ -8,7 +8,7 @@
     EAS2(a2, rFP, a2)                      # a2 <- &fp[A]
     lw    a3, OFF_FP_METHOD(rFP)           # a3 <- referrer
     PREFETCH_INST(2)                       # load rINST
-    JAL(artSet64InstanceFromMterp)
+    JAL(MterpIPutU64)
     bnez  v0, MterpPossibleException       # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mips/op_sget.S b/runtime/interpreter/mterp/mips/op_sget.S
index 635df8a..8750a17 100644
--- a/runtime/interpreter/mterp/mips/op_sget.S
+++ b/runtime/interpreter/mterp/mips/op_sget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"MterpGet32Static" }
+%default { "is_object":"0", "helper":"MterpSGetU32" }
     /*
      * General SGET handler.
      *
diff --git a/runtime/interpreter/mterp/mips/op_sget_boolean.S b/runtime/interpreter/mterp/mips/op_sget_boolean.S
index 7829970..7a7012e 100644
--- a/runtime/interpreter/mterp/mips/op_sget_boolean.S
+++ b/runtime/interpreter/mterp/mips/op_sget_boolean.S
@@ -1 +1 @@
-%include "mips/op_sget.S" {"helper":"MterpGetBooleanStatic"}
+%include "mips/op_sget.S" {"helper":"MterpSGetU8"}
diff --git a/runtime/interpreter/mterp/mips/op_sget_byte.S b/runtime/interpreter/mterp/mips/op_sget_byte.S
index ee08342..a2f1dbf 100644
--- a/runtime/interpreter/mterp/mips/op_sget_byte.S
+++ b/runtime/interpreter/mterp/mips/op_sget_byte.S
@@ -1 +1 @@
-%include "mips/op_sget.S" {"helper":"MterpGetByteStatic"}
+%include "mips/op_sget.S" {"helper":"MterpSGetI8"}
diff --git a/runtime/interpreter/mterp/mips/op_sget_char.S b/runtime/interpreter/mterp/mips/op_sget_char.S
index d8b477a..07d4041 100644
--- a/runtime/interpreter/mterp/mips/op_sget_char.S
+++ b/runtime/interpreter/mterp/mips/op_sget_char.S
@@ -1 +1 @@
-%include "mips/op_sget.S" {"helper":"MterpGetCharStatic"}
+%include "mips/op_sget.S" {"helper":"MterpSGetU16"}
diff --git a/runtime/interpreter/mterp/mips/op_sget_object.S b/runtime/interpreter/mterp/mips/op_sget_object.S
index 2dc00c3..0a3c9ee 100644
--- a/runtime/interpreter/mterp/mips/op_sget_object.S
+++ b/runtime/interpreter/mterp/mips/op_sget_object.S
@@ -1 +1 @@
-%include "mips/op_sget.S" {"is_object":"1", "helper":"MterpGetObjStatic"}
+%include "mips/op_sget.S" {"is_object":"1", "helper":"MterpSGetObj"}
diff --git a/runtime/interpreter/mterp/mips/op_sget_short.S b/runtime/interpreter/mterp/mips/op_sget_short.S
index ab55d93..2960443 100644
--- a/runtime/interpreter/mterp/mips/op_sget_short.S
+++ b/runtime/interpreter/mterp/mips/op_sget_short.S
@@ -1 +1 @@
-%include "mips/op_sget.S" {"helper":"MterpGetShortStatic"}
+%include "mips/op_sget.S" {"helper":"MterpSGetI16"}
diff --git a/runtime/interpreter/mterp/mips/op_sget_wide.S b/runtime/interpreter/mterp/mips/op_sget_wide.S
index ec4295a..76f78cb 100644
--- a/runtime/interpreter/mterp/mips/op_sget_wide.S
+++ b/runtime/interpreter/mterp/mips/op_sget_wide.S
@@ -2,12 +2,12 @@
      * 64-bit SGET handler.
      */
     /* sget-wide vAA, field@BBBB */
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref BBBB
     lw    a1, OFF_FP_METHOD(rFP)           # a1 <- method
     move  a2, rSELF                        # a2 <- self
-    JAL(MterpGet64Static)
+    JAL(MterpSGetU64)
     lw    a3, THREAD_EXCEPTION_OFFSET(rSELF)
     bnez  a3, MterpException
     GET_OPA(a1)                            # a1 <- AA
diff --git a/runtime/interpreter/mterp/mips/op_sput.S b/runtime/interpreter/mterp/mips/op_sput.S
index 37f8687..547de39 100644
--- a/runtime/interpreter/mterp/mips/op_sput.S
+++ b/runtime/interpreter/mterp/mips/op_sput.S
@@ -1,4 +1,4 @@
-%default { "helper":"MterpSet32Static"}
+%default { "helper":"MterpSPutU32"}
     /*
      * General SPUT handler.
      *
diff --git a/runtime/interpreter/mterp/mips/op_sput_boolean.S b/runtime/interpreter/mterp/mips/op_sput_boolean.S
index 6426cd4..0137430 100644
--- a/runtime/interpreter/mterp/mips/op_sput_boolean.S
+++ b/runtime/interpreter/mterp/mips/op_sput_boolean.S
@@ -1 +1 @@
-%include "mips/op_sput.S" {"helper":"MterpSetBooleanStatic"}
+%include "mips/op_sput.S" {"helper":"MterpSPutU8"}
diff --git a/runtime/interpreter/mterp/mips/op_sput_byte.S b/runtime/interpreter/mterp/mips/op_sput_byte.S
index c68d18f..5ae4256 100644
--- a/runtime/interpreter/mterp/mips/op_sput_byte.S
+++ b/runtime/interpreter/mterp/mips/op_sput_byte.S
@@ -1 +1 @@
-%include "mips/op_sput.S" {"helper":"MterpSetByteStatic"}
+%include "mips/op_sput.S" {"helper":"MterpSPutI8"}
diff --git a/runtime/interpreter/mterp/mips/op_sput_char.S b/runtime/interpreter/mterp/mips/op_sput_char.S
index 9b8983e..83787a7 100644
--- a/runtime/interpreter/mterp/mips/op_sput_char.S
+++ b/runtime/interpreter/mterp/mips/op_sput_char.S
@@ -1 +1 @@
-%include "mips/op_sput.S" {"helper":"MterpSetCharStatic"}
+%include "mips/op_sput.S" {"helper":"MterpSPutU16"}
diff --git a/runtime/interpreter/mterp/mips/op_sput_object.S b/runtime/interpreter/mterp/mips/op_sput_object.S
index 4f9034e..55c88a6 100644
--- a/runtime/interpreter/mterp/mips/op_sput_object.S
+++ b/runtime/interpreter/mterp/mips/op_sput_object.S
@@ -9,7 +9,7 @@
     move   a1, rPC
     move   a2, rINST
     move   a3, rSELF
-    JAL(MterpSputObject)
+    JAL(MterpSPutObj)
     beqz   v0, MterpException
     FETCH_ADVANCE_INST(2)               # advance rPC, load rINST
     GET_INST_OPCODE(t0)                 # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mips/op_sput_short.S b/runtime/interpreter/mterp/mips/op_sput_short.S
index 5a57ed9..df99b44 100644
--- a/runtime/interpreter/mterp/mips/op_sput_short.S
+++ b/runtime/interpreter/mterp/mips/op_sput_short.S
@@ -1 +1 @@
-%include "mips/op_sput.S" {"helper":"MterpSetShortStatic"}
+%include "mips/op_sput.S" {"helper":"MterpSPutI16"}
diff --git a/runtime/interpreter/mterp/mips/op_sput_wide.S b/runtime/interpreter/mterp/mips/op_sput_wide.S
index c090007..cfaaaee 100644
--- a/runtime/interpreter/mterp/mips/op_sput_wide.S
+++ b/runtime/interpreter/mterp/mips/op_sput_wide.S
@@ -2,7 +2,7 @@
      * 64-bit SPUT handler.
      */
     /* sput-wide vAA, field@BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPA(a1)                            # a1 <- AA
@@ -10,7 +10,7 @@
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- method
     move  a3, rSELF                        # a3 <- self
     PREFETCH_INST(2)                       # load rINST
-    JAL(MterpSet64Static)
+    JAL(MterpSPutU64)
     bnez  v0, MterpException               # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mips64/op_iget.S b/runtime/interpreter/mterp/mips64/op_iget.S
index ade4b31..a8ce94c 100644
--- a/runtime/interpreter/mterp/mips64/op_iget.S
+++ b/runtime/interpreter/mterp/mips64/op_iget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"artGet32InstanceFromCode"}
+%default { "is_object":"0", "helper":"MterpIGetU32"}
     /*
      * General instance field get.
      *
diff --git a/runtime/interpreter/mterp/mips64/op_iget_boolean.S b/runtime/interpreter/mterp/mips64/op_iget_boolean.S
index cb2c8be..dc2a42a 100644
--- a/runtime/interpreter/mterp/mips64/op_iget_boolean.S
+++ b/runtime/interpreter/mterp/mips64/op_iget_boolean.S
@@ -1 +1 @@
-%include "mips64/op_iget.S" { "helper":"artGetBooleanInstanceFromCode" }
+%include "mips64/op_iget.S" { "helper":"MterpIGetU8" }
diff --git a/runtime/interpreter/mterp/mips64/op_iget_byte.S b/runtime/interpreter/mterp/mips64/op_iget_byte.S
index 099d8d0..c5bf650 100644
--- a/runtime/interpreter/mterp/mips64/op_iget_byte.S
+++ b/runtime/interpreter/mterp/mips64/op_iget_byte.S
@@ -1 +1 @@
-%include "mips64/op_iget.S" { "helper":"artGetByteInstanceFromCode" }
+%include "mips64/op_iget.S" { "helper":"MterpIGetI8" }
diff --git a/runtime/interpreter/mterp/mips64/op_iget_char.S b/runtime/interpreter/mterp/mips64/op_iget_char.S
index 927b7af..3bf0c5a 100644
--- a/runtime/interpreter/mterp/mips64/op_iget_char.S
+++ b/runtime/interpreter/mterp/mips64/op_iget_char.S
@@ -1 +1 @@
-%include "mips64/op_iget.S" { "helper":"artGetCharInstanceFromCode" }
+%include "mips64/op_iget.S" { "helper":"MterpIGetU16" }
diff --git a/runtime/interpreter/mterp/mips64/op_iget_object.S b/runtime/interpreter/mterp/mips64/op_iget_object.S
index c658556..23fa187 100644
--- a/runtime/interpreter/mterp/mips64/op_iget_object.S
+++ b/runtime/interpreter/mterp/mips64/op_iget_object.S
@@ -1 +1 @@
-%include "mips64/op_iget.S" { "is_object":"1", "helper":"artGetObjInstanceFromCode" }
+%include "mips64/op_iget.S" { "is_object":"1", "helper":"MterpIGetObj" }
diff --git a/runtime/interpreter/mterp/mips64/op_iget_short.S b/runtime/interpreter/mterp/mips64/op_iget_short.S
index 28b5093..a9927fc 100644
--- a/runtime/interpreter/mterp/mips64/op_iget_short.S
+++ b/runtime/interpreter/mterp/mips64/op_iget_short.S
@@ -1 +1 @@
-%include "mips64/op_iget.S" { "helper":"artGetShortInstanceFromCode" }
+%include "mips64/op_iget.S" { "helper":"MterpIGetI16" }
diff --git a/runtime/interpreter/mterp/mips64/op_iget_wide.S b/runtime/interpreter/mterp/mips64/op_iget_wide.S
index 85cf670..08bf544 100644
--- a/runtime/interpreter/mterp/mips64/op_iget_wide.S
+++ b/runtime/interpreter/mterp/mips64/op_iget_wide.S
@@ -3,14 +3,14 @@
      *
      * for: iget-wide
      */
-    .extern artGet64InstanceFromCode
+    .extern MterpIGetU64
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
     GET_VREG_U a1, a1                   # a1 <- fp[B], the object pointer
     ld       a2, OFF_FP_METHOD(rFP)     # a2 <- referrer
     move     a3, rSELF                  # a3 <- self
-    jal      artGet64InstanceFromCode
+    jal      MterpIGetU64
     ld       a3, THREAD_EXCEPTION_OFFSET(rSELF)
     ext      a2, rINST, 8, 4            # a2 <- A
     PREFETCH_INST 2
diff --git a/runtime/interpreter/mterp/mips64/op_iput.S b/runtime/interpreter/mterp/mips64/op_iput.S
index a906a0f..9a789e6 100644
--- a/runtime/interpreter/mterp/mips64/op_iput.S
+++ b/runtime/interpreter/mterp/mips64/op_iput.S
@@ -1,4 +1,4 @@
-%default { "helper":"artSet32InstanceFromMterp" }
+%default { "helper":"MterpIPutU32" }
     /*
      * General 32-bit instance field put.
      *
diff --git a/runtime/interpreter/mterp/mips64/op_iput_boolean.S b/runtime/interpreter/mterp/mips64/op_iput_boolean.S
index 3034fa5..8e1d083 100644
--- a/runtime/interpreter/mterp/mips64/op_iput_boolean.S
+++ b/runtime/interpreter/mterp/mips64/op_iput_boolean.S
@@ -1 +1 @@
-%include "mips64/op_iput.S" { "helper":"artSet8InstanceFromMterp" }
+%include "mips64/op_iput.S" { "helper":"MterpIPutU8" }
diff --git a/runtime/interpreter/mterp/mips64/op_iput_byte.S b/runtime/interpreter/mterp/mips64/op_iput_byte.S
index 3034fa5..ce3b614 100644
--- a/runtime/interpreter/mterp/mips64/op_iput_byte.S
+++ b/runtime/interpreter/mterp/mips64/op_iput_byte.S
@@ -1 +1 @@
-%include "mips64/op_iput.S" { "helper":"artSet8InstanceFromMterp" }
+%include "mips64/op_iput.S" { "helper":"MterpIPutI8" }
diff --git a/runtime/interpreter/mterp/mips64/op_iput_char.S b/runtime/interpreter/mterp/mips64/op_iput_char.S
index 4c2fa28..1d587fa 100644
--- a/runtime/interpreter/mterp/mips64/op_iput_char.S
+++ b/runtime/interpreter/mterp/mips64/op_iput_char.S
@@ -1 +1 @@
-%include "mips64/op_iput.S" { "helper":"artSet16InstanceFromMterp" }
+%include "mips64/op_iput.S" { "helper":"MterpIPutU16" }
diff --git a/runtime/interpreter/mterp/mips64/op_iput_object.S b/runtime/interpreter/mterp/mips64/op_iput_object.S
index 9a42f54..dd1938e 100644
--- a/runtime/interpreter/mterp/mips64/op_iput_object.S
+++ b/runtime/interpreter/mterp/mips64/op_iput_object.S
@@ -1,10 +1,10 @@
-    .extern MterpIputObject
+    .extern MterpIPutObj
     EXPORT_PC
     daddu   a0, rFP, OFF_FP_SHADOWFRAME
     move    a1, rPC
     move    a2, rINST
     move    a3, rSELF
-    jal     MterpIputObject
+    jal     MterpIPutObj
     beqzc   v0, MterpException
     FETCH_ADVANCE_INST 2                # advance rPC, load rINST
     GET_INST_OPCODE v0                  # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mips64/op_iput_short.S b/runtime/interpreter/mterp/mips64/op_iput_short.S
index 4c2fa28..dd68bbe 100644
--- a/runtime/interpreter/mterp/mips64/op_iput_short.S
+++ b/runtime/interpreter/mterp/mips64/op_iput_short.S
@@ -1 +1 @@
-%include "mips64/op_iput.S" { "helper":"artSet16InstanceFromMterp" }
+%include "mips64/op_iput.S" { "helper":"MterpIPutI16" }
diff --git a/runtime/interpreter/mterp/mips64/op_iput_wide.S b/runtime/interpreter/mterp/mips64/op_iput_wide.S
index 9b790f8..6272690 100644
--- a/runtime/interpreter/mterp/mips64/op_iput_wide.S
+++ b/runtime/interpreter/mterp/mips64/op_iput_wide.S
@@ -1,5 +1,5 @@
     /* iput-wide vA, vB, field//CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
@@ -8,7 +8,7 @@
     dlsa     a2, a2, rFP, 2             # a2 <- &fp[A]
     ld       a3, OFF_FP_METHOD(rFP)     # a3 <- referrer
     PREFETCH_INST 2
-    jal      artSet64InstanceFromMterp
+    jal      MterpIPutU64
     bnez     v0, MterpPossibleException # bail out
     ADVANCE 2
     GET_INST_OPCODE v0                  # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mips64/op_sget.S b/runtime/interpreter/mterp/mips64/op_sget.S
index 71046db..b7b0382 100644
--- a/runtime/interpreter/mterp/mips64/op_sget.S
+++ b/runtime/interpreter/mterp/mips64/op_sget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"MterpGet32Static", "extend":"" }
+%default { "is_object":"0", "helper":"MterpSGetU32", "extend":"" }
     /*
      * General SGET handler wrapper.
      *
diff --git a/runtime/interpreter/mterp/mips64/op_sget_boolean.S b/runtime/interpreter/mterp/mips64/op_sget_boolean.S
index ec1ce9e..fe2deb1 100644
--- a/runtime/interpreter/mterp/mips64/op_sget_boolean.S
+++ b/runtime/interpreter/mterp/mips64/op_sget_boolean.S
@@ -1 +1 @@
-%include "mips64/op_sget.S" {"helper":"MterpGetBooleanStatic", "extend":"and v0, v0, 0xff"}
+%include "mips64/op_sget.S" {"helper":"MterpSGetU8", "extend":"and v0, v0, 0xff"}
diff --git a/runtime/interpreter/mterp/mips64/op_sget_byte.S b/runtime/interpreter/mterp/mips64/op_sget_byte.S
index 6a802f6..a7e2bef 100644
--- a/runtime/interpreter/mterp/mips64/op_sget_byte.S
+++ b/runtime/interpreter/mterp/mips64/op_sget_byte.S
@@ -1 +1 @@
-%include "mips64/op_sget.S" {"helper":"MterpGetByteStatic", "extend":"seb v0, v0"}
+%include "mips64/op_sget.S" {"helper":"MterpSGetI8", "extend":"seb v0, v0"}
diff --git a/runtime/interpreter/mterp/mips64/op_sget_char.S b/runtime/interpreter/mterp/mips64/op_sget_char.S
index 483d085..ed86f32 100644
--- a/runtime/interpreter/mterp/mips64/op_sget_char.S
+++ b/runtime/interpreter/mterp/mips64/op_sget_char.S
@@ -1 +1 @@
-%include "mips64/op_sget.S" {"helper":"MterpGetCharStatic", "extend":"and v0, v0, 0xffff"}
+%include "mips64/op_sget.S" {"helper":"MterpSGetU16", "extend":"and v0, v0, 0xffff"}
diff --git a/runtime/interpreter/mterp/mips64/op_sget_object.S b/runtime/interpreter/mterp/mips64/op_sget_object.S
index 2250696..3b260e6 100644
--- a/runtime/interpreter/mterp/mips64/op_sget_object.S
+++ b/runtime/interpreter/mterp/mips64/op_sget_object.S
@@ -1 +1 @@
-%include "mips64/op_sget.S" {"is_object":"1", "helper":"MterpGetObjStatic"}
+%include "mips64/op_sget.S" {"is_object":"1", "helper":"MterpSGetObj"}
diff --git a/runtime/interpreter/mterp/mips64/op_sget_short.S b/runtime/interpreter/mterp/mips64/op_sget_short.S
index b257bbb..f708a20 100644
--- a/runtime/interpreter/mterp/mips64/op_sget_short.S
+++ b/runtime/interpreter/mterp/mips64/op_sget_short.S
@@ -1 +1 @@
-%include "mips64/op_sget.S" {"helper":"MterpGetShortStatic", "extend":"seh v0, v0"}
+%include "mips64/op_sget.S" {"helper":"MterpSGetI16", "extend":"seh v0, v0"}
diff --git a/runtime/interpreter/mterp/mips64/op_sget_wide.S b/runtime/interpreter/mterp/mips64/op_sget_wide.S
index ace64f8..7c31252 100644
--- a/runtime/interpreter/mterp/mips64/op_sget_wide.S
+++ b/runtime/interpreter/mterp/mips64/op_sget_wide.S
@@ -3,12 +3,12 @@
      *
      */
     /* sget-wide vAA, field//BBBB */
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     ld      a1, OFF_FP_METHOD(rFP)
     move    a2, rSELF
-    jal     MterpGet64Static
+    jal     MterpSGetU64
     ld      a3, THREAD_EXCEPTION_OFFSET(rSELF)
     srl     a4, rINST, 8                # a4 <- AA
     bnez    a3, MterpException          # bail out
diff --git a/runtime/interpreter/mterp/mips64/op_sput.S b/runtime/interpreter/mterp/mips64/op_sput.S
index 466f333..28b8c3e 100644
--- a/runtime/interpreter/mterp/mips64/op_sput.S
+++ b/runtime/interpreter/mterp/mips64/op_sput.S
@@ -1,4 +1,4 @@
-%default { "helper":"MterpSet32Static" }
+%default { "helper":"MterpSPutU32" }
     /*
      * General SPUT handler wrapper.
      *
diff --git a/runtime/interpreter/mterp/mips64/op_sput_boolean.S b/runtime/interpreter/mterp/mips64/op_sput_boolean.S
index eba58f7..2e769d5 100644
--- a/runtime/interpreter/mterp/mips64/op_sput_boolean.S
+++ b/runtime/interpreter/mterp/mips64/op_sput_boolean.S
@@ -1 +1 @@
-%include "mips64/op_sput.S" {"helper":"MterpSetBooleanStatic"}
+%include "mips64/op_sput.S" {"helper":"MterpSPutU8"}
diff --git a/runtime/interpreter/mterp/mips64/op_sput_byte.S b/runtime/interpreter/mterp/mips64/op_sput_byte.S
index 80a26c0..0b04b59 100644
--- a/runtime/interpreter/mterp/mips64/op_sput_byte.S
+++ b/runtime/interpreter/mterp/mips64/op_sput_byte.S
@@ -1 +1 @@
-%include "mips64/op_sput.S" {"helper":"MterpSetByteStatic"}
+%include "mips64/op_sput.S" {"helper":"MterpSPutI8"}
diff --git a/runtime/interpreter/mterp/mips64/op_sput_char.S b/runtime/interpreter/mterp/mips64/op_sput_char.S
index c0d5bf3..4a80375 100644
--- a/runtime/interpreter/mterp/mips64/op_sput_char.S
+++ b/runtime/interpreter/mterp/mips64/op_sput_char.S
@@ -1 +1 @@
-%include "mips64/op_sput.S" {"helper":"MterpSetCharStatic"}
+%include "mips64/op_sput.S" {"helper":"MterpSPutU16"}
diff --git a/runtime/interpreter/mterp/mips64/op_sput_object.S b/runtime/interpreter/mterp/mips64/op_sput_object.S
index ef4c685..ff43967 100644
--- a/runtime/interpreter/mterp/mips64/op_sput_object.S
+++ b/runtime/interpreter/mterp/mips64/op_sput_object.S
@@ -1,10 +1,10 @@
-    .extern MterpSputObject
+    .extern MterpSPutObj
     EXPORT_PC
     daddu   a0, rFP, OFF_FP_SHADOWFRAME
     move    a1, rPC
     move    a2, rINST
     move    a3, rSELF
-    jal     MterpSputObject
+    jal     MterpSPutObj
     beqzc   v0, MterpException
     FETCH_ADVANCE_INST 2                # advance rPC, load rINST
     GET_INST_OPCODE v0                  # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mips64/op_sput_short.S b/runtime/interpreter/mterp/mips64/op_sput_short.S
index b001832..c00043b 100644
--- a/runtime/interpreter/mterp/mips64/op_sput_short.S
+++ b/runtime/interpreter/mterp/mips64/op_sput_short.S
@@ -1 +1 @@
-%include "mips64/op_sput.S" {"helper":"MterpSetShortStatic"}
+%include "mips64/op_sput.S" {"helper":"MterpSPutI16"}
diff --git a/runtime/interpreter/mterp/mips64/op_sput_wide.S b/runtime/interpreter/mterp/mips64/op_sput_wide.S
index aa3d5b4..bfb6983 100644
--- a/runtime/interpreter/mterp/mips64/op_sput_wide.S
+++ b/runtime/interpreter/mterp/mips64/op_sput_wide.S
@@ -3,7 +3,7 @@
      *
      */
     /* sput-wide vAA, field//BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     srl     a1, rINST, 8                # a2 <- AA
@@ -11,7 +11,7 @@
     ld      a2, OFF_FP_METHOD(rFP)
     move    a3, rSELF
     PREFETCH_INST 2                     # Get next inst, but don't advance rPC
-    jal     MterpSet64Static
+    jal     MterpSPutU64
     bnezc   v0, MterpException          # 0 on success, -1 on failure
     ADVANCE 2                           # Past exception point - now advance rPC
     GET_INST_OPCODE v0                  # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc
index e4cc6d3..7b37c9a 100644
--- a/runtime/interpreter/mterp/mterp.cc
+++ b/runtime/interpreter/mterp/mterp.cc
@@ -490,7 +490,7 @@
   return true;
 }
 
-extern "C" size_t MterpSputObject(ShadowFrame* shadow_frame, uint16_t* dex_pc_ptr,
+extern "C" size_t MterpSPutObj(ShadowFrame* shadow_frame, uint16_t* dex_pc_ptr,
                                 uint32_t inst_data, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   const Instruction* inst = Instruction::At(dex_pc_ptr);
@@ -498,7 +498,7 @@
       (self, *shadow_frame, inst, inst_data);
 }
 
-extern "C" size_t MterpIputObject(ShadowFrame* shadow_frame,
+extern "C" size_t MterpIPutObj(ShadowFrame* shadow_frame,
                                   uint16_t* dex_pc_ptr,
                                   uint32_t inst_data,
                                   Thread* self)
@@ -681,52 +681,141 @@
   return MterpShouldSwitchInterpreters();
 }
 
-extern "C" ssize_t artSet8InstanceFromMterp(uint32_t field_idx,
-                                            mirror::Object* obj,
-                                            uint8_t new_value,
-                                            ArtMethod* referrer)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveWrite, sizeof(int8_t));
-  if (LIKELY(field != nullptr && obj != nullptr)) {
-    Primitive::Type type = field->GetTypeAsPrimitiveType();
-    if (type == Primitive::kPrimBoolean) {
-      field->SetBoolean<false>(obj, new_value);
-    } else {
-      DCHECK_EQ(Primitive::kPrimByte, type);
-      field->SetByte<false>(obj, new_value);
+template<typename PrimType, typename RetType, typename Getter, FindFieldType kType>
+NO_INLINE RetType artGetInstanceFromMterp(uint32_t field_idx,
+                                          mirror::Object* obj,
+                                          ArtMethod* referrer,
+                                          Thread* self)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+  StackHandleScope<1> hs(self);
+  HandleWrapper<mirror::Object> h(hs.NewHandleWrapper(&obj));  // GC might move the object.
+  ArtField* field = FindFieldFromCode<kType, /* access_checks */ false>(
+      field_idx, referrer, self, sizeof(PrimType));
+  if (UNLIKELY(field == nullptr)) {
+    return 0;  // Will throw exception by checking with Thread::Current.
+  }
+  if (UNLIKELY(h == nullptr)) {
+    ThrowNullPointerExceptionForFieldAccess(field, /*is_read*/ true);
+    return 0;  // Will throw exception by checking with Thread::Current.
+  }
+  return Getter::Get(obj, field);
+}
+
+template<typename PrimType, typename RetType, typename Getter>
+ALWAYS_INLINE RetType artGetInstanceFromMterpFast(uint32_t field_idx,
+                                                  mirror::Object* obj,
+                                                  ArtMethod* referrer,
+                                                  Thread* self)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+  constexpr bool kIsObject = std::is_same<RetType, mirror::Object*>::value;
+  constexpr FindFieldType kType = kIsObject ? InstanceObjectRead : InstancePrimitiveRead;
+
+  // This effectively inlines the fast path from ArtMethod::GetDexCache.
+  // It avoids non-inlined call which in turn allows elimination of the prologue and epilogue.
+  if (LIKELY(!referrer->IsObsolete())) {
+    // Avoid read barriers, since we need only the pointer to the native (non-movable)
+    // DexCache field array which we can get even through from-space objects.
+    ObjPtr<mirror::Class> klass = referrer->GetDeclaringClass<kWithoutReadBarrier>();
+    mirror::DexCache* dex_cache = klass->GetDexCache<kDefaultVerifyFlags, kWithoutReadBarrier>();
+    // Try to find the desired field in DexCache.
+    ArtField* field = dex_cache->GetResolvedField(field_idx, kRuntimePointerSize);
+    if (LIKELY(field != nullptr & obj != nullptr)) {
+      if (kIsDebugBuild) {
+        // Compare the fast path and slow path.
+        StackHandleScope<1> hs(self);
+        HandleWrapper<mirror::Object> h(hs.NewHandleWrapper(&obj));  // GC might move the object.
+        DCHECK_EQ(field, (FindFieldFromCode<kType, /* access_checks */ false>(
+            field_idx, referrer, self, sizeof(PrimType))));
+      }
+      return Getter::Get(obj, field);
     }
+  }
+  // Slow path. Last and with identical arguments so that it becomes single instruction tail call.
+  return artGetInstanceFromMterp<PrimType, RetType, Getter, kType>(field_idx, obj, referrer, self);
+}
+
+#define ART_GET_FIELD_FROM_MTERP(Suffix, Kind, PrimType, RetType, Ptr)                            \
+extern "C" RetType MterpIGet ## Suffix(uint32_t field_idx,                                        \
+                                       mirror::Object* obj,                                       \
+                                       ArtMethod* referrer,                                       \
+                                       Thread* self)                                              \
+      REQUIRES_SHARED(Locks::mutator_lock_) {                                                     \
+  struct Getter { /* Specialize the field load depending on the field type */                     \
+    static RetType Get(mirror::Object* o, ArtField* f) REQUIRES_SHARED(Locks::mutator_lock_) {    \
+      return f->Get##Kind(o)Ptr;                                                                  \
+    }                                                                                             \
+  };                                                                                              \
+  return artGetInstanceFromMterpFast<PrimType, RetType, Getter>(field_idx, obj, referrer, self);  \
+}                                                                                                 \
+
+ART_GET_FIELD_FROM_MTERP(I8, Byte, int8_t, ssize_t, )
+ART_GET_FIELD_FROM_MTERP(U8, Boolean, uint8_t, size_t, )
+ART_GET_FIELD_FROM_MTERP(I16, Short, int16_t, ssize_t, )
+ART_GET_FIELD_FROM_MTERP(U16, Char, uint16_t, size_t, )
+ART_GET_FIELD_FROM_MTERP(U32, 32, uint32_t, size_t, )
+ART_GET_FIELD_FROM_MTERP(U64, 64, uint64_t, uint64_t, )
+ART_GET_FIELD_FROM_MTERP(Obj, Obj, mirror::HeapReference<mirror::Object>, mirror::Object*, .Ptr())
+
+#undef ART_GET_FIELD_FROM_MTERP
+
+extern "C" ssize_t MterpIPutU8(uint32_t field_idx,
+                               mirror::Object* obj,
+                               uint8_t new_value,
+                               ArtMethod* referrer)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ArtField* field = referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
+  if (LIKELY(field != nullptr && obj != nullptr)) {
+    field->SetBoolean<false>(obj, new_value);
     return 0;  // success
   }
   return -1;  // failure
 }
 
-extern "C" ssize_t artSet16InstanceFromMterp(uint32_t field_idx,
-                                             mirror::Object* obj,
-                                             uint16_t new_value,
-                                             ArtMethod* referrer)
+extern "C" ssize_t MterpIPutI8(uint32_t field_idx,
+                               mirror::Object* obj,
+                               uint8_t new_value,
+                               ArtMethod* referrer)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveWrite,
-                                          sizeof(int16_t));
+  ArtField* field = referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
   if (LIKELY(field != nullptr && obj != nullptr)) {
-    Primitive::Type type = field->GetTypeAsPrimitiveType();
-    if (type == Primitive::kPrimChar) {
-      field->SetChar<false>(obj, new_value);
-    } else {
-      DCHECK_EQ(Primitive::kPrimShort, type);
-      field->SetShort<false>(obj, new_value);
-    }
+    field->SetByte<false>(obj, new_value);
     return 0;  // success
   }
   return -1;  // failure
 }
 
-extern "C" ssize_t artSet32InstanceFromMterp(uint32_t field_idx,
-                                             mirror::Object* obj,
-                                             uint32_t new_value,
-                                             ArtMethod* referrer)
+extern "C" ssize_t MterpIPutU16(uint32_t field_idx,
+                                mirror::Object* obj,
+                                uint16_t new_value,
+                                ArtMethod* referrer)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveWrite,
-                                          sizeof(int32_t));
+  ArtField* field = referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
+  if (LIKELY(field != nullptr && obj != nullptr)) {
+    field->SetChar<false>(obj, new_value);
+    return 0;  // success
+  }
+  return -1;  // failure
+}
+
+extern "C" ssize_t MterpIPutI16(uint32_t field_idx,
+                                mirror::Object* obj,
+                                uint16_t new_value,
+                                ArtMethod* referrer)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ArtField* field = referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
+  if (LIKELY(field != nullptr && obj != nullptr)) {
+    field->SetShort<false>(obj, new_value);
+    return 0;  // success
+  }
+  return -1;  // failure
+}
+
+extern "C" ssize_t MterpIPutU32(uint32_t field_idx,
+                                mirror::Object* obj,
+                                uint32_t new_value,
+                                ArtMethod* referrer)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ArtField* field = referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
   if (LIKELY(field != nullptr && obj != nullptr)) {
     field->Set32<false>(obj, new_value);
     return 0;  // success
@@ -734,13 +823,12 @@
   return -1;  // failure
 }
 
-extern "C" ssize_t artSet64InstanceFromMterp(uint32_t field_idx,
-                                             mirror::Object* obj,
-                                             uint64_t* new_value,
-                                             ArtMethod* referrer)
+extern "C" ssize_t MterpIPutU64(uint32_t field_idx,
+                                mirror::Object* obj,
+                                uint64_t* new_value,
+                                ArtMethod* referrer)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  ArtField* field = FindFieldFast(field_idx, referrer, InstancePrimitiveWrite,
-                                          sizeof(int64_t));
+  ArtField* field = referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
   if (LIKELY(field != nullptr  && obj != nullptr)) {
     field->Set64<false>(obj, *new_value);
     return 0;  // success
@@ -753,8 +841,7 @@
                                               mirror::Object* new_value,
                                               ArtMethod* referrer)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  ArtField* field = FindFieldFast(field_idx, referrer, InstanceObjectWrite,
-                                          sizeof(mirror::HeapReference<mirror::Object>));
+  ArtField* field = referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
   if (LIKELY(field != nullptr && obj != nullptr)) {
     field->SetObj<false>(obj, new_value);
     return 0;  // success
@@ -781,9 +868,9 @@
   return res;
 }
 
-extern "C" int32_t MterpGetBooleanStatic(uint32_t field_idx,
-                                         ArtMethod* referrer,
-                                         Thread* self)
+extern "C" int32_t MterpSGetU8(uint32_t field_idx,
+                               ArtMethod* referrer,
+                               Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpGetStatic<uint8_t, Primitive::kPrimBoolean>(field_idx,
                                                           referrer,
@@ -791,9 +878,9 @@
                                                           &ArtField::GetBoolean);
 }
 
-extern "C" int32_t MterpGetByteStatic(uint32_t field_idx,
-                                      ArtMethod* referrer,
-                                      Thread* self)
+extern "C" int32_t MterpSGetI8(uint32_t field_idx,
+                               ArtMethod* referrer,
+                               Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpGetStatic<int8_t, Primitive::kPrimByte>(field_idx,
                                                       referrer,
@@ -801,9 +888,9 @@
                                                       &ArtField::GetByte);
 }
 
-extern "C" uint32_t MterpGetCharStatic(uint32_t field_idx,
-                                       ArtMethod* referrer,
-                                       Thread* self)
+extern "C" uint32_t MterpSGetU16(uint32_t field_idx,
+                                 ArtMethod* referrer,
+                                 Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpGetStatic<uint16_t, Primitive::kPrimChar>(field_idx,
                                                         referrer,
@@ -811,9 +898,9 @@
                                                         &ArtField::GetChar);
 }
 
-extern "C" int32_t MterpGetShortStatic(uint32_t field_idx,
-                                       ArtMethod* referrer,
-                                       Thread* self)
+extern "C" int32_t MterpSGetI16(uint32_t field_idx,
+                                ArtMethod* referrer,
+                                Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpGetStatic<int16_t, Primitive::kPrimShort>(field_idx,
                                                         referrer,
@@ -821,9 +908,9 @@
                                                         &ArtField::GetShort);
 }
 
-extern "C" mirror::Object* MterpGetObjStatic(uint32_t field_idx,
-                                             ArtMethod* referrer,
-                                             Thread* self)
+extern "C" mirror::Object* MterpSGetObj(uint32_t field_idx,
+                                        ArtMethod* referrer,
+                                        Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpGetStatic<ObjPtr<mirror::Object>, Primitive::kPrimNot>(field_idx,
                                                                      referrer,
@@ -831,9 +918,9 @@
                                                                      &ArtField::GetObject).Ptr();
 }
 
-extern "C" int32_t MterpGet32Static(uint32_t field_idx,
-                                    ArtMethod* referrer,
-                                    Thread* self)
+extern "C" int32_t MterpSGetU32(uint32_t field_idx,
+                                ArtMethod* referrer,
+                                Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpGetStatic<int32_t, Primitive::kPrimInt>(field_idx,
                                                       referrer,
@@ -841,7 +928,7 @@
                                                       &ArtField::GetInt);
 }
 
-extern "C" int64_t MterpGet64Static(uint32_t field_idx, ArtMethod* referrer, Thread* self)
+extern "C" int64_t MterpSGetU64(uint32_t field_idx, ArtMethod* referrer, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpGetStatic<int64_t, Primitive::kPrimLong>(field_idx,
                                                        referrer,
@@ -869,10 +956,10 @@
   return res;
 }
 
-extern "C" int MterpSetBooleanStatic(uint32_t field_idx,
-                                     uint8_t new_value,
-                                     ArtMethod* referrer,
-                                     Thread* self)
+extern "C" int MterpSPutU8(uint32_t field_idx,
+                           uint8_t new_value,
+                           ArtMethod* referrer,
+                           Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpSetStatic<uint8_t, Primitive::kPrimBoolean>(field_idx,
                                                           new_value,
@@ -881,10 +968,10 @@
                                                           &ArtField::SetBoolean<false>);
 }
 
-extern "C" int MterpSetByteStatic(uint32_t field_idx,
-                                  int8_t new_value,
-                                  ArtMethod* referrer,
-                                  Thread* self)
+extern "C" int MterpSPutI8(uint32_t field_idx,
+                           int8_t new_value,
+                           ArtMethod* referrer,
+                           Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpSetStatic<int8_t, Primitive::kPrimByte>(field_idx,
                                                       new_value,
@@ -893,10 +980,10 @@
                                                       &ArtField::SetByte<false>);
 }
 
-extern "C" int MterpSetCharStatic(uint32_t field_idx,
-                                  uint16_t new_value,
-                                  ArtMethod* referrer,
-                                  Thread* self)
+extern "C" int MterpSPutU16(uint32_t field_idx,
+                            uint16_t new_value,
+                            ArtMethod* referrer,
+                            Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpSetStatic<uint16_t, Primitive::kPrimChar>(field_idx,
                                                         new_value,
@@ -905,10 +992,10 @@
                                                         &ArtField::SetChar<false>);
 }
 
-extern "C" int MterpSetShortStatic(uint32_t field_idx,
-                                   int16_t new_value,
-                                   ArtMethod* referrer,
-                                   Thread* self)
+extern "C" int MterpSPutI16(uint32_t field_idx,
+                            int16_t new_value,
+                            ArtMethod* referrer,
+                            Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpSetStatic<int16_t, Primitive::kPrimShort>(field_idx,
                                                         new_value,
@@ -917,10 +1004,10 @@
                                                         &ArtField::SetShort<false>);
 }
 
-extern "C" int MterpSet32Static(uint32_t field_idx,
-                                int32_t new_value,
-                                ArtMethod* referrer,
-                                Thread* self)
+extern "C" int MterpSPutU32(uint32_t field_idx,
+                            int32_t new_value,
+                            ArtMethod* referrer,
+                            Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpSetStatic<int32_t, Primitive::kPrimInt>(field_idx,
                                                       new_value,
@@ -929,10 +1016,10 @@
                                                       &ArtField::SetInt<false>);
 }
 
-extern "C" int MterpSet64Static(uint32_t field_idx,
-                                int64_t* new_value,
-                                ArtMethod* referrer,
-                                Thread* self)
+extern "C" int MterpSPutU64(uint32_t field_idx,
+                            int64_t* new_value,
+                            ArtMethod* referrer,
+                            Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return MterpSetStatic<int64_t, Primitive::kPrimLong>(field_idx,
                                                        *new_value,
diff --git a/runtime/interpreter/mterp/out/mterp_arm.S b/runtime/interpreter/mterp/out/mterp_arm.S
index 73b957f..b73067f 100644
--- a/runtime/interpreter/mterp/out/mterp_arm.S
+++ b/runtime/interpreter/mterp/out/mterp_arm.S
@@ -2255,7 +2255,7 @@
     GET_VREG r1, r1                        @ r1<- fp[B], the object pointer
     ldr      r2, [rFP, #OFF_FP_METHOD]     @ r2<- referrer
     mov      r3, rSELF                     @ r3<- self
-    bl       artGet32InstanceFromCode
+    bl       MterpIGetU32
     ldr      r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     r2, rINST, #8, #4             @ r2<- A
     PREFETCH_INST 2
@@ -2285,7 +2285,7 @@
     GET_VREG r1, r1                        @ r1<- fp[B], the object pointer
     ldr      r2, [rFP, #OFF_FP_METHOD]     @ r2<- referrer
     mov      r3, rSELF                     @ r3<- self
-    bl       artGet64InstanceFromCode
+    bl       MterpIGetU64
     ldr      r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     r2, rINST, #8, #4             @ r2<- A
     PREFETCH_INST 2
@@ -2314,7 +2314,7 @@
     GET_VREG r1, r1                        @ r1<- fp[B], the object pointer
     ldr      r2, [rFP, #OFF_FP_METHOD]     @ r2<- referrer
     mov      r3, rSELF                     @ r3<- self
-    bl       artGetObjInstanceFromCode
+    bl       MterpIGetObj
     ldr      r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     r2, rINST, #8, #4             @ r2<- A
     PREFETCH_INST 2
@@ -2346,7 +2346,7 @@
     GET_VREG r1, r1                        @ r1<- fp[B], the object pointer
     ldr      r2, [rFP, #OFF_FP_METHOD]     @ r2<- referrer
     mov      r3, rSELF                     @ r3<- self
-    bl       artGetBooleanInstanceFromCode
+    bl       MterpIGetU8
     ldr      r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     r2, rINST, #8, #4             @ r2<- A
     PREFETCH_INST 2
@@ -2378,7 +2378,7 @@
     GET_VREG r1, r1                        @ r1<- fp[B], the object pointer
     ldr      r2, [rFP, #OFF_FP_METHOD]     @ r2<- referrer
     mov      r3, rSELF                     @ r3<- self
-    bl       artGetByteInstanceFromCode
+    bl       MterpIGetI8
     ldr      r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     r2, rINST, #8, #4             @ r2<- A
     PREFETCH_INST 2
@@ -2410,7 +2410,7 @@
     GET_VREG r1, r1                        @ r1<- fp[B], the object pointer
     ldr      r2, [rFP, #OFF_FP_METHOD]     @ r2<- referrer
     mov      r3, rSELF                     @ r3<- self
-    bl       artGetCharInstanceFromCode
+    bl       MterpIGetU16
     ldr      r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     r2, rINST, #8, #4             @ r2<- A
     PREFETCH_INST 2
@@ -2442,7 +2442,7 @@
     GET_VREG r1, r1                        @ r1<- fp[B], the object pointer
     ldr      r2, [rFP, #OFF_FP_METHOD]     @ r2<- referrer
     mov      r3, rSELF                     @ r3<- self
-    bl       artGetShortInstanceFromCode
+    bl       MterpIGetI16
     ldr      r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     r2, rINST, #8, #4             @ r2<- A
     PREFETCH_INST 2
@@ -2468,7 +2468,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet32InstanceFromMterp
+    .extern MterpIPutU32
     EXPORT_PC
     FETCH    r0, 1                      @ r0<- field ref CCCC
     mov      r1, rINST, lsr #12         @ r1<- B
@@ -2477,7 +2477,7 @@
     GET_VREG r2, r2                     @ r2<- fp[A]
     ldr      r3, [rFP, #OFF_FP_METHOD]  @ r3<- referrer
     PREFETCH_INST 2
-    bl       artSet32InstanceFromMterp
+    bl       MterpIPutU32
     cmp      r0, #0
     bne      MterpPossibleException
     ADVANCE  2                          @ advance rPC
@@ -2489,7 +2489,7 @@
 .L_op_iput_wide: /* 0x5a */
 /* File: arm/op_iput_wide.S */
     /* iput-wide vA, vB, field@CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     FETCH    r0, 1                      @ r0<- field ref CCCC
     mov      r1, rINST, lsr #12         @ r1<- B
@@ -2498,7 +2498,7 @@
     VREG_INDEX_TO_ADDR r2, r2           @ r2<- &fp[A]
     ldr      r3, [rFP, #OFF_FP_METHOD]  @ r3<- referrer
     PREFETCH_INST 2
-    bl       artSet64InstanceFromMterp
+    bl       MterpIPutU64
     cmp      r0, #0
     bne      MterpPossibleException
     ADVANCE  2                          @ advance rPC
@@ -2514,7 +2514,7 @@
     mov     r1, rPC
     mov     r2, rINST
     mov     r3, rSELF
-    bl      MterpIputObject
+    bl      MterpIPutObj
     cmp     r0, #0
     beq     MterpException
     FETCH_ADVANCE_INST 2                @ advance rPC, load rINST
@@ -2532,7 +2532,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutU8
     EXPORT_PC
     FETCH    r0, 1                      @ r0<- field ref CCCC
     mov      r1, rINST, lsr #12         @ r1<- B
@@ -2541,7 +2541,7 @@
     GET_VREG r2, r2                     @ r2<- fp[A]
     ldr      r3, [rFP, #OFF_FP_METHOD]  @ r3<- referrer
     PREFETCH_INST 2
-    bl       artSet8InstanceFromMterp
+    bl       MterpIPutU8
     cmp      r0, #0
     bne      MterpPossibleException
     ADVANCE  2                          @ advance rPC
@@ -2560,7 +2560,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutI8
     EXPORT_PC
     FETCH    r0, 1                      @ r0<- field ref CCCC
     mov      r1, rINST, lsr #12         @ r1<- B
@@ -2569,7 +2569,7 @@
     GET_VREG r2, r2                     @ r2<- fp[A]
     ldr      r3, [rFP, #OFF_FP_METHOD]  @ r3<- referrer
     PREFETCH_INST 2
-    bl       artSet8InstanceFromMterp
+    bl       MterpIPutI8
     cmp      r0, #0
     bne      MterpPossibleException
     ADVANCE  2                          @ advance rPC
@@ -2588,7 +2588,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutU16
     EXPORT_PC
     FETCH    r0, 1                      @ r0<- field ref CCCC
     mov      r1, rINST, lsr #12         @ r1<- B
@@ -2597,7 +2597,7 @@
     GET_VREG r2, r2                     @ r2<- fp[A]
     ldr      r3, [rFP, #OFF_FP_METHOD]  @ r3<- referrer
     PREFETCH_INST 2
-    bl       artSet16InstanceFromMterp
+    bl       MterpIPutU16
     cmp      r0, #0
     bne      MterpPossibleException
     ADVANCE  2                          @ advance rPC
@@ -2616,7 +2616,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutI16
     EXPORT_PC
     FETCH    r0, 1                      @ r0<- field ref CCCC
     mov      r1, rINST, lsr #12         @ r1<- B
@@ -2625,7 +2625,7 @@
     GET_VREG r2, r2                     @ r2<- fp[A]
     ldr      r3, [rFP, #OFF_FP_METHOD]  @ r3<- referrer
     PREFETCH_INST 2
-    bl       artSet16InstanceFromMterp
+    bl       MterpIPutI16
     cmp      r0, #0
     bne      MterpPossibleException
     ADVANCE  2                          @ advance rPC
@@ -2644,12 +2644,12 @@
      */
     /* op vAA, field@BBBB */
 
-    .extern MterpGet32Static
+    .extern MterpSGetU32
     EXPORT_PC
     FETCH r0, 1                         @ r0<- field ref BBBB
     ldr   r1, [rFP, #OFF_FP_METHOD]
     mov   r2, rSELF
-    bl    MterpGet32Static
+    bl    MterpSGetU32
     ldr   r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     mov   r2, rINST, lsr #8             @ r2<- AA
     PREFETCH_INST 2
@@ -2674,12 +2674,12 @@
      */
     /* sget-wide vAA, field@BBBB */
 
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC
     FETCH r0, 1                         @ r0<- field ref BBBB
     ldr   r1, [rFP, #OFF_FP_METHOD]
     mov   r2, rSELF
-    bl    MterpGet64Static
+    bl    MterpSGetU64
     ldr   r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     mov   r9, rINST, lsr #8             @ r9<- AA
     VREG_INDEX_TO_ADDR lr, r9           @ r9<- &fp[AA]
@@ -2703,12 +2703,12 @@
      */
     /* op vAA, field@BBBB */
 
-    .extern MterpGetObjStatic
+    .extern MterpSGetObj
     EXPORT_PC
     FETCH r0, 1                         @ r0<- field ref BBBB
     ldr   r1, [rFP, #OFF_FP_METHOD]
     mov   r2, rSELF
-    bl    MterpGetObjStatic
+    bl    MterpSGetObj
     ldr   r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     mov   r2, rINST, lsr #8             @ r2<- AA
     PREFETCH_INST 2
@@ -2736,12 +2736,12 @@
      */
     /* op vAA, field@BBBB */
 
-    .extern MterpGetBooleanStatic
+    .extern MterpSGetU8
     EXPORT_PC
     FETCH r0, 1                         @ r0<- field ref BBBB
     ldr   r1, [rFP, #OFF_FP_METHOD]
     mov   r2, rSELF
-    bl    MterpGetBooleanStatic
+    bl    MterpSGetU8
     ldr   r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     mov   r2, rINST, lsr #8             @ r2<- AA
     PREFETCH_INST 2
@@ -2769,12 +2769,12 @@
      */
     /* op vAA, field@BBBB */
 
-    .extern MterpGetByteStatic
+    .extern MterpSGetI8
     EXPORT_PC
     FETCH r0, 1                         @ r0<- field ref BBBB
     ldr   r1, [rFP, #OFF_FP_METHOD]
     mov   r2, rSELF
-    bl    MterpGetByteStatic
+    bl    MterpSGetI8
     ldr   r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     mov   r2, rINST, lsr #8             @ r2<- AA
     PREFETCH_INST 2
@@ -2802,12 +2802,12 @@
      */
     /* op vAA, field@BBBB */
 
-    .extern MterpGetCharStatic
+    .extern MterpSGetU16
     EXPORT_PC
     FETCH r0, 1                         @ r0<- field ref BBBB
     ldr   r1, [rFP, #OFF_FP_METHOD]
     mov   r2, rSELF
-    bl    MterpGetCharStatic
+    bl    MterpSGetU16
     ldr   r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     mov   r2, rINST, lsr #8             @ r2<- AA
     PREFETCH_INST 2
@@ -2835,12 +2835,12 @@
      */
     /* op vAA, field@BBBB */
 
-    .extern MterpGetShortStatic
+    .extern MterpSGetI16
     EXPORT_PC
     FETCH r0, 1                         @ r0<- field ref BBBB
     ldr   r1, [rFP, #OFF_FP_METHOD]
     mov   r2, rSELF
-    bl    MterpGetShortStatic
+    bl    MterpSGetI16
     ldr   r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
     mov   r2, rINST, lsr #8             @ r2<- AA
     PREFETCH_INST 2
@@ -2873,7 +2873,7 @@
     ldr     r2, [rFP, #OFF_FP_METHOD]
     mov     r3, rSELF
     PREFETCH_INST 2                     @ Get next inst, but don't advance rPC
-    bl      MterpSet32Static
+    bl      MterpSPutU32
     cmp     r0, #0                      @ 0 on success, -1 on failure
     bne     MterpException
     ADVANCE 2                           @ Past exception point - now advance rPC
@@ -2889,7 +2889,7 @@
      *
      */
     /* sput-wide vAA, field@BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     FETCH   r0, 1                       @ r0<- field ref BBBB
     mov     r1, rINST, lsr #8           @ r1<- AA
@@ -2897,7 +2897,7 @@
     ldr     r2, [rFP, #OFF_FP_METHOD]
     mov     r3, rSELF
     PREFETCH_INST 2                     @ Get next inst, but don't advance rPC
-    bl      MterpSet64Static
+    bl      MterpSPutU64
     cmp     r0, #0                      @ 0 on success, -1 on failure
     bne     MterpException
     ADVANCE 2                           @ Past exception point - now advance rPC
@@ -2913,7 +2913,7 @@
     mov     r1, rPC
     mov     r2, rINST
     mov     r3, rSELF
-    bl      MterpSputObject
+    bl      MterpSPutObj
     cmp     r0, #0
     beq     MterpException
     FETCH_ADVANCE_INST 2                @ advance rPC, load rINST
@@ -2938,7 +2938,7 @@
     ldr     r2, [rFP, #OFF_FP_METHOD]
     mov     r3, rSELF
     PREFETCH_INST 2                     @ Get next inst, but don't advance rPC
-    bl      MterpSetBooleanStatic
+    bl      MterpSPutU8
     cmp     r0, #0                      @ 0 on success, -1 on failure
     bne     MterpException
     ADVANCE 2                           @ Past exception point - now advance rPC
@@ -2964,7 +2964,7 @@
     ldr     r2, [rFP, #OFF_FP_METHOD]
     mov     r3, rSELF
     PREFETCH_INST 2                     @ Get next inst, but don't advance rPC
-    bl      MterpSetByteStatic
+    bl      MterpSPutI8
     cmp     r0, #0                      @ 0 on success, -1 on failure
     bne     MterpException
     ADVANCE 2                           @ Past exception point - now advance rPC
@@ -2990,7 +2990,7 @@
     ldr     r2, [rFP, #OFF_FP_METHOD]
     mov     r3, rSELF
     PREFETCH_INST 2                     @ Get next inst, but don't advance rPC
-    bl      MterpSetCharStatic
+    bl      MterpSPutU16
     cmp     r0, #0                      @ 0 on success, -1 on failure
     bne     MterpException
     ADVANCE 2                           @ Past exception point - now advance rPC
@@ -3016,7 +3016,7 @@
     ldr     r2, [rFP, #OFF_FP_METHOD]
     mov     r3, rSELF
     PREFETCH_INST 2                     @ Get next inst, but don't advance rPC
-    bl      MterpSetShortStatic
+    bl      MterpSPutI16
     cmp     r0, #0                      @ 0 on success, -1 on failure
     bne     MterpException
     ADVANCE 2                           @ Past exception point - now advance rPC
diff --git a/runtime/interpreter/mterp/out/mterp_arm64.S b/runtime/interpreter/mterp/out/mterp_arm64.S
index 2a0c4df..770b109 100644
--- a/runtime/interpreter/mterp/out/mterp_arm64.S
+++ b/runtime/interpreter/mterp/out/mterp_arm64.S
@@ -2192,7 +2192,7 @@
     GET_VREG w1, w1                        // w1<- fp[B], the object pointer
     ldr      x2, [xFP, #OFF_FP_METHOD]     // w2<- referrer
     mov      x3, xSELF                     // w3<- self
-    bl       artGet32InstanceFromCode
+    bl       MterpIGetU32
     ldr      x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     
     ubfx     w2, wINST, #8, #4             // w2<- A
@@ -2222,7 +2222,7 @@
     GET_VREG w1, w1                        // w1<- fp[B], the object pointer
     ldr      x2, [xFP, #OFF_FP_METHOD]     // w2<- referrer
     mov      x3, xSELF                     // w3<- self
-    bl       artGet64InstanceFromCode
+    bl       MterpIGetU64
     ldr      x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     ubfx     w2, wINST, #8, #4             // w2<- A
     PREFETCH_INST 2
@@ -2249,7 +2249,7 @@
     GET_VREG w1, w1                        // w1<- fp[B], the object pointer
     ldr      x2, [xFP, #OFF_FP_METHOD]     // w2<- referrer
     mov      x3, xSELF                     // w3<- self
-    bl       artGetObjInstanceFromCode
+    bl       MterpIGetObj
     ldr      x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     
     ubfx     w2, wINST, #8, #4             // w2<- A
@@ -2281,7 +2281,7 @@
     GET_VREG w1, w1                        // w1<- fp[B], the object pointer
     ldr      x2, [xFP, #OFF_FP_METHOD]     // w2<- referrer
     mov      x3, xSELF                     // w3<- self
-    bl       artGetBooleanInstanceFromCode
+    bl       MterpIGetU8
     ldr      x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     uxtb w0, w0
     ubfx     w2, wINST, #8, #4             // w2<- A
@@ -2313,7 +2313,7 @@
     GET_VREG w1, w1                        // w1<- fp[B], the object pointer
     ldr      x2, [xFP, #OFF_FP_METHOD]     // w2<- referrer
     mov      x3, xSELF                     // w3<- self
-    bl       artGetByteInstanceFromCode
+    bl       MterpIGetI8
     ldr      x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     sxtb w0, w0
     ubfx     w2, wINST, #8, #4             // w2<- A
@@ -2345,7 +2345,7 @@
     GET_VREG w1, w1                        // w1<- fp[B], the object pointer
     ldr      x2, [xFP, #OFF_FP_METHOD]     // w2<- referrer
     mov      x3, xSELF                     // w3<- self
-    bl       artGetCharInstanceFromCode
+    bl       MterpIGetU16
     ldr      x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     uxth w0, w0
     ubfx     w2, wINST, #8, #4             // w2<- A
@@ -2377,7 +2377,7 @@
     GET_VREG w1, w1                        // w1<- fp[B], the object pointer
     ldr      x2, [xFP, #OFF_FP_METHOD]     // w2<- referrer
     mov      x3, xSELF                     // w3<- self
-    bl       artGetShortInstanceFromCode
+    bl       MterpIGetI16
     ldr      x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     sxth w0, w0
     ubfx     w2, wINST, #8, #4             // w2<- A
@@ -2403,7 +2403,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet32InstanceFromMterp
+    .extern MterpIPutU32
     EXPORT_PC
     FETCH    w0, 1                      // w0<- field ref CCCC
     lsr      w1, wINST, #12             // w1<- B
@@ -2412,7 +2412,7 @@
     GET_VREG w2, w2                     // w2<- fp[A]
     ldr      x3, [xFP, #OFF_FP_METHOD]  // w3<- referrer
     PREFETCH_INST 2
-    bl       artSet32InstanceFromMterp
+    bl       MterpIPutU32
     cbnz     w0, MterpPossibleException
     ADVANCE  2                          // advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2423,7 +2423,7 @@
 .L_op_iput_wide: /* 0x5a */
 /* File: arm64/op_iput_wide.S */
     /* iput-wide vA, vB, field//CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     FETCH    w0, 1                      // w0<- field ref CCCC
     lsr      w1, wINST, #12             // w1<- B
@@ -2432,7 +2432,7 @@
     VREG_INDEX_TO_ADDR x2, x2           // w2<- &fp[A]
     ldr      x3, [xFP, #OFF_FP_METHOD]  // w3<- referrer
     PREFETCH_INST 2
-    bl       artSet64InstanceFromMterp
+    bl       MterpIPutU64
     cbnz     w0, MterpPossibleException
     ADVANCE  2                          // advance rPC
     GET_INST_OPCODE ip                  // extract opcode from wINST
@@ -2447,7 +2447,7 @@
     mov     x1, xPC
     mov     w2, wINST
     mov     x3, xSELF
-    bl      MterpIputObject
+    bl      MterpIPutObj
     cbz     w0, MterpException
     FETCH_ADVANCE_INST 2                // advance rPC, load rINST
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2464,7 +2464,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutU8
     EXPORT_PC
     FETCH    w0, 1                      // w0<- field ref CCCC
     lsr      w1, wINST, #12             // w1<- B
@@ -2473,7 +2473,7 @@
     GET_VREG w2, w2                     // w2<- fp[A]
     ldr      x3, [xFP, #OFF_FP_METHOD]  // w3<- referrer
     PREFETCH_INST 2
-    bl       artSet8InstanceFromMterp
+    bl       MterpIPutU8
     cbnz     w0, MterpPossibleException
     ADVANCE  2                          // advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2491,7 +2491,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutI8
     EXPORT_PC
     FETCH    w0, 1                      // w0<- field ref CCCC
     lsr      w1, wINST, #12             // w1<- B
@@ -2500,7 +2500,7 @@
     GET_VREG w2, w2                     // w2<- fp[A]
     ldr      x3, [xFP, #OFF_FP_METHOD]  // w3<- referrer
     PREFETCH_INST 2
-    bl       artSet8InstanceFromMterp
+    bl       MterpIPutI8
     cbnz     w0, MterpPossibleException
     ADVANCE  2                          // advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2518,7 +2518,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutU16
     EXPORT_PC
     FETCH    w0, 1                      // w0<- field ref CCCC
     lsr      w1, wINST, #12             // w1<- B
@@ -2527,7 +2527,7 @@
     GET_VREG w2, w2                     // w2<- fp[A]
     ldr      x3, [xFP, #OFF_FP_METHOD]  // w3<- referrer
     PREFETCH_INST 2
-    bl       artSet16InstanceFromMterp
+    bl       MterpIPutU16
     cbnz     w0, MterpPossibleException
     ADVANCE  2                          // advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2545,7 +2545,7 @@
      * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutI16
     EXPORT_PC
     FETCH    w0, 1                      // w0<- field ref CCCC
     lsr      w1, wINST, #12             // w1<- B
@@ -2554,7 +2554,7 @@
     GET_VREG w2, w2                     // w2<- fp[A]
     ldr      x3, [xFP, #OFF_FP_METHOD]  // w3<- referrer
     PREFETCH_INST 2
-    bl       artSet16InstanceFromMterp
+    bl       MterpIPutI16
     cbnz     w0, MterpPossibleException
     ADVANCE  2                          // advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2572,12 +2572,12 @@
      */
     /* op vAA, field//BBBB */
 
-    .extern MterpGet32Static
+    .extern MterpSGetU32
     EXPORT_PC
     FETCH w0, 1                         // w0<- field ref BBBB
     ldr   x1, [xFP, #OFF_FP_METHOD]
     mov   x2, xSELF
-    bl    MterpGet32Static
+    bl    MterpSGetU32
     ldr   x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     lsr   w2, wINST, #8                 // w2<- AA
     
@@ -2607,7 +2607,7 @@
     FETCH w0, 1                         // w0<- field ref BBBB
     ldr   x1, [xFP, #OFF_FP_METHOD]
     mov   x2, xSELF
-    bl    MterpGet64Static
+    bl    MterpSGetU64
     ldr   x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     lsr   w4, wINST, #8                 // w4<- AA
     cbnz  x3, MterpException            // bail out
@@ -2628,12 +2628,12 @@
      */
     /* op vAA, field//BBBB */
 
-    .extern MterpGetObjStatic
+    .extern MterpSGetObj
     EXPORT_PC
     FETCH w0, 1                         // w0<- field ref BBBB
     ldr   x1, [xFP, #OFF_FP_METHOD]
     mov   x2, xSELF
-    bl    MterpGetObjStatic
+    bl    MterpSGetObj
     ldr   x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     lsr   w2, wINST, #8                 // w2<- AA
     
@@ -2661,12 +2661,12 @@
      */
     /* op vAA, field//BBBB */
 
-    .extern MterpGetBooleanStatic
+    .extern MterpSGetU8
     EXPORT_PC
     FETCH w0, 1                         // w0<- field ref BBBB
     ldr   x1, [xFP, #OFF_FP_METHOD]
     mov   x2, xSELF
-    bl    MterpGetBooleanStatic
+    bl    MterpSGetU8
     ldr   x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     lsr   w2, wINST, #8                 // w2<- AA
     uxtb w0, w0
@@ -2694,12 +2694,12 @@
      */
     /* op vAA, field//BBBB */
 
-    .extern MterpGetByteStatic
+    .extern MterpSGetI8
     EXPORT_PC
     FETCH w0, 1                         // w0<- field ref BBBB
     ldr   x1, [xFP, #OFF_FP_METHOD]
     mov   x2, xSELF
-    bl    MterpGetByteStatic
+    bl    MterpSGetI8
     ldr   x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     lsr   w2, wINST, #8                 // w2<- AA
     sxtb w0, w0
@@ -2727,12 +2727,12 @@
      */
     /* op vAA, field//BBBB */
 
-    .extern MterpGetCharStatic
+    .extern MterpSGetU16
     EXPORT_PC
     FETCH w0, 1                         // w0<- field ref BBBB
     ldr   x1, [xFP, #OFF_FP_METHOD]
     mov   x2, xSELF
-    bl    MterpGetCharStatic
+    bl    MterpSGetU16
     ldr   x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     lsr   w2, wINST, #8                 // w2<- AA
     uxth w0, w0
@@ -2760,12 +2760,12 @@
      */
     /* op vAA, field//BBBB */
 
-    .extern MterpGetShortStatic
+    .extern MterpSGetI16
     EXPORT_PC
     FETCH w0, 1                         // w0<- field ref BBBB
     ldr   x1, [xFP, #OFF_FP_METHOD]
     mov   x2, xSELF
-    bl    MterpGetShortStatic
+    bl    MterpSGetI16
     ldr   x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
     lsr   w2, wINST, #8                 // w2<- AA
     sxth w0, w0
@@ -2798,7 +2798,7 @@
     ldr     x2, [xFP, #OFF_FP_METHOD]
     mov     x3, xSELF
     PREFETCH_INST 2                     // Get next inst, but don't advance rPC
-    bl      MterpSet32Static
+    bl      MterpSPutU32
     cbnz    w0, MterpException          // 0 on success
     ADVANCE 2                           // Past exception point - now advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2813,7 +2813,7 @@
      *
      */
     /* sput-wide vAA, field//BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     FETCH   w0, 1                       // w0<- field ref BBBB
     lsr     w1, wINST, #8               // w1<- AA
@@ -2821,7 +2821,7 @@
     ldr     x2, [xFP, #OFF_FP_METHOD]
     mov     x3, xSELF
     PREFETCH_INST 2                     // Get next inst, but don't advance rPC
-    bl      MterpSet64Static
+    bl      MterpSPutU64
     cbnz    w0, MterpException          // 0 on success, -1 on failure
     ADVANCE 2                           // Past exception point - now advance rPC
     GET_INST_OPCODE ip                  // extract opcode from wINST
@@ -2836,7 +2836,7 @@
     mov     x1, xPC
     mov     x2, xINST
     mov     x3, xSELF
-    bl      MterpSputObject
+    bl      MterpSPutObj
     cbz     w0, MterpException
     FETCH_ADVANCE_INST 2                // advance rPC, load rINST
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2860,7 +2860,7 @@
     ldr     x2, [xFP, #OFF_FP_METHOD]
     mov     x3, xSELF
     PREFETCH_INST 2                     // Get next inst, but don't advance rPC
-    bl      MterpSetBooleanStatic
+    bl      MterpSPutU8
     cbnz    w0, MterpException          // 0 on success
     ADVANCE 2                           // Past exception point - now advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2885,7 +2885,7 @@
     ldr     x2, [xFP, #OFF_FP_METHOD]
     mov     x3, xSELF
     PREFETCH_INST 2                     // Get next inst, but don't advance rPC
-    bl      MterpSetByteStatic
+    bl      MterpSPutI8
     cbnz    w0, MterpException          // 0 on success
     ADVANCE 2                           // Past exception point - now advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2910,7 +2910,7 @@
     ldr     x2, [xFP, #OFF_FP_METHOD]
     mov     x3, xSELF
     PREFETCH_INST 2                     // Get next inst, but don't advance rPC
-    bl      MterpSetCharStatic
+    bl      MterpSPutU16
     cbnz    w0, MterpException          // 0 on success
     ADVANCE 2                           // Past exception point - now advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
@@ -2935,7 +2935,7 @@
     ldr     x2, [xFP, #OFF_FP_METHOD]
     mov     x3, xSELF
     PREFETCH_INST 2                     // Get next inst, but don't advance rPC
-    bl      MterpSetShortStatic
+    bl      MterpSPutI16
     cbnz    w0, MterpException          // 0 on success
     ADVANCE 2                           // Past exception point - now advance rPC
     GET_INST_OPCODE ip                  // extract opcode from rINST
diff --git a/runtime/interpreter/mterp/out/mterp_mips.S b/runtime/interpreter/mterp/out/mterp_mips.S
index 3b86279..fb7d52e 100644
--- a/runtime/interpreter/mterp/out/mterp_mips.S
+++ b/runtime/interpreter/mterp/out/mterp_mips.S
@@ -2677,7 +2677,7 @@
     GET_VREG(a1, a1)                       # a1 <- fp[B], the object pointer
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- referrer
     move  a3, rSELF                        # a3 <- self
-    JAL(artGet32InstanceFromCode)
+    JAL(MterpIGetU32)
     lw   a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA4(a2)                           # a2<- A+
     PREFETCH_INST(2)                       # load rINST
@@ -2706,7 +2706,7 @@
     GET_VREG(a1, a1)                       # a1 <- fp[B], the object pointer
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- referrer
     move  a3, rSELF                        # a3 <- self
-    JAL(artGet64InstanceFromCode)
+    JAL(MterpIGetU64)
     lw   a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA4(a2)                           # a2<- A+
     PREFETCH_INST(2)                       # load rINST
@@ -2732,7 +2732,7 @@
     GET_VREG(a1, a1)                       # a1 <- fp[B], the object pointer
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- referrer
     move  a3, rSELF                        # a3 <- self
-    JAL(artGetObjInstanceFromCode)
+    JAL(MterpIGetObj)
     lw   a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA4(a2)                           # a2<- A+
     PREFETCH_INST(2)                       # load rINST
@@ -2763,7 +2763,7 @@
     GET_VREG(a1, a1)                       # a1 <- fp[B], the object pointer
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- referrer
     move  a3, rSELF                        # a3 <- self
-    JAL(artGetBooleanInstanceFromCode)
+    JAL(MterpIGetU8)
     lw   a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA4(a2)                           # a2<- A+
     PREFETCH_INST(2)                       # load rINST
@@ -2794,7 +2794,7 @@
     GET_VREG(a1, a1)                       # a1 <- fp[B], the object pointer
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- referrer
     move  a3, rSELF                        # a3 <- self
-    JAL(artGetByteInstanceFromCode)
+    JAL(MterpIGetI8)
     lw   a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA4(a2)                           # a2<- A+
     PREFETCH_INST(2)                       # load rINST
@@ -2825,7 +2825,7 @@
     GET_VREG(a1, a1)                       # a1 <- fp[B], the object pointer
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- referrer
     move  a3, rSELF                        # a3 <- self
-    JAL(artGetCharInstanceFromCode)
+    JAL(MterpIGetU16)
     lw   a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA4(a2)                           # a2<- A+
     PREFETCH_INST(2)                       # load rINST
@@ -2856,7 +2856,7 @@
     GET_VREG(a1, a1)                       # a1 <- fp[B], the object pointer
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- referrer
     move  a3, rSELF                        # a3 <- self
-    JAL(artGetShortInstanceFromCode)
+    JAL(MterpIGetI16)
     lw   a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA4(a2)                           # a2<- A+
     PREFETCH_INST(2)                       # load rINST
@@ -2880,7 +2880,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet32InstanceFromMterp
+    .extern MterpIPutU32
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPB(a1)                            # a1 <- B
@@ -2889,7 +2889,7 @@
     GET_VREG(a2, a2)                       # a2 <- fp[A]
     lw    a3, OFF_FP_METHOD(rFP)           # a3 <- referrer
     PREFETCH_INST(2)                       # load rINST
-    JAL(artSet32InstanceFromMterp)
+    JAL(MterpIPutU32)
     bnez  v0, MterpPossibleException       # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -2900,7 +2900,7 @@
 .L_op_iput_wide: /* 0x5a */
 /* File: mips/op_iput_wide.S */
     /* iput-wide vA, vB, field@CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPB(a1)                            # a1 <- B
@@ -2909,7 +2909,7 @@
     EAS2(a2, rFP, a2)                      # a2 <- &fp[A]
     lw    a3, OFF_FP_METHOD(rFP)           # a3 <- referrer
     PREFETCH_INST(2)                       # load rINST
-    JAL(artSet64InstanceFromMterp)
+    JAL(MterpIPutU64)
     bnez  v0, MterpPossibleException       # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -2930,7 +2930,7 @@
     move   a1, rPC
     move   a2, rINST
     move   a3, rSELF
-    JAL(MterpIputObject)
+    JAL(MterpIPutObj)
     beqz   v0, MterpException
     FETCH_ADVANCE_INST(2)               # advance rPC, load rINST
     GET_INST_OPCODE(t0)                 # extract opcode from rINST
@@ -2947,7 +2947,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutU8
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPB(a1)                            # a1 <- B
@@ -2956,7 +2956,7 @@
     GET_VREG(a2, a2)                       # a2 <- fp[A]
     lw    a3, OFF_FP_METHOD(rFP)           # a3 <- referrer
     PREFETCH_INST(2)                       # load rINST
-    JAL(artSet8InstanceFromMterp)
+    JAL(MterpIPutU8)
     bnez  v0, MterpPossibleException       # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -2974,7 +2974,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutI8
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPB(a1)                            # a1 <- B
@@ -2983,7 +2983,7 @@
     GET_VREG(a2, a2)                       # a2 <- fp[A]
     lw    a3, OFF_FP_METHOD(rFP)           # a3 <- referrer
     PREFETCH_INST(2)                       # load rINST
-    JAL(artSet8InstanceFromMterp)
+    JAL(MterpIPutI8)
     bnez  v0, MterpPossibleException       # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -3001,7 +3001,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutU16
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPB(a1)                            # a1 <- B
@@ -3010,7 +3010,7 @@
     GET_VREG(a2, a2)                       # a2 <- fp[A]
     lw    a3, OFF_FP_METHOD(rFP)           # a3 <- referrer
     PREFETCH_INST(2)                       # load rINST
-    JAL(artSet16InstanceFromMterp)
+    JAL(MterpIPutU16)
     bnez  v0, MterpPossibleException       # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -3028,7 +3028,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field@CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutI16
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPB(a1)                            # a1 <- B
@@ -3037,7 +3037,7 @@
     GET_VREG(a2, a2)                       # a2 <- fp[A]
     lw    a3, OFF_FP_METHOD(rFP)           # a3 <- referrer
     PREFETCH_INST(2)                       # load rINST
-    JAL(artSet16InstanceFromMterp)
+    JAL(MterpIPutI16)
     bnez  v0, MterpPossibleException       # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -3054,12 +3054,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field@BBBB */
-    .extern MterpGet32Static
+    .extern MterpSGetU32
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref BBBB
     lw    a1, OFF_FP_METHOD(rFP)           # a1 <- method
     move  a2, rSELF                        # a2 <- self
-    JAL(MterpGet32Static)
+    JAL(MterpSGetU32)
     lw    a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA(a2)                            # a2 <- AA
     PREFETCH_INST(2)
@@ -3080,12 +3080,12 @@
      * 64-bit SGET handler.
      */
     /* sget-wide vAA, field@BBBB */
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref BBBB
     lw    a1, OFF_FP_METHOD(rFP)           # a1 <- method
     move  a2, rSELF                        # a2 <- self
-    JAL(MterpGet64Static)
+    JAL(MterpSGetU64)
     lw    a3, THREAD_EXCEPTION_OFFSET(rSELF)
     bnez  a3, MterpException
     GET_OPA(a1)                            # a1 <- AA
@@ -3104,12 +3104,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field@BBBB */
-    .extern MterpGetObjStatic
+    .extern MterpSGetObj
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref BBBB
     lw    a1, OFF_FP_METHOD(rFP)           # a1 <- method
     move  a2, rSELF                        # a2 <- self
-    JAL(MterpGetObjStatic)
+    JAL(MterpSGetObj)
     lw    a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA(a2)                            # a2 <- AA
     PREFETCH_INST(2)
@@ -3134,12 +3134,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field@BBBB */
-    .extern MterpGetBooleanStatic
+    .extern MterpSGetU8
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref BBBB
     lw    a1, OFF_FP_METHOD(rFP)           # a1 <- method
     move  a2, rSELF                        # a2 <- self
-    JAL(MterpGetBooleanStatic)
+    JAL(MterpSGetU8)
     lw    a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA(a2)                            # a2 <- AA
     PREFETCH_INST(2)
@@ -3164,12 +3164,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field@BBBB */
-    .extern MterpGetByteStatic
+    .extern MterpSGetI8
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref BBBB
     lw    a1, OFF_FP_METHOD(rFP)           # a1 <- method
     move  a2, rSELF                        # a2 <- self
-    JAL(MterpGetByteStatic)
+    JAL(MterpSGetI8)
     lw    a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA(a2)                            # a2 <- AA
     PREFETCH_INST(2)
@@ -3194,12 +3194,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field@BBBB */
-    .extern MterpGetCharStatic
+    .extern MterpSGetU16
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref BBBB
     lw    a1, OFF_FP_METHOD(rFP)           # a1 <- method
     move  a2, rSELF                        # a2 <- self
-    JAL(MterpGetCharStatic)
+    JAL(MterpSGetU16)
     lw    a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA(a2)                            # a2 <- AA
     PREFETCH_INST(2)
@@ -3224,12 +3224,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field@BBBB */
-    .extern MterpGetShortStatic
+    .extern MterpSGetI16
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref BBBB
     lw    a1, OFF_FP_METHOD(rFP)           # a1 <- method
     move  a2, rSELF                        # a2 <- self
-    JAL(MterpGetShortStatic)
+    JAL(MterpSGetI16)
     lw    a3, THREAD_EXCEPTION_OFFSET(rSELF)
     GET_OPA(a2)                            # a2 <- AA
     PREFETCH_INST(2)
@@ -3260,7 +3260,7 @@
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- method
     move  a3, rSELF                        # a3 <- self
     PREFETCH_INST(2)                       # load rINST
-    JAL(MterpSet32Static)
+    JAL(MterpSPutU32)
     bnez  v0, MterpException               # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -3274,7 +3274,7 @@
      * 64-bit SPUT handler.
      */
     /* sput-wide vAA, field@BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC()
     FETCH(a0, 1)                           # a0 <- field ref CCCC
     GET_OPA(a1)                            # a1 <- AA
@@ -3282,7 +3282,7 @@
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- method
     move  a3, rSELF                        # a3 <- self
     PREFETCH_INST(2)                       # load rINST
-    JAL(MterpSet64Static)
+    JAL(MterpSPutU64)
     bnez  v0, MterpException               # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -3303,7 +3303,7 @@
     move   a1, rPC
     move   a2, rINST
     move   a3, rSELF
-    JAL(MterpSputObject)
+    JAL(MterpSPutObj)
     beqz   v0, MterpException
     FETCH_ADVANCE_INST(2)               # advance rPC, load rINST
     GET_INST_OPCODE(t0)                 # extract opcode from rINST
@@ -3327,7 +3327,7 @@
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- method
     move  a3, rSELF                        # a3 <- self
     PREFETCH_INST(2)                       # load rINST
-    JAL(MterpSetBooleanStatic)
+    JAL(MterpSPutU8)
     bnez  v0, MterpException               # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -3352,7 +3352,7 @@
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- method
     move  a3, rSELF                        # a3 <- self
     PREFETCH_INST(2)                       # load rINST
-    JAL(MterpSetByteStatic)
+    JAL(MterpSPutI8)
     bnez  v0, MterpException               # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -3377,7 +3377,7 @@
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- method
     move  a3, rSELF                        # a3 <- self
     PREFETCH_INST(2)                       # load rINST
-    JAL(MterpSetCharStatic)
+    JAL(MterpSPutU16)
     bnez  v0, MterpException               # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
@@ -3402,7 +3402,7 @@
     lw    a2, OFF_FP_METHOD(rFP)           # a2 <- method
     move  a3, rSELF                        # a3 <- self
     PREFETCH_INST(2)                       # load rINST
-    JAL(MterpSetShortStatic)
+    JAL(MterpSPutI16)
     bnez  v0, MterpException               # bail out
     ADVANCE(2)                             # advance rPC
     GET_INST_OPCODE(t0)                    # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/out/mterp_mips64.S b/runtime/interpreter/mterp/out/mterp_mips64.S
index 58f98df..6561691 100644
--- a/runtime/interpreter/mterp/out/mterp_mips64.S
+++ b/runtime/interpreter/mterp/out/mterp_mips64.S
@@ -2246,14 +2246,14 @@
      *
      * for: iget, iget-object, iget-boolean, iget-byte, iget-char, iget-short
      */
-    .extern artGet32InstanceFromCode
+    .extern MterpIGetU32
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
     GET_VREG_U a1, a1                   # a1 <- fp[B], the object pointer
     ld       a2, OFF_FP_METHOD(rFP)     # a2 <- referrer
     move     a3, rSELF                  # a3 <- self
-    jal      artGet32InstanceFromCode
+    jal      MterpIGetU32
     ld       a3, THREAD_EXCEPTION_OFFSET(rSELF)
     ext      a2, rINST, 8, 4            # a2 <- A
     PREFETCH_INST 2
@@ -2276,14 +2276,14 @@
      *
      * for: iget-wide
      */
-    .extern artGet64InstanceFromCode
+    .extern MterpIGetU64
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
     GET_VREG_U a1, a1                   # a1 <- fp[B], the object pointer
     ld       a2, OFF_FP_METHOD(rFP)     # a2 <- referrer
     move     a3, rSELF                  # a3 <- self
-    jal      artGet64InstanceFromCode
+    jal      MterpIGetU64
     ld       a3, THREAD_EXCEPTION_OFFSET(rSELF)
     ext      a2, rINST, 8, 4            # a2 <- A
     PREFETCH_INST 2
@@ -2303,14 +2303,14 @@
      *
      * for: iget, iget-object, iget-boolean, iget-byte, iget-char, iget-short
      */
-    .extern artGetObjInstanceFromCode
+    .extern MterpIGetObj
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
     GET_VREG_U a1, a1                   # a1 <- fp[B], the object pointer
     ld       a2, OFF_FP_METHOD(rFP)     # a2 <- referrer
     move     a3, rSELF                  # a3 <- self
-    jal      artGetObjInstanceFromCode
+    jal      MterpIGetObj
     ld       a3, THREAD_EXCEPTION_OFFSET(rSELF)
     ext      a2, rINST, 8, 4            # a2 <- A
     PREFETCH_INST 2
@@ -2335,14 +2335,14 @@
      *
      * for: iget, iget-object, iget-boolean, iget-byte, iget-char, iget-short
      */
-    .extern artGetBooleanInstanceFromCode
+    .extern MterpIGetU8
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
     GET_VREG_U a1, a1                   # a1 <- fp[B], the object pointer
     ld       a2, OFF_FP_METHOD(rFP)     # a2 <- referrer
     move     a3, rSELF                  # a3 <- self
-    jal      artGetBooleanInstanceFromCode
+    jal      MterpIGetU8
     ld       a3, THREAD_EXCEPTION_OFFSET(rSELF)
     ext      a2, rINST, 8, 4            # a2 <- A
     PREFETCH_INST 2
@@ -2367,14 +2367,14 @@
      *
      * for: iget, iget-object, iget-boolean, iget-byte, iget-char, iget-short
      */
-    .extern artGetByteInstanceFromCode
+    .extern MterpIGetI8
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
     GET_VREG_U a1, a1                   # a1 <- fp[B], the object pointer
     ld       a2, OFF_FP_METHOD(rFP)     # a2 <- referrer
     move     a3, rSELF                  # a3 <- self
-    jal      artGetByteInstanceFromCode
+    jal      MterpIGetI8
     ld       a3, THREAD_EXCEPTION_OFFSET(rSELF)
     ext      a2, rINST, 8, 4            # a2 <- A
     PREFETCH_INST 2
@@ -2399,14 +2399,14 @@
      *
      * for: iget, iget-object, iget-boolean, iget-byte, iget-char, iget-short
      */
-    .extern artGetCharInstanceFromCode
+    .extern MterpIGetU16
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
     GET_VREG_U a1, a1                   # a1 <- fp[B], the object pointer
     ld       a2, OFF_FP_METHOD(rFP)     # a2 <- referrer
     move     a3, rSELF                  # a3 <- self
-    jal      artGetCharInstanceFromCode
+    jal      MterpIGetU16
     ld       a3, THREAD_EXCEPTION_OFFSET(rSELF)
     ext      a2, rINST, 8, 4            # a2 <- A
     PREFETCH_INST 2
@@ -2431,14 +2431,14 @@
      *
      * for: iget, iget-object, iget-boolean, iget-byte, iget-char, iget-short
      */
-    .extern artGetShortInstanceFromCode
+    .extern MterpIGetI16
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
     GET_VREG_U a1, a1                   # a1 <- fp[B], the object pointer
     ld       a2, OFF_FP_METHOD(rFP)     # a2 <- referrer
     move     a3, rSELF                  # a3 <- self
-    jal      artGetShortInstanceFromCode
+    jal      MterpIGetI16
     ld       a3, THREAD_EXCEPTION_OFFSET(rSELF)
     ext      a2, rINST, 8, 4            # a2 <- A
     PREFETCH_INST 2
@@ -2463,7 +2463,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet32InstanceFromMterp
+    .extern MterpIPutU32
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref CCCC
     srl     a1, rINST, 12               # a1 <- B
@@ -2472,7 +2472,7 @@
     GET_VREG a2, a2                     # a2 <- fp[A]
     ld      a3, OFF_FP_METHOD(rFP)      # a3 <- referrer
     PREFETCH_INST 2
-    jal     artSet32InstanceFromMterp
+    jal     MterpIPutU32
     bnez    v0, MterpPossibleException  # bail out
     ADVANCE 2
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2483,7 +2483,7 @@
 .L_op_iput_wide: /* 0x5a */
 /* File: mips64/op_iput_wide.S */
     /* iput-wide vA, vB, field//CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     lhu      a0, 2(rPC)                 # a0 <- field ref CCCC
     srl      a1, rINST, 12              # a1 <- B
@@ -2492,7 +2492,7 @@
     dlsa     a2, a2, rFP, 2             # a2 <- &fp[A]
     ld       a3, OFF_FP_METHOD(rFP)     # a3 <- referrer
     PREFETCH_INST 2
-    jal      artSet64InstanceFromMterp
+    jal      MterpIPutU64
     bnez     v0, MterpPossibleException # bail out
     ADVANCE 2
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2502,13 +2502,13 @@
     .balign 128
 .L_op_iput_object: /* 0x5b */
 /* File: mips64/op_iput_object.S */
-    .extern MterpIputObject
+    .extern MterpIPutObj
     EXPORT_PC
     daddu   a0, rFP, OFF_FP_SHADOWFRAME
     move    a1, rPC
     move    a2, rINST
     move    a3, rSELF
-    jal     MterpIputObject
+    jal     MterpIPutObj
     beqzc   v0, MterpException
     FETCH_ADVANCE_INST 2                # advance rPC, load rINST
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2525,7 +2525,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutU8
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref CCCC
     srl     a1, rINST, 12               # a1 <- B
@@ -2534,7 +2534,7 @@
     GET_VREG a2, a2                     # a2 <- fp[A]
     ld      a3, OFF_FP_METHOD(rFP)      # a3 <- referrer
     PREFETCH_INST 2
-    jal     artSet8InstanceFromMterp
+    jal     MterpIPutU8
     bnez    v0, MterpPossibleException  # bail out
     ADVANCE 2
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2552,7 +2552,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutI8
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref CCCC
     srl     a1, rINST, 12               # a1 <- B
@@ -2561,7 +2561,7 @@
     GET_VREG a2, a2                     # a2 <- fp[A]
     ld      a3, OFF_FP_METHOD(rFP)      # a3 <- referrer
     PREFETCH_INST 2
-    jal     artSet8InstanceFromMterp
+    jal     MterpIPutI8
     bnez    v0, MterpPossibleException  # bail out
     ADVANCE 2
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2579,7 +2579,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutU16
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref CCCC
     srl     a1, rINST, 12               # a1 <- B
@@ -2588,7 +2588,7 @@
     GET_VREG a2, a2                     # a2 <- fp[A]
     ld      a3, OFF_FP_METHOD(rFP)      # a3 <- referrer
     PREFETCH_INST 2
-    jal     artSet16InstanceFromMterp
+    jal     MterpIPutU16
     bnez    v0, MterpPossibleException  # bail out
     ADVANCE 2
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2606,7 +2606,7 @@
      * for: iput, iput-boolean, iput-byte, iput-char, iput-short
      */
     /* op vA, vB, field//CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutI16
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref CCCC
     srl     a1, rINST, 12               # a1 <- B
@@ -2615,7 +2615,7 @@
     GET_VREG a2, a2                     # a2 <- fp[A]
     ld      a3, OFF_FP_METHOD(rFP)      # a3 <- referrer
     PREFETCH_INST 2
-    jal     artSet16InstanceFromMterp
+    jal     MterpIPutI16
     bnez    v0, MterpPossibleException  # bail out
     ADVANCE 2
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2632,12 +2632,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpGet32Static
+    .extern MterpSGetU32
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     ld      a1, OFF_FP_METHOD(rFP)
     move    a2, rSELF
-    jal     MterpGet32Static
+    jal     MterpSGetU32
     ld      a3, THREAD_EXCEPTION_OFFSET(rSELF)
     srl     a2, rINST, 8                # a2 <- AA
     
@@ -2661,12 +2661,12 @@
      *
      */
     /* sget-wide vAA, field//BBBB */
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     ld      a1, OFF_FP_METHOD(rFP)
     move    a2, rSELF
-    jal     MterpGet64Static
+    jal     MterpSGetU64
     ld      a3, THREAD_EXCEPTION_OFFSET(rSELF)
     srl     a4, rINST, 8                # a4 <- AA
     bnez    a3, MterpException          # bail out
@@ -2686,12 +2686,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpGetObjStatic
+    .extern MterpSGetObj
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     ld      a1, OFF_FP_METHOD(rFP)
     move    a2, rSELF
-    jal     MterpGetObjStatic
+    jal     MterpSGetObj
     ld      a3, THREAD_EXCEPTION_OFFSET(rSELF)
     srl     a2, rINST, 8                # a2 <- AA
     
@@ -2718,12 +2718,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpGetBooleanStatic
+    .extern MterpSGetU8
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     ld      a1, OFF_FP_METHOD(rFP)
     move    a2, rSELF
-    jal     MterpGetBooleanStatic
+    jal     MterpSGetU8
     ld      a3, THREAD_EXCEPTION_OFFSET(rSELF)
     srl     a2, rINST, 8                # a2 <- AA
     and v0, v0, 0xff
@@ -2750,12 +2750,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpGetByteStatic
+    .extern MterpSGetI8
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     ld      a1, OFF_FP_METHOD(rFP)
     move    a2, rSELF
-    jal     MterpGetByteStatic
+    jal     MterpSGetI8
     ld      a3, THREAD_EXCEPTION_OFFSET(rSELF)
     srl     a2, rINST, 8                # a2 <- AA
     seb v0, v0
@@ -2782,12 +2782,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpGetCharStatic
+    .extern MterpSGetU16
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     ld      a1, OFF_FP_METHOD(rFP)
     move    a2, rSELF
-    jal     MterpGetCharStatic
+    jal     MterpSGetU16
     ld      a3, THREAD_EXCEPTION_OFFSET(rSELF)
     srl     a2, rINST, 8                # a2 <- AA
     and v0, v0, 0xffff
@@ -2814,12 +2814,12 @@
      * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpGetShortStatic
+    .extern MterpSGetI16
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     ld      a1, OFF_FP_METHOD(rFP)
     move    a2, rSELF
-    jal     MterpGetShortStatic
+    jal     MterpSGetI16
     ld      a3, THREAD_EXCEPTION_OFFSET(rSELF)
     srl     a2, rINST, 8                # a2 <- AA
     seh v0, v0
@@ -2845,7 +2845,7 @@
      * for: sput, sput-boolean, sput-byte, sput-char, sput-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpSet32Static
+    .extern MterpSPutU32
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     srl     a3, rINST, 8                # a3 <- AA
@@ -2853,7 +2853,7 @@
     ld      a2, OFF_FP_METHOD(rFP)
     move    a3, rSELF
     PREFETCH_INST 2                     # Get next inst, but don't advance rPC
-    jal     MterpSet32Static
+    jal     MterpSPutU32
     bnezc   v0, MterpException          # 0 on success
     ADVANCE 2                           # Past exception point - now advance rPC
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2868,7 +2868,7 @@
      *
      */
     /* sput-wide vAA, field//BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     srl     a1, rINST, 8                # a2 <- AA
@@ -2876,7 +2876,7 @@
     ld      a2, OFF_FP_METHOD(rFP)
     move    a3, rSELF
     PREFETCH_INST 2                     # Get next inst, but don't advance rPC
-    jal     MterpSet64Static
+    jal     MterpSPutU64
     bnezc   v0, MterpException          # 0 on success, -1 on failure
     ADVANCE 2                           # Past exception point - now advance rPC
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2886,13 +2886,13 @@
     .balign 128
 .L_op_sput_object: /* 0x69 */
 /* File: mips64/op_sput_object.S */
-    .extern MterpSputObject
+    .extern MterpSPutObj
     EXPORT_PC
     daddu   a0, rFP, OFF_FP_SHADOWFRAME
     move    a1, rPC
     move    a2, rINST
     move    a3, rSELF
-    jal     MterpSputObject
+    jal     MterpSPutObj
     beqzc   v0, MterpException
     FETCH_ADVANCE_INST 2                # advance rPC, load rINST
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2909,7 +2909,7 @@
      * for: sput, sput-boolean, sput-byte, sput-char, sput-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpSetBooleanStatic
+    .extern MterpSPutU8
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     srl     a3, rINST, 8                # a3 <- AA
@@ -2917,7 +2917,7 @@
     ld      a2, OFF_FP_METHOD(rFP)
     move    a3, rSELF
     PREFETCH_INST 2                     # Get next inst, but don't advance rPC
-    jal     MterpSetBooleanStatic
+    jal     MterpSPutU8
     bnezc   v0, MterpException          # 0 on success
     ADVANCE 2                           # Past exception point - now advance rPC
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2935,7 +2935,7 @@
      * for: sput, sput-boolean, sput-byte, sput-char, sput-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpSetByteStatic
+    .extern MterpSPutI8
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     srl     a3, rINST, 8                # a3 <- AA
@@ -2943,7 +2943,7 @@
     ld      a2, OFF_FP_METHOD(rFP)
     move    a3, rSELF
     PREFETCH_INST 2                     # Get next inst, but don't advance rPC
-    jal     MterpSetByteStatic
+    jal     MterpSPutI8
     bnezc   v0, MterpException          # 0 on success
     ADVANCE 2                           # Past exception point - now advance rPC
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2961,7 +2961,7 @@
      * for: sput, sput-boolean, sput-byte, sput-char, sput-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpSetCharStatic
+    .extern MterpSPutU16
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     srl     a3, rINST, 8                # a3 <- AA
@@ -2969,7 +2969,7 @@
     ld      a2, OFF_FP_METHOD(rFP)
     move    a3, rSELF
     PREFETCH_INST 2                     # Get next inst, but don't advance rPC
-    jal     MterpSetCharStatic
+    jal     MterpSPutU16
     bnezc   v0, MterpException          # 0 on success
     ADVANCE 2                           # Past exception point - now advance rPC
     GET_INST_OPCODE v0                  # extract opcode from rINST
@@ -2987,7 +2987,7 @@
      * for: sput, sput-boolean, sput-byte, sput-char, sput-short
      */
     /* op vAA, field//BBBB */
-    .extern MterpSetShortStatic
+    .extern MterpSPutI16
     EXPORT_PC
     lhu     a0, 2(rPC)                  # a0 <- field ref BBBB
     srl     a3, rINST, 8                # a3 <- AA
@@ -2995,7 +2995,7 @@
     ld      a2, OFF_FP_METHOD(rFP)
     move    a3, rSELF
     PREFETCH_INST 2                     # Get next inst, but don't advance rPC
-    jal     MterpSetShortStatic
+    jal     MterpSPutI16
     bnezc   v0, MterpException          # 0 on success
     ADVANCE 2                           # Past exception point - now advance rPC
     GET_INST_OPCODE v0                  # extract opcode from rINST
diff --git a/runtime/interpreter/mterp/out/mterp_x86.S b/runtime/interpreter/mterp/out/mterp_x86.S
index 6be70cc..c78eb49 100644
--- a/runtime/interpreter/mterp/out/mterp_x86.S
+++ b/runtime/interpreter/mterp/out/mterp_x86.S
@@ -2132,7 +2132,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     mov     rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(artGet32InstanceFromCode)
+    call    SYMBOL(MterpIGetU32)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2165,7 +2165,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     mov     rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(artGet64InstanceFromCode)
+    call    SYMBOL(MterpIGetU64)
     mov     rSELF, %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
     jnz     MterpException                  # bail out
@@ -2196,7 +2196,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     mov     rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(artGetObjInstanceFromCode)
+    call    SYMBOL(MterpIGetObj)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2231,7 +2231,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     mov     rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(artGetBooleanInstanceFromCode)
+    call    SYMBOL(MterpIGetU8)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2266,7 +2266,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     mov     rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(artGetByteInstanceFromCode)
+    call    SYMBOL(MterpIGetI8)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2301,7 +2301,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     mov     rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(artGetCharInstanceFromCode)
+    call    SYMBOL(MterpIGetU16)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2336,7 +2336,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     mov     rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(artGetShortInstanceFromCode)
+    call    SYMBOL(MterpIGetI16)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2360,7 +2360,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet32InstanceFromMterp
+    .extern MterpIPutU32
     EXPORT_PC
     movzwl  2(rPC), %eax                    # eax<- 0000CCCC
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2373,7 +2373,7 @@
     movl    %eax, OUT_ARG2(%esp)            # fp[A]
     movl    OFF_FP_METHOD(rFP), %eax
     movl    %eax, OUT_ARG3(%esp)            # referrer
-    call    SYMBOL(artSet32InstanceFromMterp)
+    call    SYMBOL(MterpIPutU32)
     testb   %al, %al
     jnz     MterpPossibleException
     RESTORE_IBASE
@@ -2384,7 +2384,7 @@
 .L_op_iput_wide: /* 0x5a */
 /* File: x86/op_iput_wide.S */
     /* iput-wide vA, vB, field@CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     movzwl  2(rPC), %eax                    # eax <- 0000CCCC
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2397,7 +2397,7 @@
     movl    %eax, OUT_ARG2(%esp)            # &fp[A]
     movl    OFF_FP_METHOD(rFP), %eax
     movl    %eax, OUT_ARG3(%esp)            # referrer
-    call    SYMBOL(artSet64InstanceFromMterp)
+    call    SYMBOL(MterpIPutU64)
     testb   %al, %al
     jnz     MterpPossibleException
     RESTORE_IBASE
@@ -2415,7 +2415,7 @@
     movl    rINST, OUT_ARG2(%esp)
     movl    rSELF, %eax
     movl    %eax, OUT_ARG3(%esp)
-    call    SYMBOL(MterpIputObject)
+    call    SYMBOL(MterpIPutObj)
     testb   %al, %al
     jz      MterpException
     RESTORE_IBASE
@@ -2432,7 +2432,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutU8
     EXPORT_PC
     movzwl  2(rPC), %eax                    # eax<- 0000CCCC
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2445,7 +2445,7 @@
     movl    %eax, OUT_ARG2(%esp)            # fp[A]
     movl    OFF_FP_METHOD(rFP), %eax
     movl    %eax, OUT_ARG3(%esp)            # referrer
-    call    SYMBOL(artSet8InstanceFromMterp)
+    call    SYMBOL(MterpIPutU8)
     testb   %al, %al
     jnz     MterpPossibleException
     RESTORE_IBASE
@@ -2463,7 +2463,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutI8
     EXPORT_PC
     movzwl  2(rPC), %eax                    # eax<- 0000CCCC
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2476,7 +2476,7 @@
     movl    %eax, OUT_ARG2(%esp)            # fp[A]
     movl    OFF_FP_METHOD(rFP), %eax
     movl    %eax, OUT_ARG3(%esp)            # referrer
-    call    SYMBOL(artSet8InstanceFromMterp)
+    call    SYMBOL(MterpIPutI8)
     testb   %al, %al
     jnz     MterpPossibleException
     RESTORE_IBASE
@@ -2494,7 +2494,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutU16
     EXPORT_PC
     movzwl  2(rPC), %eax                    # eax<- 0000CCCC
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2507,7 +2507,7 @@
     movl    %eax, OUT_ARG2(%esp)            # fp[A]
     movl    OFF_FP_METHOD(rFP), %eax
     movl    %eax, OUT_ARG3(%esp)            # referrer
-    call    SYMBOL(artSet16InstanceFromMterp)
+    call    SYMBOL(MterpIPutU16)
     testb   %al, %al
     jnz     MterpPossibleException
     RESTORE_IBASE
@@ -2525,7 +2525,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutI16
     EXPORT_PC
     movzwl  2(rPC), %eax                    # eax<- 0000CCCC
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2538,7 +2538,7 @@
     movl    %eax, OUT_ARG2(%esp)            # fp[A]
     movl    OFF_FP_METHOD(rFP), %eax
     movl    %eax, OUT_ARG3(%esp)            # referrer
-    call    SYMBOL(artSet16InstanceFromMterp)
+    call    SYMBOL(MterpIPutI16)
     testb   %al, %al
     jnz     MterpPossibleException
     RESTORE_IBASE
@@ -2555,7 +2555,7 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpGet32Static
+    .extern MterpSGetU32
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2563,7 +2563,7 @@
     movl    %eax, OUT_ARG1(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG2(%esp)            # self
-    call    SYMBOL(MterpGet32Static)
+    call    SYMBOL(MterpSGetU32)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2584,7 +2584,7 @@
  *
  */
     /* sget-wide vAA, field@BBBB */
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2592,7 +2592,7 @@
     movl    %eax, OUT_ARG1(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG2(%esp)            # self
-    call    SYMBOL(MterpGet64Static)
+    call    SYMBOL(MterpSGetU64)
     movl    rSELF, %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
     jnz     MterpException
@@ -2612,7 +2612,7 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetObjStatic
+    .extern MterpSGetObj
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2620,7 +2620,7 @@
     movl    %eax, OUT_ARG1(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG2(%esp)            # self
-    call    SYMBOL(MterpGetObjStatic)
+    call    SYMBOL(MterpSGetObj)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2644,7 +2644,7 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetBooleanStatic
+    .extern MterpSGetU8
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2652,7 +2652,7 @@
     movl    %eax, OUT_ARG1(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG2(%esp)            # self
-    call    SYMBOL(MterpGetBooleanStatic)
+    call    SYMBOL(MterpSGetU8)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2676,7 +2676,7 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetByteStatic
+    .extern MterpSGetI8
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2684,7 +2684,7 @@
     movl    %eax, OUT_ARG1(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG2(%esp)            # self
-    call    SYMBOL(MterpGetByteStatic)
+    call    SYMBOL(MterpSGetI8)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2708,7 +2708,7 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetCharStatic
+    .extern MterpSGetU16
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2716,7 +2716,7 @@
     movl    %eax, OUT_ARG1(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG2(%esp)            # self
-    call    SYMBOL(MterpGetCharStatic)
+    call    SYMBOL(MterpSGetU16)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2740,7 +2740,7 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetShortStatic
+    .extern MterpSGetI16
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -2748,7 +2748,7 @@
     movl    %eax, OUT_ARG1(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG2(%esp)            # self
-    call    SYMBOL(MterpGetShortStatic)
+    call    SYMBOL(MterpSGetI16)
     movl    rSELF, %ecx
     RESTORE_IBASE_FROM_SELF %ecx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%ecx)
@@ -2771,7 +2771,7 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSet32Static
+    .extern MterpSPutU32
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref BBBB
@@ -2781,7 +2781,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(MterpSet32Static)
+    call    SYMBOL(MterpSPutU32)
     testb   %al, %al
     jnz     MterpException
     RESTORE_IBASE
@@ -2796,7 +2796,7 @@
  *
  */
     /* sput-wide vAA, field@BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref BBBB
@@ -2806,7 +2806,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(MterpSet64Static)
+    call    SYMBOL(MterpSPutU64)
     testb   %al, %al
     jnz     MterpException
     RESTORE_IBASE
@@ -2824,7 +2824,7 @@
     movl    rINST, OUT_ARG2(%esp)
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)
-    call    SYMBOL(MterpSputObject)
+    call    SYMBOL(MterpSPutObj)
     testb   %al, %al
     jz      MterpException
     RESTORE_IBASE
@@ -2841,7 +2841,7 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSetBooleanStatic
+    .extern MterpSPutU8
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref BBBB
@@ -2851,7 +2851,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(MterpSetBooleanStatic)
+    call    SYMBOL(MterpSPutU8)
     testb   %al, %al
     jnz     MterpException
     RESTORE_IBASE
@@ -2869,7 +2869,7 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSetByteStatic
+    .extern MterpSPutI8
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref BBBB
@@ -2879,7 +2879,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(MterpSetByteStatic)
+    call    SYMBOL(MterpSPutI8)
     testb   %al, %al
     jnz     MterpException
     RESTORE_IBASE
@@ -2897,7 +2897,7 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSetCharStatic
+    .extern MterpSPutU16
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref BBBB
@@ -2907,7 +2907,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(MterpSetCharStatic)
+    call    SYMBOL(MterpSPutU16)
     testb   %al, %al
     jnz     MterpException
     RESTORE_IBASE
@@ -2925,7 +2925,7 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSetShortStatic
+    .extern MterpSPutI16
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref BBBB
@@ -2935,7 +2935,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(MterpSetShortStatic)
+    call    SYMBOL(MterpSPutI16)
     testb   %al, %al
     jnz     MterpException
     RESTORE_IBASE
diff --git a/runtime/interpreter/mterp/out/mterp_x86_64.S b/runtime/interpreter/mterp/out/mterp_x86_64.S
index 562cf7c..524dce4 100644
--- a/runtime/interpreter/mterp/out/mterp_x86_64.S
+++ b/runtime/interpreter/mterp/out/mterp_x86_64.S
@@ -2075,7 +2075,7 @@
     GET_VREG OUT_32_ARG1, %rcx              # the object pointer
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(artGet32InstanceFromCode)
+    call    SYMBOL(MterpIGetU32)
     movq    rSELF, %rcx
     cmpq    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException                  # bail out
@@ -2108,7 +2108,7 @@
     GET_VREG OUT_32_ARG1, %rcx              # the object pointer
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(artGet64InstanceFromCode)
+    call    SYMBOL(MterpIGetU64)
     movq    rSELF, %rcx
     cmpq    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException                  # bail out
@@ -2142,7 +2142,7 @@
     GET_VREG OUT_32_ARG1, %rcx              # the object pointer
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(artGetObjInstanceFromCode)
+    call    SYMBOL(MterpIGetObj)
     movq    rSELF, %rcx
     cmpq    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException                  # bail out
@@ -2176,7 +2176,7 @@
     GET_VREG OUT_32_ARG1, %rcx              # the object pointer
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(artGetBooleanInstanceFromCode)
+    call    SYMBOL(MterpIGetU8)
     movq    rSELF, %rcx
     cmpq    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException                  # bail out
@@ -2210,7 +2210,7 @@
     GET_VREG OUT_32_ARG1, %rcx              # the object pointer
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(artGetByteInstanceFromCode)
+    call    SYMBOL(MterpIGetI8)
     movq    rSELF, %rcx
     cmpq    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException                  # bail out
@@ -2244,7 +2244,7 @@
     GET_VREG OUT_32_ARG1, %rcx              # the object pointer
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(artGetCharInstanceFromCode)
+    call    SYMBOL(MterpIGetU16)
     movq    rSELF, %rcx
     cmpq    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException                  # bail out
@@ -2278,7 +2278,7 @@
     GET_VREG OUT_32_ARG1, %rcx              # the object pointer
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(artGetShortInstanceFromCode)
+    call    SYMBOL(MterpIGetI16)
     movq    rSELF, %rcx
     cmpq    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException                  # bail out
@@ -2305,7 +2305,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet32InstanceFromMterp
+    .extern MterpIPutU32
     EXPORT_PC
     movzwl  2(rPC), OUT_32_ARG0             # field ref <- 0000CCCC
     movzbq  rINSTbl, %rcx                   # rcx<- BA
@@ -2314,7 +2314,7 @@
     andb    $0xf, rINSTbl                  # rINST<- A
     GET_VREG OUT_32_ARG2, rINSTq            # fp[A]
     movq    OFF_FP_METHOD(rFP), OUT_ARG3    # referrer
-    call    SYMBOL(artSet32InstanceFromMterp)
+    call    SYMBOL(MterpIPutU32)
     testb   %al, %al
     jnz     MterpPossibleException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2324,7 +2324,7 @@
 .L_op_iput_wide: /* 0x5a */
 /* File: x86_64/op_iput_wide.S */
     /* iput-wide vA, vB, field@CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movzbq  rINSTbl, %rcx                   # rcx <- BA
@@ -2333,7 +2333,7 @@
     andb    $0xf, rINSTbl                  # rINST <- A
     leaq    VREG_ADDRESS(rINSTq), OUT_ARG2  # &fp[A]
     movq    OFF_FP_METHOD(rFP), OUT_ARG3    # referrer
-    call    SYMBOL(artSet64InstanceFromMterp)
+    call    SYMBOL(MterpIPutU64)
     testb   %al, %al
     jnz     MterpPossibleException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2348,7 +2348,7 @@
     REFRESH_INST 91
     movl    rINST, OUT_32_ARG2
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(MterpIputObject)
+    call    SYMBOL(MterpIPutObj)
     testb   %al, %al
     jz      MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2364,7 +2364,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutU8
     EXPORT_PC
     movzwl  2(rPC), OUT_32_ARG0             # field ref <- 0000CCCC
     movzbq  rINSTbl, %rcx                   # rcx<- BA
@@ -2373,7 +2373,7 @@
     andb    $0xf, rINSTbl                  # rINST<- A
     GET_VREG OUT_32_ARG2, rINSTq            # fp[A]
     movq    OFF_FP_METHOD(rFP), OUT_ARG3    # referrer
-    call    SYMBOL(artSet8InstanceFromMterp)
+    call    SYMBOL(MterpIPutU8)
     testb   %al, %al
     jnz     MterpPossibleException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2390,7 +2390,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet8InstanceFromMterp
+    .extern MterpIPutI8
     EXPORT_PC
     movzwl  2(rPC), OUT_32_ARG0             # field ref <- 0000CCCC
     movzbq  rINSTbl, %rcx                   # rcx<- BA
@@ -2399,7 +2399,7 @@
     andb    $0xf, rINSTbl                  # rINST<- A
     GET_VREG OUT_32_ARG2, rINSTq            # fp[A]
     movq    OFF_FP_METHOD(rFP), OUT_ARG3    # referrer
-    call    SYMBOL(artSet8InstanceFromMterp)
+    call    SYMBOL(MterpIPutI8)
     testb   %al, %al
     jnz     MterpPossibleException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2416,7 +2416,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutU16
     EXPORT_PC
     movzwl  2(rPC), OUT_32_ARG0             # field ref <- 0000CCCC
     movzbq  rINSTbl, %rcx                   # rcx<- BA
@@ -2425,7 +2425,7 @@
     andb    $0xf, rINSTbl                  # rINST<- A
     GET_VREG OUT_32_ARG2, rINSTq            # fp[A]
     movq    OFF_FP_METHOD(rFP), OUT_ARG3    # referrer
-    call    SYMBOL(artSet16InstanceFromMterp)
+    call    SYMBOL(MterpIPutU16)
     testb   %al, %al
     jnz     MterpPossibleException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2442,7 +2442,7 @@
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern artSet16InstanceFromMterp
+    .extern MterpIPutI16
     EXPORT_PC
     movzwl  2(rPC), OUT_32_ARG0             # field ref <- 0000CCCC
     movzbq  rINSTbl, %rcx                   # rcx<- BA
@@ -2451,7 +2451,7 @@
     andb    $0xf, rINSTbl                  # rINST<- A
     GET_VREG OUT_32_ARG2, rINSTq            # fp[A]
     movq    OFF_FP_METHOD(rFP), OUT_ARG3    # referrer
-    call    SYMBOL(artSet16InstanceFromMterp)
+    call    SYMBOL(MterpIPutI16)
     testb   %al, %al
     jnz     MterpPossibleException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2467,12 +2467,12 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short, sget-wide
  */
     /* op vAA, field@BBBB */
-    .extern MterpGet32Static
+    .extern MterpSGetU32
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movq    OFF_FP_METHOD(rFP), OUT_ARG1    # referrer
     movq    rSELF, OUT_ARG2                 # self
-    call    SYMBOL(MterpGet32Static)
+    call    SYMBOL(MterpSGetU32)
     movq    rSELF, %rcx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException
@@ -2498,12 +2498,12 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short, sget-wide
  */
     /* op vAA, field@BBBB */
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movq    OFF_FP_METHOD(rFP), OUT_ARG1    # referrer
     movq    rSELF, OUT_ARG2                 # self
-    call    SYMBOL(MterpGet64Static)
+    call    SYMBOL(MterpSGetU64)
     movq    rSELF, %rcx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException
@@ -2530,12 +2530,12 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short, sget-wide
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetObjStatic
+    .extern MterpSGetObj
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movq    OFF_FP_METHOD(rFP), OUT_ARG1    # referrer
     movq    rSELF, OUT_ARG2                 # self
-    call    SYMBOL(MterpGetObjStatic)
+    call    SYMBOL(MterpSGetObj)
     movq    rSELF, %rcx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException
@@ -2562,12 +2562,12 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short, sget-wide
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetBooleanStatic
+    .extern MterpSGetU8
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movq    OFF_FP_METHOD(rFP), OUT_ARG1    # referrer
     movq    rSELF, OUT_ARG2                 # self
-    call    SYMBOL(MterpGetBooleanStatic)
+    call    SYMBOL(MterpSGetU8)
     movq    rSELF, %rcx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException
@@ -2594,12 +2594,12 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short, sget-wide
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetByteStatic
+    .extern MterpSGetI8
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movq    OFF_FP_METHOD(rFP), OUT_ARG1    # referrer
     movq    rSELF, OUT_ARG2                 # self
-    call    SYMBOL(MterpGetByteStatic)
+    call    SYMBOL(MterpSGetI8)
     movq    rSELF, %rcx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException
@@ -2626,12 +2626,12 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short, sget-wide
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetCharStatic
+    .extern MterpSGetU16
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movq    OFF_FP_METHOD(rFP), OUT_ARG1    # referrer
     movq    rSELF, OUT_ARG2                 # self
-    call    SYMBOL(MterpGetCharStatic)
+    call    SYMBOL(MterpSGetU16)
     movq    rSELF, %rcx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException
@@ -2658,12 +2658,12 @@
  * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short, sget-wide
  */
     /* op vAA, field@BBBB */
-    .extern MterpGetShortStatic
+    .extern MterpSGetI16
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movq    OFF_FP_METHOD(rFP), OUT_ARG1    # referrer
     movq    rSELF, OUT_ARG2                 # self
-    call    SYMBOL(MterpGetShortStatic)
+    call    SYMBOL(MterpSGetI16)
     movq    rSELF, %rcx
     cmpl    $0, THREAD_EXCEPTION_OFFSET(%rcx)
     jnz     MterpException
@@ -2689,13 +2689,13 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSet32Static
+    .extern MterpSPutU32
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref BBBB
     GET_VREG OUT_32_ARG1, rINSTq            # fp[AA]
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3                 # self
-    call    SYMBOL(MterpSet32Static)
+    call    SYMBOL(MterpSPutU32)
     testb   %al, %al
     jnz     MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2709,13 +2709,13 @@
  *
  */
     /* sput-wide vAA, field@BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref BBBB
     leaq    VREG_ADDRESS(rINSTq), OUT_ARG1  # &fp[AA]
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3                 # self
-    call    SYMBOL(MterpSet64Static)
+    call    SYMBOL(MterpSPutU64)
     testb   %al, %al
     jnz     MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2730,7 +2730,7 @@
     REFRESH_INST 105
     movq    rINSTq, OUT_ARG2
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(MterpSputObject)
+    call    SYMBOL(MterpSPutObj)
     testb   %al, %al
     jz      MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2746,13 +2746,13 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSetBooleanStatic
+    .extern MterpSPutU8
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref BBBB
     GET_VREG OUT_32_ARG1, rINSTq            # fp[AA]
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3                 # self
-    call    SYMBOL(MterpSetBooleanStatic)
+    call    SYMBOL(MterpSPutU8)
     testb   %al, %al
     jnz     MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2769,13 +2769,13 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSetByteStatic
+    .extern MterpSPutI8
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref BBBB
     GET_VREG OUT_32_ARG1, rINSTq            # fp[AA]
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3                 # self
-    call    SYMBOL(MterpSetByteStatic)
+    call    SYMBOL(MterpSPutI8)
     testb   %al, %al
     jnz     MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2792,13 +2792,13 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSetCharStatic
+    .extern MterpSPutU16
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref BBBB
     GET_VREG OUT_32_ARG1, rINSTq            # fp[AA]
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3                 # self
-    call    SYMBOL(MterpSetCharStatic)
+    call    SYMBOL(MterpSPutU16)
     testb   %al, %al
     jnz     MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
@@ -2815,13 +2815,13 @@
  * for: sput, sput-boolean, sput-byte, sput-char, sput-short
  */
     /* op vAA, field@BBBB */
-    .extern MterpSetShortStatic
+    .extern MterpSPutI16
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref BBBB
     GET_VREG OUT_32_ARG1, rINSTq            # fp[AA]
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3                 # self
-    call    SYMBOL(MterpSetShortStatic)
+    call    SYMBOL(MterpSPutI16)
     testb   %al, %al
     jnz     MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
diff --git a/runtime/interpreter/mterp/x86/op_iget.S b/runtime/interpreter/mterp/x86/op_iget.S
index e3304ba..0af1bec 100644
--- a/runtime/interpreter/mterp/x86/op_iget.S
+++ b/runtime/interpreter/mterp/x86/op_iget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"artGet32InstanceFromCode"}
+%default { "is_object":"0", "helper":"MterpIGetU32"}
 /*
  * General instance field get.
  *
diff --git a/runtime/interpreter/mterp/x86/op_iget_boolean.S b/runtime/interpreter/mterp/x86/op_iget_boolean.S
index 9ddad04..ddccc41 100644
--- a/runtime/interpreter/mterp/x86/op_iget_boolean.S
+++ b/runtime/interpreter/mterp/x86/op_iget_boolean.S
@@ -1 +1 @@
-%include "x86/op_iget.S" { "helper":"artGetBooleanInstanceFromCode" }
+%include "x86/op_iget.S" { "helper":"MterpIGetU8" }
diff --git a/runtime/interpreter/mterp/x86/op_iget_byte.S b/runtime/interpreter/mterp/x86/op_iget_byte.S
index 8250788..cd46d3d 100644
--- a/runtime/interpreter/mterp/x86/op_iget_byte.S
+++ b/runtime/interpreter/mterp/x86/op_iget_byte.S
@@ -1 +1 @@
-%include "x86/op_iget.S" { "helper":"artGetByteInstanceFromCode" }
+%include "x86/op_iget.S" { "helper":"MterpIGetI8" }
diff --git a/runtime/interpreter/mterp/x86/op_iget_char.S b/runtime/interpreter/mterp/x86/op_iget_char.S
index e9d2156..9969734 100644
--- a/runtime/interpreter/mterp/x86/op_iget_char.S
+++ b/runtime/interpreter/mterp/x86/op_iget_char.S
@@ -1 +1 @@
-%include "x86/op_iget.S" { "helper":"artGetCharInstanceFromCode" }
+%include "x86/op_iget.S" { "helper":"MterpIGetU16" }
diff --git a/runtime/interpreter/mterp/x86/op_iget_object.S b/runtime/interpreter/mterp/x86/op_iget_object.S
index 3abeefc..3d421fc 100644
--- a/runtime/interpreter/mterp/x86/op_iget_object.S
+++ b/runtime/interpreter/mterp/x86/op_iget_object.S
@@ -1 +1 @@
-%include "x86/op_iget.S" { "is_object":"1", "helper":"artGetObjInstanceFromCode" }
+%include "x86/op_iget.S" { "is_object":"1", "helper":"MterpIGetObj" }
diff --git a/runtime/interpreter/mterp/x86/op_iget_short.S b/runtime/interpreter/mterp/x86/op_iget_short.S
index c8fad89..c7477f5 100644
--- a/runtime/interpreter/mterp/x86/op_iget_short.S
+++ b/runtime/interpreter/mterp/x86/op_iget_short.S
@@ -1 +1 @@
-%include "x86/op_iget.S" { "helper":"artGetShortInstanceFromCode" }
+%include "x86/op_iget.S" { "helper":"MterpIGetI16" }
diff --git a/runtime/interpreter/mterp/x86/op_iget_wide.S b/runtime/interpreter/mterp/x86/op_iget_wide.S
index a5d7e69..da27df9 100644
--- a/runtime/interpreter/mterp/x86/op_iget_wide.S
+++ b/runtime/interpreter/mterp/x86/op_iget_wide.S
@@ -14,7 +14,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     mov     rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(artGet64InstanceFromCode)
+    call    SYMBOL(MterpIGetU64)
     mov     rSELF, %ecx
     cmpl    $$0, THREAD_EXCEPTION_OFFSET(%ecx)
     jnz     MterpException                  # bail out
diff --git a/runtime/interpreter/mterp/x86/op_iput.S b/runtime/interpreter/mterp/x86/op_iput.S
index c847e2d..4c6603a 100644
--- a/runtime/interpreter/mterp/x86/op_iput.S
+++ b/runtime/interpreter/mterp/x86/op_iput.S
@@ -1,11 +1,11 @@
-%default { "handler":"artSet32InstanceFromMterp" }
+%default { "helper":"MterpIPutU32" }
 /*
  * General 32-bit instance field put.
  *
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern $handler
+    .extern $helper
     EXPORT_PC
     movzwl  2(rPC), %eax                    # eax<- 0000CCCC
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -18,7 +18,7 @@
     movl    %eax, OUT_ARG2(%esp)            # fp[A]
     movl    OFF_FP_METHOD(rFP), %eax
     movl    %eax, OUT_ARG3(%esp)            # referrer
-    call    SYMBOL($handler)
+    call    SYMBOL($helper)
     testb   %al, %al
     jnz     MterpPossibleException
     RESTORE_IBASE
diff --git a/runtime/interpreter/mterp/x86/op_iput_boolean.S b/runtime/interpreter/mterp/x86/op_iput_boolean.S
index 11cab88..fdd5303 100644
--- a/runtime/interpreter/mterp/x86/op_iput_boolean.S
+++ b/runtime/interpreter/mterp/x86/op_iput_boolean.S
@@ -1 +1 @@
-%include "x86/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "x86/op_iput.S" { "helper":"MterpIPutU8" }
diff --git a/runtime/interpreter/mterp/x86/op_iput_byte.S b/runtime/interpreter/mterp/x86/op_iput_byte.S
index 11cab88..b81850c 100644
--- a/runtime/interpreter/mterp/x86/op_iput_byte.S
+++ b/runtime/interpreter/mterp/x86/op_iput_byte.S
@@ -1 +1 @@
-%include "x86/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "x86/op_iput.S" { "helper":"MterpIPutI8" }
diff --git a/runtime/interpreter/mterp/x86/op_iput_char.S b/runtime/interpreter/mterp/x86/op_iput_char.S
index abbf2bd..dde3853 100644
--- a/runtime/interpreter/mterp/x86/op_iput_char.S
+++ b/runtime/interpreter/mterp/x86/op_iput_char.S
@@ -1 +1 @@
-%include "x86/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "x86/op_iput.S" { "helper":"MterpIPutU16" }
diff --git a/runtime/interpreter/mterp/x86/op_iput_object.S b/runtime/interpreter/mterp/x86/op_iput_object.S
index e013697..56e026e 100644
--- a/runtime/interpreter/mterp/x86/op_iput_object.S
+++ b/runtime/interpreter/mterp/x86/op_iput_object.S
@@ -6,7 +6,7 @@
     movl    rINST, OUT_ARG2(%esp)
     movl    rSELF, %eax
     movl    %eax, OUT_ARG3(%esp)
-    call    SYMBOL(MterpIputObject)
+    call    SYMBOL(MterpIPutObj)
     testb   %al, %al
     jz      MterpException
     RESTORE_IBASE
diff --git a/runtime/interpreter/mterp/x86/op_iput_short.S b/runtime/interpreter/mterp/x86/op_iput_short.S
index abbf2bd..130e875 100644
--- a/runtime/interpreter/mterp/x86/op_iput_short.S
+++ b/runtime/interpreter/mterp/x86/op_iput_short.S
@@ -1 +1 @@
-%include "x86/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "x86/op_iput.S" { "helper":"MterpIPutI16" }
diff --git a/runtime/interpreter/mterp/x86/op_iput_wide.S b/runtime/interpreter/mterp/x86/op_iput_wide.S
index 122eecf..ea22b91 100644
--- a/runtime/interpreter/mterp/x86/op_iput_wide.S
+++ b/runtime/interpreter/mterp/x86/op_iput_wide.S
@@ -1,5 +1,5 @@
     /* iput-wide vA, vB, field@CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     movzwl  2(rPC), %eax                    # eax <- 0000CCCC
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -12,7 +12,7 @@
     movl    %eax, OUT_ARG2(%esp)            # &fp[A]
     movl    OFF_FP_METHOD(rFP), %eax
     movl    %eax, OUT_ARG3(%esp)            # referrer
-    call    SYMBOL(artSet64InstanceFromMterp)
+    call    SYMBOL(MterpIPutU64)
     testb   %al, %al
     jnz     MterpPossibleException
     RESTORE_IBASE
diff --git a/runtime/interpreter/mterp/x86/op_sget.S b/runtime/interpreter/mterp/x86/op_sget.S
index 6e42d32..66c7b0b 100644
--- a/runtime/interpreter/mterp/x86/op_sget.S
+++ b/runtime/interpreter/mterp/x86/op_sget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"MterpGet32Static" }
+%default { "is_object":"0", "helper":"MterpSGetU32" }
 /*
  * General SGET handler wrapper.
  *
diff --git a/runtime/interpreter/mterp/x86/op_sget_boolean.S b/runtime/interpreter/mterp/x86/op_sget_boolean.S
index 5fa2bf0..3936eea 100644
--- a/runtime/interpreter/mterp/x86/op_sget_boolean.S
+++ b/runtime/interpreter/mterp/x86/op_sget_boolean.S
@@ -1 +1 @@
-%include "x86/op_sget.S" {"helper":"MterpGetBooleanStatic"}
+%include "x86/op_sget.S" {"helper":"MterpSGetU8"}
diff --git a/runtime/interpreter/mterp/x86/op_sget_byte.S b/runtime/interpreter/mterp/x86/op_sget_byte.S
index ef812f1..967586d 100644
--- a/runtime/interpreter/mterp/x86/op_sget_byte.S
+++ b/runtime/interpreter/mterp/x86/op_sget_byte.S
@@ -1 +1 @@
-%include "x86/op_sget.S" {"helper":"MterpGetByteStatic"}
+%include "x86/op_sget.S" {"helper":"MterpSGetI8"}
diff --git a/runtime/interpreter/mterp/x86/op_sget_char.S b/runtime/interpreter/mterp/x86/op_sget_char.S
index 3bc34ef..b706f18 100644
--- a/runtime/interpreter/mterp/x86/op_sget_char.S
+++ b/runtime/interpreter/mterp/x86/op_sget_char.S
@@ -1 +1 @@
-%include "x86/op_sget.S" {"helper":"MterpGetCharStatic"}
+%include "x86/op_sget.S" {"helper":"MterpSGetU16"}
diff --git a/runtime/interpreter/mterp/x86/op_sget_object.S b/runtime/interpreter/mterp/x86/op_sget_object.S
index b829e75..eac8836 100644
--- a/runtime/interpreter/mterp/x86/op_sget_object.S
+++ b/runtime/interpreter/mterp/x86/op_sget_object.S
@@ -1 +1 @@
-%include "x86/op_sget.S" {"is_object":"1", "helper":"MterpGetObjStatic"}
+%include "x86/op_sget.S" {"is_object":"1", "helper":"MterpSGetObj"}
diff --git a/runtime/interpreter/mterp/x86/op_sget_short.S b/runtime/interpreter/mterp/x86/op_sget_short.S
index 449cf6f..ee058a6 100644
--- a/runtime/interpreter/mterp/x86/op_sget_short.S
+++ b/runtime/interpreter/mterp/x86/op_sget_short.S
@@ -1 +1 @@
-%include "x86/op_sget.S" {"helper":"MterpGetShortStatic"}
+%include "x86/op_sget.S" {"helper":"MterpSGetI16"}
diff --git a/runtime/interpreter/mterp/x86/op_sget_wide.S b/runtime/interpreter/mterp/x86/op_sget_wide.S
index a605bcf..994cc3a 100644
--- a/runtime/interpreter/mterp/x86/op_sget_wide.S
+++ b/runtime/interpreter/mterp/x86/op_sget_wide.S
@@ -3,7 +3,7 @@
  *
  */
     /* sget-wide vAA, field@BBBB */
-    .extern MterpGet64Static
+    .extern MterpSGetU64
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref CCCC
@@ -11,7 +11,7 @@
     movl    %eax, OUT_ARG1(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG2(%esp)            # self
-    call    SYMBOL(MterpGet64Static)
+    call    SYMBOL(MterpSGetU64)
     movl    rSELF, %ecx
     cmpl    $$0, THREAD_EXCEPTION_OFFSET(%ecx)
     jnz     MterpException
diff --git a/runtime/interpreter/mterp/x86/op_sput.S b/runtime/interpreter/mterp/x86/op_sput.S
index 99f6088..e99e7a7 100644
--- a/runtime/interpreter/mterp/x86/op_sput.S
+++ b/runtime/interpreter/mterp/x86/op_sput.S
@@ -1,4 +1,4 @@
-%default { "helper":"MterpSet32Static"}
+%default { "helper":"MterpSPutU32"}
 /*
  * General SPUT handler wrapper.
  *
diff --git a/runtime/interpreter/mterp/x86/op_sput_boolean.S b/runtime/interpreter/mterp/x86/op_sput_boolean.S
index a7fffda..c6aa7c4 100644
--- a/runtime/interpreter/mterp/x86/op_sput_boolean.S
+++ b/runtime/interpreter/mterp/x86/op_sput_boolean.S
@@ -1 +1 @@
-%include "x86/op_sput.S" {"helper":"MterpSetBooleanStatic"}
+%include "x86/op_sput.S" {"helper":"MterpSPutU8"}
diff --git a/runtime/interpreter/mterp/x86/op_sput_byte.S b/runtime/interpreter/mterp/x86/op_sput_byte.S
index 3a5ff92..fd504a8 100644
--- a/runtime/interpreter/mterp/x86/op_sput_byte.S
+++ b/runtime/interpreter/mterp/x86/op_sput_byte.S
@@ -1 +1 @@
-%include "x86/op_sput.S" {"helper":"MterpSetByteStatic"}
+%include "x86/op_sput.S" {"helper":"MterpSPutI8"}
diff --git a/runtime/interpreter/mterp/x86/op_sput_char.S b/runtime/interpreter/mterp/x86/op_sput_char.S
index 565cc2a..b4d0997 100644
--- a/runtime/interpreter/mterp/x86/op_sput_char.S
+++ b/runtime/interpreter/mterp/x86/op_sput_char.S
@@ -1 +1 @@
-%include "x86/op_sput.S" {"helper":"MterpSetCharStatic"}
+%include "x86/op_sput.S" {"helper":"MterpSPutU16"}
diff --git a/runtime/interpreter/mterp/x86/op_sput_object.S b/runtime/interpreter/mterp/x86/op_sput_object.S
index 0db5177..941b072 100644
--- a/runtime/interpreter/mterp/x86/op_sput_object.S
+++ b/runtime/interpreter/mterp/x86/op_sput_object.S
@@ -6,7 +6,7 @@
     movl    rINST, OUT_ARG2(%esp)
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)
-    call    SYMBOL(MterpSputObject)
+    call    SYMBOL(MterpSPutObj)
     testb   %al, %al
     jz      MterpException
     RESTORE_IBASE
diff --git a/runtime/interpreter/mterp/x86/op_sput_short.S b/runtime/interpreter/mterp/x86/op_sput_short.S
index 85c3441..eba01bd 100644
--- a/runtime/interpreter/mterp/x86/op_sput_short.S
+++ b/runtime/interpreter/mterp/x86/op_sput_short.S
@@ -1 +1 @@
-%include "x86/op_sput.S" {"helper":"MterpSetShortStatic"}
+%include "x86/op_sput.S" {"helper":"MterpSPutI16"}
diff --git a/runtime/interpreter/mterp/x86/op_sput_wide.S b/runtime/interpreter/mterp/x86/op_sput_wide.S
index 8cc7e28..f581507 100644
--- a/runtime/interpreter/mterp/x86/op_sput_wide.S
+++ b/runtime/interpreter/mterp/x86/op_sput_wide.S
@@ -3,7 +3,7 @@
  *
  */
     /* sput-wide vAA, field@BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     movzwl  2(rPC), %eax
     movl    %eax, OUT_ARG0(%esp)            # field ref BBBB
@@ -13,7 +13,7 @@
     movl    %eax, OUT_ARG2(%esp)            # referrer
     movl    rSELF, %ecx
     movl    %ecx, OUT_ARG3(%esp)            # self
-    call    SYMBOL(MterpSet64Static)
+    call    SYMBOL(MterpSPutU64)
     testb   %al, %al
     jnz     MterpException
     RESTORE_IBASE
diff --git a/runtime/interpreter/mterp/x86_64/op_iget.S b/runtime/interpreter/mterp/x86_64/op_iget.S
index df43efe..5c6cab6 100644
--- a/runtime/interpreter/mterp/x86_64/op_iget.S
+++ b/runtime/interpreter/mterp/x86_64/op_iget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"artGet32InstanceFromCode", "wide":"0"}
+%default { "is_object":"0", "helper":"MterpIGetU32", "wide":"0"}
 /*
  * General instance field get.
  *
diff --git a/runtime/interpreter/mterp/x86_64/op_iget_boolean.S b/runtime/interpreter/mterp/x86_64/op_iget_boolean.S
index 6ac5523..18e9264 100644
--- a/runtime/interpreter/mterp/x86_64/op_iget_boolean.S
+++ b/runtime/interpreter/mterp/x86_64/op_iget_boolean.S
@@ -1 +1 @@
-%include "x86_64/op_iget.S" { "helper":"artGetBooleanInstanceFromCode" }
+%include "x86_64/op_iget.S" { "helper":"MterpIGetU8" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iget_byte.S b/runtime/interpreter/mterp/x86_64/op_iget_byte.S
index 6a861b1..bec0ad5 100644
--- a/runtime/interpreter/mterp/x86_64/op_iget_byte.S
+++ b/runtime/interpreter/mterp/x86_64/op_iget_byte.S
@@ -1 +1 @@
-%include "x86_64/op_iget.S" { "helper":"artGetByteInstanceFromCode" }
+%include "x86_64/op_iget.S" { "helper":"MterpIGetI8" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iget_char.S b/runtime/interpreter/mterp/x86_64/op_iget_char.S
index 021a0f1..5e22b88 100644
--- a/runtime/interpreter/mterp/x86_64/op_iget_char.S
+++ b/runtime/interpreter/mterp/x86_64/op_iget_char.S
@@ -1 +1 @@
-%include "x86_64/op_iget.S" { "helper":"artGetCharInstanceFromCode" }
+%include "x86_64/op_iget.S" { "helper":"MterpIGetU16" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iget_object.S b/runtime/interpreter/mterp/x86_64/op_iget_object.S
index d92bc9c..bcef1d2 100644
--- a/runtime/interpreter/mterp/x86_64/op_iget_object.S
+++ b/runtime/interpreter/mterp/x86_64/op_iget_object.S
@@ -1 +1 @@
-%include "x86_64/op_iget.S" { "is_object":"1", "helper":"artGetObjInstanceFromCode" }
+%include "x86_64/op_iget.S" { "is_object":"1", "helper":"MterpIGetObj" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iget_short.S b/runtime/interpreter/mterp/x86_64/op_iget_short.S
index f158bea..14c49f7 100644
--- a/runtime/interpreter/mterp/x86_64/op_iget_short.S
+++ b/runtime/interpreter/mterp/x86_64/op_iget_short.S
@@ -1 +1 @@
-%include "x86_64/op_iget.S" { "helper":"artGetShortInstanceFromCode" }
+%include "x86_64/op_iget.S" { "helper":"MterpIGetI16" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iget_wide.S b/runtime/interpreter/mterp/x86_64/op_iget_wide.S
index 74bb9ff..d9d1744 100644
--- a/runtime/interpreter/mterp/x86_64/op_iget_wide.S
+++ b/runtime/interpreter/mterp/x86_64/op_iget_wide.S
@@ -1 +1 @@
-%include "x86_64/op_iget.S" { "helper":"artGet64InstanceFromCode", "wide":"1" }
+%include "x86_64/op_iget.S" { "helper":"MterpIGetU64", "wide":"1" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iput.S b/runtime/interpreter/mterp/x86_64/op_iput.S
index 6b7cb1c..12affdb 100644
--- a/runtime/interpreter/mterp/x86_64/op_iput.S
+++ b/runtime/interpreter/mterp/x86_64/op_iput.S
@@ -1,11 +1,11 @@
-%default { "handler":"artSet32InstanceFromMterp"}
+%default { "helper":"MterpIPutU32"}
 /*
  * General 32-bit instance field put.
  *
  * for: iput, iput-object, iput-boolean, iput-byte, iput-char, iput-short
  */
     /* op vA, vB, field@CCCC */
-    .extern $handler
+    .extern $helper
     EXPORT_PC
     movzwl  2(rPC), OUT_32_ARG0             # field ref <- 0000CCCC
     movzbq  rINSTbl, %rcx                   # rcx<- BA
@@ -14,7 +14,7 @@
     andb    $$0xf, rINSTbl                  # rINST<- A
     GET_VREG OUT_32_ARG2, rINSTq            # fp[A]
     movq    OFF_FP_METHOD(rFP), OUT_ARG3    # referrer
-    call    SYMBOL($handler)
+    call    SYMBOL($helper)
     testb   %al, %al
     jnz     MterpPossibleException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
diff --git a/runtime/interpreter/mterp/x86_64/op_iput_boolean.S b/runtime/interpreter/mterp/x86_64/op_iput_boolean.S
index cb4b1cd..06bbd70 100644
--- a/runtime/interpreter/mterp/x86_64/op_iput_boolean.S
+++ b/runtime/interpreter/mterp/x86_64/op_iput_boolean.S
@@ -1 +1 @@
-%include "x86_64/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "x86_64/op_iput.S" { "helper":"MterpIPutU8" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iput_byte.S b/runtime/interpreter/mterp/x86_64/op_iput_byte.S
index cb4b1cd..53f9008 100644
--- a/runtime/interpreter/mterp/x86_64/op_iput_byte.S
+++ b/runtime/interpreter/mterp/x86_64/op_iput_byte.S
@@ -1 +1 @@
-%include "x86_64/op_iput.S" { "handler":"artSet8InstanceFromMterp" }
+%include "x86_64/op_iput.S" { "helper":"MterpIPutI8" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iput_char.S b/runtime/interpreter/mterp/x86_64/op_iput_char.S
index b4e147c..4736f5e 100644
--- a/runtime/interpreter/mterp/x86_64/op_iput_char.S
+++ b/runtime/interpreter/mterp/x86_64/op_iput_char.S
@@ -1 +1 @@
-%include "x86_64/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "x86_64/op_iput.S" { "helper":"MterpIPutU16" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iput_object.S b/runtime/interpreter/mterp/x86_64/op_iput_object.S
index 828712d..22648cd 100644
--- a/runtime/interpreter/mterp/x86_64/op_iput_object.S
+++ b/runtime/interpreter/mterp/x86_64/op_iput_object.S
@@ -4,7 +4,7 @@
     REFRESH_INST ${opnum}
     movl    rINST, OUT_32_ARG2
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(MterpIputObject)
+    call    SYMBOL(MterpIPutObj)
     testb   %al, %al
     jz      MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
diff --git a/runtime/interpreter/mterp/x86_64/op_iput_short.S b/runtime/interpreter/mterp/x86_64/op_iput_short.S
index b4e147c..dca5735 100644
--- a/runtime/interpreter/mterp/x86_64/op_iput_short.S
+++ b/runtime/interpreter/mterp/x86_64/op_iput_short.S
@@ -1 +1 @@
-%include "x86_64/op_iput.S" { "handler":"artSet16InstanceFromMterp" }
+%include "x86_64/op_iput.S" { "helper":"MterpIPutI16" }
diff --git a/runtime/interpreter/mterp/x86_64/op_iput_wide.S b/runtime/interpreter/mterp/x86_64/op_iput_wide.S
index e59717b..4f8c47c 100644
--- a/runtime/interpreter/mterp/x86_64/op_iput_wide.S
+++ b/runtime/interpreter/mterp/x86_64/op_iput_wide.S
@@ -1,5 +1,5 @@
     /* iput-wide vA, vB, field@CCCC */
-    .extern artSet64InstanceFromMterp
+    .extern MterpIPutU64
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref CCCC
     movzbq  rINSTbl, %rcx                   # rcx <- BA
@@ -8,7 +8,7 @@
     andb    $$0xf, rINSTbl                  # rINST <- A
     leaq    VREG_ADDRESS(rINSTq), OUT_ARG2  # &fp[A]
     movq    OFF_FP_METHOD(rFP), OUT_ARG3    # referrer
-    call    SYMBOL(artSet64InstanceFromMterp)
+    call    SYMBOL(MterpIPutU64)
     testb   %al, %al
     jnz     MterpPossibleException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
diff --git a/runtime/interpreter/mterp/x86_64/op_sget.S b/runtime/interpreter/mterp/x86_64/op_sget.S
index e996c77..c15ac1e 100644
--- a/runtime/interpreter/mterp/x86_64/op_sget.S
+++ b/runtime/interpreter/mterp/x86_64/op_sget.S
@@ -1,4 +1,4 @@
-%default { "is_object":"0", "helper":"MterpGet32Static", "wide":"0" }
+%default { "is_object":"0", "helper":"MterpSGetU32", "wide":"0" }
 /*
  * General SGET handler wrapper.
  *
diff --git a/runtime/interpreter/mterp/x86_64/op_sget_boolean.S b/runtime/interpreter/mterp/x86_64/op_sget_boolean.S
index ee772ad..e5a4e41 100644
--- a/runtime/interpreter/mterp/x86_64/op_sget_boolean.S
+++ b/runtime/interpreter/mterp/x86_64/op_sget_boolean.S
@@ -1 +1 @@
-%include "x86_64/op_sget.S" {"helper":"MterpGetBooleanStatic"}
+%include "x86_64/op_sget.S" {"helper":"MterpSGetU8"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sget_byte.S b/runtime/interpreter/mterp/x86_64/op_sget_byte.S
index f65ea49..4602f7d 100644
--- a/runtime/interpreter/mterp/x86_64/op_sget_byte.S
+++ b/runtime/interpreter/mterp/x86_64/op_sget_byte.S
@@ -1 +1 @@
-%include "x86_64/op_sget.S" {"helper":"MterpGetByteStatic"}
+%include "x86_64/op_sget.S" {"helper":"MterpSGetI8"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sget_char.S b/runtime/interpreter/mterp/x86_64/op_sget_char.S
index 3972551..a094a54 100644
--- a/runtime/interpreter/mterp/x86_64/op_sget_char.S
+++ b/runtime/interpreter/mterp/x86_64/op_sget_char.S
@@ -1 +1 @@
-%include "x86_64/op_sget.S" {"helper":"MterpGetCharStatic"}
+%include "x86_64/op_sget.S" {"helper":"MterpSGetU16"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sget_object.S b/runtime/interpreter/mterp/x86_64/op_sget_object.S
index a0bbfd8..94597b1 100644
--- a/runtime/interpreter/mterp/x86_64/op_sget_object.S
+++ b/runtime/interpreter/mterp/x86_64/op_sget_object.S
@@ -1 +1 @@
-%include "x86_64/op_sget.S" {"is_object":"1", "helper":"MterpGetObjStatic"}
+%include "x86_64/op_sget.S" {"is_object":"1", "helper":"MterpSGetObj"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sget_short.S b/runtime/interpreter/mterp/x86_64/op_sget_short.S
index df212dc..dee5c24 100644
--- a/runtime/interpreter/mterp/x86_64/op_sget_short.S
+++ b/runtime/interpreter/mterp/x86_64/op_sget_short.S
@@ -1 +1 @@
-%include "x86_64/op_sget.S" {"helper":"MterpGetShortStatic"}
+%include "x86_64/op_sget.S" {"helper":"MterpSGetI16"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sget_wide.S b/runtime/interpreter/mterp/x86_64/op_sget_wide.S
index 1e98e28..65ddb8a 100644
--- a/runtime/interpreter/mterp/x86_64/op_sget_wide.S
+++ b/runtime/interpreter/mterp/x86_64/op_sget_wide.S
@@ -1 +1 @@
-%include "x86_64/op_sget.S" {"helper":"MterpGet64Static", "wide":"1"}
+%include "x86_64/op_sget.S" {"helper":"MterpSGetU64", "wide":"1"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sput.S b/runtime/interpreter/mterp/x86_64/op_sput.S
index 9705619..9a33d52 100644
--- a/runtime/interpreter/mterp/x86_64/op_sput.S
+++ b/runtime/interpreter/mterp/x86_64/op_sput.S
@@ -1,4 +1,4 @@
-%default { "helper":"MterpSet32Static"}
+%default { "helper":"MterpSPutU32"}
 /*
  * General SPUT handler wrapper.
  *
diff --git a/runtime/interpreter/mterp/x86_64/op_sput_boolean.S b/runtime/interpreter/mterp/x86_64/op_sput_boolean.S
index 8bf4a62..ea9acbf 100644
--- a/runtime/interpreter/mterp/x86_64/op_sput_boolean.S
+++ b/runtime/interpreter/mterp/x86_64/op_sput_boolean.S
@@ -1 +1 @@
-%include "x86_64/op_sput.S" {"helper":"MterpSetBooleanStatic"}
+%include "x86_64/op_sput.S" {"helper":"MterpSPutU8"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sput_byte.S b/runtime/interpreter/mterp/x86_64/op_sput_byte.S
index 5bb26eb..62c9e20 100644
--- a/runtime/interpreter/mterp/x86_64/op_sput_byte.S
+++ b/runtime/interpreter/mterp/x86_64/op_sput_byte.S
@@ -1 +1 @@
-%include "x86_64/op_sput.S" {"helper":"MterpSetByteStatic"}
+%include "x86_64/op_sput.S" {"helper":"MterpSPutI8"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sput_char.S b/runtime/interpreter/mterp/x86_64/op_sput_char.S
index 42b244e..ab0196e 100644
--- a/runtime/interpreter/mterp/x86_64/op_sput_char.S
+++ b/runtime/interpreter/mterp/x86_64/op_sput_char.S
@@ -1 +1 @@
-%include "x86_64/op_sput.S" {"helper":"MterpSetCharStatic"}
+%include "x86_64/op_sput.S" {"helper":"MterpSPutU16"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sput_object.S b/runtime/interpreter/mterp/x86_64/op_sput_object.S
index eb5a376..8a47074 100644
--- a/runtime/interpreter/mterp/x86_64/op_sput_object.S
+++ b/runtime/interpreter/mterp/x86_64/op_sput_object.S
@@ -4,7 +4,7 @@
     REFRESH_INST ${opnum}
     movq    rINSTq, OUT_ARG2
     movq    rSELF, OUT_ARG3
-    call    SYMBOL(MterpSputObject)
+    call    SYMBOL(MterpSPutObj)
     testb   %al, %al
     jz      MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
diff --git a/runtime/interpreter/mterp/x86_64/op_sput_short.S b/runtime/interpreter/mterp/x86_64/op_sput_short.S
index 9670092..f73a3fc 100644
--- a/runtime/interpreter/mterp/x86_64/op_sput_short.S
+++ b/runtime/interpreter/mterp/x86_64/op_sput_short.S
@@ -1 +1 @@
-%include "x86_64/op_sput.S" {"helper":"MterpSetShortStatic"}
+%include "x86_64/op_sput.S" {"helper":"MterpSPutI16"}
diff --git a/runtime/interpreter/mterp/x86_64/op_sput_wide.S b/runtime/interpreter/mterp/x86_64/op_sput_wide.S
index a21bcb5..464d169 100644
--- a/runtime/interpreter/mterp/x86_64/op_sput_wide.S
+++ b/runtime/interpreter/mterp/x86_64/op_sput_wide.S
@@ -3,13 +3,13 @@
  *
  */
     /* sput-wide vAA, field@BBBB */
-    .extern MterpSet64Static
+    .extern MterpSPutU64
     EXPORT_PC
     movzwq  2(rPC), OUT_ARG0                # field ref BBBB
     leaq    VREG_ADDRESS(rINSTq), OUT_ARG1  # &fp[AA]
     movq    OFF_FP_METHOD(rFP), OUT_ARG2    # referrer
     movq    rSELF, OUT_ARG3                 # self
-    call    SYMBOL(MterpSet64Static)
+    call    SYMBOL(MterpSPutU64)
     testb   %al, %al
     jnz     MterpException
     ADVANCE_PC_FETCH_AND_GOTO_NEXT 2
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index feabc94..d4b51af 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -517,24 +517,23 @@
   result->SetZ(class_name == nullptr);
 }
 
-static std::unique_ptr<MemMap> FindAndExtractEntry(const std::string& jar_file,
-                                                   const char* entry_name,
-                                                   size_t* size,
-                                                   std::string* error_msg) {
+static MemMap FindAndExtractEntry(const std::string& jar_file,
+                                  const char* entry_name,
+                                  size_t* size,
+                                  std::string* error_msg) {
   CHECK(size != nullptr);
 
   std::unique_ptr<ZipArchive> zip_archive(ZipArchive::Open(jar_file.c_str(), error_msg));
   if (zip_archive == nullptr) {
-    return nullptr;
+    return MemMap::Invalid();
   }
   std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(entry_name, error_msg));
   if (zip_entry == nullptr) {
-    return nullptr;
+    return MemMap::Invalid();
   }
-  std::unique_ptr<MemMap> tmp_map(
-      zip_entry->ExtractToMemMap(jar_file.c_str(), entry_name, error_msg));
-  if (tmp_map == nullptr) {
-    return nullptr;
+  MemMap tmp_map = zip_entry->ExtractToMemMap(jar_file.c_str(), entry_name, error_msg);
+  if (!tmp_map.IsValid()) {
+    return MemMap::Invalid();
   }
 
   // OK, from here everything seems fine.
@@ -577,18 +576,18 @@
     return;
   }
 
-  std::unique_ptr<MemMap> mem_map;
+  MemMap mem_map;
   size_t map_size;
   std::string last_error_msg;  // Only store the last message (we could concatenate).
 
   for (const std::string& jar_file : split) {
     mem_map = FindAndExtractEntry(jar_file, resource_cstr, &map_size, &last_error_msg);
-    if (mem_map != nullptr) {
+    if (mem_map.IsValid()) {
       break;
     }
   }
 
-  if (mem_map == nullptr) {
+  if (!mem_map.IsValid()) {
     // Didn't find it. There's a good chance this will be the same at runtime, but still
     // conservatively abort the transaction here.
     AbortTransactionOrFail(self,
@@ -607,9 +606,9 @@
     return;
   }
   // Copy in content.
-  memcpy(h_array->GetData(), mem_map->Begin(), map_size);
+  memcpy(h_array->GetData(), mem_map.Begin(), map_size);
   // Be proactive releasing memory.
-  mem_map.reset();
+  mem_map.Reset();
 
   // Create a ByteArrayInputStream.
   Handle<mirror::Class> h_class(hs.NewHandle(
@@ -1969,7 +1968,7 @@
   const auto& iter = invoke_handlers_.find(name);
   if (iter != invoke_handlers_.end()) {
     // Clear out the result in case it's not zeroed out.
-    result->SetL(0);
+    result->SetL(nullptr);
 
     // Push the shadow frame. This is so the failing method can be seen in abort dumps.
     self->PushShadowFrame(shadow_frame);
@@ -1990,7 +1989,7 @@
   const auto& iter = jni_handlers_.find(name);
   if (iter != jni_handlers_.end()) {
     // Clear out the result in case it's not zeroed out.
-    result->SetL(0);
+    result->SetL(nullptr);
     (*iter->second)(self, method, receiver, args, result);
   } else if (Runtime::Current()->IsActiveTransaction()) {
     AbortTransactionF(self, "Attempt to invoke native method in non-started runtime: %s",
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
index 9409b76..0353ea7 100644
--- a/runtime/jdwp/jdwp_event.cc
+++ b/runtime/jdwp/jdwp_event.cc
@@ -1159,7 +1159,7 @@
   }
   basket.className = Dbg::GetClassName(basket.locationClass.Get());
   basket.exceptionClass.Assign(exception_object->GetClass());
-  basket.caught = (pCatchLoc->method != 0);
+  basket.caught = (pCatchLoc->method != nullptr);
   basket.thisPtr.Assign(thisPtr);
 
   /* don't try to post an exception caused by the debugger */
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index b92affa..d9c7900 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -205,15 +205,16 @@
   // We could do PC-relative addressing to avoid this problem, but that
   // would require reserving code and data area before submitting, which
   // means more windows for the code memory to be RWX.
-  std::unique_ptr<MemMap> data_map(MemMap::MapAnonymous(
-      "data-code-cache", nullptr,
+  MemMap data_map = MemMap::MapAnonymous(
+      "data-code-cache",
+      /* addr */ nullptr,
       max_capacity,
       kProtData,
       /* low_4gb */ true,
       /* reuse */ false,
       &error_str,
-      use_ashmem));
-  if (data_map == nullptr) {
+      use_ashmem);
+  if (!data_map.IsValid()) {
     std::ostringstream oss;
     oss << "Failed to create read write cache: " << error_str << " size=" << max_capacity;
     *error_msg = oss.str();
@@ -229,26 +230,23 @@
   size_t data_size = max_capacity / 2;
   size_t code_size = max_capacity - data_size;
   DCHECK_EQ(code_size + data_size, max_capacity);
-  uint8_t* divider = data_map->Begin() + data_size;
+  uint8_t* divider = data_map.Begin() + data_size;
 
-  MemMap* code_map = data_map->RemapAtEnd(
-      divider,
-      "jit-code-cache",
-      memmap_flags_prot_code | PROT_WRITE,
-      &error_str, use_ashmem);
-  if (code_map == nullptr) {
+  MemMap code_map = data_map.RemapAtEnd(
+      divider, "jit-code-cache", memmap_flags_prot_code | PROT_WRITE, &error_str, use_ashmem);
+  if (!code_map.IsValid()) {
     std::ostringstream oss;
     oss << "Failed to create read write execute cache: " << error_str << " size=" << max_capacity;
     *error_msg = oss.str();
     return nullptr;
   }
-  DCHECK_EQ(code_map->Begin(), divider);
+  DCHECK_EQ(code_map.Begin(), divider);
   data_size = initial_capacity / 2;
   code_size = initial_capacity - data_size;
   DCHECK_EQ(code_size + data_size, initial_capacity);
   return new JitCodeCache(
-      code_map,
-      data_map.release(),
+      std::move(code_map),
+      std::move(data_map),
       code_size,
       data_size,
       max_capacity,
@@ -256,8 +254,8 @@
       memmap_flags_prot_code);
 }
 
-JitCodeCache::JitCodeCache(MemMap* code_map,
-                           MemMap* data_map,
+JitCodeCache::JitCodeCache(MemMap&& code_map,
+                           MemMap&& data_map,
                            size_t initial_code_capacity,
                            size_t initial_data_capacity,
                            size_t max_capacity,
@@ -266,8 +264,8 @@
     : lock_("Jit code cache", kJitCodeCacheLock),
       lock_cond_("Jit code cache condition variable", lock_),
       collection_in_progress_(false),
-      code_map_(code_map),
-      data_map_(data_map),
+      code_map_(std::move(code_map)),
+      data_map_(std::move(data_map)),
       max_capacity_(max_capacity),
       current_capacity_(initial_code_capacity + initial_data_capacity),
       code_end_(initial_code_capacity),
@@ -287,8 +285,8 @@
       memmap_flags_prot_code_(memmap_flags_prot_code) {
 
   DCHECK_GE(max_capacity, initial_code_capacity + initial_data_capacity);
-  code_mspace_ = create_mspace_with_base(code_map_->Begin(), code_end_, false /*locked*/);
-  data_mspace_ = create_mspace_with_base(data_map_->Begin(), data_end_, false /*locked*/);
+  code_mspace_ = create_mspace_with_base(code_map_.Begin(), code_end_, false /*locked*/);
+  data_mspace_ = create_mspace_with_base(data_map_.Begin(), data_end_, false /*locked*/);
 
   if (code_mspace_ == nullptr || data_mspace_ == nullptr) {
     PLOG(FATAL) << "create_mspace_with_base failed";
@@ -298,13 +296,13 @@
 
   CheckedCall(mprotect,
               "mprotect jit code cache",
-              code_map_->Begin(),
-              code_map_->Size(),
+              code_map_.Begin(),
+              code_map_.Size(),
               memmap_flags_prot_code_);
   CheckedCall(mprotect,
               "mprotect jit data cache",
-              data_map_->Begin(),
-              data_map_->Size(),
+              data_map_.Begin(),
+              data_map_.Size(),
               kProtData);
 
   VLOG(jit) << "Created jit code cache: initial data size="
@@ -316,7 +314,7 @@
 JitCodeCache::~JitCodeCache() {}
 
 bool JitCodeCache::ContainsPc(const void* ptr) const {
-  return code_map_->Begin() <= ptr && ptr < code_map_->End();
+  return code_map_.Begin() <= ptr && ptr < code_map_.End();
 }
 
 bool JitCodeCache::WillExecuteJitCode(ArtMethod* method) {
@@ -387,8 +385,8 @@
     CheckedCall(
         mprotect,
         "make code writable",
-        code_cache_->code_map_->Begin(),
-        code_cache_->code_map_->Size(),
+        code_cache_->code_map_.Begin(),
+        code_cache_->code_map_.Size(),
         code_cache_->memmap_flags_prot_code_ | PROT_WRITE);
   }
 
@@ -397,8 +395,8 @@
     CheckedCall(
         mprotect,
         "make code protected",
-        code_cache_->code_map_->Begin(),
-        code_cache_->code_map_->Size(),
+        code_cache_->code_map_.Begin(),
+        code_cache_->code_map_.Size(),
         code_cache_->memmap_flags_prot_code_);
   }
 
@@ -608,17 +606,17 @@
 
 void JitCodeCache::FreeAllMethodHeaders(
     const std::unordered_set<OatQuickMethodHeader*>& method_headers) {
-  {
-    MutexLock mu(Thread::Current(), *Locks::cha_lock_);
-    Runtime::Current()->GetClassLinker()->GetClassHierarchyAnalysis()
-        ->RemoveDependentsWithMethodHeaders(method_headers);
-  }
-
   // We need to remove entries in method_headers from CHA dependencies
   // first since once we do FreeCode() below, the memory can be reused
   // so it's possible for the same method_header to start representing
   // different compile code.
   MutexLock mu(Thread::Current(), lock_);
+  {
+    MutexLock mu2(Thread::Current(), *Locks::cha_lock_);
+    Runtime::Current()->GetClassLinker()->GetClassHierarchyAnalysis()
+        ->RemoveDependentsWithMethodHeaders(method_headers);
+  }
+
   ScopedCodeCacheWrite scc(this);
   for (const OatQuickMethodHeader* method_header : method_headers) {
     FreeCodeAndData(method_header->GetCode());
@@ -742,6 +740,18 @@
   method->SetCounter(std::min(jit_warmup_threshold - 1, 1));
 }
 
+void JitCodeCache::WaitForPotentialCollectionToCompleteRunnable(Thread* self) {
+  while (collection_in_progress_) {
+    lock_.Unlock(self);
+    {
+      ScopedThreadSuspension sts(self, kSuspended);
+      MutexLock mu(self, lock_);
+      WaitForPotentialCollectionToComplete(self);
+    }
+    lock_.Lock(self);
+  }
+}
+
 uint8_t* JitCodeCache::CommitCodeInternal(Thread* self,
                                           ArtMethod* method,
                                           uint8_t* stack_map,
@@ -755,6 +765,13 @@
                                           const ArenaSet<ArtMethod*>&
                                               cha_single_implementation_list) {
   DCHECK(!method->IsNative() || !osr);
+
+  if (!method->IsNative()) {
+    // We need to do this before grabbing the lock_ because it needs to be able to see the string
+    // InternTable. Native methods do not have roots.
+    DCheckRootsAreValid(roots);
+  }
+
   size_t alignment = GetInstructionSetAlignment(kRuntimeISA);
   // Ensure the header ends up at expected instruction alignment.
   size_t header_size = RoundUp(sizeof(OatQuickMethodHeader), alignment);
@@ -763,44 +780,45 @@
   OatQuickMethodHeader* method_header = nullptr;
   uint8_t* code_ptr = nullptr;
   uint8_t* memory = nullptr;
+  MutexLock mu(self, lock_);
+  // We need to make sure that there will be no jit-gcs going on and wait for any ongoing one to
+  // finish.
+  WaitForPotentialCollectionToCompleteRunnable(self);
   {
-    ScopedThreadSuspension sts(self, kSuspended);
-    MutexLock mu(self, lock_);
-    WaitForPotentialCollectionToComplete(self);
-    {
-      ScopedCodeCacheWrite scc(this);
-      memory = AllocateCode(total_size);
-      if (memory == nullptr) {
-        return nullptr;
-      }
-      code_ptr = memory + header_size;
+    ScopedCodeCacheWrite scc(this);
+    memory = AllocateCode(total_size);
+    if (memory == nullptr) {
+      return nullptr;
+    }
+    code_ptr = memory + header_size;
 
-      std::copy(code, code + code_size, code_ptr);
-      method_header = OatQuickMethodHeader::FromCodePointer(code_ptr);
-      new (method_header) OatQuickMethodHeader(
-          (stack_map != nullptr) ? code_ptr - stack_map : 0u,
-          code_size);
-      // Flush caches before we remove write permission because some ARMv8 Qualcomm kernels may
-      // trigger a segfault if a page fault occurs when requesting a cache maintenance operation.
-      // This is a kernel bug that we need to work around until affected devices (e.g. Nexus 5X and
-      // 6P) stop being supported or their kernels are fixed.
-      //
-      // For reference, this behavior is caused by this commit:
-      // https://android.googlesource.com/kernel/msm/+/3fbe6bc28a6b9939d0650f2f17eb5216c719950c
-      FlushInstructionCache(reinterpret_cast<char*>(code_ptr),
-                            reinterpret_cast<char*>(code_ptr + code_size));
-      DCHECK(!Runtime::Current()->IsAotCompiler());
-      if (has_should_deoptimize_flag) {
-        method_header->SetHasShouldDeoptimizeFlag();
-      }
+    std::copy(code, code + code_size, code_ptr);
+    method_header = OatQuickMethodHeader::FromCodePointer(code_ptr);
+    new (method_header) OatQuickMethodHeader(
+        (stack_map != nullptr) ? code_ptr - stack_map : 0u,
+        code_size);
+    // Flush caches before we remove write permission because some ARMv8 Qualcomm kernels may
+    // trigger a segfault if a page fault occurs when requesting a cache maintenance operation.
+    // This is a kernel bug that we need to work around until affected devices (e.g. Nexus 5X and
+    // 6P) stop being supported or their kernels are fixed.
+    //
+    // For reference, this behavior is caused by this commit:
+    // https://android.googlesource.com/kernel/msm/+/3fbe6bc28a6b9939d0650f2f17eb5216c719950c
+    FlushInstructionCache(reinterpret_cast<char*>(code_ptr),
+                          reinterpret_cast<char*>(code_ptr + code_size));
+    DCHECK(!Runtime::Current()->IsAotCompiler());
+    if (has_should_deoptimize_flag) {
+      method_header->SetHasShouldDeoptimizeFlag();
     }
 
     number_of_compilations_++;
   }
   // We need to update the entry point in the runnable state for the instrumentation.
   {
-    // Need cha_lock_ for checking all single-implementation flags and register
-    // dependencies.
+    // The following needs to be guarded by cha_lock_ also. Otherwise it's possible that the
+    // compiled code is considered invalidated by some class linking, but below we still make the
+    // compiled code valid for the method.  Need cha_lock_ for checking all single-implementation
+    // flags and register dependencies.
     MutexLock cha_mu(self, *Locks::cha_lock_);
     bool single_impl_still_valid = true;
     for (ArtMethod* single_impl : cha_single_implementation_list) {
@@ -826,16 +844,6 @@
           single_impl, method, method_header);
     }
 
-    if (!method->IsNative()) {
-      // We need to do this before grabbing the lock_ because it needs to be able to see the string
-      // InternTable. Native methods do not have roots.
-      DCheckRootsAreValid(roots);
-    }
-
-    // The following needs to be guarded by cha_lock_ also. Otherwise it's
-    // possible that the compiled code is considered invalidated by some class linking,
-    // but below we still make the compiled code valid for the method.
-    MutexLock mu(self, lock_);
     if (UNLIKELY(method->IsNative())) {
       auto it = jni_stubs_map_.find(JniStubKey(method));
       DCHECK(it != jni_stubs_map_.end())
@@ -867,11 +875,6 @@
             method, method_header->GetEntryPoint());
       }
     }
-    if (collection_in_progress_) {
-      // We need to update the live bitmap if there is a GC to ensure it sees this new
-      // code.
-      GetLiveBitmap()->AtomicTestAndSet(FromCodeToAllocation(code_ptr));
-    }
     VLOG(jit)
         << "JIT added (osr=" << std::boolalpha << osr << std::noboolalpha << ") "
         << ArtMethod::PrettyMethod(method) << "@" << method
@@ -1232,8 +1235,8 @@
       number_of_collections_++;
       live_bitmap_.reset(CodeCacheBitmap::Create(
           "code-cache-bitmap",
-          reinterpret_cast<uintptr_t>(code_map_->Begin()),
-          reinterpret_cast<uintptr_t>(code_map_->Begin() + current_capacity_ / 2)));
+          reinterpret_cast<uintptr_t>(code_map_.Begin()),
+          reinterpret_cast<uintptr_t>(code_map_.Begin() + current_capacity_ / 2)));
       collection_in_progress_ = true;
     }
   }
@@ -1605,12 +1608,12 @@
   if (code_mspace_ == mspace) {
     size_t result = code_end_;
     code_end_ += increment;
-    return reinterpret_cast<void*>(result + code_map_->Begin());
+    return reinterpret_cast<void*>(result + code_map_.Begin());
   } else {
     DCHECK_EQ(data_mspace_, mspace);
     size_t result = data_end_;
     data_end_ += increment;
-    return reinterpret_cast<void*>(result + data_map_->Begin());
+    return reinterpret_cast<void*>(result + data_map_.Begin());
   }
 }
 
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index 29f9c9c..a4a0f8f 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -28,6 +28,7 @@
 #include "base/atomic.h"
 #include "base/histogram.h"
 #include "base/macros.h"
+#include "base/mem_map.h"
 #include "base/mutex.h"
 #include "base/safe_map.h"
 
@@ -39,7 +40,6 @@
 class InlineCache;
 class IsMarkedVisitor;
 class JitJniStubTestHelper;
-class MemMap;
 class OatQuickMethodHeader;
 struct ProfileMethodInfo;
 class ProfilingInfo;
@@ -279,8 +279,8 @@
 
  private:
   // Take ownership of maps.
-  JitCodeCache(MemMap* code_map,
-               MemMap* data_map,
+  JitCodeCache(MemMap&& code_map,
+               MemMap&& data_map,
                size_t initial_code_capacity,
                size_t initial_data_capacity,
                size_t max_capacity,
@@ -314,6 +314,12 @@
       REQUIRES(lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // If a collection is in progress, wait for it to finish. Must be called with the mutator lock.
+  // The non-mutator lock version should be used if possible. This method will release then
+  // re-acquire the mutator lock.
+  void WaitForPotentialCollectionToCompleteRunnable(Thread* self)
+      REQUIRES(lock_, !Roles::uninterruptible_) REQUIRES_SHARED(Locks::mutator_lock_);
+
   // If a collection is in progress, wait for it to finish. Return
   // whether the thread actually waited.
   bool WaitForPotentialCollectionToComplete(Thread* self)
@@ -390,9 +396,9 @@
   // Whether there is a code cache collection in progress.
   bool collection_in_progress_ GUARDED_BY(lock_);
   // Mem map which holds code.
-  std::unique_ptr<MemMap> code_map_;
+  MemMap code_map_;
   // Mem map which holds data (stack maps and profiling info).
-  std::unique_ptr<MemMap> data_map_;
+  MemMap data_map_;
   // The opaque mspace for allocating code.
   void* code_mspace_ GUARDED_BY(lock_);
   // The opaque mspace for allocating data.
diff --git a/runtime/jni/check_jni.cc b/runtime/jni/check_jni.cc
index 66bd74b..c5e8830 100644
--- a/runtime/jni/check_jni.cc
+++ b/runtime/jni/check_jni.cc
@@ -1462,7 +1462,7 @@
         break;
       }
     }
-    return 0;
+    return nullptr;
   }
 
   void AbortF(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
diff --git a/runtime/lock_word.h b/runtime/lock_word.h
index ce7fe34..ac7890c 100644
--- a/runtime/lock_word.h
+++ b/runtime/lock_word.h
@@ -210,7 +210,7 @@
 
   void SetReadBarrierState(uint32_t rb_state) {
     DCHECK_EQ(rb_state & ~kReadBarrierStateMask, 0U);
-    DCHECK(rb_state == ReadBarrier::WhiteState() ||
+    DCHECK(rb_state == ReadBarrier::NonGrayState() ||
            rb_state == ReadBarrier::GrayState()) << rb_state;
     DCHECK_NE(static_cast<uint32_t>(GetState()), static_cast<uint32_t>(kForwardingAddress));
     // Clear and or the bits.
@@ -288,7 +288,7 @@
       if (!kUseReadBarrier) {
         DCHECK_EQ(rb_state, 0U);
       } else {
-        DCHECK(rb_state == ReadBarrier::WhiteState() ||
+        DCHECK(rb_state == ReadBarrier::NonGrayState() ||
                rb_state == ReadBarrier::GrayState()) << rb_state;
       }
     }
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index 01a32a2..570fc48 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -484,7 +484,7 @@
                                                       first_dest_reg,
                                                       new_shadow_frame)) {
           DCHECK(self->IsExceptionPending());
-          result->SetL(0);
+          result->SetL(nullptr);
           return false;
         }
       } else {
@@ -500,7 +500,7 @@
                                                     operands,
                                                     new_shadow_frame)) {
           DCHECK(self->IsExceptionPending());
-          result->SetL(0);
+          result->SetL(nullptr);
           return false;
         }
       }
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index bc72517..51dc1a4 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -32,12 +32,12 @@
 #include "dex_cache.h"
 #include "gc/heap-inl.h"
 #include "iftable.h"
-#include "subtype_check.h"
 #include "object-inl.h"
 #include "object_array.h"
 #include "read_barrier-inl.h"
 #include "runtime.h"
 #include "string.h"
+#include "subtype_check.h"
 
 namespace art {
 namespace mirror {
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index ab5fb85..87f4f0a 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -157,12 +157,12 @@
                 "String dex cache size is not a power of 2.");
 
   // Size of field dex cache. Needs to be a power of 2 for entrypoint assumptions to hold.
-  static constexpr size_t kDexCacheFieldCacheSize = 1024;
+  static constexpr size_t kDexCacheFieldCacheSize = 512;
   static_assert(IsPowerOfTwo(kDexCacheFieldCacheSize),
                 "Field dex cache size is not a power of 2.");
 
   // Size of method dex cache. Needs to be a power of 2 for entrypoint assumptions to hold.
-  static constexpr size_t kDexCacheMethodCacheSize = 1024;
+  static constexpr size_t kDexCacheMethodCacheSize = 512;
   static_assert(IsPowerOfTwo(kDexCacheMethodCacheSize),
                 "Method dex cache size is not a power of 2.");
 
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 47f0a29..bd89907 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -119,7 +119,7 @@
 inline void Object::AssertReadBarrierState() const {
   CHECK(kUseBakerReadBarrier);
   Object* obj = const_cast<Object*>(this);
-  DCHECK_EQ(obj->GetReadBarrierState(), ReadBarrier::WhiteState())
+  DCHECK_EQ(obj->GetReadBarrierState(), ReadBarrier::NonGrayState())
       << "Bad Baker pointer: obj=" << obj << " rb_state" << obj->GetReadBarrierState();
 }
 
diff --git a/runtime/mirror/object-readbarrier-inl.h b/runtime/mirror/object-readbarrier-inl.h
index df50d06..cc375bd 100644
--- a/runtime/mirror/object-readbarrier-inl.h
+++ b/runtime/mirror/object-readbarrier-inl.h
@@ -168,10 +168,11 @@
     expected_lw.SetReadBarrierState(expected_rb_state);
     new_lw = lw;
     new_lw.SetReadBarrierState(rb_state);
-    // ConcurrentCopying::ProcessMarkStackRef uses this with kCasRelease == true.
-    // If kCasRelease == true, use a CAS release so that when GC updates all the fields of
-    // an object and then changes the object from gray to black, the field updates (stores) will be
-    // visible (won't be reordered after this CAS.)
+    // ConcurrentCopying::ProcessMarkStackRef uses this with
+    // `kMemoryOrder` == `std::memory_order_release`.
+    // If `kMemoryOrder` == `std::memory_order_release`, use a CAS release so that when GC updates
+    // all the fields of an object and then changes the object from gray to black (non-gray), the
+    // field updates (stores) will be visible (won't be reordered after this CAS.)
   } while (!CasLockWord(expected_lw, new_lw, CASMode::kWeak, kMemoryOrder));
   return true;
 }
diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h
index 2801928..47aded3 100644
--- a/runtime/mirror/object.h
+++ b/runtime/mirror/object.h
@@ -117,7 +117,7 @@
   ALWAYS_INLINE bool AtomicSetMarkBit(uint32_t expected_mark_bit, uint32_t mark_bit)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Assert that the read barrier state is in the default (white) state.
+  // Assert that the read barrier state is in the default (white, i.e. non-gray) state.
   ALWAYS_INLINE void AssertReadBarrierState() const REQUIRES_SHARED(Locks::mutator_lock_);
 
   // The verifier treats all interfaces as java.lang.Object and relies on runtime checks in
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index cdba6b2..71fabd0 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -163,33 +163,33 @@
   void operator=(const NullableScopedUtfChars&);
 };
 
-static std::unique_ptr<MemMap> AllocateDexMemoryMap(JNIEnv* env, jint start, jint end) {
+static MemMap AllocateDexMemoryMap(JNIEnv* env, jint start, jint end) {
   if (end <= start) {
     ScopedObjectAccess soa(env);
     ThrowWrappedIOException("Bad range");
-    return nullptr;
+    return MemMap::Invalid();
   }
 
   std::string error_message;
   size_t length = static_cast<size_t>(end - start);
-  std::unique_ptr<MemMap> dex_mem_map(MemMap::MapAnonymous("DEX data",
-                                                           nullptr,
-                                                           length,
-                                                           PROT_READ | PROT_WRITE,
-                                                           /* low_4gb */ false,
-                                                           /* reuse */ false,
-                                                           &error_message));
-  if (dex_mem_map == nullptr) {
+  MemMap dex_mem_map = MemMap::MapAnonymous("DEX data",
+                                            /* addr */ nullptr,
+                                            length,
+                                            PROT_READ | PROT_WRITE,
+                                            /* low_4gb */ false,
+                                            &error_message);
+  if (!dex_mem_map.IsValid()) {
     ScopedObjectAccess soa(env);
     ThrowWrappedIOException("%s", error_message.c_str());
+    return MemMap::Invalid();
   }
   return dex_mem_map;
 }
 
-static const DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) {
+static const DexFile* CreateDexFile(JNIEnv* env, MemMap&& dex_mem_map) {
   std::string location = StringPrintf("Anonymous-DexFile@%p-%p",
-                                      dex_mem_map->Begin(),
-                                      dex_mem_map->End());
+                                      dex_mem_map.Begin(),
+                                      dex_mem_map.End());
   std::string error_message;
   const ArtDexFileLoader dex_file_loader;
   std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(location,
@@ -213,7 +213,7 @@
   return dex_file.release();
 }
 
-static jobject CreateSingleDexFileCookie(JNIEnv* env, std::unique_ptr<MemMap> data) {
+static jobject CreateSingleDexFileCookie(JNIEnv* env, MemMap&& data) {
   std::unique_ptr<const DexFile> dex_file(CreateDexFile(env, std::move(data)));
   if (dex_file.get() == nullptr) {
     DCHECK(env->ExceptionCheck());
@@ -233,17 +233,17 @@
   if (base_address == nullptr) {
     ScopedObjectAccess soa(env);
     ThrowWrappedIOException("dexFileBuffer not direct");
-    return 0;
+    return nullptr;
   }
 
-  std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end));
-  if (dex_mem_map == nullptr) {
+  MemMap dex_mem_map = AllocateDexMemoryMap(env, start, end);
+  if (!dex_mem_map.IsValid()) {
     DCHECK(Thread::Current()->IsExceptionPending());
-    return 0;
+    return nullptr;
   }
 
   size_t length = static_cast<size_t>(end - start);
-  memcpy(dex_mem_map->Begin(), base_address, length);
+  memcpy(dex_mem_map.Begin(), base_address, length);
   return CreateSingleDexFileCookie(env, std::move(dex_mem_map));
 }
 
@@ -252,13 +252,13 @@
                                              jbyteArray buffer,
                                              jint start,
                                              jint end) {
-  std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end));
-  if (dex_mem_map == nullptr) {
+  MemMap dex_mem_map = AllocateDexMemoryMap(env, start, end);
+  if (!dex_mem_map.IsValid()) {
     DCHECK(Thread::Current()->IsExceptionPending());
-    return 0;
+    return nullptr;
   }
 
-  auto destination = reinterpret_cast<jbyte*>(dex_mem_map.get()->Begin());
+  auto destination = reinterpret_cast<jbyte*>(dex_mem_map.Begin());
   env->GetByteArrayRegion(buffer, start, end - start, destination);
   return CreateSingleDexFileCookie(env, std::move(dex_mem_map));
 }
@@ -273,7 +273,7 @@
                                          jobjectArray dex_elements) {
   ScopedUtfChars sourceName(env, javaSourceName);
   if (sourceName.c_str() == nullptr) {
-    return 0;
+    return nullptr;
   }
 
   Runtime* const runtime = Runtime::Current();
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index f1e267b..7ac4086 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -388,7 +388,7 @@
 // as PSS, private/shared dirty/shared data are available via
 // /proc/<pid>/smaps.
 static void VMDebug_getHeapSpaceStats(JNIEnv* env, jclass, jlongArray data) {
-  jlong* arr = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(data, 0));
+  jlong* arr = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(data, nullptr));
   if (arr == nullptr || env->GetArrayLength(data) < 9) {
     return;
   }
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 3227c69..9b3fd16 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -675,6 +675,13 @@
   Runtime::Current()->SetDedupeHiddenApiWarnings(dedupe);
 }
 
+static void VMRuntime_setProcessPackageName(JNIEnv* env,
+                                            jclass klass ATTRIBUTE_UNUSED,
+                                            jstring java_package_name) {
+  ScopedUtfChars package_name(env, java_package_name);
+  Runtime::Current()->SetProcessPackageName(package_name.c_str());
+}
+
 static JNINativeMethod gMethods[] = {
   FAST_NATIVE_METHOD(VMRuntime, addressOf, "(Ljava/lang/Object;)J"),
   NATIVE_METHOD(VMRuntime, bootClassPath, "()Ljava/lang/String;"),
@@ -718,6 +725,7 @@
   NATIVE_METHOD(VMRuntime, didPruneDalvikCache, "()Z"),
   NATIVE_METHOD(VMRuntime, setSystemDaemonThreadPriority, "()V"),
   NATIVE_METHOD(VMRuntime, setDedupeHiddenApiWarnings, "(Z)V"),
+  NATIVE_METHOD(VMRuntime, setProcessPackageName, "(Ljava/lang/String;)V"),
 };
 
 void register_dalvik_system_VMRuntime(JNIEnv* env) {
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index 13871f7..b7f0a7a 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -122,7 +122,7 @@
   return thread->HoldsLock(object);
 }
 
-static void Thread_nativeInterrupt(JNIEnv* env, jobject java_thread) {
+static void Thread_interrupt0(JNIEnv* env, jobject java_thread) {
   ScopedFastNativeObjectAccess soa(env);
   MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
   Thread* thread = Thread::FromManagedThread(soa, java_thread);
@@ -131,7 +131,7 @@
   }
 }
 
-static void Thread_nativeSetName(JNIEnv* env, jobject peer, jstring java_name) {
+static void Thread_setNativeName(JNIEnv* env, jobject peer, jstring java_name) {
   ScopedUtfChars name(env, java_name);
   {
     ScopedObjectAccess soa(env);
@@ -168,7 +168,7 @@
  * from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY (1-10), with "normal"
  * threads at Thread.NORM_PRIORITY (5).
  */
-static void Thread_nativeSetPriority(JNIEnv* env, jobject java_thread, jint new_priority) {
+static void Thread_setPriority0(JNIEnv* env, jobject java_thread, jint new_priority) {
   ScopedObjectAccess soa(env);
   MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
   Thread* thread = Thread::FromManagedThread(soa, java_thread);
@@ -200,9 +200,9 @@
   NATIVE_METHOD(Thread, nativeCreate, "(Ljava/lang/Thread;JZ)V"),
   NATIVE_METHOD(Thread, nativeGetStatus, "(Z)I"),
   NATIVE_METHOD(Thread, holdsLock, "(Ljava/lang/Object;)Z"),
-  FAST_NATIVE_METHOD(Thread, nativeInterrupt, "()V"),
-  NATIVE_METHOD(Thread, nativeSetName, "(Ljava/lang/String;)V"),
-  NATIVE_METHOD(Thread, nativeSetPriority, "(I)V"),
+  FAST_NATIVE_METHOD(Thread, interrupt0, "()V"),
+  NATIVE_METHOD(Thread, setNativeName, "(Ljava/lang/String;)V"),
+  NATIVE_METHOD(Thread, setPriority0, "(I)V"),
   FAST_NATIVE_METHOD(Thread, sleep, "(Ljava/lang/Object;JI)V"),
   NATIVE_METHOD(Thread, yield, "()V"),
 };
diff --git a/runtime/native/java_lang_reflect_Constructor.cc b/runtime/native/java_lang_reflect_Constructor.cc
index e54674f..4b4d6e3 100644
--- a/runtime/native/java_lang_reflect_Constructor.cc
+++ b/runtime/native/java_lang_reflect_Constructor.cc
@@ -121,7 +121,7 @@
 static jobject Constructor_newInstanceFromSerialization(JNIEnv* env, jclass unused ATTRIBUTE_UNUSED,
                                                         jclass ctorClass, jclass allocClass) {
     jmethodID ctor = env->GetMethodID(ctorClass, "<init>", "()V");
-    DCHECK(ctor != NULL);
+    DCHECK(ctor != nullptr);
     return env->NewObject(allocClass, ctor);
 }
 
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 58e16ed..c7daef8 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -956,7 +956,7 @@
   void* dlopen_handle_;  // TODO: Unique_ptr with custom deleter.
 
   // Dummy memory map objects corresponding to the regions mapped by dlopen.
-  std::vector<std::unique_ptr<MemMap>> dlopen_mmaps_;
+  std::vector<MemMap> dlopen_mmaps_;
 
   // The number of shared objects the linker told us about before loading. Used to
   // (optimistically) optimize the PreSetup stage (see comment there).
@@ -1122,8 +1122,8 @@
             uint8_t* vaddr = reinterpret_cast<uint8_t*>(info->dlpi_addr +
                 info->dlpi_phdr[i].p_vaddr);
             size_t memsz = info->dlpi_phdr[i].p_memsz;
-            MemMap* mmap = MemMap::MapDummy(info->dlpi_name, vaddr, memsz);
-            context->dlopen_mmaps_->push_back(std::unique_ptr<MemMap>(mmap));
+            MemMap mmap = MemMap::MapDummy(info->dlpi_name, vaddr, memsz);
+            context->dlopen_mmaps_->push_back(std::move(mmap));
           }
         }
         return 1;  // Stop iteration and return 1 from dl_iterate_phdr.
@@ -1131,7 +1131,7 @@
       return 0;  // Continue iteration and return 0 from dl_iterate_phdr when finished.
     }
     const uint8_t* const begin_;
-    std::vector<std::unique_ptr<MemMap>>* const dlopen_mmaps_;
+    std::vector<MemMap>* const dlopen_mmaps_;
     const size_t shared_objects_before;
     size_t shared_objects_seen;
   };
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 5f87bf0..4ed26fa 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -550,7 +550,7 @@
   const IndexBssMapping* const method_bss_mapping_ = nullptr;
   const IndexBssMapping* const type_bss_mapping_ = nullptr;
   const IndexBssMapping* const string_bss_mapping_ = nullptr;
-  const uint32_t* const oat_class_offsets_pointer_ = 0u;
+  const uint32_t* const oat_class_offsets_pointer_ = nullptr;
   TypeLookupTable lookup_table_;
   const DexLayoutSections* const dex_layout_sections_ = nullptr;
 
diff --git a/runtime/read_barrier-inl.h b/runtime/read_barrier-inl.h
index 640fa7e..672303a 100644
--- a/runtime/read_barrier-inl.h
+++ b/runtime/read_barrier-inl.h
@@ -255,13 +255,13 @@
 }
 
 inline bool ReadBarrier::IsGray(mirror::Object* obj, uintptr_t* fake_address_dependency) {
-  return obj->GetReadBarrierState(fake_address_dependency) == gray_state_;
+  return obj->GetReadBarrierState(fake_address_dependency) == kGrayState;
 }
 
 inline bool ReadBarrier::IsGray(mirror::Object* obj) {
   // Use a load-acquire to load the read barrier bit to avoid reordering with the subsequent load.
   // GetReadBarrierStateAcquire() has load-acquire semantics.
-  return obj->GetReadBarrierStateAcquire() == gray_state_;
+  return obj->GetReadBarrierStateAcquire() == kGrayState;
 }
 
 }  // namespace art
diff --git a/runtime/read_barrier.h b/runtime/read_barrier.h
index e8df2ad..0741da6 100644
--- a/runtime/read_barrier.h
+++ b/runtime/read_barrier.h
@@ -102,11 +102,11 @@
   // ALWAYS_INLINE on this caused a performance regression b/26744236.
   static mirror::Object* Mark(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  static constexpr uint32_t WhiteState() {
-    return white_state_;
+  static constexpr uint32_t NonGrayState() {
+    return kNonGrayState;
   }
   static constexpr uint32_t GrayState() {
-    return gray_state_;
+    return kGrayState;
   }
 
   // fake_address_dependency will be zero which should be bitwise-or'ed with the address of the
@@ -122,12 +122,13 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   static bool IsValidReadBarrierState(uint32_t rb_state) {
-    return rb_state == white_state_ || rb_state == gray_state_;
+    return rb_state == kNonGrayState || rb_state == kGrayState;
   }
 
-  static constexpr uint32_t white_state_ = 0x0;    // Not marked.
-  static constexpr uint32_t gray_state_ = 0x1;     // Marked, but not marked through. On mark stack.
-  static constexpr uint32_t rb_state_mask_ = 0x1;  // The low bits for white|gray.
+ private:
+  static constexpr uint32_t kNonGrayState = 0x0;  // White (not marked) or black (marked through).
+  static constexpr uint32_t kGrayState = 0x1;     // Marked, but not marked through. On mark stack.
+  static constexpr uint32_t kRBStateMask = 0x1;   // The low bits for non-gray|gray.
 };
 
 }  // namespace art
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index facebda..30d4587 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -425,7 +425,7 @@
   low_4gb_arena_pool_.reset();
   arena_pool_.reset();
   jit_arena_pool_.reset();
-  protected_fault_page_.reset();
+  protected_fault_page_.Reset();
   MemMap::Shutdown();
 
   // TODO: acquire a static mutex on Runtime to avoid racing.
@@ -1162,18 +1162,17 @@
   {
     constexpr uintptr_t kSentinelAddr =
         RoundDown(static_cast<uintptr_t>(Context::kBadGprBase), kPageSize);
-    protected_fault_page_.reset(MemMap::MapAnonymous("Sentinel fault page",
-                                                     reinterpret_cast<uint8_t*>(kSentinelAddr),
-                                                     kPageSize,
-                                                     PROT_NONE,
-                                                     /* low_4g */ true,
-                                                     /* reuse */ false,
-                                                     /* error_msg */ nullptr));
-    if (protected_fault_page_ == nullptr) {
+    protected_fault_page_ = MemMap::MapAnonymous("Sentinel fault page",
+                                                 reinterpret_cast<uint8_t*>(kSentinelAddr),
+                                                 kPageSize,
+                                                 PROT_NONE,
+                                                 /* low_4g */ true,
+                                                 /* error_msg */ nullptr);
+    if (!protected_fault_page_.IsValid()) {
       LOG(WARNING) << "Could not reserve sentinel fault page";
-    } else if (reinterpret_cast<uintptr_t>(protected_fault_page_->Begin()) != kSentinelAddr) {
+    } else if (reinterpret_cast<uintptr_t>(protected_fault_page_.Begin()) != kSentinelAddr) {
       LOG(WARNING) << "Could not reserve sentinel fault page at the right address.";
-      protected_fault_page_.reset();
+      protected_fault_page_.Reset();
     }
   }
 
diff --git a/runtime/runtime.h b/runtime/runtime.h
index ca93e24..f98d7b9 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -29,6 +29,7 @@
 
 #include "arch/instruction_set.h"
 #include "base/macros.h"
+#include "base/mem_map.h"
 #include "base/mutex.h"
 #include "deoptimization_kind.h"
 #include "dex/dex_file_types.h"
@@ -86,7 +87,6 @@
 class IsMarkedVisitor;
 class JavaVMExt;
 class LinearAlloc;
-class MemMap;
 class MonitorList;
 class MonitorPool;
 class NullPointerHandler;
@@ -579,6 +579,18 @@
     return hidden_api_access_event_log_rate_;
   }
 
+  const std::string& GetProcessPackageName() const {
+    return process_package_name_;
+  }
+
+  void SetProcessPackageName(const char* package_name) {
+    if (package_name == nullptr) {
+      process_package_name_.clear();
+    } else {
+      process_package_name_ = package_name;
+    }
+  }
+
   bool IsDexFileFallbackEnabled() const {
     return allow_dex_file_fallback_;
   }
@@ -1031,10 +1043,13 @@
   // when there is a warning. This is only used for testing.
   bool always_set_hidden_api_warning_flag_;
 
-  // How often to log hidden API access to the event log. An integer between 0 (never)
-  // and 0x10000 (always).
+  // How often to log hidden API access to the event log. An integer between 0
+  // (never) and 0x10000 (always).
   uint32_t hidden_api_access_event_log_rate_;
 
+  // The package of the app running in this process.
+  std::string process_package_name_;
+
   // Whether threads should dump their native stack on SIGQUIT.
   bool dump_native_stack_on_sig_quit_;
 
@@ -1075,7 +1090,7 @@
   std::atomic<uint32_t> deoptimization_counts_[
       static_cast<uint32_t>(DeoptimizationKind::kLast) + 1];
 
-  std::unique_ptr<MemMap> protected_fault_page_;
+  MemMap protected_fault_page_;
 
   uint32_t verifier_logging_threshold_ms_;
 
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index 794ac19..ed0472f 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -190,19 +190,18 @@
 
 TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) {
   std::string error_msg;
-  std::unique_ptr<MemMap> stack(MemMap::MapAnonymous("ThreadLifecycleCallback Thread",
-                                                     nullptr,
-                                                     128 * kPageSize,  // Just some small stack.
-                                                     PROT_READ | PROT_WRITE,
-                                                     false,
-                                                     false,
-                                                     &error_msg));
-  ASSERT_FALSE(stack == nullptr) << error_msg;
+  MemMap stack = MemMap::MapAnonymous("ThreadLifecycleCallback Thread",
+                                      /* addr */ nullptr,
+                                      128 * kPageSize,  // Just some small stack.
+                                      PROT_READ | PROT_WRITE,
+                                      /* low_4gb */ false,
+                                      &error_msg);
+  ASSERT_TRUE(stack.IsValid()) << error_msg;
 
   const char* reason = "ThreadLifecycleCallback test thread";
   pthread_attr_t attr;
   CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), reason);
-  CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack->Begin(), stack->Size()), reason);
+  CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack.Begin(), stack.Size()), reason);
   pthread_t pthread;
   CHECK_PTHREAD_CALL(pthread_create,
                      (&pthread,
diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc
index d1000c5..62dec15 100644
--- a/runtime/stack_map.cc
+++ b/runtime/stack_map.cc
@@ -31,83 +31,70 @@
   : CodeInfo(header->GetOptimizedCodeInfoPtr(), flags) {
 }
 
+// Returns true if the decoded table was deduped.
 template<typename Accessor>
-ALWAYS_INLINE static void DecodeTable(BitTable<Accessor>& table,
-                                      BitMemoryReader& reader,
-                                      const uint8_t* data) {
+ALWAYS_INLINE static bool DecodeTable(BitTable<Accessor>& table, BitMemoryReader& reader) {
   bool is_deduped = reader.ReadBit();
   if (is_deduped) {
-    // 'data' points to the start of the reader's data.
-    uint32_t current_bit_offset = reader.GetBitOffset();
-    uint32_t bit_offset_backwards = DecodeVarintBits(reader) - current_bit_offset;
-    uint32_t byte_offset_backwards = BitsToBytesRoundUp(bit_offset_backwards);
-    BitMemoryReader reader2(data - byte_offset_backwards,
-                            byte_offset_backwards * kBitsPerByte - bit_offset_backwards);
+    ssize_t bit_offset = reader.NumberOfReadBits() - reader.ReadVarint();
+    BitMemoryReader reader2(reader.data(), bit_offset);  // The offset is negative.
     table.Decode(reader2);
   } else {
     table.Decode(reader);
   }
+  return is_deduped;
 }
 
 void CodeInfo::Decode(const uint8_t* data, DecodeFlags flags) {
   BitMemoryReader reader(data);
-  packed_frame_size_ = DecodeVarintBits(reader);
-  core_spill_mask_ = DecodeVarintBits(reader);
-  fp_spill_mask_ = DecodeVarintBits(reader);
-  number_of_dex_registers_ = DecodeVarintBits(reader);
-  DecodeTable(stack_maps_, reader, data);
-  DecodeTable(register_masks_, reader, data);
-  DecodeTable(stack_masks_, reader, data);
-  if (flags & DecodeFlags::GcMasksOnly) {
-    return;
-  }
-  DecodeTable(inline_infos_, reader, data);
-  DecodeTable(method_infos_, reader, data);
-  if (flags & DecodeFlags::InlineInfoOnly) {
-    return;
-  }
-  DecodeTable(dex_register_masks_, reader, data);
-  DecodeTable(dex_register_maps_, reader, data);
-  DecodeTable(dex_register_catalog_, reader, data);
-  size_in_bits_ = reader.GetBitOffset();
+  ForEachHeaderField([this, &reader](auto member_pointer) {
+    this->*member_pointer = reader.ReadVarint();
+  });
+  ForEachBitTableField([this, &reader](auto member_pointer) {
+    DecodeTable(this->*member_pointer, reader);
+  }, flags);
+  size_in_bits_ = reader.NumberOfReadBits();
 }
 
-template<typename Accessor>
-ALWAYS_INLINE static void DedupeTable(BitMemoryWriter<std::vector<uint8_t>>& writer,
-                                      BitMemoryReader& reader,
-                                      CodeInfo::DedupeMap* dedupe_map) {
-  bool is_deduped = reader.ReadBit();
-  DCHECK(!is_deduped);
-  BitTable<Accessor> bit_table(reader);
-  BitMemoryRegion region = reader.Tail(bit_table.BitSize());
-  auto it = dedupe_map->insert(std::make_pair(region, writer.GetBitOffset() + 1 /* dedupe bit */));
-  if (it.second /* new bit table */ || region.size_in_bits() < 32) {
-    writer.WriteBit(false);  // Is not deduped.
-    writer.WriteRegion(region);
-  } else {
-    writer.WriteBit(true);  // Is deduped.
-    EncodeVarintBits(writer, writer.GetBitOffset() - it.first->second);
-  }
-}
+size_t CodeInfo::Deduper::Dedupe(const uint8_t* code_info_data) {
+  writer_.ByteAlign();
+  size_t deduped_offset = writer_.NumberOfWrittenBits() / kBitsPerByte;
+  BitMemoryReader reader(code_info_data);
+  CodeInfo code_info;  // Temporary storage for decoded data.
+  ForEachHeaderField([this, &reader, &code_info](auto member_pointer) {
+    code_info.*member_pointer = reader.ReadVarint();
+    writer_.WriteVarint(code_info.*member_pointer);
+  });
+  ForEachBitTableField([this, &reader, &code_info](auto member_pointer) {
+    bool is_deduped = reader.ReadBit();
+    DCHECK(!is_deduped);
+    size_t bit_table_start = reader.NumberOfReadBits();
+    (code_info.*member_pointer).Decode(reader);
+    BitMemoryRegion region = reader.GetReadRegion().Subregion(bit_table_start);
+    auto it = dedupe_map_.insert(std::make_pair(region, /* placeholder */ 0));
+    if (it.second /* new bit table */ || region.size_in_bits() < 32) {
+      writer_.WriteBit(false);  // Is not deduped.
+      it.first->second = writer_.NumberOfWrittenBits();
+      writer_.WriteRegion(region);
+    } else {
+      writer_.WriteBit(true);  // Is deduped.
+      size_t bit_offset = writer_.NumberOfWrittenBits();
+      writer_.WriteVarint(bit_offset - it.first->second);
+    }
+  });
 
-size_t CodeInfo::Dedupe(std::vector<uint8_t>* out, const uint8_t* in, DedupeMap* dedupe_map) {
-  // Remember the current offset in the output buffer so that we can return it later.
-  const size_t result = out->size();
-  BitMemoryReader reader(in);
-  BitMemoryWriter<std::vector<uint8_t>> writer(out, /* bit_offset */ out->size() * kBitsPerByte);
-  EncodeVarintBits(writer, DecodeVarintBits(reader));  // packed_frame_size_.
-  EncodeVarintBits(writer, DecodeVarintBits(reader));  // core_spill_mask_.
-  EncodeVarintBits(writer, DecodeVarintBits(reader));  // fp_spill_mask_.
-  EncodeVarintBits(writer, DecodeVarintBits(reader));  // number_of_dex_registers_.
-  DedupeTable<StackMap>(writer, reader, dedupe_map);
-  DedupeTable<RegisterMask>(writer, reader, dedupe_map);
-  DedupeTable<MaskInfo>(writer, reader, dedupe_map);
-  DedupeTable<InlineInfo>(writer, reader, dedupe_map);
-  DedupeTable<MethodInfo>(writer, reader, dedupe_map);
-  DedupeTable<MaskInfo>(writer, reader, dedupe_map);
-  DedupeTable<DexRegisterMapInfo>(writer, reader, dedupe_map);
-  DedupeTable<DexRegisterInfo>(writer, reader, dedupe_map);
-  return result;
+  if (kIsDebugBuild) {
+    CodeInfo old_code_info(code_info_data);
+    CodeInfo new_code_info(writer_.data() + deduped_offset);
+    ForEachHeaderField([&old_code_info, &new_code_info](auto member_pointer) {
+      DCHECK_EQ(old_code_info.*member_pointer, new_code_info.*member_pointer);
+    });
+    ForEachBitTableField([&old_code_info, &new_code_info](auto member_pointer) {
+      DCHECK((old_code_info.*member_pointer).Equals(new_code_info.*member_pointer));
+    });
+  }
+
+  return deduped_offset;
 }
 
 BitTable<StackMap>::const_iterator CodeInfo::BinarySearchNativePc(uint32_t packed_pc) const {
@@ -194,33 +181,32 @@
   }
 }
 
-template<typename Accessor>
-static void AddTableSizeStats(const char* table_name,
-                              const BitTable<Accessor>& table,
-                              /*out*/ Stats* parent) {
-  Stats* table_stats = parent->Child(table_name);
-  table_stats->AddBits(table.BitSize());
-  table_stats->Child("Header")->AddBits(table.HeaderBitSize());
-  const char* const* column_names = GetBitTableColumnNames<Accessor>();
-  for (size_t c = 0; c < table.NumColumns(); c++) {
-    if (table.NumColumnBits(c) > 0) {
-      Stats* column_stats = table_stats->Child(column_names[c]);
-      column_stats->AddBits(table.NumRows() * table.NumColumnBits(c), table.NumRows());
+// Decode the CodeInfo while collecting size statistics.
+void CodeInfo::CollectSizeStats(const uint8_t* code_info_data, /*out*/ Stats* parent) {
+  Stats* codeinfo_stats = parent->Child("CodeInfo");
+  BitMemoryReader reader(code_info_data);
+  ForEachHeaderField([&reader](auto) { reader.ReadVarint(); });
+  codeinfo_stats->Child("Header")->AddBits(reader.NumberOfReadBits());
+  CodeInfo code_info;  // Temporary storage for decoded tables.
+  ForEachBitTableField([codeinfo_stats, &reader, &code_info](auto member_pointer) {
+    auto& table = code_info.*member_pointer;
+    size_t bit_offset = reader.NumberOfReadBits();
+    bool deduped = DecodeTable(table, reader);
+    if (deduped) {
+      codeinfo_stats->Child("DedupeOffset")->AddBits(reader.NumberOfReadBits() - bit_offset);
+    } else {
+      Stats* table_stats = codeinfo_stats->Child(table.GetName());
+      table_stats->AddBits(reader.NumberOfReadBits() - bit_offset);
+      const char* const* column_names = table.GetColumnNames();
+      for (size_t c = 0; c < table.NumColumns(); c++) {
+        if (table.NumColumnBits(c) > 0) {
+          Stats* column_stats = table_stats->Child(column_names[c]);
+          column_stats->AddBits(table.NumRows() * table.NumColumnBits(c), table.NumRows());
+        }
+      }
     }
-  }
-}
-
-void CodeInfo::AddSizeStats(/*out*/ Stats* parent) const {
-  Stats* stats = parent->Child("CodeInfo");
-  stats->AddBytes(Size());
-  AddTableSizeStats<StackMap>("StackMaps", stack_maps_, stats);
-  AddTableSizeStats<RegisterMask>("RegisterMasks", register_masks_, stats);
-  AddTableSizeStats<MaskInfo>("StackMasks", stack_masks_, stats);
-  AddTableSizeStats<InlineInfo>("InlineInfos", inline_infos_, stats);
-  AddTableSizeStats<MethodInfo>("MethodInfo", method_infos_, stats);
-  AddTableSizeStats<MaskInfo>("DexRegisterMasks", dex_register_masks_, stats);
-  AddTableSizeStats<DexRegisterMapInfo>("DexRegisterMaps", dex_register_maps_, stats);
-  AddTableSizeStats<DexRegisterInfo>("DexRegisterCatalog", dex_register_catalog_, stats);
+  });
+  codeinfo_stats->AddBytes(BitsToBytesRoundUp(reader.NumberOfReadBits()));
 }
 
 void DexRegisterMap::Dump(VariableIndentationOutputStream* vios) const {
@@ -236,56 +222,49 @@
   }
 }
 
-template<typename Accessor>
-static void DumpTable(VariableIndentationOutputStream* vios,
-                      const char* table_name,
-                      const BitTable<Accessor>& table,
-                      bool verbose,
-                      bool is_mask = false) {
-  if (table.NumRows() != 0) {
-    vios->Stream() << table_name << " BitSize=" << table.BitSize();
-    vios->Stream() << " Rows=" << table.NumRows() << " Bits={";
-    const char* const* column_names = GetBitTableColumnNames<Accessor>();
-    for (size_t c = 0; c < table.NumColumns(); c++) {
-      vios->Stream() << (c != 0 ? " " : "");
-      vios->Stream() << column_names[c] << "=" << table.NumColumnBits(c);
-    }
-    vios->Stream() << "}\n";
-    if (verbose) {
-      ScopedIndentation indent1(vios);
-      for (size_t r = 0; r < table.NumRows(); r++) {
-        vios->Stream() << "[" << std::right << std::setw(3) << r << "]={";
-        for (size_t c = 0; c < table.NumColumns(); c++) {
-          vios->Stream() << (c != 0 ? " " : "");
-          if (is_mask) {
-            BitMemoryRegion bits = table.GetBitMemoryRegion(r, c);
-            for (size_t b = 0, e = bits.size_in_bits(); b < e; b++) {
-              vios->Stream() << bits.LoadBit(e - b - 1);
-            }
-          } else {
-            vios->Stream() << std::right << std::setw(8) << static_cast<int32_t>(table.Get(r, c));
-          }
-        }
-        vios->Stream() << "}\n";
-      }
-    }
-  }
-}
-
 void CodeInfo::Dump(VariableIndentationOutputStream* vios,
                     uint32_t code_offset,
                     bool verbose,
                     InstructionSet instruction_set) const {
-  vios->Stream() << "CodeInfo\n";
+  vios->Stream() << "CodeInfo BitSize=" << size_in_bits_
+    << " FrameSize:" << packed_frame_size_ * kStackAlignment
+    << " CoreSpillMask:" << std::hex << core_spill_mask_
+    << " FpSpillMask:" << std::hex << fp_spill_mask_
+    << " NumberOfDexRegisters:" << std::dec << number_of_dex_registers_
+    << "\n";
   ScopedIndentation indent1(vios);
-  DumpTable<StackMap>(vios, "StackMaps", stack_maps_, verbose);
-  DumpTable<RegisterMask>(vios, "RegisterMasks", register_masks_, verbose);
-  DumpTable<MaskInfo>(vios, "StackMasks", stack_masks_, verbose, true /* is_mask */);
-  DumpTable<InlineInfo>(vios, "InlineInfos", inline_infos_, verbose);
-  DumpTable<MethodInfo>(vios, "MethodInfo", method_infos_, verbose);
-  DumpTable<MaskInfo>(vios, "DexRegisterMasks", dex_register_masks_, verbose, true /* is_mask */);
-  DumpTable<DexRegisterMapInfo>(vios, "DexRegisterMaps", dex_register_maps_, verbose);
-  DumpTable<DexRegisterInfo>(vios, "DexRegisterCatalog", dex_register_catalog_, verbose);
+  ForEachBitTableField([this, &vios, verbose](auto member_pointer) {
+    const auto& table = this->*member_pointer;
+    if (table.NumRows() != 0) {
+      vios->Stream() << table.GetName() << " BitSize=" << table.DataBitSize();
+      vios->Stream() << " Rows=" << table.NumRows() << " Bits={";
+      const char* const* column_names = table.GetColumnNames();
+      for (size_t c = 0; c < table.NumColumns(); c++) {
+        vios->Stream() << (c != 0 ? " " : "");
+        vios->Stream() << column_names[c] << "=" << table.NumColumnBits(c);
+      }
+      vios->Stream() << "}\n";
+      if (verbose) {
+        ScopedIndentation indent1(vios);
+        for (size_t r = 0; r < table.NumRows(); r++) {
+          vios->Stream() << "[" << std::right << std::setw(3) << r << "]={";
+          for (size_t c = 0; c < table.NumColumns(); c++) {
+            vios->Stream() << (c != 0 ? " " : "");
+            if (&table == static_cast<const void*>(&stack_masks_) ||
+                &table == static_cast<const void*>(&dex_register_masks_)) {
+              BitMemoryRegion bits = table.GetBitMemoryRegion(r, c);
+              for (size_t b = 0, e = bits.size_in_bits(); b < e; b++) {
+                vios->Stream() << bits.LoadBit(e - b - 1);
+              }
+            } else {
+              vios->Stream() << std::right << std::setw(8) << static_cast<int32_t>(table.Get(r, c));
+            }
+          }
+          vios->Stream() << "}\n";
+        }
+      }
+    }
+  });
 
   // Display stack maps along with (live) Dex register maps.
   if (verbose) {
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index d6db05a..5f44286 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -128,7 +128,7 @@
     OSR = 1,
     Debug = 2,
   };
-  BIT_TABLE_HEADER()
+  BIT_TABLE_HEADER(StackMap)
   BIT_TABLE_COLUMN(0, Kind)
   BIT_TABLE_COLUMN(1, PackedNativePc)
   BIT_TABLE_COLUMN(2, DexPc)
@@ -174,7 +174,7 @@
  */
 class InlineInfo : public BitTableAccessor<6> {
  public:
-  BIT_TABLE_HEADER()
+  BIT_TABLE_HEADER(InlineInfo)
   BIT_TABLE_COLUMN(0, IsLast)  // Determines if there are further rows for further depths.
   BIT_TABLE_COLUMN(1, DexPc)
   BIT_TABLE_COLUMN(2, MethodInfoIndex)
@@ -201,21 +201,27 @@
             const StackMap& stack_map) const;
 };
 
-class MaskInfo : public BitTableAccessor<1> {
+class StackMask : public BitTableAccessor<1> {
  public:
-  BIT_TABLE_HEADER()
+  BIT_TABLE_HEADER(StackMask)
+  BIT_TABLE_COLUMN(0, Mask)
+};
+
+class DexRegisterMask : public BitTableAccessor<1> {
+ public:
+  BIT_TABLE_HEADER(DexRegisterMask)
   BIT_TABLE_COLUMN(0, Mask)
 };
 
 class DexRegisterMapInfo : public BitTableAccessor<1> {
  public:
-  BIT_TABLE_HEADER()
+  BIT_TABLE_HEADER(DexRegisterMapInfo)
   BIT_TABLE_COLUMN(0, CatalogueIndex)
 };
 
 class DexRegisterInfo : public BitTableAccessor<2> {
  public:
-  BIT_TABLE_HEADER()
+  BIT_TABLE_HEADER(DexRegisterInfo)
   BIT_TABLE_COLUMN(0, Kind)
   BIT_TABLE_COLUMN(1, PackedValue)
 
@@ -246,7 +252,7 @@
 // therefore it is worth encoding the mask as value+shift.
 class RegisterMask : public BitTableAccessor<2> {
  public:
-  BIT_TABLE_HEADER()
+  BIT_TABLE_HEADER(RegisterMask)
   BIT_TABLE_COLUMN(0, Value)
   BIT_TABLE_COLUMN(1, Shift)
 
@@ -259,7 +265,7 @@
 // Separating them greatly improves dedup efficiency of the other tables.
 class MethodInfo : public BitTableAccessor<1> {
  public:
-  BIT_TABLE_HEADER()
+  BIT_TABLE_HEADER(MethodInfo)
   BIT_TABLE_COLUMN(0, MethodIndex)
 };
 
@@ -269,8 +275,25 @@
  */
 class CodeInfo {
  public:
+  class Deduper {
+   public:
+    explicit Deduper(std::vector<uint8_t>* output) : writer_(output) {
+      DCHECK_EQ(output->size(), 0u);
+    }
+
+    // Copy CodeInfo into output while de-duplicating the internal bit tables.
+    // It returns the byte offset of the copied CodeInfo within the output.
+    size_t Dedupe(const uint8_t* code_info);
+
+   private:
+    BitMemoryWriter<std::vector<uint8_t>> writer_;
+
+    // Deduplicate at BitTable level. The value is bit offset within the output.
+    std::map<BitMemoryRegion, uint32_t, BitMemoryRegion::Less> dedupe_map_;
+  };
+
   enum DecodeFlags {
-    Default = 0,
+    AllTables = 0,
     // Limits the decoding only to the data needed by GC.
     GcMasksOnly = 1,
     // Limits the decoding only to the main stack map table and inline info table.
@@ -278,11 +301,11 @@
     InlineInfoOnly = 2,
   };
 
-  explicit CodeInfo(const uint8_t* data, DecodeFlags flags = DecodeFlags::Default) {
+  explicit CodeInfo(const uint8_t* data, DecodeFlags flags = AllTables) {
     Decode(reinterpret_cast<const uint8_t*>(data), flags);
   }
 
-  explicit CodeInfo(const OatQuickMethodHeader* header, DecodeFlags flags = DecodeFlags::Default);
+  explicit CodeInfo(const OatQuickMethodHeader* header, DecodeFlags flags = AllTables);
 
   size_t Size() const {
     return BitsToBytesRoundUp(size_in_bits_);
@@ -411,27 +434,19 @@
             InstructionSet instruction_set) const;
 
   // Accumulate code info size statistics into the given Stats tree.
-  void AddSizeStats(/*out*/ Stats* parent) const;
+  static void CollectSizeStats(const uint8_t* code_info, /*out*/ Stats* parent);
 
   ALWAYS_INLINE static QuickMethodFrameInfo DecodeFrameInfo(const uint8_t* data) {
     BitMemoryReader reader(data);
     return QuickMethodFrameInfo(
-        DecodeVarintBits(reader) * kStackAlignment,  // Decode packed_frame_size_ and unpack.
-        DecodeVarintBits(reader),  // core_spill_mask_.
-        DecodeVarintBits(reader));  // fp_spill_mask_.
+        reader.ReadVarint() * kStackAlignment,  // Decode packed_frame_size_ and unpack.
+        reader.ReadVarint(),  // core_spill_mask_.
+        reader.ReadVarint());  // fp_spill_mask_.
   }
 
-  typedef std::map<BitMemoryRegion, uint32_t, BitMemoryRegion::Less> DedupeMap;
-
-  // Copy CodeInfo data while de-duplicating the internal bit tables.
-  // The 'out' vector must be reused between Dedupe calls (it does not have to be empty).
-  // The 'dedupe_map' stores the bit offsets of bit tables within the 'out' vector.
-  // It returns the byte offset of the copied CodeInfo within the 'out' vector.
-  static size_t Dedupe(std::vector<uint8_t>* out,
-                       const uint8_t* in,
-                       /*inout*/ DedupeMap* dedupe_map);
-
  private:
+  CodeInfo() {}
+
   // Returns lower bound (fist stack map which has pc greater or equal than the desired one).
   // It ignores catch stack maps at the end (it is the same as if they had maximum pc value).
   BitTable<StackMap>::const_iterator BinarySearchNativePc(uint32_t packed_pc) const;
@@ -443,16 +458,44 @@
 
   void Decode(const uint8_t* data, DecodeFlags flags);
 
+  // Invokes the callback with member pointer of each header field.
+  template<typename Callback>
+  ALWAYS_INLINE static void ForEachHeaderField(Callback callback) {
+    callback(&CodeInfo::packed_frame_size_);
+    callback(&CodeInfo::core_spill_mask_);
+    callback(&CodeInfo::fp_spill_mask_);
+    callback(&CodeInfo::number_of_dex_registers_);
+  }
+
+  // Invokes the callback with member pointer of each BitTable field.
+  template<typename Callback>
+  ALWAYS_INLINE static void ForEachBitTableField(Callback callback, DecodeFlags flags = AllTables) {
+    callback(&CodeInfo::stack_maps_);
+    callback(&CodeInfo::register_masks_);
+    callback(&CodeInfo::stack_masks_);
+    if (flags & DecodeFlags::GcMasksOnly) {
+      return;
+    }
+    callback(&CodeInfo::inline_infos_);
+    callback(&CodeInfo::method_infos_);
+    if (flags & DecodeFlags::InlineInfoOnly) {
+      return;
+    }
+    callback(&CodeInfo::dex_register_masks_);
+    callback(&CodeInfo::dex_register_maps_);
+    callback(&CodeInfo::dex_register_catalog_);
+  }
+
   uint32_t packed_frame_size_;  // Frame size in kStackAlignment units.
   uint32_t core_spill_mask_;
   uint32_t fp_spill_mask_;
   uint32_t number_of_dex_registers_;
   BitTable<StackMap> stack_maps_;
   BitTable<RegisterMask> register_masks_;
-  BitTable<MaskInfo> stack_masks_;
+  BitTable<StackMask> stack_masks_;
   BitTable<InlineInfo> inline_infos_;
   BitTable<MethodInfo> method_infos_;
-  BitTable<MaskInfo> dex_register_masks_;
+  BitTable<DexRegisterMask> dex_register_masks_;
   BitTable<DexRegisterMapInfo> dex_register_maps_;
   BitTable<DexRegisterInfo> dex_register_catalog_;
   uint32_t size_in_bits_ = 0;
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 0703a07..df7f19d 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -3605,7 +3605,7 @@
           reinterpret_cast<uintptr_t>(cur_quick_frame));
       uintptr_t native_pc_offset = method_header->NativeQuickPcOffset(GetCurrentQuickFramePc());
       CodeInfo code_info(method_header, kPrecise
-          ? CodeInfo::DecodeFlags::Default  // We will need dex register maps.
+          ? CodeInfo::DecodeFlags::AllTables  // We will need dex register maps.
           : CodeInfo::DecodeFlags::GcMasksOnly);
       StackMap map = code_info.GetStackMapForNativePcOffset(native_pc_offset);
       DCHECK(map.IsValid());
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index ba333f6..9222024 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -1476,6 +1476,9 @@
         list_.remove(self);
         break;
       }
+      // In the case where we are not suspended yet, sleep to leave other threads time to execute.
+      // This is important if there are realtime threads. b/111277984
+      usleep(1);
     }
     // We failed to remove the thread due to a suspend request, loop and try again.
   }
diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc
index 26ca190..28fc59c 100644
--- a/runtime/thread_pool.cc
+++ b/runtime/thread_pool.cc
@@ -46,19 +46,23 @@
   // Add an inaccessible page to catch stack overflow.
   stack_size += kPageSize;
   std::string error_msg;
-  stack_.reset(MemMap::MapAnonymous(name.c_str(), nullptr, stack_size, PROT_READ | PROT_WRITE,
-                                    false, false, &error_msg));
-  CHECK(stack_.get() != nullptr) << error_msg;
-  CHECK_ALIGNED(stack_->Begin(), kPageSize);
+  stack_ = MemMap::MapAnonymous(name.c_str(),
+                                /* addr */ nullptr,
+                                stack_size,
+                                PROT_READ | PROT_WRITE,
+                                /* low_4gb */ false,
+                                &error_msg);
+  CHECK(stack_.IsValid()) << error_msg;
+  CHECK_ALIGNED(stack_.Begin(), kPageSize);
   CheckedCall(mprotect,
               "mprotect bottom page of thread pool worker stack",
-              stack_->Begin(),
+              stack_.Begin(),
               kPageSize,
               PROT_NONE);
   const char* reason = "new thread pool worker thread";
   pthread_attr_t attr;
   CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), reason);
-  CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack_->Begin(), stack_->Size()), reason);
+  CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack_.Begin(), stack_.Size()), reason);
   CHECK_PTHREAD_CALL(pthread_create, (&pthread_, &attr, &Callback, this), reason);
   CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), reason);
 }
diff --git a/runtime/thread_pool.h b/runtime/thread_pool.h
index 2784953..98a1193 100644
--- a/runtime/thread_pool.h
+++ b/runtime/thread_pool.h
@@ -53,8 +53,8 @@
   static const size_t kDefaultStackSize = 1 * MB;
 
   size_t GetStackSize() const {
-    DCHECK(stack_.get() != nullptr);
-    return stack_->Size();
+    DCHECK(stack_.IsValid());
+    return stack_.Size();
   }
 
   virtual ~ThreadPoolWorker();
@@ -71,7 +71,7 @@
 
   ThreadPool* const thread_pool_;
   const std::string name_;
-  std::unique_ptr<MemMap> stack_;
+  MemMap stack_;
   pthread_t pthread_;
   Thread* thread_;
 
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index 32aa86d..a7dc225 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -144,7 +144,7 @@
     mmap_reuse = false;
   }
   CHECK(!mmap_reuse || mmap_addr != nullptr);
-  std::unique_ptr<MemMap> mmap(MemMap::MapFileAtAddress(
+  MemMap mmap = MemMap::MapFileAtAddress(
       mmap_addr,
       vdex_length,
       (writable || unquicken) ? PROT_READ | PROT_WRITE : PROT_READ,
@@ -154,13 +154,13 @@
       low_4gb,
       mmap_reuse,
       vdex_filename.c_str(),
-      error_msg));
-  if (mmap == nullptr) {
+      error_msg);
+  if (!mmap.IsValid()) {
     *error_msg = "Failed to mmap file " + vdex_filename + " : " + *error_msg;
     return nullptr;
   }
 
-  std::unique_ptr<VdexFile> vdex(new VdexFile(mmap.release()));
+  std::unique_ptr<VdexFile> vdex(new VdexFile(std::move(mmap)));
   if (!vdex->IsValid()) {
     *error_msg = "Vdex file is not valid";
     return nullptr;
@@ -175,7 +175,7 @@
                     /* decompile_return_instruction */ false);
     // Update the quickening info size to pretend there isn't any.
     size_t offset = vdex->GetDexSectionHeaderOffset();
-    reinterpret_cast<DexSectionHeader*>(vdex->mmap_->Begin() + offset)->quickening_info_size_ = 0;
+    reinterpret_cast<DexSectionHeader*>(vdex->mmap_.Begin() + offset)->quickening_info_size_ = 0;
   }
 
   *error_msg = "Success";
@@ -299,10 +299,6 @@
               decompile_return_instruction);
         }
       }
-      method.UnHideAccessFlags();
-    }
-    for (const ClassAccessor::Field& field : class_accessor.GetFields()) {
-      field.UnHideAccessFlags();
     }
   }
 }
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index 866a57e..a39ec31 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -153,7 +153,7 @@
   typedef uint32_t VdexChecksum;
   using QuickeningTableOffsetType = uint32_t;
 
-  explicit VdexFile(MemMap* mmap) : mmap_(mmap) {}
+  explicit VdexFile(MemMap&& mmap) : mmap_(std::move(mmap)) {}
 
   // Returns nullptr if the vdex file cannot be opened or is not valid.
   // The mmap_* parameters can be left empty (nullptr/0/false) to allocate at random address.
@@ -215,9 +215,9 @@
                          error_msg);
   }
 
-  const uint8_t* Begin() const { return mmap_->Begin(); }
-  const uint8_t* End() const { return mmap_->End(); }
-  size_t Size() const { return mmap_->Size(); }
+  const uint8_t* Begin() const { return mmap_.Begin(); }
+  const uint8_t* End() const { return mmap_.End(); }
+  size_t Size() const { return mmap_.Size(); }
 
   const VerifierDepsHeader& GetVerifierDepsHeader() const {
     return *reinterpret_cast<const VerifierDepsHeader*>(Begin());
@@ -260,7 +260,7 @@
   }
 
   bool IsValid() const {
-    return mmap_->Size() >= sizeof(VerifierDepsHeader) && GetVerifierDepsHeader().IsValid();
+    return mmap_.Size() >= sizeof(VerifierDepsHeader) && GetVerifierDepsHeader().IsValid();
   }
 
   // This method is for iterating over the dex files in the vdex. If `cursor` is null,
@@ -328,7 +328,7 @@
     return DexBegin() + GetDexSectionHeader().GetDexSize();
   }
 
-  std::unique_ptr<MemMap> mmap_;
+  MemMap mmap_;
 
   DISALLOW_COPY_AND_ASSIGN(VdexFile);
 };
diff --git a/test/1000-non-moving-space-stress/expected.txt b/test/1000-non-moving-space-stress/expected.txt
new file mode 100644
index 0000000..b0aad4d
--- /dev/null
+++ b/test/1000-non-moving-space-stress/expected.txt
@@ -0,0 +1 @@
+passed
diff --git a/test/1000-non-moving-space-stress/info.txt b/test/1000-non-moving-space-stress/info.txt
new file mode 100644
index 0000000..a20459f
--- /dev/null
+++ b/test/1000-non-moving-space-stress/info.txt
@@ -0,0 +1,5 @@
+Regression test for a bug that used to trigger GC crashes during a
+sticky-bit CC (young-generation) collection involving an unreachable
+newly allocated object in the non-moving space with a dangling
+reference to an object cleared or moved from a newly allocated region
+of the region space.
diff --git a/test/1000-non-moving-space-stress/src-art/Main.java b/test/1000-non-moving-space-stress/src-art/Main.java
new file mode 100644
index 0000000..18bfdd3
--- /dev/null
+++ b/test/1000-non-moving-space-stress/src-art/Main.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 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 dalvik.system.VMRuntime;
+
+public class Main {
+
+  public static void main(String[] args) throws Exception {
+    VMRuntime runtime = VMRuntime.getRuntime();
+
+    try {
+      int N = 1024 * 1024;
+      int S = 512;
+      for (int n = 0; n < N; ++n) {
+        // Allocate unreachable objects.
+        $noinline$Alloc(runtime);
+        // Allocate an object with a substantial size to increase memory
+        // pressure and eventually trigger non-explicit garbage collection
+        // (explicit garbage collections triggered by java.lang.Runtime.gc()
+        // are always full GCs). Upon garbage collection, the objects
+        // allocated in $noinline$Alloc used to trigger a crash.
+        Object[] moving_array = new Object[S];
+      }
+    } catch (OutOfMemoryError e) {
+      // Stop here.
+    }
+    System.out.println("passed");
+  }
+
+  // When using the Concurrent Copying (CC) collector (default collector),
+  // this method allocates an object in the non-moving space and an object
+  // in the region space, make the former reference the later, and returns
+  // nothing (so that none of these objects are reachable when upon return).
+  static void $noinline$Alloc(VMRuntime runtime) {
+    Object[] non_moving_array = (Object[]) runtime.newNonMovableArray(Object.class, 1);
+    // Small object, unlikely to trigger garbage collection.
+    non_moving_array[0] = new Object();
+  }
+
+}
diff --git a/test/160-read-barrier-stress/src/Main.java b/test/160-read-barrier-stress/src/Main.java
index 7e130ce..5865094 100644
--- a/test/160-read-barrier-stress/src/Main.java
+++ b/test/160-read-barrier-stress/src/Main.java
@@ -14,23 +14,117 @@
  * limitations under the License.
  */
 
+import java.lang.reflect.Field;
+import sun.misc.Unsafe;
+
 public class Main {
-    public static void main(String[] args) {
+    public static void main(String[] args) throws Exception {
+        testFieldReads();
+        testArrayReadsWithConstIndex();
+        testArrayReadsWithNonConstIndex();
+        testGcRoots();
+        testUnsafeGet();
+        testUnsafeCas();
+        testUnsafeCasRegression();
+    }
+
+    public static void testFieldReads() {
         // Initialize local variables for comparison.
         Object f0000 = manyFields.testField0000;
         Object f1024 = manyFields.testField1024;
         Object f4444 = manyFields.testField4444;
         Object f4999 = manyFields.testField4999;
+
+        // Continually check reads from `manyFields` while allocating
+        // over 64MiB memory (with heap size limited to 16MiB), ensuring we run GC and
+        // stress the read barrier implementation if concurrent collector is enabled.
+        for (int i = 0; i != 64 * 1024; ++i) {
+            allocateAtLeast1KiB();
+            ManyFields mf = manyFields;  // Load the volatile `manyFields` once on each iteration.
+            // Test reference field access.
+            assertSameObject(f0000, mf.testField0000);
+            assertDifferentObject(f0000, mf.testField0001);
+            assertSameObject(f1024, mf.testField1024);
+            assertSameObject(f4444, mf.testField4444);
+            assertDifferentObject(f4999, mf.testField4998);
+            assertSameObject(f4999, mf.testField4999);
+        }
+    }
+
+    public static void testArrayReadsWithConstIndex() {
+        // Initialize local variables for comparison.
+        Object f0000 = new Integer(0);
+        Object f1024 = new Integer(1024);
+        Object f4444 = new Integer(4444);
+        Object f4999 = new Integer(4999);
         // Initialize largeArray for comparison.
         largeArray[0] = f0000;
+        Object tmp = new Integer(1);
+        largeArray[1] = tmp;
         largeArray[1024] = f1024;
         largeArray[4444] = f4444;
+        tmp = new Integer(4998);
+        largeArray[4998] = tmp;
         largeArray[4999] = f4999;
+        tmp = null;  // Do not keep a reference to objects in largeArray[1] or largeArray[4998].
+
+        // Continually check reads from `largeArray` with constant indexes while allocating
+        // over 64MiB memory (with heap size limited to 16MiB), ensuring we run GC and
+        // stress the read barrier implementation if concurrent collector is enabled.
+        for (int i = 0; i != 64 * 1024; ++i) {
+            allocateAtLeast1KiB();
+            Object[] la = largeArray;    // Load the volatile `largeArray` once on each iteration.
+            // Test array access with constant index.
+            assertSameObject(f0000, la[0]);
+            assertDifferentObject(f0000, la[1]);
+            assertSameObject(f1024, la[1024]);
+            assertSameObject(f4444, la[4444]);
+            assertDifferentObject(f4999, la[4998]);
+            assertSameObject(f4999, la[4999]);
+        }
+    }
+
+    public static void testArrayReadsWithNonConstIndex() {
+        // Initialize local variables for comparison.
+        Object f0000 = new Integer(0);
+        Object f1024 = new Integer(1024);
+        Object f4444 = new Integer(4444);
+        Object f4999 = new Integer(4999);
+        // Initialize largeArray for comparison.
+        largeArray[0] = f0000;
+        Object tmp = new Integer(1);
+        largeArray[1] = tmp;
+        largeArray[1024] = f1024;
+        largeArray[4444] = f4444;
+        tmp = new Integer(4998);
+        largeArray[4998] = tmp;
+        largeArray[4999] = f4999;
+        tmp = null;  // Do not keep a reference to objects in largeArray[1] or largeArray[4998].
         // Read indexes, they cannot be considered constant because the variables are volatile.
         int i0 = index0;
+        int i1 = index1;
         int i1024 = index1024;
         int i4444 = index4444;
+        int i4998 = index4998;
         int i4999 = index4999;
+
+        // Continually check reads from `largeArray` with non-constant indexes while allocating
+        // over 64MiB memory (with heap size limited to 16MiB), ensuring we run GC and
+        // stress the read barrier implementation if concurrent collector is enabled.
+        for (int i = 0; i != 64 * 1024; ++i) {
+            allocateAtLeast1KiB();
+            Object[] la = largeArray;    // Load the volatile `largeArray` once on each iteration.
+            // Test array access with non-constant index.
+            assertSameObject(f0000, la[i0]);
+            assertDifferentObject(f0000, la[i1]);
+            assertSameObject(f1024, la[i1024]);
+            assertSameObject(f4444, la[i4444]);
+            assertDifferentObject(f4999, la[i4998]);
+            assertSameObject(f4999, la[i4999]);
+        }
+    }
+
+    public static void testGcRoots() {
         // Initialize strings, hide this under a condition based on a volatile field.
         String testString0 = null;
         String testString1 = null;
@@ -50,23 +144,6 @@
         // stress the read barrier implementation if concurrent collector is enabled.
         for (int i = 0; i != 64 * 1024; ++i) {
             allocateAtLeast1KiB();
-            ManyFields mf = manyFields;  // Load the volatile `manyFields` once on each iteration.
-            Object[] la = largeArray;    // Load the volatile `largeArray` once on each iteration.
-            // Test reference field access.
-            assertSameObject(f0000, mf.testField0000);
-            assertSameObject(f1024, mf.testField1024);
-            assertSameObject(f4444, mf.testField4444);
-            assertSameObject(f4999, mf.testField4999);
-            // Test array access with constant index.
-            assertSameObject(f0000, la[0]);
-            assertSameObject(f1024, la[1024]);
-            assertSameObject(f4444, la[4444]);
-            assertSameObject(f4999, la[4999]);
-            // Test array access with non-constant index.
-            assertSameObject(f0000, la[i0]);
-            assertSameObject(f1024, la[i1024]);
-            assertSameObject(f4444, la[i4444]);
-            assertSameObject(f4999, la[i4999]);
             // Test GC roots.
             if (index0 != 12345678) {
               assertSameObject(testString0, "testString0");
@@ -74,14 +151,142 @@
               assertSameObject(testString2, "testString2");
               assertSameObject(testString3, "testString3");
             }
-            // TODO: Stress GC roots (const-string/const-class, kBssEntry/kReferrersClass).
+            // TODO: Stress GC roots (const-class, kBssEntry/kReferrersClass).
+        }
+    }
+
+    public static void testUnsafeGet() throws Exception {
+        // Initialize local variables for comparison.
+        Object f0000 = manyFields.testField0000;
+        Object f1024 = manyFields.testField1024;
+        Object f4444 = manyFields.testField4444;
+        Object f4999 = manyFields.testField4999;
+        // Initialize Unsafe.
+        Unsafe unsafe = getUnsafe();
+        long f0000Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField0000"));
+        long f0001Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField0001"));
+        long f1024Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField1024"));
+        long f4444Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField4444"));
+        long f4998Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField4998"));
+        long f4999Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField4999"));
+
+        // Continually check unsafe.GetObject() while allocating
+        // over 64MiB memory (with heap size limited to 16MiB), ensuring we run GC and
+        // stress the read barrier implementation if concurrent collector is enabled.
+        for (int i = 0; i != 64 * 1024; ++i) {
+            allocateAtLeast1KiB();
+            ManyFields mf = manyFields;  // Load the volatile `manyFields` once on each iteration.
+            // Test Unsafe.getObject().
+            assertSameObject(f0000, unsafe.getObject(mf, f0000Offset));
+            assertDifferentObject(f0000, unsafe.getObject(mf, f0001Offset));
+            assertSameObject(f1024, unsafe.getObject(mf, f1024Offset));
+            assertSameObject(f4444, unsafe.getObject(mf, f4444Offset));
+            assertDifferentObject(f4999, unsafe.getObject(mf, f4998Offset));
+            assertSameObject(f4999, unsafe.getObject(mf, f4999Offset));
+        }
+    }
+
+    public static void testUnsafeCas() throws Exception {
+        // Initialize local variables for comparison.
+        Object f0000 = manyFields.testField0000;
+        Object f1024 = manyFields.testField1024;
+        Object f4444 = manyFields.testField4444;
+        Object f4999 = manyFields.testField4999;
+        // Initialize Unsafe.
+        Unsafe unsafe = getUnsafe();
+        long f0000Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField0000"));
+        long f0001Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField0001"));
+        long f1024Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField1024"));
+        long f4444Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField4444"));
+        long f4998Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField4998"));
+        long f4999Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField4999"));
+
+        // Continually check Unsafe.compareAndSwapObject() while allocating
+        // over 64MiB memory (with heap size limited to 16MiB), ensuring we run GC and
+        // stress the read barrier implementation if concurrent collector is enabled.
+        for (int i = 0; i != 64 * 1024; ++i) {
+            allocateAtLeast1KiB();
+            ManyFields mf = manyFields;  // Load the volatile `manyFields` once on each iteration.
+            // Test Unsafe.compareAndSwapObject().
+            assertEqual(false, unsafe.compareAndSwapObject(mf, f0000Offset, f1024, f4444));
+            assertEqual(false, unsafe.compareAndSwapObject(mf, f0001Offset, f1024, f4444));
+            assertEqual(true, unsafe.compareAndSwapObject(mf, f1024Offset, f1024, f4444));
+            assertEqual(true, unsafe.compareAndSwapObject(mf, f1024Offset, f4444, f1024));
+            assertEqual(false, unsafe.compareAndSwapObject(mf, f1024Offset, f4444, f1024));
+            assertEqual(false, unsafe.compareAndSwapObject(mf, f4444Offset, f1024, f4444));
+            assertEqual(false, unsafe.compareAndSwapObject(mf, f4998Offset, f1024, f4444));
+            assertEqual(false, unsafe.compareAndSwapObject(mf, f4999Offset, f1024, f4444));
+        }
+    }
+
+    public static void testUnsafeCasRegression() throws Exception {
+        // Initialize local variables for comparison.
+        Object f0000 = manyFields.testField0000;
+        // Initialize Unsafe.
+        Unsafe unsafe = getUnsafe();
+        long f0001Offset =
+            unsafe.objectFieldOffset(ManyFields.class.getField("testField0001"));
+
+        // Continually check Unsafe.compareAndSwapObject() while allocating
+        // over 64MiB memory (with heap size limited to 16MiB), ensuring we run GC and
+        // stress the read barrier implementation if concurrent collector is enabled.
+        for (int i = 0; i != 64 * 1024; ++i) {
+            allocateAtLeast1KiB();
+            ManyFields mf = manyFields;  // Load the volatile `manyFields` once on each iteration.
+
+            // With https://android-review.googlesource.com/729224 , the intrinsic could
+            // erroneously clobber r0 on ARM for Baker read barriers because the introspection
+            // entrypoint would read the destination register from bits 12-15 of the instruction
+            // ADD (register, T3) with no shift, assuming to see LDR (immediate, T3), getting
+            // the output register number as 0 instead of the actual destination in bits 8-11.
+            // As a regression test, call a $noinline$ method which returns the result in r0,
+            // do the UnsafeCasObject and check the result of the $noinline$ call (register
+            // allocator should leave the result in r0, clobbered by the broken intrinsic).
+            int x = $noinline$foo();
+            unsafe.compareAndSwapObject(mf, f0001Offset, f0000, null);  // Ignore the result.
+            if (x != 42) {
+              throw new Error();
+            }
+        }
+    }
+
+    public static int $noinline$foo() { return 42; }
+
+    public static void assertDifferentObject(Object lhs, Object rhs) {
+        if (lhs == rhs) {
+            throw new Error("Same objects: " + lhs + " and " + rhs);
         }
     }
 
     public static void assertSameObject(Object lhs, Object rhs) {
-        if (lhs != rhs) {
-            throw new Error("Different objects: " + lhs + " and " + rhs);
-        }
+      if (lhs != rhs) {
+          throw new Error("Different objects: " + lhs + " and " + rhs);
+      }
+  }
+
+    public static void assertEqual(boolean expected, boolean actual) {
+      if (expected != actual) {
+        throw new Error("Expected " + expected +", got " + actual);
+      }
+    }
+
+    public static Unsafe getUnsafe() throws Exception {
+      Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+      Field f = unsafeClass.getDeclaredField("theUnsafe");
+      f.setAccessible(true);
+      return (Unsafe) f.get(null);
     }
 
     public static void allocateAtLeast1KiB() {
@@ -97,8 +302,10 @@
     public static volatile ManyFields manyFields = new ManyFields();
     public static volatile Object[] largeArray = new Object[5000];
     public static volatile int index0 = 0;
+    public static volatile int index1 = 1;
     public static volatile int index1024 = 1024;
     public static volatile int index4444 = 4444;
+    public static volatile int index4998 = 4998;
     public static volatile int index4999 = 4999;
 
     // We shall retain some allocated memory and release old allocations
diff --git a/test/305-other-fault-handler/fault_handler.cc b/test/305-other-fault-handler/fault_handler.cc
index 211d142..093a93f 100644
--- a/test/305-other-fault-handler/fault_handler.cc
+++ b/test/305-other-fault-handler/fault_handler.cc
@@ -33,7 +33,7 @@
  public:
   explicit TestFaultHandler(FaultManager* manager)
       : FaultHandler(manager),
-        map_error_(""),
+        map_error_(),
         target_map_(MemMap::MapAnonymous("test-305-mmap",
                                          /* addr */ nullptr,
                                          /* byte_count */ kPageSize,
@@ -43,7 +43,7 @@
                                          /* error_msg */ &map_error_,
                                          /* use_ashmem */ false)),
         was_hit_(false) {
-    CHECK(target_map_ != nullptr) << "Unable to create segfault target address " << map_error_;
+    CHECK(target_map_.IsValid()) << "Unable to create segfault target address " << map_error_;
     manager_->AddHandler(this, /*in_generated_code*/false);
   }
 
@@ -59,16 +59,16 @@
     was_hit_ = true;
 
     LOG(INFO) << "SEGV Caught. mprotecting map.";
-    CHECK(target_map_->Protect(PROT_READ | PROT_WRITE)) << "Failed to mprotect R/W";
+    CHECK(target_map_.Protect(PROT_READ | PROT_WRITE)) << "Failed to mprotect R/W";
     LOG(INFO) << "Setting value to be read.";
     *GetTargetPointer() = kDataValue;
     LOG(INFO) << "Changing prot to be read-only.";
-    CHECK(target_map_->Protect(PROT_READ)) << "Failed to mprotect R-only";
+    CHECK(target_map_.Protect(PROT_READ)) << "Failed to mprotect R-only";
     return true;
   }
 
   void CauseSegfault() {
-    CHECK_EQ(target_map_->GetProtect(), PROT_NONE);
+    CHECK_EQ(target_map_.GetProtect(), PROT_NONE);
 
     // This will segfault. The handler should deal with it though and we will get a value out of it.
     uint32_t data = *GetTargetPointer();
@@ -78,19 +78,19 @@
 
     CHECK(was_hit_);
     CHECK_EQ(data, kDataValue) << "Unexpected read value from mmap";
-    CHECK_EQ(target_map_->GetProtect(), PROT_READ);
+    CHECK_EQ(target_map_.GetProtect(), PROT_READ);
     LOG(INFO) << "Success!";
   }
 
  private:
   uint32_t* GetTargetPointer() {
-    return reinterpret_cast<uint32_t*>(target_map_->Begin() + 8);
+    return reinterpret_cast<uint32_t*>(target_map_.Begin() + 8);
   }
 
   static constexpr uint32_t kDataValue = 0xDEADBEEF;
 
   std::string map_error_;
-  std::unique_ptr<MemMap> target_map_;
+  MemMap target_map_;
   bool was_hit_;
 };
 
diff --git a/test/458-checker-instruct-simplification/src/Main.java b/test/458-checker-instruct-simplification/src/Main.java
index 9e714f5..5ffb75f 100644
--- a/test/458-checker-instruct-simplification/src/Main.java
+++ b/test/458-checker-instruct-simplification/src/Main.java
@@ -1070,7 +1070,7 @@
   /// CHECK-DAG:     <<Phi1:i\d+>>     Phi [<<Const0>>,<<Const1>>]
   /// CHECK-DAG:     <<Cond:z\d+>>     Equal [<<Phi1>>,<<Const2>>]
   /// CHECK-DAG:                       If [<<Cond>>]
-  /// CHECK-DAG:     <<Phi2:i\d+>>     Phi [<<Const1>>,<<Const0>>]
+  /// CHECK-DAG:     <<Phi2:i\d+>>     Phi [<<Const0>>,<<Const1>>]
   /// CHECK-DAG:                       Return [<<Phi2>>]
 
   /// CHECK-START: boolean Main.$noinline$EqualBoolVsIntConst(boolean) dead_code_elimination$after_inlining (after)
@@ -1096,7 +1096,7 @@
   /// CHECK-DAG:     <<Phi1:i\d+>>     Phi [<<Const0>>,<<Const1>>]
   /// CHECK-DAG:     <<Cond:z\d+>>     NotEqual [<<Phi1>>,<<Const2>>]
   /// CHECK-DAG:                       If [<<Cond>>]
-  /// CHECK-DAG:     <<Phi2:i\d+>>     Phi [<<Const1>>,<<Const0>>]
+  /// CHECK-DAG:     <<Phi2:i\d+>>     Phi [<<Const0>>,<<Const1>>]
   /// CHECK-DAG:                       Return [<<Phi2>>]
 
   /// CHECK-START: boolean Main.$noinline$NotEqualBoolVsIntConst(boolean) dead_code_elimination$after_inlining (after)
diff --git a/test/563-checker-fakestring/smali/TestCase.smali b/test/563-checker-fakestring/smali/TestCase.smali
index 9d10bd7..0fe39ee 100644
--- a/test/563-checker-fakestring/smali/TestCase.smali
+++ b/test/563-checker-fakestring/smali/TestCase.smali
@@ -305,3 +305,35 @@
    return-object v0
 
 .end method
+
+## CHECK-START: java.lang.String TestCase.loopAndStringInitAndPhi(byte[], boolean) register (after)
+## CHECK:                        NewInstance
+## CHECK-NOT:                    NewInstance
+## CHECK-DAG:   <<Invoke1:l\d+>> InvokeStaticOrDirect method_name:java.lang.String.<init>
+## CHECK-DAG:   <<Invoke2:l\d+>> InvokeStaticOrDirect method_name:java.lang.String.<init>
+## CHECK-DAG:   <<Phi:l\d+>>     Phi [<<Invoke2>>,<<Invoke1>>]
+## CHECK-DAG:                    Return [<<Phi>>]
+.method public static loopAndStringInitAndPhi([BZ)Ljava/lang/String;
+   .registers 4
+
+   if-nez p1, :allocate_other
+   new-instance v0, Ljava/lang/String;
+
+   # Loop
+   :loop_header
+   if-eqz p1, :loop_exit
+   goto :loop_header
+
+   :loop_exit
+   const-string v1, "UTF8"
+   invoke-direct {v0, p0, v1}, Ljava/lang/String;-><init>([BLjava/lang/String;)V
+   goto : exit
+
+   :allocate_other
+   const-string v1, "UTF8"
+   new-instance v0, Ljava/lang/String;
+   invoke-direct {v0, p0, v1}, Ljava/lang/String;-><init>([BLjava/lang/String;)V
+   :exit
+   return-object v0
+
+.end method
diff --git a/test/563-checker-fakestring/src/Main.java b/test/563-checker-fakestring/src/Main.java
index 3639d59..df9e9dc 100644
--- a/test/563-checker-fakestring/src/Main.java
+++ b/test/563-checker-fakestring/src/Main.java
@@ -113,7 +113,6 @@
       result = (String) m.invoke(null, new Object[] { testData, false });
       assertEqual(testString, result);
     }
-
     {
       Method m = c.getMethod(
           "deoptimizeNewInstanceAfterLoop", int[].class, byte[].class, int.class);
@@ -127,6 +126,13 @@
         }
       }
     }
+    {
+      Method m = c.getMethod("loopAndStringInitAndPhi", byte[].class, boolean.class);
+      String result = (String) m.invoke(null, new Object[] { testData, true });
+      assertEqual(testString, result);
+      result = (String) m.invoke(null, new Object[] { testData, false });
+      assertEqual(testString, result);
+    }
   }
 
   public static boolean doThrow = false;
diff --git a/test/618-checker-induction/src/Main.java b/test/618-checker-induction/src/Main.java
index 1460725..dd76e41 100644
--- a/test/618-checker-induction/src/Main.java
+++ b/test/618-checker-induction/src/Main.java
@@ -290,7 +290,7 @@
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
   /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:<<Loop1>>
   /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:<<Loop1>>
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: int Main.closedFormNested() loop_optimization (after)
   /// CHECK-NOT:               Phi
@@ -313,7 +313,7 @@
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
   /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:<<Loop1>>
   /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:<<Loop1>>
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: int Main.closedFormNestedAlt() loop_optimization (after)
   /// CHECK-NOT:               Phi
@@ -547,7 +547,7 @@
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop1>>      outer_loop:none
   /// CHECK-DAG: <<Phi3:i\d+>> Phi               loop:<<Loop2:B\d+>> outer_loop:none
   /// CHECK-DAG: <<Phi4:i\d+>> Phi               loop:<<Loop2>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi4>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi3>>] loop:none
   /// CHECK-EVAL: "<<Loop1>>" != "<<Loop2>>"
   //
   /// CHECK-START: int Main.closedFeed() loop_optimization (after)
@@ -634,7 +634,7 @@
   /// CHECK-START: boolean Main.periodicBoolIdiom1() loop_optimization (before)
   /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom1() loop_optimization (after)
   /// CHECK-NOT:               Phi
@@ -653,7 +653,7 @@
   /// CHECK-START: boolean Main.periodicBoolIdiom2() loop_optimization (before)
   /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom2() loop_optimization (after)
   /// CHECK-NOT:               Phi
@@ -672,7 +672,7 @@
   /// CHECK-START: boolean Main.periodicBoolIdiom3() loop_optimization (before)
   /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom3() loop_optimization (after)
   /// CHECK-NOT:               Phi
@@ -691,7 +691,7 @@
   /// CHECK-START: boolean Main.periodicBoolIdiom1N(boolean, int) loop_optimization (before)
   /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom1N(boolean, int) loop_optimization (after)
   /// CHECK-NOT:               Phi
@@ -705,7 +705,7 @@
   /// CHECK-START: boolean Main.periodicBoolIdiom2N(boolean, int) loop_optimization (before)
   /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom2N(boolean, int) loop_optimization (after)
   /// CHECK-NOT:               Phi
@@ -719,7 +719,7 @@
   /// CHECK-START: boolean Main.periodicBoolIdiom3N(boolean, int) loop_optimization (before)
   /// CHECK-DAG: <<Phi1:i\d+>> Phi               loop:<<Loop:B\d+>> outer_loop:none
   /// CHECK-DAG: <<Phi2:i\d+>> Phi               loop:<<Loop>>      outer_loop:none
-  /// CHECK-DAG:               Return [<<Phi1>>] loop:none
+  /// CHECK-DAG:               Return [<<Phi2>>] loop:none
   //
   /// CHECK-START: boolean Main.periodicBoolIdiom3N(boolean, int) loop_optimization (after)
   /// CHECK-NOT:               Phi
diff --git a/test/635-checker-arm64-volatile-load-cc/src/Main.java b/test/635-checker-arm64-volatile-load-cc/src/Main.java
index 6a26e94..89fad4c 100644
--- a/test/635-checker-arm64-volatile-load-cc/src/Main.java
+++ b/test/635-checker-arm64-volatile-load-cc/src/Main.java
@@ -255,9 +255,9 @@
 
   /// CHECK-START-ARM64: void Main.testStaticVolatileFieldGetWithLargeOffset() disassembly (after)
   /// CHECK:               StaticFieldGet
-  /// CHECK:                 mov x17, #<<Offset:0x[0-9a-f]{4}>>
-  /// CHECK:                 add x16, {{x\d+}}, x17
-  /// CHECK:                 ldar {{w\d+}}, [x16]
+  /// CHECK:                 mov <<Kind:x|w>><<Temp1:\d+>>, #<<Offset:0x[0-9a-f]{4}>>
+  /// CHECK:                 add <<Kind>><<Temp2:\d+>>, <<Kind>>{{\d+}}, <<Kind>><<Temp1>>
+  /// CHECK:                 ldar {{w\d+}}, [x<<Temp2>>]
   static void testStaticVolatileFieldGetWithLargeOffset() {
     // The offset of this static field cannot be encoded as an immediate on ARM64.
     Object s = s999;
@@ -265,9 +265,9 @@
 
   /// CHECK-START-ARM64: void Main.testInstanceVolatileFieldGetWithLargeOffset() disassembly (after)
   /// CHECK:               InstanceFieldGet
-  /// CHECK:                 mov x17, #<<Offset:0x[0-9a-f]{4}>>
-  /// CHECK:                 add x16, {{x\d+}}, x17
-  /// CHECK:                 ldar {{w\d+}}, [x16]
+  /// CHECK:                 mov <<Kind:x|w>><<Temp1:\d+>>, #<<Offset:0x[0-9a-f]{4}>>
+  /// CHECK:                 add <<Kind>><<Temp2:\d+>>, <<Kind>>{{\d+}}, <<Kind>><<Temp1>>
+  /// CHECK:                 ldar {{w\d+}}, [x<<Temp2>>]
   void testInstanceVolatileFieldGetWithLargeOffset() {
     // The offset of this instance field cannot be encoded as an immediate on ARM64.
     Object i = i1029;
diff --git a/test/669-checker-break/src/Main.java b/test/669-checker-break/src/Main.java
index e59061b..c40e4a6 100644
--- a/test/669-checker-break/src/Main.java
+++ b/test/669-checker-break/src/Main.java
@@ -232,8 +232,8 @@
   /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0                  loop:none
   /// CHECK-DAG: <<One:i\d+>>  IntConstant 1                  loop:none
   /// CHECK-DAG: <<Nil:l\d+>>  NullCheck [<<Par>>]                 loop:none
-  /// CHECK-DAG: <<Phi:i\d+>>  Phi [<<Zero>>,<<AddI:i\d+>>]   loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Red:i\d+>>  Phi [<<Zero>>,<<RedI:i\d+>>]   loop:<<Loop>>      outer_loop:none
+  /// CHECK-DAG: <<Red:i\d+>>  Phi [<<Zero>>,<<RedI:i\d+>>]   loop:<<Loop:B\d+>> outer_loop:none
+  /// CHECK-DAG: <<Phi:i\d+>>  Phi [<<Zero>>,<<AddI:i\d+>>]   loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG: <<Bnd:i\d+>>  BoundsCheck [<<Phi>>,{{i\d+}}] loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG: <<Get:i\d+>>  ArrayGet [<<Nil>>,<<Bnd>>]     loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG: <<RedI>>      Add [<<Red>>,<<Get>>]          loop:<<Loop>>      outer_loop:none
@@ -248,8 +248,8 @@
   /// CHECK-DAG: <<Zero:i\d+>> IntConstant 0                      loop:none
   /// CHECK-DAG: <<One:i\d+>>  IntConstant 1                      loop:none
   /// CHECK-DAG: <<Nil:l\d+>>  NullCheck [<<Par>>]                loop:none
-  /// CHECK-DAG: <<Phi:i\d+>>  Phi [<<Zero>>,<<AddI:i\d+>>]       loop:<<Loop:B\d+>> outer_loop:none
-  /// CHECK-DAG: <<Red:i\d+>>  Phi [<<Zero>>,<<RedI:i\d+>>]       loop:<<Loop>>      outer_loop:none
+  /// CHECK-DAG: <<Red:i\d+>>  Phi [<<Zero>>,<<RedI:i\d+>>]       loop:<<Loop:B\d+>> outer_loop:none
+  /// CHECK-DAG: <<Phi:i\d+>>  Phi [<<Zero>>,<<AddI:i\d+>>]       loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG: <<LE:z\d+>>   LessThanOrEqual [<<Phi>>,{{i\d+}}] loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG:               If [<<LE>>]                        loop:<<Loop>>      outer_loop:none
   /// CHECK-DAG: <<Bnd:i\d+>>  BoundsCheck [<<Phi>>,{{i\d+}}]     loop:<<Loop>>      outer_loop:none
diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc
index 04c3fbf..96754c3 100644
--- a/test/674-hiddenapi/hiddenapi.cc
+++ b/test/674-hiddenapi/hiddenapi.cc
@@ -63,8 +63,8 @@
 
 static jobject NewInstance(JNIEnv* env, jclass klass) {
   jmethodID constructor = env->GetMethodID(klass, "<init>", "()V");
-  if (constructor == NULL) {
-    return NULL;
+  if (constructor == nullptr) {
+    return nullptr;
   }
   return env->NewObject(klass, constructor);
 }
@@ -74,7 +74,7 @@
   ScopedUtfChars utf_name(env, name);
   jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I")
                              : env->GetFieldID(klass, utf_name.c_str(), "I");
-  if (field == NULL) {
+  if (field == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
@@ -87,7 +87,7 @@
   ScopedUtfChars utf_name(env, name);
   jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I")
                              : env->GetFieldID(klass, utf_name.c_str(), "I");
-  if (field == NULL) {
+  if (field == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
@@ -95,7 +95,7 @@
     env->GetStaticIntField(klass, field);
   } else {
     jobject obj = NewInstance(env, klass);
-    if (obj == NULL) {
+    if (obj == nullptr) {
       env->ExceptionDescribe();
       env->ExceptionClear();
       return JNI_FALSE;
@@ -117,7 +117,7 @@
   ScopedUtfChars utf_name(env, name);
   jfieldID field = is_static ? env->GetStaticFieldID(klass, utf_name.c_str(), "I")
                              : env->GetFieldID(klass, utf_name.c_str(), "I");
-  if (field == NULL) {
+  if (field == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
@@ -125,7 +125,7 @@
     env->SetStaticIntField(klass, field, 42);
   } else {
     jobject obj = NewInstance(env, klass);
-    if (obj == NULL) {
+    if (obj == nullptr) {
       env->ExceptionDescribe();
       env->ExceptionClear();
       return JNI_FALSE;
@@ -147,7 +147,7 @@
   ScopedUtfChars utf_name(env, name);
   jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I")
                                : env->GetMethodID(klass, utf_name.c_str(), "()I");
-  if (method == NULL) {
+  if (method == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
@@ -160,7 +160,7 @@
   ScopedUtfChars utf_name(env, name);
   jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I")
                                : env->GetMethodID(klass, utf_name.c_str(), "()I");
-  if (method == NULL) {
+  if (method == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
@@ -169,7 +169,7 @@
     env->CallStaticIntMethodA(klass, method, nullptr);
   } else {
     jobject obj = NewInstance(env, klass);
-    if (obj == NULL) {
+    if (obj == nullptr) {
       env->ExceptionDescribe();
       env->ExceptionClear();
       return JNI_FALSE;
@@ -191,7 +191,7 @@
   ScopedUtfChars utf_name(env, name);
   jmethodID method = is_static ? env->GetStaticMethodID(klass, utf_name.c_str(), "()I")
                                : env->GetMethodID(klass, utf_name.c_str(), "()I");
-  if (method == NULL) {
+  if (method == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
@@ -200,7 +200,7 @@
     env->CallStaticIntMethod(klass, method);
   } else {
     jobject obj = NewInstance(env, klass);
-    if (obj == NULL) {
+    if (obj == nullptr) {
       env->ExceptionDescribe();
       env->ExceptionClear();
       return JNI_FALSE;
@@ -224,7 +224,7 @@
     JNIEnv* env, jclass, jclass klass, jstring args) {
   ScopedUtfChars utf_args(env, args);
   jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str());
-  if (constructor == NULL) {
+  if (constructor == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
@@ -236,7 +236,7 @@
     JNIEnv* env, jclass, jclass klass, jstring args) {
   ScopedUtfChars utf_args(env, args);
   jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str());
-  if (constructor == NULL) {
+  if (constructor == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
@@ -261,7 +261,7 @@
     JNIEnv* env, jclass, jclass klass, jstring args) {
   ScopedUtfChars utf_args(env, args);
   jmethodID constructor = env->GetMethodID(klass, "<init>", utf_args.c_str());
-  if (constructor == NULL) {
+  if (constructor == nullptr) {
     env->ExceptionClear();
     return JNI_FALSE;
   }
diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt
index 75ee112..b263308 100644
--- a/test/988-method-trace/expected.txt
+++ b/test/988-method-trace/expected.txt
@@ -70,6 +70,27 @@
 fibonacci(5)=5
 .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
 <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
+=> art.Test988$NativeOp()
+.=> public java.lang.Object()
+.<= public java.lang.Object() -> <null: null>
+<= art.Test988$NativeOp() -> <null: null>
+=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator)
+.=> public int art.Test988$NativeOp.applyAsInt(int)
+..=> static int art.Test988.nativeFibonacci(int)
+..<= static int art.Test988.nativeFibonacci(int) -> <class java.lang.Integer: 5>
+.<= public int art.Test988$NativeOp.applyAsInt(int) -> <class java.lang.Integer: 5>
+.=> public art.Test988$FibResult(java.lang.String,int,int)
+..=> public java.lang.Object()
+..<= public java.lang.Object() -> <null: null>
+.<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
+.=> public boolean java.util.ArrayList.add(java.lang.Object)
+..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+fibonacci(5)=5
+.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
 => art.Test988$IterOp()
 .=> public java.lang.Object()
 .<= public java.lang.Object() -> <null: null>
@@ -147,8 +168,8 @@
 .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0
 	art.Test988.iter_fibonacci(Test988.java:255)
 	art.Test988$IterOp.applyAsInt(Test988.java:250)
-	art.Test988.doFibTest(Test988.java:378)
-	art.Test988.run(Test988.java:336)
+	art.Test988.doFibTest(Test988.java:388)
+	art.Test988.run(Test988.java:344)
 	<additional hidden frames>
 >
 ....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -167,8 +188,8 @@
 fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0
 	art.Test988.iter_fibonacci(Test988.java:255)
 	art.Test988$IterOp.applyAsInt(Test988.java:250)
-	art.Test988.doFibTest(Test988.java:378)
-	art.Test988.run(Test988.java:336)
+	art.Test988.doFibTest(Test988.java:388)
+	art.Test988.run(Test988.java:344)
 	<additional hidden frames>
 
 .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
@@ -250,8 +271,8 @@
 .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0
 	art.Test988.fibonacci(Test988.java:277)
 	art.Test988$RecurOp.applyAsInt(Test988.java:272)
-	art.Test988.doFibTest(Test988.java:378)
-	art.Test988.run(Test988.java:337)
+	art.Test988.doFibTest(Test988.java:388)
+	art.Test988.run(Test988.java:345)
 	<additional hidden frames>
 >
 ....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -270,8 +291,53 @@
 fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0
 	art.Test988.fibonacci(Test988.java:277)
 	art.Test988$RecurOp.applyAsInt(Test988.java:272)
-	art.Test988.doFibTest(Test988.java:378)
-	art.Test988.run(Test988.java:337)
+	art.Test988.doFibTest(Test988.java:388)
+	art.Test988.run(Test988.java:345)
+	<additional hidden frames>
+
+.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
+=> art.Test988$NativeOp()
+.=> public java.lang.Object()
+.<= public java.lang.Object() -> <null: null>
+<= art.Test988$NativeOp() -> <null: null>
+=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator)
+.=> public int art.Test988$NativeOp.applyAsInt(int)
+..=> static int art.Test988.nativeFibonacci(int)
+...=> public java.lang.Error(java.lang.String)
+....=> public java.lang.Throwable(java.lang.String)
+.....=> public java.lang.Object()
+.....<= public java.lang.Object() -> <null: null>
+.....=> public static final java.util.List java.util.Collections.emptyList()
+.....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []>
+.....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace()
+......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
+......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
+.....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: bad argument
+	art.Test988.nativeFibonacci(Native Method)
+	art.Test988$NativeOp.applyAsInt(Test988.java:287)
+	art.Test988.doFibTest(Test988.java:388)
+	art.Test988.run(Test988.java:346)
+	<additional hidden frames>
+>
+....<= public java.lang.Throwable(java.lang.String) -> <null: null>
+...<= public java.lang.Error(java.lang.String) -> <null: null>
+..<= static int art.Test988.nativeFibonacci(int) EXCEPTION
+.<= public int art.Test988$NativeOp.applyAsInt(int) EXCEPTION
+.=> public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable)
+..=> public java.lang.Object()
+..<= public java.lang.Object() -> <null: null>
+.<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
+.=> public boolean java.util.ArrayList.add(java.lang.Object)
+..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+fibonacci(-19) -> java.lang.Error: bad argument
+	art.Test988.nativeFibonacci(Native Method)
+	art.Test988$NativeOp.applyAsInt(Test988.java:287)
+	art.Test988.doFibTest(Test988.java:388)
+	art.Test988.run(Test988.java:346)
 	<additional hidden frames>
 
 .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java
index 5720d1d..075e075 100644
--- a/test/988-method-trace/src/art/Test988.java
+++ b/test/988-method-trace/src/art/Test988.java
@@ -282,6 +282,13 @@
         }
     }
 
+    static final class NativeOp implements IntUnaryOperator {
+      public int applyAsInt(int x) {
+        return nativeFibonacci(x);
+      }
+    }
+    static native int nativeFibonacci(int n);
+
     static final class TestRunnableInvokeHandler implements InvocationHandler {
       public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
         return null;
@@ -333,8 +340,10 @@
             Thread.currentThread());
         doFibTest(30, new IterOp());
         doFibTest(5, new RecurOp());
+        doFibTest(5, new NativeOp());
         doFibTest(-19, new IterOp());
         doFibTest(-19, new RecurOp());
+        doFibTest(-19, new NativeOp());
 
         runnable.run();
 
@@ -358,6 +367,7 @@
       ArrayList.class.toString();
       RecurOp.class.toString();
       IterOp.class.toString();
+      NativeOp.class.toString();
       StringBuilder.class.toString();
       Runnable.class.toString();
       TestRunnableInvokeHandler.class.toString();
diff --git a/test/988-method-trace/trace_fib.cc b/test/988-method-trace/trace_fib.cc
new file mode 100644
index 0000000..682f273
--- /dev/null
+++ b/test/988-method-trace/trace_fib.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+
+namespace art {
+namespace Test988MethodTrace {
+
+extern "C" JNIEXPORT jint JNICALL Java_art_Test988_nativeFibonacci(JNIEnv* env, jclass, jint n) {
+  if (n < 0) {
+    env->ThrowNew(env->FindClass("java/lang/Error"), "bad argument");
+    return -1;
+  } else if (n == 0) {
+    return 0;
+  }
+  jint x = 1;
+  jint y = 1;
+  for (jint i = 3; i <= n; i++) {
+    jint z = x + y;
+    x = y;
+    y = z;
+  }
+  return y;
+}
+
+}  // namespace Test988MethodTrace
+}  // namespace art
+
diff --git a/test/Android.bp b/test/Android.bp
index a3de382..e265651 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -262,6 +262,7 @@
         "984-obsolete-invoke/obsolete_invoke.cc",
         "986-native-method-bind/native_bind.cc",
         "987-agent-bind/agent_bind.cc",
+        "988-method-trace/trace_fib.cc",
         "989-method-trace-throw/method_trace.cc",
         "991-field-trace-2/field_trace.cc",
         "992-source-data/source_file.cc",
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 9378bff..a280f85 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -986,6 +986,7 @@
                   "678-quickening",
                   "679-locks",
                   "999-redefine-hiddenapi",
+                  "1000-non-moving-space-stress",
                   "1951-monitor-enter-no-suspend"],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]
diff --git a/test/testrunner/target_config.py b/test/testrunner/target_config.py
index d5a4d45..47b1e4e 100644
--- a/test/testrunner/target_config.py
+++ b/test/testrunner/target_config.py
@@ -265,13 +265,11 @@
             'ART_USE_READ_BARRIER' : 'false'
         }
     },
-    # TODO: Remove this configuration, when the ART Buildbot is no
-    # longer using it for 'host-x86_64-valgrind'.
-    'art-gtest-valgrind64': {
-      # Disabled: Valgrind is no longer supported.
-      # 'make' : 'valgrind-test-art-host64',
-        'env': {
-            'ART_USE_READ_BARRIER' : 'false'
+    'art-generational-cc': {
+        'make' : 'test-art-host-gtest',
+        'run-test' : [],
+        'env' : {
+            'ART_USE_GENERATIONAL_CC' : 'true'
         }
     },
 
diff --git a/tools/art b/tools/art
index 62df7eb..aebf5a6 100644
--- a/tools/art
+++ b/tools/art
@@ -359,7 +359,7 @@
 fi
 
 if [ "$PERF" != "" ]; then
-  LAUNCH_WRAPPER="perf record -g -o $ANDROID_DATA/perf.data -e cycles:u $LAUNCH_WRAPPER"
+  LAUNCH_WRAPPER="perf record -g --call-graph dwarf -F 10000 -o $ANDROID_DATA/perf.data -e cycles:u $LAUNCH_WRAPPER"
   EXTRA_OPTIONS+=(-Xcompiler-option --generate-debug-info)
 fi
 
diff --git a/tools/art_verifier/Android.bp b/tools/art_verifier/Android.bp
index af9744f..afd52fb 100644
--- a/tools/art_verifier/Android.bp
+++ b/tools/art_verifier/Android.bp
@@ -36,6 +36,9 @@
                 "libtombstoned_client_static",
             ],
         },
+        darwin: {
+            enabled: false,
+        },
     },
 }
 
diff --git a/tools/class2greylist/Android.bp b/tools/class2greylist/Android.bp
index 7b1233b..1e3cdff 100644
--- a/tools/class2greylist/Android.bp
+++ b/tools/class2greylist/Android.bp
@@ -20,6 +20,7 @@
   static_libs: [
     "commons-cli-1.2",
     "apache-bcel",
+    "guava",
   ],
 }
 
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationContext.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationContext.java
new file mode 100644
index 0000000..eb54a33
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationContext.java
@@ -0,0 +1,66 @@
+package com.android.class2greylist;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.classfile.FieldOrMethod;
+import org.apache.bcel.classfile.JavaClass;
+
+import java.util.Locale;
+
+/**
+ * Encapsulates context for a single annotation on a class member.
+ */
+public class AnnotationContext {
+
+    public final Status status;
+    public final FieldOrMethod member;
+    public final JavaClass definingClass;
+    public final String signatureFormatString;
+
+    public AnnotationContext(
+            Status status,
+            FieldOrMethod member,
+            JavaClass definingClass,
+            String signatureFormatString) {
+        this.status = status;
+        this.member = member;
+        this.definingClass = definingClass;
+        this.signatureFormatString = signatureFormatString;
+    }
+
+    /**
+     * @return the full descriptor of enclosing class.
+     */
+    public String getClassDescriptor() {
+        // JavaClass.getName() returns the Java-style name (with . not /), so we must fetch
+        // the original class name from the constant pool.
+        return definingClass.getConstantPool().getConstantString(
+                definingClass.getClassNameIndex(), Const.CONSTANT_Class);
+    }
+
+    /**
+     * @return the full descriptor of this member, in the format expected in
+     * the greylist.
+     */
+    public String getMemberDescriptor() {
+        return String.format(Locale.US, signatureFormatString,
+                getClassDescriptor(), member.getName(), member.getSignature());
+    }
+
+    /**
+     * Report an error in this context. The final error message will include
+     * the class and member names, and the source file name.
+     */
+    public void reportError(String message, Object... args) {
+        StringBuilder error = new StringBuilder();
+        error.append(definingClass.getSourceFileName())
+                .append(": ")
+                .append(definingClass.getClassName())
+                .append(".")
+                .append(member.getName())
+                .append(": ")
+                .append(String.format(Locale.US, message, args));
+
+        status.error(error.toString());
+    }
+
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java
new file mode 100644
index 0000000..92d2ab6
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationHandler.java
@@ -0,0 +1,11 @@
+package com.android.class2greylist;
+
+import org.apache.bcel.classfile.AnnotationEntry;
+
+/**
+ * Interface for an annotation handler, which handle individual annotations on
+ * class members.
+ */
+public interface AnnotationHandler {
+    void handleAnnotation(AnnotationEntry annotation, AnnotationContext context);
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java
index 6b9ef16..b805b30 100644
--- a/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java
+++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java
@@ -16,41 +16,40 @@
 
 package com.android.class2greylist;
 
-import org.apache.bcel.Const;
 import org.apache.bcel.classfile.AnnotationEntry;
 import org.apache.bcel.classfile.DescendingVisitor;
-import org.apache.bcel.classfile.ElementValuePair;
 import org.apache.bcel.classfile.EmptyVisitor;
 import org.apache.bcel.classfile.Field;
 import org.apache.bcel.classfile.FieldOrMethod;
 import org.apache.bcel.classfile.JavaClass;
 import org.apache.bcel.classfile.Method;
 
-import java.util.Locale;
+import java.util.Map;
 
 /**
- * Visits a JavaClass instance and pulls out all members annotated with a
- * specific annotation. The signatures of such members are passed to {@link
- * Status#greylistEntry(String)}. Any errors result in a call to {@link
- * Status#error(String)}.
- *
- * If the annotation has a property "expectedSignature" the generated signature
- * will be verified against the one specified there. If it differs, an error
- * will be generated.
+ * Visits a JavaClass instance and passes any annotated members to a {@link AnnotationHandler}
+ * according to the map provided.
  */
 public class AnnotationVisitor extends EmptyVisitor {
 
-    private static final String EXPECTED_SIGNATURE = "expectedSignature";
-
     private final JavaClass mClass;
-    private final String mAnnotationType;
     private final Status mStatus;
     private final DescendingVisitor mDescendingVisitor;
+    private final Map<String, AnnotationHandler> mAnnotationHandlers;
 
-    public AnnotationVisitor(JavaClass clazz, String annotation, Status d) {
+    /**
+     * Creates a visitor for a class.
+     *
+     * @param clazz Class to visit
+     * @param status For reporting debug information
+     * @param handlers Map of {@link AnnotationHandler}. The keys should be annotation names, as
+     *                 class descriptors.
+     */
+    public AnnotationVisitor(JavaClass clazz, Status status,
+            Map<String, AnnotationHandler> handlers) {
         mClass = clazz;
-        mAnnotationType = annotation;
-        mStatus = d;
+        mStatus = status;
+        mAnnotationHandlers = handlers;
         mDescendingVisitor = new DescendingVisitor(clazz, this);
     }
 
@@ -59,13 +58,6 @@
         mDescendingVisitor.visit();
     }
 
-    private static String getClassDescriptor(JavaClass clazz) {
-        // JavaClass.getName() returns the Java-style name (with . not /), so we must fetch
-        // the original class name from the constant pool.
-        return clazz.getConstantPool().getConstantString(
-                clazz.getClassNameIndex(), Const.CONSTANT_Class);
-    }
-
     @Override
     public void visitMethod(Method method) {
         visitMember(method, "L%s;->%s%s");
@@ -77,47 +69,15 @@
     }
 
     private void visitMember(FieldOrMethod member, String signatureFormatString) {
-        JavaClass definingClass = (JavaClass) mDescendingVisitor.predecessor();
         mStatus.debug("Visit member %s : %s", member.getName(), member.getSignature());
+        AnnotationContext context = new AnnotationContext(mStatus, member,
+                (JavaClass) mDescendingVisitor.predecessor(), signatureFormatString);
         for (AnnotationEntry a : member.getAnnotationEntries()) {
-            if (mAnnotationType.equals(a.getAnnotationType())) {
-                mStatus.debug("Member has annotation %s", mAnnotationType);
-                boolean bridge = (member.getAccessFlags() & Const.ACC_BRIDGE) != 0;
-                if (bridge) {
-                    mStatus.debug("Member is a bridge", mAnnotationType);
-                }
-                String signature = String.format(Locale.US, signatureFormatString,
-                        getClassDescriptor(definingClass), member.getName(), member.getSignature());
-                for (ElementValuePair property : a.getElementValuePairs()) {
-                    switch (property.getNameString()) {
-                        case EXPECTED_SIGNATURE:
-                            String expected = property.getValue().stringifyValue();
-                            // Don't enforce for bridge methods; they're generated so won't match.
-                            if (!bridge && !signature.equals(expected)) {
-                                error(definingClass, member,
-                                        "Expected signature does not match generated:\n"
-                                                + "Expected:  %s\n"
-                                                + "Generated: %s", expected, signature);
-                            }
-                            break;
-                    }
-                }
-                mStatus.greylistEntry(signature);
+            if (mAnnotationHandlers.containsKey(a.getAnnotationType())) {
+                mStatus.debug("Member has annotation %s for which we have a handler",
+                        a.getAnnotationType());
+                mAnnotationHandlers.get(a.getAnnotationType()).handleAnnotation(a, context);
             }
         }
     }
-
-    private void error(JavaClass clazz, FieldOrMethod member, String message, Object... args) {
-        StringBuilder error = new StringBuilder();
-        error.append(clazz.getSourceFileName())
-                .append(": ")
-                .append(clazz.getClassName())
-                .append(".")
-                .append(member.getName())
-                .append(": ")
-                .append(String.format(Locale.US, message, args));
-
-        mStatus.error(error.toString());
-    }
-
 }
diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
index 3e9e320..694a5f4 100644
--- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
+++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java
@@ -16,6 +16,12 @@
 
 package com.android.class2greylist;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.GnuParser;
@@ -23,9 +29,16 @@
 import org.apache.commons.cli.OptionBuilder;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
-import org.apache.commons.cli.PatternOptionBuilder;
 
+import java.io.File;
 import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Build time tool for extracting a list of members from jar files that have the @UsedByApps
@@ -33,11 +46,41 @@
  */
 public class Class2Greylist {
 
-    private static final String ANNOTATION_TYPE = "Landroid/annotation/UnsupportedAppUsage;";
+    private static final String GREYLIST_ANNOTATION = "Landroid/annotation/UnsupportedAppUsage;";
+    private static final Set<String> WHITELIST_ANNOTATIONS = ImmutableSet.of();
+
+    private final Status mStatus;
+    private final String mPublicApiListFile;
+    private final String[] mPerSdkOutputFiles;
+    private final String mWhitelistFile;
+    private final String[] mJarFiles;
+    private final GreylistConsumer mOutput;
+    private final Set<Integer> mAllowedSdkVersions;
+    private final Set<String> mPublicApis;
+
 
     public static void main(String[] args) {
         Options options = new Options();
         options.addOption(OptionBuilder
+                .withLongOpt("public-api-list")
+                .hasArgs(1)
+                .withDescription("Public API list file. Used to de-dupe bridge methods.")
+                .create("p"));
+        options.addOption(OptionBuilder
+                .withLongOpt("write-greylist")
+                .hasArgs()
+                .withDescription(
+                        "Specify file to write greylist to. Can be specified multiple times. " +
+                        "Format is either just a filename, or \"int:filename\". If an integer is " +
+                        "given, members with a matching maxTargetSdk are written to the file; if " +
+                        "no integer is given, members with no maxTargetSdk are written.")
+                .create("g"));
+        options.addOption(OptionBuilder
+                .withLongOpt("write-whitelist")
+                .hasArgs(1)
+                .withDescription("Specify file to write whitelist to.")
+                .create('w'));
+        options.addOption(OptionBuilder
                 .withLongOpt("debug")
                 .hasArgs(0)
                 .withDescription("Enable debug")
@@ -62,6 +105,7 @@
             help(options);
         }
 
+
         String[] jarFiles = cmd.getArgs();
         if (jarFiles.length == 0) {
             System.err.println("Error: no jar files specified.");
@@ -69,18 +113,18 @@
         }
 
         Status status = new Status(cmd.hasOption('d'));
-
-        for (String jarFile : jarFiles) {
-            status.debug("Processing jar file %s", jarFile);
-            try {
-                JarReader reader = new JarReader(status, jarFile);
-                reader.stream().forEach(clazz -> new AnnotationVisitor(
-                        clazz, ANNOTATION_TYPE, status).visit());
-                reader.close();
-            } catch (IOException e) {
-                status.error(e);
-            }
+        try {
+            Class2Greylist c2gl = new Class2Greylist(
+                    status,
+                    cmd.getOptionValue('p', null),
+                    cmd.getOptionValues('g'),
+                    cmd.getOptionValue('w', null),
+                    jarFiles);
+            c2gl.main();
+        } catch (IOException e) {
+            status.error(e);
         }
+
         if (status.ok()) {
             System.exit(0);
         } else {
@@ -89,6 +133,94 @@
 
     }
 
+    @VisibleForTesting
+    Class2Greylist(Status status, String publicApiListFile, String[] perSdkLevelOutputFiles,
+            String whitelistOutputFile, String[] jarFiles) throws IOException {
+        mStatus = status;
+        mPublicApiListFile = publicApiListFile;
+        mPerSdkOutputFiles = perSdkLevelOutputFiles;
+        mWhitelistFile = whitelistOutputFile;
+        mJarFiles = jarFiles;
+        if (mPerSdkOutputFiles != null) {
+            Map<Integer, String> outputFiles = readGreylistMap(mStatus, mPerSdkOutputFiles);
+            mOutput = new FileWritingGreylistConsumer(mStatus, outputFiles, mWhitelistFile);
+            mAllowedSdkVersions = outputFiles.keySet();
+        } else {
+            // TODO remove this once per-SDK greylist support integrated into the build.
+            // Right now, mPerSdkOutputFiles is always null as the build never passes the
+            // corresponding command lind flags. Once the build is updated, can remove this.
+            mOutput = new SystemOutGreylistConsumer();
+            mAllowedSdkVersions = new HashSet<>(Arrays.asList(null, 26, 28));
+        }
+
+        if (mPublicApiListFile != null) {
+            mPublicApis = Sets.newHashSet(
+                    Files.readLines(new File(mPublicApiListFile), Charset.forName("UTF-8")));
+        } else {
+            mPublicApis = Collections.emptySet();
+        }
+    }
+
+    private Map<String, AnnotationHandler> createAnnotationHandlers() {
+        return ImmutableMap.<String, AnnotationHandler>builder()
+                .put(GreylistAnnotationHandler.ANNOTATION_NAME,
+                        new GreylistAnnotationHandler(
+                                mStatus, mOutput, mPublicApis, mAllowedSdkVersions))
+                .put(CovariantReturnTypeHandler.ANNOTATION_NAME,
+                        new CovariantReturnTypeHandler(mOutput, mPublicApis))
+                .put(CovariantReturnTypeMultiHandler.ANNOTATION_NAME,
+                        new CovariantReturnTypeMultiHandler(mOutput, mPublicApis))
+                .build();
+    }
+
+    private void main() throws IOException {
+        Map<String, AnnotationHandler> handlers = createAnnotationHandlers();
+        for (String jarFile : mJarFiles) {
+            mStatus.debug("Processing jar file %s", jarFile);
+            try {
+                JarReader reader = new JarReader(mStatus, jarFile);
+                reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, mStatus, handlers)
+                        .visit());
+                reader.close();
+            } catch (IOException e) {
+                mStatus.error(e);
+            }
+        }
+        mOutput.close();
+    }
+
+    @VisibleForTesting
+    static Map<Integer, String> readGreylistMap(Status status, String[] argValues) {
+        Map<Integer, String> map = new HashMap<>();
+        for (String sdkFile : argValues) {
+            Integer maxTargetSdk = null;
+            String filename;
+            int colonPos = sdkFile.indexOf(':');
+            if (colonPos != -1) {
+                try {
+                    maxTargetSdk = Integer.valueOf(sdkFile.substring(0, colonPos));
+                } catch (NumberFormatException nfe) {
+                    status.error("Not a valid integer: %s from argument value '%s'",
+                            sdkFile.substring(0, colonPos), sdkFile);
+                }
+                filename = sdkFile.substring(colonPos + 1);
+                if (filename.length() == 0) {
+                    status.error("Not a valid file name: %s from argument value '%s'",
+                            filename, sdkFile);
+                }
+            } else {
+                maxTargetSdk = null;
+                filename = sdkFile;
+            }
+            if (map.containsKey(maxTargetSdk)) {
+                status.error("Multiple output files for maxTargetSdk %s", maxTargetSdk);
+            } else {
+                map.put(maxTargetSdk, filename);
+            }
+        }
+        return map;
+    }
+
     private static void help(Options options) {
         new HelpFormatter().printHelp(
                 "class2greylist path/to/classes.jar [classes2.jar ...]",
diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java
new file mode 100644
index 0000000..afd15b4
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeHandler.java
@@ -0,0 +1,89 @@
+package com.android.class2greylist;
+
+import com.google.common.base.Preconditions;
+
+import org.apache.bcel.classfile.AnnotationEntry;
+import org.apache.bcel.classfile.ElementValuePair;
+import org.apache.bcel.classfile.Method;
+
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Handles {@code CovariantReturnType} annotations, generating whitelist
+ * entries from them.
+ *
+ * <p>A whitelist entry is generated with the same descriptor as the original
+ * method, but with the return type replaced with than specified by the
+ * {@link #RETURN_TYPE} property.
+ *
+ * <p>Methods are also validated against the public API list, to assert that
+ * the annotated method is already a public API.
+ */
+public class CovariantReturnTypeHandler implements AnnotationHandler {
+
+    private static final String SHORT_NAME = "CovariantReturnType";
+    public static final String ANNOTATION_NAME = "Ldalvik/annotation/codegen/CovariantReturnType;";
+
+    private static final String RETURN_TYPE = "returnType";
+
+    private final GreylistConsumer mConsumer;
+    private final Set<String> mPublicApis;
+
+    public CovariantReturnTypeHandler(GreylistConsumer consumer, Set<String> publicApis) {
+        mConsumer = consumer;
+        mPublicApis = publicApis;
+    }
+
+    @Override
+    public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
+        // Verify that the annotation has been applied to what we expect, and
+        // has the right form. Note, this should not strictly be necessary, as
+        // the annotation has a target of just 'method' and the property
+        // returnType does not have a default value, but checking makes the code
+        // less brittle to future changes.
+        if (!(context.member instanceof Method)) {
+            context.reportError("Cannot specify %s on a field", RETURN_TYPE);
+            return;
+        }
+        String returnType = findReturnType(annotation);
+        if (returnType == null) {
+            context.reportError("No %s set on @%s", RETURN_TYPE, SHORT_NAME);
+            return;
+        }
+        if (!mPublicApis.contains(context.getMemberDescriptor())) {
+            context.reportError("Found @%s on non-SDK method", SHORT_NAME);
+            return;
+        }
+
+        // Generate the signature of overload that we expect the annotation will
+        // cause the platform dexer to create.
+        String typeSignature = context.member.getSignature();
+        int closingBrace = typeSignature.indexOf(')');
+        Preconditions.checkState(closingBrace != -1,
+                "No ) found in method type signature %s", typeSignature);
+        typeSignature = new StringBuilder()
+                .append(typeSignature.substring(0, closingBrace + 1))
+                .append(returnType)
+                .toString();
+        String signature = String.format(Locale.US, context.signatureFormatString,
+                context.getClassDescriptor(), context.member.getName(), typeSignature);
+
+        if (mPublicApis.contains(signature)) {
+            context.reportError("Signature %s generated from @%s already exists as a public API",
+                    signature, SHORT_NAME);
+            return;
+        }
+        mConsumer.whitelistEntry(signature);
+    }
+
+    private String findReturnType(AnnotationEntry a) {
+        for (ElementValuePair property : a.getElementValuePairs()) {
+            if (property.getNameString().equals(RETURN_TYPE)) {
+                return property.getValue().stringifyValue();
+            }
+        }
+        // not found
+        return null;
+    }
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
new file mode 100644
index 0000000..bd0bf79
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/CovariantReturnTypeMultiHandler.java
@@ -0,0 +1,72 @@
+package com.android.class2greylist;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
+import org.apache.bcel.classfile.AnnotationElementValue;
+import org.apache.bcel.classfile.AnnotationEntry;
+import org.apache.bcel.classfile.ArrayElementValue;
+import org.apache.bcel.classfile.ElementValue;
+import org.apache.bcel.classfile.ElementValuePair;
+
+import java.util.Set;
+
+/**
+ * Handles {@code CovariantReturnType$CovariantReturnTypes} annotations, which
+ * are generated by the compiler when multiple {@code CovariantReturnType}
+ * annotations appear on a single method.
+ *
+ * <p>The enclosed annotations are passed to {@link CovariantReturnTypeHandler}.
+ */
+public class CovariantReturnTypeMultiHandler implements AnnotationHandler {
+
+    public static final String ANNOTATION_NAME =
+            "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;";
+
+    private static final String VALUE = "value";
+
+    private final CovariantReturnTypeHandler mWrappedHandler;
+    private final String mInnerAnnotationName;
+
+    public CovariantReturnTypeMultiHandler(GreylistConsumer consumer, Set<String> publicApis) {
+        this(consumer, publicApis, CovariantReturnTypeHandler.ANNOTATION_NAME);
+    }
+
+    @VisibleForTesting
+    public CovariantReturnTypeMultiHandler(GreylistConsumer consumer, Set<String> publicApis,
+            String innerAnnotationName) {
+        mWrappedHandler = new CovariantReturnTypeHandler(consumer, publicApis);
+        mInnerAnnotationName = innerAnnotationName;
+    }
+
+    @Override
+    public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
+        // Verify that the annotation has the form we expect
+        ElementValuePair value = findValue(annotation);
+        if (value == null) {
+            context.reportError("No value found on CovariantReturnType$CovariantReturnTypes");
+            return;
+        }
+        Preconditions.checkArgument(value.getValue() instanceof ArrayElementValue);
+        ArrayElementValue array = (ArrayElementValue) value.getValue();
+
+        // call wrapped handler on each enclosed annotation:
+        for (ElementValue v : array.getElementValuesArray()) {
+            Preconditions.checkArgument(v instanceof AnnotationElementValue);
+            AnnotationElementValue aev = (AnnotationElementValue) v;
+            Preconditions.checkArgument(
+                    aev.getAnnotationEntry().getAnnotationType().equals(mInnerAnnotationName));
+            mWrappedHandler.handleAnnotation(aev.getAnnotationEntry(), context);
+        }
+    }
+
+    private ElementValuePair findValue(AnnotationEntry a) {
+        for (ElementValuePair property : a.getElementValuePairs()) {
+            if (property.getNameString().equals(VALUE)) {
+                return property;
+            }
+        }
+        // not found
+        return null;
+    }
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java
new file mode 100644
index 0000000..9f33467
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/FileWritingGreylistConsumer.java
@@ -0,0 +1,66 @@
+package com.android.class2greylist;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FileWritingGreylistConsumer implements GreylistConsumer {
+
+    private final Status mStatus;
+    private final Map<Integer, PrintStream> mSdkToPrintStreamMap;
+    private final PrintStream mWhitelistStream;
+
+    private static PrintStream openFile(String filename) throws FileNotFoundException {
+        if (filename == null) {
+            return null;
+        }
+        return new PrintStream(new FileOutputStream(new File(filename)));
+    }
+
+    private static Map<Integer, PrintStream> openFiles(
+            Map<Integer, String> filenames) throws FileNotFoundException {
+        Map<Integer, PrintStream> streams = new HashMap<>();
+        for (Map.Entry<Integer, String> entry : filenames.entrySet()) {
+            streams.put(entry.getKey(), openFile(entry.getValue()));
+        }
+        return streams;
+    }
+
+    public FileWritingGreylistConsumer(Status status, Map<Integer, String> sdkToFilenameMap,
+            String whitelistFile) throws FileNotFoundException {
+        mStatus = status;
+        mSdkToPrintStreamMap = openFiles(sdkToFilenameMap);
+        mWhitelistStream = openFile(whitelistFile);
+    }
+
+    @Override
+    public void greylistEntry(String signature, Integer maxTargetSdk) {
+        PrintStream p = mSdkToPrintStreamMap.get(maxTargetSdk);
+        if (p == null) {
+            mStatus.error("No output file for signature %s with maxTargetSdk of %d", signature,
+                    maxTargetSdk == null ? "<absent>" : maxTargetSdk.toString());
+            return;
+        }
+        p.println(signature);
+    }
+
+    @Override
+    public void whitelistEntry(String signature) {
+        if (mWhitelistStream != null) {
+            mWhitelistStream.println(signature);
+        }
+    }
+
+    @Override
+    public void close() {
+        for (PrintStream p : mSdkToPrintStreamMap.values()) {
+            p.close();
+        }
+        if (mWhitelistStream != null) {
+            mWhitelistStream.close();
+        }
+    }
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java b/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java
new file mode 100644
index 0000000..460f2c3
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/GreylistAnnotationHandler.java
@@ -0,0 +1,146 @@
+package com.android.class2greylist;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.classfile.AnnotationEntry;
+import org.apache.bcel.classfile.ElementValue;
+import org.apache.bcel.classfile.ElementValuePair;
+import org.apache.bcel.classfile.FieldOrMethod;
+import org.apache.bcel.classfile.Method;
+import org.apache.bcel.classfile.SimpleElementValue;
+
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * Processes {@code UnsupportedAppUsage} annotations to generate greylist
+ * entries.
+ *
+ * Any annotations with a {@link #EXPECTED_SIGNATURE} property will have their
+ * generated signature verified against this, and an error will be reported if
+ * it does not match. Exclusions are made for bridge methods.
+ *
+ * Any {@link #MAX_TARGET_SDK} properties will be validated against the given
+ * set of valid values, then passed through to the greylist consumer.
+ */
+public class GreylistAnnotationHandler implements AnnotationHandler {
+
+    public static final String ANNOTATION_NAME = "Landroid/annotation/UnsupportedAppUsage;";
+
+    // properties of greylist annotations:
+    private static final String EXPECTED_SIGNATURE = "expectedSignature";
+    private static final String MAX_TARGET_SDK = "maxTargetSdk";
+
+    private final Status mStatus;
+    private final Predicate<GreylistMember> mGreylistFilter;
+    private final GreylistConsumer mGreylistConsumer;
+    private final Set<Integer> mValidMaxTargetSdkValues;
+
+    /**
+     * Represents a member of a class file (a field or method).
+     */
+    @VisibleForTesting
+    public static class GreylistMember {
+
+        /**
+         * Signature of this member.
+         */
+        public final String signature;
+        /**
+         * Indicates if this is a synthetic bridge method.
+         */
+        public final boolean bridge;
+        /**
+         * Max target SDK of property this member, if it is set, else null.
+         *
+         * Note: even though the annotation itself specified a default value,
+         * that default value is not encoded into instances of the annotation
+         * in class files. So when no value is specified in source, it will
+         * result in null appearing in here.
+         */
+        public final Integer maxTargetSdk;
+
+        public GreylistMember(String signature, boolean bridge, Integer maxTargetSdk) {
+            this.signature = signature;
+            this.bridge = bridge;
+            this.maxTargetSdk = maxTargetSdk;
+        }
+    }
+
+    public GreylistAnnotationHandler(
+            Status status,
+            GreylistConsumer greylistConsumer,
+            Set<String> publicApis,
+            Set<Integer> validMaxTargetSdkValues) {
+        this(status, greylistConsumer,
+                member -> !(member.bridge && publicApis.contains(member.signature)),
+                validMaxTargetSdkValues);
+    }
+
+    @VisibleForTesting
+    public GreylistAnnotationHandler(
+            Status status,
+            GreylistConsumer greylistConsumer,
+            Predicate<GreylistMember> greylistFilter,
+            Set<Integer> validMaxTargetSdkValues) {
+        mStatus = status;
+        mGreylistConsumer = greylistConsumer;
+        mGreylistFilter = greylistFilter;
+        mValidMaxTargetSdkValues = validMaxTargetSdkValues;
+    }
+
+    @Override
+    public void handleAnnotation(AnnotationEntry annotation, AnnotationContext context) {
+        FieldOrMethod member = context.member;
+        boolean bridge = (member instanceof Method)
+                && (member.getAccessFlags() & Const.ACC_BRIDGE) != 0;
+        if (bridge) {
+            mStatus.debug("Member is a bridge");
+        }
+        String signature = context.getMemberDescriptor();
+        Integer maxTargetSdk = null;
+        for (ElementValuePair property : annotation.getElementValuePairs()) {
+            switch (property.getNameString()) {
+                case EXPECTED_SIGNATURE:
+                    verifyExpectedSignature(context, property, signature, bridge);
+                    break;
+                case MAX_TARGET_SDK:
+                    maxTargetSdk = verifyAndGetMaxTargetSdk(context, property);
+                    break;
+            }
+        }
+        if (mGreylistFilter.test(new GreylistMember(signature, bridge, maxTargetSdk))) {
+            mGreylistConsumer.greylistEntry(signature, maxTargetSdk);
+        }
+    }
+
+    private void verifyExpectedSignature(AnnotationContext context, ElementValuePair property,
+            String signature, boolean isBridge) {
+        String expected = property.getValue().stringifyValue();
+        // Don't enforce for bridge methods; they're generated so won't match.
+        if (!isBridge && !signature.equals(expected)) {
+            context.reportError("Expected signature does not match generated:\n"
+                            + "Expected:  %s\n"
+                            + "Generated: %s", expected, signature);
+        }
+    }
+
+    private Integer verifyAndGetMaxTargetSdk(AnnotationContext context, ElementValuePair property) {
+        if (property.getValue().getElementValueType() != ElementValue.PRIMITIVE_INT) {
+            context.reportError("Expected property %s to be of type int; got %d",
+                    property.getNameString(), property.getValue().getElementValueType());
+        }
+        int value = ((SimpleElementValue) property.getValue()).getValueInt();
+        if (!mValidMaxTargetSdkValues.contains(value)) {
+            context.reportError("Invalid value for %s: got %d, expected one of [%s]",
+                    property.getNameString(),
+                    value,
+                    Joiner.on(",").join(mValidMaxTargetSdkValues));
+            return null;
+        }
+        return value;
+    }
+
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java
new file mode 100644
index 0000000..fd855e8
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/GreylistConsumer.java
@@ -0,0 +1,20 @@
+package com.android.class2greylist;
+
+public interface GreylistConsumer {
+    /**
+     * Handle a new greylist entry.
+     *
+     * @param signature Signature of the member.
+     * @param maxTargetSdk maxTargetSdk value from the annotation, or null if none set.
+     */
+    void greylistEntry(String signature, Integer maxTargetSdk);
+
+    /**
+     * Handle a new whitelist entry.
+     *
+     * @param signature Signature of the member.
+     */
+    void whitelistEntry(String signature);
+
+    void close();
+}
diff --git a/tools/class2greylist/src/com/android/class2greylist/Status.java b/tools/class2greylist/src/com/android/class2greylist/Status.java
index d707898..b5ee9f1 100644
--- a/tools/class2greylist/src/com/android/class2greylist/Status.java
+++ b/tools/class2greylist/src/com/android/class2greylist/Status.java
@@ -42,16 +42,12 @@
         mHasErrors = true;
     }
 
-    public void error(String message) {
+    public void error(String message, Object... args) {
         System.err.print(ERROR);
-        System.err.println(message);
+        System.err.println(String.format(Locale.US, message, args));
         mHasErrors = true;
     }
 
-    public void greylistEntry(String signature) {
-        System.out.println(signature);
-    }
-
     public boolean ok() {
         return !mHasErrors;
     }
diff --git a/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java b/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java
new file mode 100644
index 0000000..ad5ad70
--- /dev/null
+++ b/tools/class2greylist/src/com/android/class2greylist/SystemOutGreylistConsumer.java
@@ -0,0 +1,18 @@
+package com.android.class2greylist;
+
+public class SystemOutGreylistConsumer implements GreylistConsumer {
+    @Override
+    public void greylistEntry(String signature, Integer maxTargetSdk) {
+        System.out.println(signature);
+    }
+
+    @Override
+    public void whitelistEntry(String signature) {
+        // Ignore. This class is only used when no grey/white lists are
+        // specified, so we have nowhere to write whitelist entries.
+    }
+
+    @Override
+    public void close() {
+    }
+}
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java
new file mode 100644
index 0000000..8f4a76f
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationHandlerTestBase.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package com.android.class2greylist;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.withSettings;
+
+import com.android.javac.Javac;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+import java.io.IOException;
+
+public class AnnotationHandlerTestBase {
+
+    @Rule
+    public TestName mTestName = new TestName();
+
+    protected Javac mJavac;
+    protected GreylistConsumer mConsumer;
+    protected Status mStatus;
+
+    @Before
+    public void baseSetup() throws IOException {
+        System.out.println(String.format("\n============== STARTING TEST: %s ==============\n",
+                mTestName.getMethodName()));
+        mConsumer = mock(GreylistConsumer.class);
+        mStatus = mock(Status.class, withSettings().verboseLogging());
+        mJavac = new Javac();
+    }
+
+    protected void assertNoErrors() {
+        verify(mStatus, never()).error(any(Throwable.class));
+        verify(mStatus, never()).error(any(), any());
+    }
+}
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java b/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java
new file mode 100644
index 0000000..cb75dd3
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/Class2GreylistTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package com.android.class2greylist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.mockito.Mock;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class Class2GreylistTest {
+
+    @Mock
+    Status mStatus;
+    @Rule
+    public TestName mTestName = new TestName();
+
+    @Before
+    public void setup() throws IOException {
+        System.out.println(String.format("\n============== STARTING TEST: %s ==============\n",
+                mTestName.getMethodName()));
+        initMocks(this);
+    }
+
+    @Test
+    public void testReadGreylistMap() throws IOException {
+        Map<Integer, String> map = Class2Greylist.readGreylistMap(mStatus,
+                new String[]{"noApi", "1:apiOne", "3:apiThree"});
+        verifyZeroInteractions(mStatus);
+        assertThat(map).containsExactly(null, "noApi", 1, "apiOne", 3, "apiThree");
+    }
+
+    @Test
+    public void testReadGreylistMapDuplicate() throws IOException {
+        Class2Greylist.readGreylistMap(mStatus,
+                new String[]{"noApi", "1:apiOne", "1:anotherOne"});
+        verify(mStatus, atLeastOnce()).error(any(), any());
+    }
+
+    @Test
+    public void testReadGreylistMapDuplicateNoApi() {
+        Class2Greylist.readGreylistMap(mStatus,
+                new String[]{"noApi", "anotherNoApi", "1:apiOne"});
+        verify(mStatus, atLeastOnce()).error(any(), any());
+    }
+
+    @Test
+    public void testReadGreylistMapInvalidInt() throws IOException {
+        Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "a:apiOne"});
+        verify(mStatus, atLeastOnce()).error(any(), any());
+    }
+
+    @Test
+    public void testReadGreylistMapNoFilename() throws IOException {
+        Class2Greylist.readGreylistMap(mStatus, new String[]{"noApi", "1:"});
+        verify(mStatus, atLeastOnce()).error(any(), any());
+    }
+}
+
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java
new file mode 100644
index 0000000..10fae9b
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeHandlerTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package com.android.class2greylist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static java.util.Collections.emptySet;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class CovariantReturnTypeHandlerTest extends AnnotationHandlerTestBase {
+
+    private static final String ANNOTATION = "Lannotation/Annotation;";
+
+    @Before
+    public void setup() throws IOException {
+        // To keep the test simpler and more concise, we don't use the real
+        // @CovariantReturnType annotation here, but use our own @Annotation.
+        // It doesn't have to match the real annotation, just have the same
+        // property (returnType).
+        mJavac.addSource("annotation.Annotation", Joiner.on('\n').join(
+                "package annotation;",
+                "import static java.lang.annotation.RetentionPolicy.CLASS;",
+                "import java.lang.annotation.Retention;",
+                "@Retention(CLASS)",
+                "public @interface Annotation {",
+                "  Class<?> returnType();",
+                "}"));
+    }
+
+    @Test
+    public void testReturnTypeWhitelisted() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Annotation;",
+                "public class Class {",
+                "  @Annotation(returnType=Integer.class)",
+                "  public String method() {return null;}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION,
+                        new CovariantReturnTypeHandler(
+                                mConsumer,
+                                ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;")));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        assertNoErrors();
+        verify(mConsumer, times(1)).whitelistEntry(eq("La/b/Class;->method()Ljava/lang/Integer;"));
+    }
+
+    @Test
+    public void testAnnotatedMemberNotPublicApi() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Annotation;",
+                "public class Class {",
+                "  @Annotation(returnType=Integer.class)",
+                "  public String method() {return null;}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION,
+                        new CovariantReturnTypeHandler(
+                                mConsumer,
+                                emptySet()));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        verify(mStatus, atLeastOnce()).error(any(), any());
+    }
+
+    @Test
+    public void testReturnTypeAlreadyWhitelisted() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Annotation;",
+                "public class Class {",
+                "  @Annotation(returnType=Integer.class)",
+                "  public String method() {return null;}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION,
+                        new CovariantReturnTypeHandler(
+                                mConsumer,
+                                ImmutableSet.of(
+                                        "La/b/Class;->method()Ljava/lang/String;",
+                                        "La/b/Class;->method()Ljava/lang/Integer;"
+                                )));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        verify(mStatus, atLeastOnce()).error(any(), any());
+    }
+
+    @Test
+    public void testAnnotationOnField() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Annotation;",
+                "public class Class {",
+                "  @Annotation(returnType=Integer.class)",
+                "  public String field;",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION,
+                        new CovariantReturnTypeHandler(
+                                mConsumer,
+                                emptySet()));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        verify(mStatus, atLeastOnce()).error(any(), any());
+    }
+}
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java
new file mode 100644
index 0000000..7f4ce62
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/CovariantReturnTypeMultiHandlerTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package com.android.class2greylist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static java.util.Collections.emptySet;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class CovariantReturnTypeMultiHandlerTest extends AnnotationHandlerTestBase {
+
+
+    @Before
+    public void setup() throws IOException {
+        // To keep the test simpler and more concise, we don't use the real
+        // @CovariantReturnType annotation here, but use our own @Annotation
+        // and @Annotation.Multi that have the same semantics. It doesn't have
+        // to match the real annotation, just have the same properties
+        // (returnType and value).
+        mJavac.addSource("annotation.Annotation", Joiner.on('\n').join(
+                "package annotation;",
+                "import static java.lang.annotation.RetentionPolicy.CLASS;",
+                "import java.lang.annotation.Repeatable;",
+                "import java.lang.annotation.Retention;",
+                "@Repeatable(Annotation.Multi.class)",
+                "@Retention(CLASS)",
+                "public @interface Annotation {",
+                "  Class<?> returnType();",
+                "  @Retention(CLASS)",
+                "  @interface Multi {",
+                "    Annotation[] value();",
+                "  }",
+                "}"));
+    }
+
+    @Test
+    public void testReturnTypeMulti() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Annotation;",
+                "public class Class {",
+                "  @Annotation(returnType=Integer.class)",
+                "  @Annotation(returnType=Long.class)",
+                "  public String method() {return null;}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of("Lannotation/Annotation$Multi;",
+                        new CovariantReturnTypeMultiHandler(
+                                mConsumer,
+                                ImmutableSet.of("La/b/Class;->method()Ljava/lang/String;"),
+                                "Lannotation/Annotation;"));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> whitelist = ArgumentCaptor.forClass(String.class);
+        verify(mConsumer, times(2)).whitelistEntry(whitelist.capture());
+        assertThat(whitelist.getAllValues()).containsExactly(
+                "La/b/Class;->method()Ljava/lang/Integer;",
+                "La/b/Class;->method()Ljava/lang/Long;"
+        );
+    }
+
+    @Test
+    public void testReturnTypeMultiNotPublicApi() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Annotation;",
+                "public class Class {",
+                "  @Annotation(returnType=Integer.class)",
+                "  @Annotation(returnType=Long.class)",
+                "  public String method() {return null;}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of("Lannotation/Annotation$Multi;",
+                        new CovariantReturnTypeMultiHandler(
+                                mConsumer,
+                                emptySet(),
+                                "Lannotation/Annotation;"));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        verify(mStatus, atLeastOnce()).error(any(), any());
+    }
+}
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java b/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java
new file mode 100644
index 0000000..1a4bfb8
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/GreylistAnnotationHandlerTest.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package com.android.class2greylist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static java.util.Collections.emptySet;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+public class GreylistAnnotationHandlerTest extends AnnotationHandlerTestBase {
+
+    private static final String ANNOTATION = "Lannotation/Anno;";
+
+    @Before
+    public void setup() throws IOException {
+        mJavac.addSource("annotation.Anno", Joiner.on('\n').join(
+                "package annotation;",
+                "import static java.lang.annotation.RetentionPolicy.CLASS;",
+                "import java.lang.annotation.Retention;",
+                "@Retention(CLASS)",
+                "public @interface Anno {",
+                "  String expectedSignature() default \"\";",
+                "  int maxTargetSdk() default Integer.MAX_VALUE;",
+                "}"));
+    }
+
+    private GreylistAnnotationHandler createGreylistHandler(
+            Predicate<GreylistAnnotationHandler.GreylistMember> greylistFilter,
+            Set<Integer> validMaxTargetSdkValues) {
+        return new GreylistAnnotationHandler(
+                mStatus, mConsumer, greylistFilter, validMaxTargetSdkValues);
+    }
+
+    @Test
+    public void testGreylistMethod() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno",
+                "  public void method() {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+        ).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
+    }
+
+    @Test
+    public void testGreylistConstructor() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno",
+                "  public Class() {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+        ).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getValue()).isEqualTo("La/b/Class;-><init>()V");
+    }
+
+    @Test
+    public void testGreylistField() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno",
+                "  public int i;",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+        ).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->i:I");
+    }
+
+    @Test
+    public void testGreylistMethodExpectedSignature() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno(expectedSignature=\"La/b/Class;->method()V\")",
+                "  public void method() {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+        ).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
+    }
+
+    @Test
+    public void testGreylistMethodExpectedSignatureWrong() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno(expectedSignature=\"La/b/Class;->nomethod()V\")",
+                "  public void method() {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+        ).visit();
+
+        verify(mStatus, times(1)).error(any(), any());
+    }
+
+    @Test
+    public void testGreylistInnerClassMethod() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  public class Inner {",
+                "    @Anno",
+                "    public void method() {}",
+                "  }",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), mStatus,
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+        ).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getValue()).isEqualTo("La/b/Class$Inner;->method()V");
+    }
+
+    @Test
+    public void testMethodNotGreylisted() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "public class Class {",
+                "  public void method() {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+        ).visit();
+
+        assertNoErrors();
+        verify(mConsumer, never()).greylistEntry(any(String.class), any());
+    }
+
+    @Test
+    public void testMethodArgGenerics() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class<T extends String> {",
+                "  @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
+                "  public void method(T arg) {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()))
+        ).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
+    }
+
+    @Test
+    public void testOverrideMethodWithBridge() throws IOException {
+        mJavac.addSource("a.b.Base", Joiner.on('\n').join(
+                "package a.b;",
+                "abstract class Base<T> {",
+                "  protected abstract void method(T arg);",
+                "}"));
+
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class<T extends String> extends Base<T> {",
+                "  @Override",
+                "  @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
+                "  public void method(T arg) {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        // A bridge method is generated for the above, so we expect 2 greylist entries.
+        verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getAllValues()).containsExactly(
+                "La/b/Class;->method(Ljava/lang/Object;)V",
+                "La/b/Class;->method(Ljava/lang/String;)V");
+    }
+
+    @Test
+    public void testOverridePublicMethodWithBridge() throws IOException {
+        mJavac.addSource("a.b.Base", Joiner.on('\n').join(
+                "package a.b;",
+                "public abstract class Base<T> {",
+                "  public void method(T arg) {}",
+                "}"));
+
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class<T extends String> extends Base<T> {",
+                "  @Override",
+                "  @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
+                "  public void method(T arg) {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        // A bridge method is generated for the above, so we expect 2 greylist entries.
+        verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getAllValues()).containsExactly(
+                "La/b/Class;->method(Ljava/lang/Object;)V",
+                "La/b/Class;->method(Ljava/lang/String;)V");
+    }
+
+    @Test
+    public void testBridgeMethodsFromInterface() throws IOException {
+        mJavac.addSource("a.b.Interface", Joiner.on('\n').join(
+                "package a.b;",
+                "public interface Interface {",
+                "  public void method(Object arg);",
+                "}"));
+
+        mJavac.addSource("a.b.Base", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "class Base {",
+                "  @Anno(expectedSignature=\"La/b/Base;->method(Ljava/lang/Object;)V\")",
+                "  public void method(Object arg) {}",
+                "}"));
+
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "public class Class extends Base implements Interface {",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Interface"), mStatus, handlerMap)
+                .visit();
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        // A bridge method is generated for the above, so we expect 2 greylist entries.
+        verify(mConsumer, times(2)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getAllValues()).containsExactly(
+                "La/b/Class;->method(Ljava/lang/Object;)V",
+                "La/b/Base;->method(Ljava/lang/Object;)V");
+    }
+
+    @Test
+    public void testPublicBridgeExcluded() throws IOException {
+        mJavac.addSource("a.b.Base", Joiner.on('\n').join(
+                "package a.b;",
+                "public abstract class Base<T> {",
+                "  public void method(T arg) {}",
+                "}"));
+
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class<T extends String> extends Base<T> {",
+                "  @Override",
+                "  @Anno",
+                "  public void method(T arg) {}",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Set<String> publicApis = Sets.newHashSet(
+                "La/b/Base;->method(Ljava/lang/Object;)V",
+                "La/b/Class;->method(Ljava/lang/Object;)V");
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION,
+                        new GreylistAnnotationHandler(
+                                mStatus,
+                                mConsumer,
+                                publicApis,
+                                emptySet()));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        // The bridge method generated for the above, is a public API so should be excluded
+        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
+    }
+
+    @Test
+    public void testVolatileField() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno(expectedSignature=\"La/b/Class;->field:I\")",
+                "  public volatile int field;",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(
+                        member -> !member.bridge, // exclude bridge methods
+                        emptySet()));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+        assertNoErrors();
+        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
+        verify(mConsumer, times(1)).greylistEntry(greylist.capture(), any());
+        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->field:I");
+    }
+
+    @Test
+    public void testVolatileFieldWrongSignature() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno(expectedSignature=\"La/b/Class;->wrong:I\")",
+                "  public volatile int field;",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, emptySet()));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+        verify(mStatus, times(1)).error(any(), any());
+    }
+
+    @Test
+    public void testMethodMaxTargetSdk() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno(maxTargetSdk=1)",
+                "  public int field;",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(
+                        x -> true,
+                        ImmutableSet.of(1)));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+        assertNoErrors();
+        ArgumentCaptor<Integer> maxTargetSdk = ArgumentCaptor.forClass(Integer.class);
+        verify(mConsumer, times(1)).greylistEntry(any(), maxTargetSdk.capture());
+        assertThat(maxTargetSdk.getValue()).isEqualTo(1);
+    }
+
+    @Test
+    public void testMethodNoMaxTargetSdk() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno",
+                "  public int field;",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(
+                        x -> true,
+                        ImmutableSet.of(1)));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+        assertNoErrors();
+        ArgumentCaptor<Integer> maxTargetSdk = ArgumentCaptor.forClass(Integer.class);
+        verify(mConsumer, times(1)).greylistEntry(any(), maxTargetSdk.capture());
+        assertThat(maxTargetSdk.getValue()).isEqualTo(null);
+    }
+
+    @Test
+    public void testMethodMaxTargetSdkOutOfRange() throws IOException {
+        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+                "package a.b;",
+                "import annotation.Anno;",
+                "public class Class {",
+                "  @Anno(maxTargetSdk=2)",
+                "  public int field;",
+                "}"));
+        assertThat(mJavac.compile()).isTrue();
+
+        Map<String, AnnotationHandler> handlerMap =
+                ImmutableMap.of(ANNOTATION, createGreylistHandler(
+                        x -> true,
+                        ImmutableSet.of(1)));
+        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
+        verify(mStatus, times(1)).error(any(), any());
+    }
+
+}
diff --git a/tools/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java b/tools/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java
deleted file mode 100644
index a4ad20c..0000000
--- a/tools/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-package com.android.javac;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.withSettings;
-
-import com.android.class2greylist.AnnotationVisitor;
-import com.android.class2greylist.Status;
-
-import com.google.common.base.Joiner;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.mockito.ArgumentCaptor;
-
-import java.io.IOException;
-
-public class AnnotationVisitorTest {
-
-    private static final String ANNOTATION = "Lannotation/Anno;";
-
-    @Rule
-    public TestName mTestName = new TestName();
-
-    private Javac mJavac;
-    private Status mStatus;
-
-    @Before
-    public void setup() throws IOException {
-        System.out.println(String.format("\n============== STARTING TEST: %s ==============\n",
-                mTestName.getMethodName()));
-        mStatus = mock(Status.class, withSettings().verboseLogging());
-        mJavac = new Javac();
-        mJavac.addSource("annotation.Anno", Joiner.on('\n').join(
-                "package annotation;",
-                 "import static java.lang.annotation.RetentionPolicy.CLASS;",
-                 "import java.lang.annotation.Retention;",
-                "import java.lang.annotation.Target;",
-                "@Retention(CLASS)",
-                "public @interface Anno {",
-                "  String expectedSignature() default \"\";",
-                "}"));
-    }
-
-    private void assertNoErrors() {
-        verify(mStatus, never()).error(any(Throwable.class));
-        verify(mStatus, never()).error(any(String.class));
-    }
-
-    @Test
-    public void testGreylistMethod() throws IOException {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class {",
-                "  @Anno",
-                "  public void method() {}",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mStatus, times(1)).greylistEntry(greylist.capture());
-        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
-    }
-
-    @Test
-    public void testGreylistConstructor() throws IOException {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class {",
-                "  @Anno",
-                "  public Class() {}",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mStatus, times(1)).greylistEntry(greylist.capture());
-        assertThat(greylist.getValue()).isEqualTo("La/b/Class;-><init>()V");
-    }
-
-    @Test
-    public void testGreylistField() throws IOException {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class {",
-                "  @Anno",
-                "  public int i;",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mStatus, times(1)).greylistEntry(greylist.capture());
-        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->i:I");
-    }
-
-    @Test
-    public void testGreylistMethodExpectedSignature() throws IOException {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class {",
-                "  @Anno(expectedSignature=\"La/b/Class;->method()V\")",
-                "  public void method() {}",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mStatus, times(1)).greylistEntry(greylist.capture());
-        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
-    }
-
-    @Test
-    public void testGreylistMethodExpectedSignatureWrong() throws IOException {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class {",
-                "  @Anno(expectedSignature=\"La/b/Class;->nomethod()V\")",
-                "  public void method() {}",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        verify(mStatus, times(1)).error(any(String.class));
-    }
-
-    @Test
-    public void testGreylistInnerClassMethod() throws IOException {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class {",
-                "  public class Inner {",
-                "    @Anno",
-                "    public void method() {}",
-                "  }",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), ANNOTATION,
-                mStatus).visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mStatus, times(1)).greylistEntry(greylist.capture());
-        assertThat(greylist.getValue()).isEqualTo("La/b/Class$Inner;->method()V");
-    }
-
-    @Test
-    public void testMethodNotGreylisted() throws IOException {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "public class Class {",
-                "  public void method() {}",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        assertNoErrors();
-        verify(mStatus, never()).greylistEntry(any(String.class));
-    }
-
-    @Test
-    public void testMethodArgGenerics() throws IOException {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class<T extends String> {",
-                "  @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
-                "  public void method(T arg) {}",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        verify(mStatus, times(1)).greylistEntry(greylist.capture());
-        assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
-    }
-
-    @Test
-    public void testOverrideMethodWithBridge() throws IOException {
-        mJavac.addSource("a.b.Base", Joiner.on('\n').join(
-                "package a.b;",
-                "abstract class Base<T> {",
-                "  protected abstract void method(T arg);",
-                "}"));
-
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class<T extends String> extends Base<T> {",
-                "  @Override",
-                "  @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
-                "  public void method(T arg) {}",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, mStatus)
-                .visit();
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        // A bridge method is generated for the above, so we expect 2 greylist entries.
-        verify(mStatus, times(2)).greylistEntry(greylist.capture());
-        assertThat(greylist.getAllValues()).containsExactly(
-                "La/b/Class;->method(Ljava/lang/Object;)V",
-                "La/b/Class;->method(Ljava/lang/String;)V");
-    }
-
-    @Test
-    public void testOverridePublicMethodWithBridge() throws IOException {
-        mJavac.addSource("a.b.Base", Joiner.on('\n').join(
-                "package a.b;",
-                "public abstract class Base<T> {",
-                "  public void method(T arg) {}",
-                "}"));
-
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "public class Class<T extends String> extends Base<T> {",
-                "  @Override",
-                "  @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
-                "  public void method(T arg) {}",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), ANNOTATION, mStatus)
-                .visit();
-        new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
-                .visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        // A bridge method is generated for the above, so we expect 2 greylist entries.
-        verify(mStatus, times(2)).greylistEntry(greylist.capture());
-        assertThat(greylist.getAllValues()).containsExactly(
-                "La/b/Class;->method(Ljava/lang/Object;)V",
-                "La/b/Class;->method(Ljava/lang/String;)V");
-    }
-
-    @Test
-    public void testBridgeMethodsFromInterface() throws IOException {
-        mJavac.addSource("a.b.Interface", Joiner.on('\n').join(
-                "package a.b;",
-                "public interface Interface {",
-                "  public void method(Object arg);",
-                "}"));
-
-        mJavac.addSource("a.b.Base", Joiner.on('\n').join(
-                "package a.b;",
-                "import annotation.Anno;",
-                "class Base {",
-                "  @Anno(expectedSignature=\"La/b/Base;->method(Ljava/lang/Object;)V\")",
-                "  public void method(Object arg) {}",
-                "}"));
-
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
-                "package a.b;",
-                "public class Class extends Base implements Interface {",
-                "}"));
-        assertThat(mJavac.compile()).isTrue();
-
-        new AnnotationVisitor(
-                mJavac.getCompiledClass("a.b.Interface"), ANNOTATION, mStatus).visit();
-        new AnnotationVisitor(
-                mJavac.getCompiledClass("a.b.Base"), ANNOTATION, mStatus).visit();
-        new AnnotationVisitor(
-                mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus).visit();
-
-        assertNoErrors();
-        ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
-        // A bridge method is generated for the above, so we expect 2 greylist entries.
-        verify(mStatus, times(2)).greylistEntry(greylist.capture());
-        assertThat(greylist.getAllValues()).containsExactly(
-                "La/b/Class;->method(Ljava/lang/Object;)V",
-                "La/b/Base;->method(Ljava/lang/Object;)V");
-    }
-}
diff --git a/tools/dexanalyze/dexanalyze_bytecode.cc b/tools/dexanalyze/dexanalyze_bytecode.cc
index 1c5a5d5..0bb3f91 100644
--- a/tools/dexanalyze/dexanalyze_bytecode.cc
+++ b/tools/dexanalyze/dexanalyze_bytecode.cc
@@ -164,13 +164,7 @@
                                               std::map<size_t, TypeLinkage>& types) {
   TypeLinkage& current_type = types[current_class_type.index_];
   bool skip_next = false;
-  size_t last_start = 0u;
   for (auto inst = code_item.begin(); ; ++inst) {
-    if (!count_types && last_start != buffer_.size()) {
-      // Register the instruction blob.
-      ++instruction_freq_[std::vector<uint8_t>(buffer_.begin() + last_start, buffer_.end())];
-      last_start = buffer_.size();
-    }
     if (inst == code_item.end()) {
       break;
     }
@@ -334,31 +328,31 @@
               }
             }
 
-            bool result = false;
             uint32_t type_idx = current_type.types_.Get(receiver_type.index_);
             uint32_t local_idx = types[receiver_type.index_].methods_.Get(method_idx);
-            ExtendPrefix(&type_idx, &local_idx);
-            ExtendPrefix(&dest_reg, &local_idx);
-            if (arg_count == 0) {
-              result = InstNibbles(opcode, {dest_reg, type_idx, local_idx});
-            } else if (arg_count == 1) {
-              result = InstNibbles(opcode, {dest_reg, type_idx, local_idx, args[0]});
-            } else if (arg_count == 2) {
-              result = InstNibbles(opcode, {dest_reg, type_idx, local_idx, args[0],
-                                            args[1]});
-            } else if (arg_count == 3) {
-              result = InstNibbles(opcode, {dest_reg, type_idx, local_idx, args[0],
-                                            args[1], args[2]});
-            } else if (arg_count == 4) {
-              result = InstNibbles(opcode, {dest_reg, type_idx, local_idx, args[0],
-                                            args[1], args[2], args[3]});
-            } else if (arg_count == 5) {
-              result = InstNibbles(opcode, {dest_reg, type_idx, local_idx, args[0],
-                                            args[1], args[2], args[3], args[4]});
-            }
 
-            if (result) {
+            // If true, we always put the return value in r0.
+            static constexpr bool kMoveToDestReg = true;
+
+            std::vector<uint32_t> new_args;
+            if (kMoveToDestReg && arg_count % 2 == 1) {
+              // Use the extra nibble to sneak in part of the type index.
+              new_args.push_back(local_idx >> 4);
+              local_idx ^= local_idx & 0xF0;
+            }
+            ExtendPrefix(&type_idx, &local_idx);
+            new_args.push_back(type_idx);
+            new_args.push_back(local_idx);
+            if (!kMoveToDestReg) {
+              ExtendPrefix(&dest_reg, &local_idx);
+              new_args.push_back(dest_reg);
+            }
+            new_args.insert(new_args.end(), args, args + arg_count);
+            if (InstNibbles(opcode, new_args)) {
               skip_next = next_move_result;
+              if (kMoveToDestReg && dest_reg != 0u) {
+                CHECK(InstNibbles(Instruction::MOVE, {dest_reg >> 4, dest_reg & 0xF}));
+              }
               continue;
             }
           }
@@ -466,8 +460,11 @@
 
 void NewRegisterInstructions::Add(Instruction::Code opcode, const Instruction& inst) {
   const uint8_t* start = reinterpret_cast<const uint8_t*>(&inst);
+  const size_t buffer_start = buffer_.size();
   buffer_.push_back(opcode);
   buffer_.insert(buffer_.end(), start + 1, start + 2 * inst.SizeInCodeUnits());
+  // Register the instruction blob.
+  ++instruction_freq_[std::vector<uint8_t>(buffer_.begin() + buffer_start, buffer_.end())];
 }
 
 void NewRegisterInstructions::ExtendPrefix(uint32_t* value1, uint32_t* value2) {
@@ -500,17 +497,6 @@
   *value2 &= 0XF;
 }
 
-bool NewRegisterInstructions::InstNibblesAndIndex(uint8_t opcode,
-                                             uint16_t idx,
-                                             const std::vector<uint32_t>& args) {
-  if (!InstNibbles(opcode, args)) {
-    return false;
-  }
-  buffer_.push_back(static_cast<uint8_t>(idx >> 8));
-  buffer_.push_back(static_cast<uint8_t>(idx));
-  return true;
-}
-
 bool NewRegisterInstructions::InstNibbles(uint8_t opcode, const std::vector<uint32_t>& args) {
   if (verbose_level_ >= VerboseLevel::kEverything) {
     std::cout << " ==> " << Instruction::Name(static_cast<Instruction::Code>(opcode)) << " ";
@@ -526,6 +512,7 @@
       return false;
     }
   }
+  const size_t buffer_start = buffer_.size();
   buffer_.push_back(opcode);
   for (size_t i = 0; i < args.size(); i += 2) {
     buffer_.push_back(args[i] << 4);
@@ -536,6 +523,8 @@
   while (buffer_.size() % alignment_ != 0) {
     buffer_.push_back(0);
   }
+  // Register the instruction blob.
+  ++instruction_freq_[std::vector<uint8_t>(buffer_.begin() + buffer_start, buffer_.end())];
   return true;
 }
 
diff --git a/tools/dexanalyze/dexanalyze_bytecode.h b/tools/dexanalyze/dexanalyze_bytecode.h
index ed40ba7..db009b0 100644
--- a/tools/dexanalyze/dexanalyze_bytecode.h
+++ b/tools/dexanalyze/dexanalyze_bytecode.h
@@ -64,7 +64,6 @@
                        bool count_types,
                        std::map<size_t, TypeLinkage>& types);
   void Add(Instruction::Code opcode, const Instruction& inst);
-  bool InstNibblesAndIndex(uint8_t opcode, uint16_t idx, const std::vector<uint32_t>& args);
   bool InstNibbles(uint8_t opcode, const std::vector<uint32_t>& args);
   void ExtendPrefix(uint32_t* value1, uint32_t* value2);
   bool Enabled(BytecodeExperiment experiment) const {
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index 0381381..bf8a1b7 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -26,6 +26,7 @@
 #include "base/os.h"
 #include "base/unix_file/fd_file.h"
 #include "dex/art_dex_file_loader.h"
+#include "dex/class_accessor-inl.h"
 #include "dex/dex_file-inl.h"
 #include "dex/hidden_api_access_flags.h"
 
@@ -75,7 +76,9 @@
   UsageError("");
   UsageError("  Command \"list\": dump lists of public and private API");
   UsageError("    --boot-dex=<filename>: dex file which belongs to boot class path");
-  UsageError("    --stub-dex=<filename>: dex/apk file which belongs to SDK API stubs");
+  UsageError("    --stub-classpath=<filenames>: colon-separated list of dex/apk files");
+  UsageError("        which form API stubs of boot class path. Multiple classpaths can");
+  UsageError("        be specified");
   UsageError("");
   UsageError("    --out-public=<filename>: output file for a list of all public APIs");
   UsageError("    --out-private=<filename>: output file for a list of all private APIs");
@@ -89,42 +92,33 @@
   return std::find(vec.begin(), vec.end(), elem) != vec.end();
 }
 
-class DexClass {
+class DexClass : public ClassAccessor {
  public:
-  DexClass(const DexFile& dex_file, uint32_t idx)
-      : dex_file_(dex_file), class_def_(dex_file.GetClassDef(idx)) {}
+  explicit DexClass(const ClassAccessor& accessor) : ClassAccessor(accessor) {}
 
-  const DexFile& GetDexFile() const { return dex_file_; }
-  const uint8_t* GetData() const { return dex_file_.GetClassData(class_def_); }
+  const uint8_t* GetData() const { return dex_file_.GetClassData(GetClassDef()); }
 
-  const dex::TypeIndex GetClassIndex() const { return class_def_.class_idx_; }
-  const dex::TypeIndex GetSuperclassIndex() const { return class_def_.superclass_idx_; }
+  const dex::TypeIndex GetSuperclassIndex() const { return GetClassDef().superclass_idx_; }
 
   bool HasSuperclass() const { return dex_file_.IsTypeIndexValid(GetSuperclassIndex()); }
 
-  std::string GetDescriptor() const { return dex_file_.GetClassDescriptor(class_def_); }
-
   std::string GetSuperclassDescriptor() const {
-    if (HasSuperclass()) {
-      return dex_file_.StringByTypeIdx(GetSuperclassIndex());
-    } else {
-      return "";
-    }
+    return HasSuperclass() ? dex_file_.StringByTypeIdx(GetSuperclassIndex()) : "";
   }
 
   std::set<std::string> GetInterfaceDescriptors() const {
     std::set<std::string> list;
-    const DexFile::TypeList* ifaces = dex_file_.GetInterfacesList(class_def_);
+    const DexFile::TypeList* ifaces = dex_file_.GetInterfacesList(GetClassDef());
     for (uint32_t i = 0; ifaces != nullptr && i < ifaces->Size(); ++i) {
       list.insert(dex_file_.StringByTypeIdx(ifaces->GetTypeItem(i).type_idx_));
     }
     return list;
   }
 
-  inline bool IsVisible() const { return HasAccessFlags(kAccPublic); }
+  inline bool IsPublic() const { return HasAccessFlags(kAccPublic); }
 
   inline bool Equals(const DexClass& other) const {
-    bool equals = GetDescriptor() == other.GetDescriptor();
+    bool equals = strcmp(GetDescriptor(), other.GetDescriptor()) == 0;
     if (equals) {
       // TODO(dbrazdil): Check that methods/fields match as well once b/111116543 is fixed.
       CHECK_EQ(GetAccessFlags(), other.GetAccessFlags());
@@ -135,39 +129,40 @@
   }
 
  private:
-  uint32_t GetAccessFlags() const { return class_def_.access_flags_; }
+  uint32_t GetAccessFlags() const { return GetClassDef().access_flags_; }
   bool HasAccessFlags(uint32_t mask) const { return (GetAccessFlags() & mask) == mask; }
-
-  const DexFile& dex_file_;
-  const DexFile::ClassDef& class_def_;
 };
 
 class DexMember {
  public:
-  DexMember(const DexClass& klass, const ClassDataItemIterator& it)
-      : klass_(klass), it_(it) {
-    DCHECK_EQ(IsMethod() ? GetMethodId().class_idx_ : GetFieldId().class_idx_,
-              klass_.GetClassIndex());
+  DexMember(const DexClass& klass, const ClassAccessor::Field& item)
+      : klass_(klass), item_(item), is_method_(false) {
+    DCHECK_EQ(GetFieldId().class_idx_, klass.GetClassIdx());
+  }
+
+  DexMember(const DexClass& klass, const ClassAccessor::Method& item)
+      : klass_(klass), item_(item), is_method_(true) {
+    DCHECK_EQ(GetMethodId().class_idx_, klass.GetClassIdx());
   }
 
   inline const DexClass& GetDeclaringClass() const { return klass_; }
 
   // Sets hidden bits in access flags and writes them back into the DEX in memory.
-  // Note that this will not update the cached data of ClassDataItemIterator
+  // Note that this will not update the cached data of the class accessor
   // until it iterates over this item again and therefore will fail a CHECK if
   // it is called multiple times on the same DexMember.
-  void SetHidden(HiddenApiAccessFlags::ApiList value) {
-    const uint32_t old_flags = it_.GetRawMemberAccessFlags();
+  void SetHidden(HiddenApiAccessFlags::ApiList value) const {
+    const uint32_t old_flags = item_.GetRawAccessFlags();
     const uint32_t new_flags = HiddenApiAccessFlags::EncodeForDex(old_flags, value);
     CHECK_EQ(UnsignedLeb128Size(new_flags), UnsignedLeb128Size(old_flags));
 
     // Locate the LEB128-encoded access flags in class data.
     // `ptr` initially points to the next ClassData item. We iterate backwards
     // until we hit the terminating byte of the previous Leb128 value.
-    const uint8_t* ptr = it_.DataPointer();
+    const uint8_t* ptr = item_.GetDataPointer();
     if (IsMethod()) {
       ptr = ReverseSearchUnsignedLeb128(ptr);
-      DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), it_.GetMethodCodeItemOffset());
+      DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), GetMethod().GetCodeItemOffset());
     }
     ptr = ReverseSearchUnsignedLeb128(ptr);
     DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), old_flags);
@@ -176,23 +171,23 @@
     UpdateUnsignedLeb128(const_cast<uint8_t*>(ptr), new_flags);
   }
 
-  inline bool IsMethod() const { return it_.IsAtMethod(); }
-  inline bool IsVirtualMethod() const { return it_.IsAtVirtualMethod(); }
+  inline bool IsMethod() const { return is_method_; }
+  inline bool IsVirtualMethod() const { return IsMethod() && !GetMethod().IsStaticOrDirect(); }
+  inline bool IsConstructor() const { return IsMethod() && HasAccessFlags(kAccConstructor); }
 
-  // Returns true if the member is public/protected and is in a public class.
-  inline bool IsVisible() const {
-    return GetDeclaringClass().IsVisible() &&
-           (HasAccessFlags(kAccPublic) || HasAccessFlags(kAccProtected));
+  inline bool IsPublicOrProtected() const {
+    return HasAccessFlags(kAccPublic) || HasAccessFlags(kAccProtected);
   }
 
   // Constructs a string with a unique signature of this class member.
   std::string GetApiEntry() const {
     std::stringstream ss;
-    ss << klass_.GetDescriptor() << "->" << GetName() << (IsMethod() ? "" : ":") << GetSignature();
+    ss << klass_.GetDescriptor() << "->" << GetName() << (IsMethod() ? "" : ":")
+       << GetSignature();
     return ss.str();
   }
 
-  inline bool operator==(const DexMember& other) {
+  inline bool operator==(const DexMember& other) const {
     // These need to match if they should resolve to one another.
     bool equals = IsMethod() == other.IsMethod() &&
                   GetName() == other.GetName() &&
@@ -207,31 +202,37 @@
   }
 
  private:
-  inline uint32_t GetAccessFlags() const { return it_.GetMemberAccessFlags(); }
+  inline uint32_t GetAccessFlags() const { return item_.GetAccessFlags(); }
   inline uint32_t HasAccessFlags(uint32_t mask) const { return (GetAccessFlags() & mask) == mask; }
 
   inline std::string GetName() const {
-    return IsMethod() ? klass_.GetDexFile().GetMethodName(GetMethodId())
-                      : klass_.GetDexFile().GetFieldName(GetFieldId());
+    return IsMethod() ? item_.GetDexFile().GetMethodName(GetMethodId())
+                      : item_.GetDexFile().GetFieldName(GetFieldId());
   }
 
   inline std::string GetSignature() const {
-    return IsMethod() ? klass_.GetDexFile().GetMethodSignature(GetMethodId()).ToString()
-                      : klass_.GetDexFile().GetFieldTypeDescriptor(GetFieldId());
+    return IsMethod() ? item_.GetDexFile().GetMethodSignature(GetMethodId()).ToString()
+                      : item_.GetDexFile().GetFieldTypeDescriptor(GetFieldId());
+  }
+
+  inline const ClassAccessor::Method& GetMethod() const {
+    DCHECK(IsMethod());
+    return down_cast<const ClassAccessor::Method&>(item_);
   }
 
   inline const DexFile::MethodId& GetMethodId() const {
     DCHECK(IsMethod());
-    return klass_.GetDexFile().GetMethodId(it_.GetMemberIndex());
+    return item_.GetDexFile().GetMethodId(item_.GetIndex());
   }
 
   inline const DexFile::FieldId& GetFieldId() const {
     DCHECK(!IsMethod());
-    return klass_.GetDexFile().GetFieldId(it_.GetMemberIndex());
+    return item_.GetDexFile().GetFieldId(item_.GetIndex());
   }
 
   const DexClass& klass_;
-  const ClassDataItemIterator& it_;
+  const ClassAccessor::BaseItem& item_;
+  const bool is_method_;
 };
 
 class ClassPath FINAL {
@@ -243,22 +244,20 @@
   template<typename Fn>
   void ForEachDexClass(Fn fn) {
     for (auto& dex_file : dex_files_) {
-      for (uint32_t class_idx = 0; class_idx < dex_file->NumClassDefs(); ++class_idx) {
-        DexClass klass(*dex_file, class_idx);
-        fn(klass);
+      for (ClassAccessor accessor : dex_file->GetClasses()) {
+        fn(DexClass(accessor));
       }
     }
   }
 
   template<typename Fn>
   void ForEachDexMember(Fn fn) {
-    ForEachDexClass([&fn](DexClass& klass) {
-      const uint8_t* klass_data = klass.GetData();
-      if (klass_data != nullptr) {
-        for (ClassDataItemIterator it(klass.GetDexFile(), klass_data); it.HasNext(); it.Next()) {
-          DexMember member(klass, it);
-          fn(member);
-        }
+    ForEachDexClass([&fn](const DexClass& klass) {
+      for (const ClassAccessor::Field& field : klass.GetFields()) {
+        fn(DexMember(klass, field));
+      }
+      for (const ClassAccessor::Method& method : klass.GetMethods()) {
+        fn(DexMember(klass, method));
       }
     });
   }
@@ -344,6 +343,24 @@
     return ForEachResolvableMember_Impl(other, fn) != ResolutionResult::kNotFound;
   }
 
+  // Returns true if this class contains at least one member matching `other`.
+  bool HasMatchingMember(const DexMember& other) {
+    return ForEachMatchingMember(
+        other, [](const DexMember&) { return true; }) != ResolutionResult::kNotFound;
+  }
+
+  // Recursively iterates over all subclasses of this class and invokes `fn`
+  // on each one. If `fn` returns false for a particular subclass, exploring its
+  // subclasses is skipped.
+  template<typename Fn>
+  void ForEachSubClass(Fn fn) {
+    for (HierarchyClass* subclass : extended_by_) {
+      if (fn(subclass)) {
+        subclass->ForEachSubClass(fn);
+      }
+    }
+  }
+
  private:
   // Result of resolution which takes into account whether the member was found
   // for the first time or not. This is just a performance optimization to prevent
@@ -397,16 +414,18 @@
   template<typename Fn>
   ResolutionResult ForEachMatchingMember(const DexMember& other, Fn fn) {
     ResolutionResult found = ResolutionResult::kNotFound;
+    auto compare_member = [&](const DexMember& member) {
+      if (member == other) {
+        found = Accumulate(found, fn(member) ? ResolutionResult::kFoundNew
+                                             : ResolutionResult::kFoundOld);
+      }
+    };
     for (const DexClass& dex_class : dex_classes_) {
-      const uint8_t* data = dex_class.GetData();
-      if (data != nullptr) {
-        for (ClassDataItemIterator it(dex_class.GetDexFile(), data); it.HasNext(); it.Next()) {
-          DexMember member(dex_class, it);
-          if (member == other) {
-            found = Accumulate(found, fn(member) ? ResolutionResult::kFoundNew
-                                                 : ResolutionResult::kFoundOld);
-          }
-        }
+      for (const ClassAccessor::Field& field : dex_class.GetFields()) {
+        compare_member(DexMember(dex_class, field));
+      }
+      for (const ClassAccessor::Method& method : dex_class.GetMethods()) {
+        compare_member(DexMember(dex_class, method));
       }
     }
     return found;
@@ -438,7 +457,7 @@
 
 class Hierarchy FINAL {
  public:
-  explicit Hierarchy(ClassPath& class_path) : class_path_(class_path) {
+  explicit Hierarchy(ClassPath& classpath) : classpath_(classpath) {
     BuildClassHierarchy();
   }
 
@@ -454,6 +473,48 @@
     return (klass != nullptr) && klass->ForEachResolvableMember(other, fn);
   }
 
+  // Returns true if `member`, which belongs to this classpath, is visible to
+  // code in child class loaders.
+  bool IsMemberVisible(const DexMember& member) {
+    if (!member.IsPublicOrProtected()) {
+      // Member is private or package-private. Cannot be visible.
+      return false;
+    } else if (member.GetDeclaringClass().IsPublic()) {
+      // Member is public or protected, and class is public. It must be visible.
+      return true;
+    } else if (member.IsConstructor()) {
+      // Member is public or protected constructor and class is not public.
+      // Must be hidden because it cannot be implicitly exposed by a subclass.
+      return false;
+    } else {
+      // Member is public or protected method, but class is not public. Check if
+      // it is exposed through a public subclass.
+      // Example code (`foo` exposed by ClassB):
+      //   class ClassA { public void foo() { ... } }
+      //   public class ClassB extends ClassA {}
+      HierarchyClass* klass = FindClass(member.GetDeclaringClass().GetDescriptor());
+      CHECK(klass != nullptr);
+      bool visible = false;
+      klass->ForEachSubClass([&visible, &member](HierarchyClass* subclass) {
+        if (subclass->HasMatchingMember(member)) {
+          // There is a member which matches `member` in `subclass`, either
+          // a virtual method overriding `member` or a field overshadowing
+          // `member`. In either case, `member` remains hidden.
+          CHECK(member.IsVirtualMethod() || !member.IsMethod());
+          return false;  // do not explore deeper
+        } else if (subclass->GetOneDexClass().IsPublic()) {
+          // `subclass` inherits and exposes `member`.
+          visible = true;
+          return false;  // do not explore deeper
+        } else {
+          // `subclass` inherits `member` but does not expose it.
+          return true;   // explore deeper
+        }
+      });
+      return visible;
+    }
+  }
+
  private:
   HierarchyClass* FindClass(const std::string& descriptor) {
     auto it = classes_.find(descriptor);
@@ -467,7 +528,7 @@
   void BuildClassHierarchy() {
     // Create one HierarchyClass entry in `classes_` per class descriptor
     // and add all DexClass objects with the same descriptor to that entry.
-    class_path_.ForEachDexClass([this](DexClass& klass) {
+    classpath_.ForEachDexClass([this](const DexClass& klass) {
       classes_[klass.GetDescriptor()].AddDexClass(klass);
     });
 
@@ -494,7 +555,7 @@
     }
   }
 
-  ClassPath& class_path_;
+  ClassPath& classpath_;
   std::map<std::string, HierarchyClass> classes_;
 };
 
@@ -547,8 +608,9 @@
           const StringPiece option(argv[i]);
           if (option.starts_with("--boot-dex=")) {
             boot_dex_paths_.push_back(option.substr(strlen("--boot-dex=")).ToString());
-          } else if (option.starts_with("--stub-dex=")) {
-            stub_dex_paths_.push_back(option.substr(strlen("--stub-dex=")).ToString());
+          } else if (option.starts_with("--stub-classpath=")) {
+            stub_classpaths_.push_back(android::base::Split(
+                option.substr(strlen("--stub-classpath=")).ToString(), ":"));
           } else if (option.starts_with("--out-public=")) {
             out_public_path_ = option.substr(strlen("--out-public=")).ToString();
           } else if (option.starts_with("--out-private=")) {
@@ -578,19 +640,15 @@
     OpenApiFile(blacklist_path_, api_list, HiddenApiAccessFlags::kBlacklist);
 
     // Open all dex files.
-    ClassPath boot_class_path(boot_dex_paths_, /* open_writable */ true);
+    ClassPath boot_classpath(boot_dex_paths_, /* open_writable */ true);
 
     // Set access flags of all members.
-    boot_class_path.ForEachDexMember([&api_list](DexMember& boot_member) {
+    boot_classpath.ForEachDexMember([&api_list](const DexMember& boot_member) {
       auto it = api_list.find(boot_member.GetApiEntry());
-      if (it == api_list.end()) {
-        boot_member.SetHidden(HiddenApiAccessFlags::kWhitelist);
-      } else {
-        boot_member.SetHidden(it->second);
-      }
+      boot_member.SetHidden(it == api_list.end() ? HiddenApiAccessFlags::kWhitelist : it->second);
     });
 
-    boot_class_path.UpdateDexChecksums();
+    boot_classpath.UpdateDexChecksums();
   }
 
   void OpenApiFile(const std::string& path,
@@ -614,7 +672,7 @@
   void ListApi() {
     if (boot_dex_paths_.empty()) {
       Usage("No boot DEX files specified");
-    } else if (stub_dex_paths_.empty()) {
+    } else if (stub_classpaths_.empty()) {
       Usage("No stub DEX files specified");
     } else if (out_public_path_.empty()) {
       Usage("No public API output path specified");
@@ -630,39 +688,43 @@
     std::set<std::string> unresolved;
 
     // Open all dex files.
-    ClassPath stub_class_path(stub_dex_paths_, /* open_writable */ false);
-    ClassPath boot_class_path(boot_dex_paths_, /* open_writable */ false);
-    Hierarchy boot_hierarchy(boot_class_path);
+    ClassPath boot_classpath(boot_dex_paths_, /* open_writable */ false);
+    Hierarchy boot_hierarchy(boot_classpath);
 
     // Mark all boot dex members private.
-    boot_class_path.ForEachDexMember([&boot_members](DexMember& boot_member) {
+    boot_classpath.ForEachDexMember([&boot_members](const DexMember& boot_member) {
       boot_members[boot_member.GetApiEntry()] = false;
     });
 
     // Resolve each SDK dex member against the framework and mark it white.
-    stub_class_path.ForEachDexMember(
-        [&boot_hierarchy, &boot_members, &unresolved](DexMember& stub_member) {
-          if (!stub_member.IsVisible()) {
-            // Typically fake constructors and inner-class `this` fields.
-            return;
-          }
-          bool resolved = boot_hierarchy.ForEachResolvableMember(
-              stub_member,
-              [&boot_members](DexMember& boot_member) {
-                std::string entry = boot_member.GetApiEntry();
-                auto it = boot_members.find(entry);
-                CHECK(it != boot_members.end());
-                if (it->second) {
-                  return false;  // has been marked before
-                } else {
-                  it->second = true;
-                  return true;  // marked for the first time
-                }
-              });
-          if (!resolved) {
-            unresolved.insert(stub_member.GetApiEntry());
-          }
-        });
+    for (const std::vector<std::string>& stub_classpath_dex : stub_classpaths_) {
+      ClassPath stub_classpath(stub_classpath_dex, /* open_writable */ false);
+      Hierarchy stub_hierarchy(stub_classpath);
+      stub_classpath.ForEachDexMember(
+          [&stub_hierarchy, &boot_hierarchy, &boot_members, &unresolved](
+              const DexMember& stub_member) {
+            if (!stub_hierarchy.IsMemberVisible(stub_member)) {
+              // Typically fake constructors and inner-class `this` fields.
+              return;
+            }
+            bool resolved = boot_hierarchy.ForEachResolvableMember(
+                stub_member,
+                [&boot_members](const DexMember& boot_member) {
+                  std::string entry = boot_member.GetApiEntry();
+                  auto it = boot_members.find(entry);
+                  CHECK(it != boot_members.end());
+                  if (it->second) {
+                    return false;  // has been marked before
+                  } else {
+                    it->second = true;
+                    return true;  // marked for the first time
+                  }
+                });
+            if (!resolved) {
+              unresolved.insert(stub_member.GetApiEntry());
+            }
+          });
+    }
 
     // Print errors.
     for (const std::string& str : unresolved) {
@@ -685,7 +747,10 @@
 
   // Paths to DEX files which should be processed.
   std::vector<std::string> boot_dex_paths_;
-  std::vector<std::string> stub_dex_paths_;
+
+  // Set of public API stub classpaths. Each classpath is formed by a list
+  // of DEX/APK files in the order they appear on the classpath.
+  std::vector<std::vector<std::string>> stub_classpaths_;
 
   // Paths to text files which contain the lists of API members.
   std::string light_greylist_path_;
diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc
index aa87f21..b50f684 100644
--- a/tools/hiddenapi/hiddenapi_test.cc
+++ b/tools/hiddenapi/hiddenapi_test.cc
@@ -20,6 +20,7 @@
 #include "base/zip_archive.h"
 #include "common_runtime_test.h"
 #include "dex/art_dex_file_loader.h"
+#include "dex/class_accessor-inl.h"
 #include "dex/dex_file-inl.h"
 #include "exec_utils.h"
 
@@ -114,40 +115,27 @@
   }
 
   const DexFile::ClassDef& FindClass(const char* desc, const DexFile& dex_file) {
-    for (uint32_t i = 0; i < dex_file.NumClassDefs(); ++i) {
-      const DexFile::ClassDef& class_def = dex_file.GetClassDef(i);
-      if (strcmp(desc, dex_file.GetClassDescriptor(class_def)) == 0) {
-        return class_def;
-      }
-    }
-    LOG(FATAL) << "Could not find class " << desc;
-    UNREACHABLE();
+    const DexFile::TypeId* type_id = dex_file.FindTypeId(desc);
+    CHECK(type_id != nullptr) << "Could not find class " << desc;
+    const DexFile::ClassDef* found = dex_file.FindClassDef(dex_file.GetIndexForTypeId(*type_id));
+    CHECK(found != nullptr) << "Could not find class " << desc;
+    return *found;
   }
 
   HiddenApiAccessFlags::ApiList GetFieldHiddenFlags(const char* name,
                                                     uint32_t expected_visibility,
                                                     const DexFile::ClassDef& class_def,
                                                     const DexFile& dex_file) {
-    const uint8_t* class_data = dex_file.GetClassData(class_def);
-    if (class_data == nullptr) {
-      LOG(FATAL) << "Class " << dex_file.GetClassDescriptor(class_def) << " has no data";
-      UNREACHABLE();
-    }
+    ClassAccessor accessor(dex_file, class_def);
+    CHECK(accessor.HasClassData()) << "Class " << accessor.GetDescriptor() << " has no data";
 
-    for (ClassDataItemIterator it(dex_file, class_data); it.HasNext(); it.Next()) {
-      if (it.IsAtMethod()) {
-        break;
-      }
-      const DexFile::FieldId& fid = dex_file.GetFieldId(it.GetMemberIndex());
+    for (const ClassAccessor::Field& field : accessor.GetFields()) {
+      const DexFile::FieldId& fid = dex_file.GetFieldId(field.GetIndex());
       if (strcmp(name, dex_file.GetFieldName(fid)) == 0) {
-        uint32_t actual_visibility = it.GetFieldAccessFlags() & kAccVisibilityFlags;
-        if (actual_visibility != expected_visibility) {
-          LOG(FATAL) << "Field " << name << " in class " << dex_file.GetClassDescriptor(class_def)
-                     << " does not have the expected visibility flags (" << expected_visibility
-                     << " != " << actual_visibility << ")";
-          UNREACHABLE();
-        }
-        return it.DecodeHiddenAccessFlags();
+        const uint32_t actual_visibility = field.GetAccessFlags() & kAccVisibilityFlags;
+        CHECK_EQ(actual_visibility, expected_visibility)
+            << "Field " << name << " in class " << accessor.GetDescriptor();
+        return field.DecodeHiddenAccessFlags();
       }
     }
 
@@ -161,31 +149,18 @@
                                                      bool expected_native,
                                                      const DexFile::ClassDef& class_def,
                                                      const DexFile& dex_file) {
-    const uint8_t* class_data = dex_file.GetClassData(class_def);
-    if (class_data == nullptr) {
-      LOG(FATAL) << "Class " << dex_file.GetClassDescriptor(class_def) << " has no data";
-      UNREACHABLE();
-    }
+    ClassAccessor accessor(dex_file, class_def);
+    CHECK(accessor.HasClassData()) << "Class " << accessor.GetDescriptor() << " has no data";
 
-    for (ClassDataItemIterator it(dex_file, class_data); it.HasNext(); it.Next()) {
-      if (!it.IsAtMethod()) {
-        continue;
-      }
-      const DexFile::MethodId& mid = dex_file.GetMethodId(it.GetMemberIndex());
+    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+      const DexFile::MethodId& mid = dex_file.GetMethodId(method.GetIndex());
       if (strcmp(name, dex_file.GetMethodName(mid)) == 0) {
-        if (expected_native != it.MemberIsNative()) {
-          LOG(FATAL) << "Expected native=" << expected_native << " for method " << name
-                     << " in class " << dex_file.GetClassDescriptor(class_def);
-          UNREACHABLE();
-        }
-        uint32_t actual_visibility = it.GetMethodAccessFlags() & kAccVisibilityFlags;
-        if (actual_visibility != expected_visibility) {
-          LOG(FATAL) << "Method " << name << " in class " << dex_file.GetClassDescriptor(class_def)
-                     << " does not have the expected visibility flags (" << expected_visibility
-                     << " != " << actual_visibility << ")";
-          UNREACHABLE();
-        }
-        return it.DecodeHiddenAccessFlags();
+        CHECK_EQ(expected_native, method.MemberIsNative())
+            << "Method " << name << " in class " << accessor.GetDescriptor();
+        const uint32_t actual_visibility = method.GetAccessFlags() & kAccVisibilityFlags;
+        CHECK_EQ(actual_visibility, expected_visibility)
+            << "Method " << name << " in class " << accessor.GetDescriptor();
+        return method.DecodeHiddenAccessFlags();
       }
     }
 
diff --git a/tools/jfuzz/jfuzz.cc b/tools/jfuzz/jfuzz.cc
index 60c6275..a97a99c 100644
--- a/tools/jfuzz/jfuzz.cc
+++ b/tools/jfuzz/jfuzz.cc
@@ -1302,7 +1302,7 @@
 int32_t main(int32_t argc, char** argv) {
   // Time-based seed.
   struct timeval tp;
-  gettimeofday(&tp, NULL);
+  gettimeofday(&tp, nullptr);
 
   // Defaults.
   uint32_t seed = (tp.tv_sec * 1000000 + tp.tv_usec);
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 60aac3c..264217e 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -214,5 +214,15 @@
   description: "java.io.IOException: Error writing ASN.1 encoding",
   result: EXEC_FAILED,
   names: ["libcore.javax.crypto.spec.AlgorithmParametersTestGCM#testEncoding"]
+},
+{
+  description: "Tests fail because mockito can not read android.os.Build$VERSION",
+  result: EXEC_FAILED,
+  bug: 111704422,
+  names: ["libcore.java.lang.ThreadTest#testUncaughtExceptionPreHandler_calledBeforeDefaultHandler",
+          "libcore.java.lang.ThreadTest#testUncaughtExceptionPreHandler_noDefaultHandler",
+          "libcore.javax.crypto.CipherInputStreamTest#testCloseTwice",
+          "libcore.libcore.io.BlockGuardOsTest#test_android_getaddrinfo_networkPolicy",
+          "libcore.libcore.io.BlockGuardOsTest#test_checkNewMethodsInPosix"]
 }
 ]
diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt
index f0e4932..942a4e0 100644
--- a/tools/libcore_gcstress_debug_failures.txt
+++ b/tools/libcore_gcstress_debug_failures.txt
@@ -9,12 +9,14 @@
   result: EXEC_FAILED,
   modes: [device],
   names: ["jsr166.CompletableFutureTest#testCompleteOnTimeout_completed",
+          "jsr166.CompletableFutureTest#testDelayedExecutor",
           "libcore.libcore.icu.TransliteratorTest#testAll",
           "libcore.libcore.icu.RelativeDateTimeFormatterTest#test_bug25821045",
           "libcore.libcore.icu.RelativeDateTimeFormatterTest#test_bug25883157",
           "libcore.java.lang.ref.ReferenceQueueTest#testRemoveWithDelayedResultAndTimeout",
           "libcore.java.util.TimeZoneTest#testSetDefaultDeadlock",
           "libcore.javax.crypto.CipherBasicsTest#testBasicEncryption",
+          "org.apache.harmony.tests.java.text.MessageFormatTest#test_parseLjava_lang_String",
           "org.apache.harmony.tests.java.util.TimerTest#testThrowingTaskKillsTimerThread"]
 },
 {
diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh
index aff009a..2d39b2a 100755
--- a/tools/run-libcore-tests.sh
+++ b/tools/run-libcore-tests.sh
@@ -156,7 +156,11 @@
 # Increase the timeout, as vogar cannot set individual test
 # timeout when being asked to run packages, and some tests go above
 # the default timeout.
-vogar_args="$vogar_args --timeout 480"
+if $gcstress && $debug && $device_mode; then
+  vogar_args="$vogar_args --timeout 1440"
+else
+  vogar_args="$vogar_args --timeout 480"
+fi
 
 # set the toolchain to use.
 vogar_args="$vogar_args --toolchain d8 --language CUR"
@@ -191,4 +195,7 @@
 # Run the tests using vogar.
 echo "Running tests for the following test packages:"
 echo ${working_packages[@]} | tr " " "\n"
-vogar $vogar_args $expectations $(cparg $DEPS) ${working_packages[@]}
+
+cmd="vogar $vogar_args $expectations $(cparg $DEPS) ${working_packages[@]}"
+echo "Running $cmd"
+eval $cmd
diff --git a/tools/teardown-buildbot-device.sh b/tools/teardown-buildbot-device.sh
index be68b9f..6634fb4 100755
--- a/tools/teardown-buildbot-device.sh
+++ b/tools/teardown-buildbot-device.sh
@@ -42,15 +42,14 @@
     # that started this process.
     for_all_chroot_process() {
       local action=$1
-      for link in $(adb shell ls -d "/proc/*/root"); do
-        local root=$(adb shell readlink "$link")
-        if [[ "x$root" = "x$ART_TEST_CHROOT" ]]; then
-          local dir=$(dirname "$link")
-          local pid=$(basename "$dir")
-          local cmdline=$(adb shell cat "$dir"/cmdline | tr '\000' ' ')
-          $action "$pid" "$cmdline"
-        fi
-      done
+      adb shell ls -ld "/proc/*/root" \
+        | sed -n -e "s,^.* \\(/proc/.*/root\\) -> $ART_TEST_CHROOT\$,\\1,p" \
+        | while read link; do
+            local dir=$(dirname "$link")
+            local pid=$(basename "$dir")
+            local cmdline=$(adb shell cat "$dir"/cmdline | tr '\000' ' ')
+            $action "$pid" "$cmdline"
+          done
     }
 
     # display_process PID CMDLINE
diff --git a/tools/veridex/flow_analysis.cc b/tools/veridex/flow_analysis.cc
index d4f7e5f..f5eb4ea 100644
--- a/tools/veridex/flow_analysis.cc
+++ b/tools/veridex/flow_analysis.cc
@@ -17,6 +17,7 @@
 #include "flow_analysis.h"
 
 #include "dex/bytecode_utils.h"
+#include "dex/class_accessor-inl.h"
 #include "dex/code_item_accessors-inl.h"
 #include "dex/dex_instruction-inl.h"
 #include "dex/dex_file-inl.h"
@@ -26,6 +27,13 @@
 
 namespace art {
 
+VeriFlowAnalysis::VeriFlowAnalysis(VeridexResolver* resolver,
+                                   const ClassAccessor::Method& method)
+    : resolver_(resolver),
+      method_id_(method.GetIndex()),
+      code_item_accessor_(method.GetInstructionsAndData()),
+      dex_registers_(code_item_accessor_.InsnsSizeInCodeUnits()),
+      instruction_infos_(code_item_accessor_.InsnsSizeInCodeUnits()) {}
 
 void VeriFlowAnalysis::SetAsBranchTarget(uint32_t dex_pc) {
   if (dex_registers_[dex_pc] == nullptr) {
diff --git a/tools/veridex/flow_analysis.h b/tools/veridex/flow_analysis.h
index fc09360..9c86024 100644
--- a/tools/veridex/flow_analysis.h
+++ b/tools/veridex/flow_analysis.h
@@ -17,6 +17,7 @@
 #ifndef ART_TOOLS_VERIDEX_FLOW_ANALYSIS_H_
 #define ART_TOOLS_VERIDEX_FLOW_ANALYSIS_H_
 
+#include "dex/class_accessor.h"
 #include "dex/code_item_accessors.h"
 #include "dex/dex_file_reference.h"
 #include "dex/method_reference.h"
@@ -108,12 +109,7 @@
 
 class VeriFlowAnalysis {
  public:
-  VeriFlowAnalysis(VeridexResolver* resolver, const ClassDataItemIterator& it)
-      : resolver_(resolver),
-        method_id_(it.GetMemberIndex()),
-        code_item_accessor_(resolver->GetDexFile(), it.GetMethodCodeItem()),
-        dex_registers_(code_item_accessor_.InsnsSizeInCodeUnits()),
-        instruction_infos_(code_item_accessor_.InsnsSizeInCodeUnits()) {}
+  VeriFlowAnalysis(VeridexResolver* resolver, const ClassAccessor::Method& method);
 
   void Run();
 
@@ -189,8 +185,8 @@
 // Collects all reflection uses.
 class FlowAnalysisCollector : public VeriFlowAnalysis {
  public:
-  FlowAnalysisCollector(VeridexResolver* resolver, const ClassDataItemIterator& it)
-      : VeriFlowAnalysis(resolver, it) {}
+  FlowAnalysisCollector(VeridexResolver* resolver, const ClassAccessor::Method& method)
+      : VeriFlowAnalysis(resolver, method) {}
 
   const std::vector<ReflectAccessInfo>& GetUses() const {
     return uses_;
@@ -208,9 +204,9 @@
 class FlowAnalysisSubstitutor : public VeriFlowAnalysis {
  public:
   FlowAnalysisSubstitutor(VeridexResolver* resolver,
-                          const ClassDataItemIterator& it,
+                          const ClassAccessor::Method& method,
                           const std::map<MethodReference, std::vector<ReflectAccessInfo>>& accesses)
-      : VeriFlowAnalysis(resolver, it), accesses_(accesses) {}
+      : VeriFlowAnalysis(resolver, method), accesses_(accesses) {}
 
   const std::vector<ReflectAccessInfo>& GetUses() const {
     return uses_;
diff --git a/tools/veridex/hidden_api_finder.cc b/tools/veridex/hidden_api_finder.cc
index 8c6139f..4eba10e 100644
--- a/tools/veridex/hidden_api_finder.cc
+++ b/tools/veridex/hidden_api_finder.cc
@@ -16,6 +16,7 @@
 
 #include "hidden_api_finder.h"
 
+#include "dex/class_accessor-inl.h"
 #include "dex/code_item_accessors-inl.h"
 #include "dex/dex_instruction-inl.h"
 #include "dex/dex_file.h"
@@ -62,23 +63,9 @@
   }
   // Note: we collect strings constants only referenced in code items as the string table
   // contains other kind of strings (eg types).
-  size_t class_def_count = dex_file.NumClassDefs();
-  for (size_t class_def_index = 0; class_def_index < class_def_count; ++class_def_index) {
-    const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index);
-    const uint8_t* class_data = dex_file.GetClassData(class_def);
-    if (class_data == nullptr) {
-      // Empty class.
-      continue;
-    }
-    ClassDataItemIterator it(dex_file, class_data);
-    it.SkipAllFields();
-    for (; it.HasNextMethod(); it.Next()) {
-      const DexFile::CodeItem* code_item = it.GetMethodCodeItem();
-      if (code_item == nullptr) {
-        continue;
-      }
-      CodeItemDataAccessor code_item_accessor(dex_file, code_item);
-      for (const DexInstructionPcPair& inst : code_item_accessor) {
+  for (ClassAccessor accessor : dex_file.GetClasses()) {
+    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+      for (const DexInstructionPcPair& inst : method.GetInstructions()) {
         switch (inst->Opcode()) {
           case Instruction::CONST_STRING: {
             dex::StringIndex string_index(inst->VRegB_21c());
@@ -103,8 +90,7 @@
                 // We only keep track of the location for strings, as these will be the
                 // field/method names the user is interested in.
                 strings_.insert(name);
-                reflection_locations_[name].push_back(
-                    MethodReference(&dex_file, it.GetMemberIndex()));
+                reflection_locations_[name].push_back(method.GetReference());
               }
             }
             break;
@@ -114,8 +100,7 @@
           case Instruction::INVOKE_STATIC:
           case Instruction::INVOKE_SUPER:
           case Instruction::INVOKE_VIRTUAL: {
-            CheckMethod(
-                inst->VRegB_35c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+            CheckMethod(inst->VRegB_35c(), resolver, method.GetReference());
             break;
           }
 
@@ -124,8 +109,7 @@
           case Instruction::INVOKE_STATIC_RANGE:
           case Instruction::INVOKE_SUPER_RANGE:
           case Instruction::INVOKE_VIRTUAL_RANGE: {
-            CheckMethod(
-                inst->VRegB_3rc(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+            CheckMethod(inst->VRegB_3rc(), resolver, method.GetReference());
             break;
           }
 
@@ -136,8 +120,7 @@
           case Instruction::IGET_BYTE:
           case Instruction::IGET_CHAR:
           case Instruction::IGET_SHORT: {
-            CheckField(
-                inst->VRegC_22c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+            CheckField(inst->VRegC_22c(), resolver, method.GetReference());
             break;
           }
 
@@ -148,8 +131,7 @@
           case Instruction::IPUT_BYTE:
           case Instruction::IPUT_CHAR:
           case Instruction::IPUT_SHORT: {
-            CheckField(
-                inst->VRegC_22c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+            CheckField(inst->VRegC_22c(), resolver, method.GetReference());
             break;
           }
 
@@ -160,8 +142,7 @@
           case Instruction::SGET_BYTE:
           case Instruction::SGET_CHAR:
           case Instruction::SGET_SHORT: {
-            CheckField(
-                inst->VRegB_21c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+            CheckField(inst->VRegB_21c(), resolver, method.GetReference());
             break;
           }
 
@@ -172,8 +153,7 @@
           case Instruction::SPUT_BYTE:
           case Instruction::SPUT_CHAR:
           case Instruction::SPUT_SHORT: {
-            CheckField(
-                inst->VRegB_21c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+            CheckField(inst->VRegB_21c(), resolver, method.GetReference());
             break;
           }
 
diff --git a/tools/veridex/precise_hidden_api_finder.cc b/tools/veridex/precise_hidden_api_finder.cc
index 89754c2..445221e 100644
--- a/tools/veridex/precise_hidden_api_finder.cc
+++ b/tools/veridex/precise_hidden_api_finder.cc
@@ -16,6 +16,7 @@
 
 #include "precise_hidden_api_finder.h"
 
+#include "dex/class_accessor-inl.h"
 #include "dex/code_item_accessors-inl.h"
 #include "dex/dex_instruction-inl.h"
 #include "dex/dex_file.h"
@@ -31,25 +32,13 @@
 
 void PreciseHiddenApiFinder::RunInternal(
     const std::vector<std::unique_ptr<VeridexResolver>>& resolvers,
-    const std::function<void(VeridexResolver*, const ClassDataItemIterator&)>& action) {
+    const std::function<void(VeridexResolver*, const ClassAccessor::Method&)>& action) {
   for (const std::unique_ptr<VeridexResolver>& resolver : resolvers) {
-    const DexFile& dex_file = resolver->GetDexFile();
-    size_t class_def_count = dex_file.NumClassDefs();
-    for (size_t class_def_index = 0; class_def_index < class_def_count; ++class_def_index) {
-      const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index);
-      const uint8_t* class_data = dex_file.GetClassData(class_def);
-      if (class_data == nullptr) {
-        // Empty class.
-        continue;
-      }
-      ClassDataItemIterator it(dex_file, class_data);
-      it.SkipAllFields();
-      for (; it.HasNextMethod(); it.Next()) {
-        const DexFile::CodeItem* code_item = it.GetMethodCodeItem();
-        if (code_item == nullptr) {
-          continue;
+    for (ClassAccessor accessor : resolver->GetDexFile().GetClasses()) {
+      for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+        if (method.GetCodeItem() != nullptr) {
+          action(resolver.get(), method);
         }
-        action(resolver.get(), it);
       }
     }
   }
@@ -68,10 +57,10 @@
 
 void PreciseHiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers) {
   // Collect reflection uses.
-  RunInternal(resolvers, [this] (VeridexResolver* resolver, const ClassDataItemIterator& it) {
-    FlowAnalysisCollector collector(resolver, it);
+  RunInternal(resolvers, [this] (VeridexResolver* resolver, const ClassAccessor::Method& method) {
+    FlowAnalysisCollector collector(resolver, method);
     collector.Run();
-    AddUsesAt(collector.GetUses(), MethodReference(&resolver->GetDexFile(), it.GetMemberIndex()));
+    AddUsesAt(collector.GetUses(), method.GetReference());
   });
 
   // For non-final reflection uses, do a limited fixed point calculation over the code to try
@@ -84,11 +73,11 @@
     std::map<MethodReference, std::vector<ReflectAccessInfo>> current_uses
         = std::move(abstract_uses_);
     RunInternal(resolvers,
-                [this, current_uses] (VeridexResolver* resolver, const ClassDataItemIterator& it) {
-      FlowAnalysisSubstitutor substitutor(resolver, it, current_uses);
+                [this, current_uses] (VeridexResolver* resolver,
+                                      const ClassAccessor::Method& method) {
+      FlowAnalysisSubstitutor substitutor(resolver, method, current_uses);
       substitutor.Run();
-      AddUsesAt(substitutor.GetUses(),
-                MethodReference(&resolver->GetDexFile(), it.GetMemberIndex()));
+      AddUsesAt(substitutor.GetUses(), method.GetReference());
     });
   }
 }
diff --git a/tools/veridex/precise_hidden_api_finder.h b/tools/veridex/precise_hidden_api_finder.h
index 1c4d0ae..8c5126c 100644
--- a/tools/veridex/precise_hidden_api_finder.h
+++ b/tools/veridex/precise_hidden_api_finder.h
@@ -48,7 +48,7 @@
   // Run over all methods of all dex files, and call `action` on each.
   void RunInternal(
       const std::vector<std::unique_ptr<VeridexResolver>>& resolvers,
-      const std::function<void(VeridexResolver*, const ClassDataItemIterator&)>& action);
+      const std::function<void(VeridexResolver*, const ClassAccessor::Method&)>& action);
 
   // Add uses found in method `ref`.
   void AddUsesAt(const std::vector<ReflectAccessInfo>& accesses, MethodReference ref);
diff --git a/tools/veridex/resolver.cc b/tools/veridex/resolver.cc
index 9113039..56729ff 100644
--- a/tools/veridex/resolver.cc
+++ b/tools/veridex/resolver.cc
@@ -16,6 +16,7 @@
 
 #include "resolver.h"
 
+#include "dex/class_accessor-inl.h"
 #include "dex/dex_file-inl.h"
 #include "dex/primitive.h"
 #include "hidden_api.h"
@@ -24,34 +25,22 @@
 namespace art {
 
 void VeridexResolver::Run() {
-  size_t class_def_count = dex_file_.NumClassDefs();
-  for (size_t class_def_index = 0; class_def_index < class_def_count; ++class_def_index) {
-    const DexFile::ClassDef& class_def = dex_file_.GetClassDef(class_def_index);
-    std::string name(dex_file_.StringByTypeIdx(class_def.class_idx_));
+  for (ClassAccessor accessor : dex_file_.GetClasses()) {
+    std::string name(accessor.GetDescriptor());
     auto existing = type_map_.find(name);
+    const uint32_t type_idx = accessor.GetClassIdx().index_;
     if (existing != type_map_.end()) {
       // Class already exists, cache it and move on.
-      type_infos_[class_def.class_idx_.index_] = *existing->second;
+      type_infos_[type_idx] = *existing->second;
       continue;
     }
-    type_infos_[class_def.class_idx_.index_] = VeriClass(Primitive::Type::kPrimNot, 0, &class_def);
-    type_map_[name] = &(type_infos_[class_def.class_idx_.index_]);
-
-    const uint8_t* class_data = dex_file_.GetClassData(class_def);
-    if (class_data == nullptr) {
-      // Empty class.
-      continue;
+    type_infos_[type_idx] = VeriClass(Primitive::Type::kPrimNot, 0, &accessor.GetClassDef());
+    type_map_[name] = &type_infos_[type_idx];
+    for (const ClassAccessor::Field& field : accessor.GetFields()) {
+      field_infos_[field.GetIndex()] = field.GetDataPointer();
     }
-
-    ClassDataItemIterator it(dex_file_, class_data);
-    for (; it.HasNextStaticField(); it.Next()) {
-      field_infos_[it.GetMemberIndex()] = it.DataPointer();
-    }
-    for (; it.HasNextInstanceField(); it.Next()) {
-      field_infos_[it.GetMemberIndex()] = it.DataPointer();
-    }
-    for (; it.HasNextMethod(); it.Next()) {
-      method_infos_[it.GetMemberIndex()] = it.DataPointer();
+    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
+      method_infos_[method.GetIndex()] = method.GetDataPointer();
     }
   }
 }
@@ -148,18 +137,14 @@
 
   // Look at methods declared in `kls`.
   const DexFile& other_dex_file = resolver->dex_file_;
-  const uint8_t* class_data = other_dex_file.GetClassData(*kls.GetClassDef());
-  if (class_data != nullptr) {
-    ClassDataItemIterator it(other_dex_file, class_data);
-    it.SkipAllFields();
-    for (; it.HasNextMethod(); it.Next()) {
-      const DexFile::MethodId& other_method_id = other_dex_file.GetMethodId(it.GetMemberIndex());
-      if (HasSameNameAndSignature(other_dex_file,
-                                  other_method_id,
-                                  method_name,
-                                  method_signature)) {
-        return it.DataPointer();
-      }
+  ClassAccessor other_dex_accessor(other_dex_file, *kls.GetClassDef());
+  for (const ClassAccessor::Method& method : other_dex_accessor.GetMethods()) {
+    const DexFile::MethodId& other_method_id = other_dex_file.GetMethodId(method.GetIndex());
+    if (HasSameNameAndSignature(other_dex_file,
+                                other_method_id,
+                                method_name,
+                                method_signature)) {
+      return method.GetDataPointer();
     }
   }
 
@@ -207,17 +192,14 @@
 
   // Look at fields declared in `kls`.
   const DexFile& other_dex_file = resolver->dex_file_;
-  const uint8_t* class_data = other_dex_file.GetClassData(*kls.GetClassDef());
-  if (class_data != nullptr) {
-    ClassDataItemIterator it(other_dex_file, class_data);
-    for (; it.HasNextStaticField() || it.HasNextInstanceField(); it.Next()) {
-      const DexFile::FieldId& other_field_id = other_dex_file.GetFieldId(it.GetMemberIndex());
-      if (HasSameNameAndType(other_dex_file,
-                             other_field_id,
-                             field_name,
-                             field_type)) {
-        return it.DataPointer();
-      }
+  ClassAccessor other_dex_accessor(other_dex_file, *kls.GetClassDef());
+  for (const ClassAccessor::Field& field : other_dex_accessor.GetFields()) {
+    const DexFile::FieldId& other_field_id = other_dex_file.GetFieldId(field.GetIndex());
+    if (HasSameNameAndType(other_dex_file,
+                           other_field_id,
+                           field_name,
+                           field_type)) {
+      return field.GetDataPointer();
     }
   }
 
@@ -260,18 +242,13 @@
   }
   VeridexResolver* resolver = GetResolverOf(kls);
   const DexFile& other_dex_file = resolver->dex_file_;
-  const uint8_t* class_data = other_dex_file.GetClassData(*kls.GetClassDef());
-  if (class_data != nullptr) {
-    ClassDataItemIterator it(other_dex_file, class_data);
-    it.SkipAllFields();
-    for (; it.HasNextMethod(); it.Next()) {
-      const DexFile::MethodId& other_method_id = other_dex_file.GetMethodId(it.GetMemberIndex());
-      if (HasSameNameAndSignature(other_dex_file,
-                                  other_method_id,
-                                  method_name,
-                                  type)) {
-        return it.DataPointer();
-      }
+  ClassAccessor other_dex_accessor(other_dex_file, *kls.GetClassDef());
+  for (const ClassAccessor::Method& method : other_dex_accessor.GetMethods()) {
+    if (HasSameNameAndSignature(other_dex_file,
+                                other_dex_file.GetMethodId(method.GetIndex()),
+                                method_name,
+                                type)) {
+      return method.GetDataPointer();
     }
   }
   return nullptr;