Implement shared counters for boot image / zygote methods.

Add a thread-local counter which the interpreter increments. When
hitting zero, the interpreter goes into the runtime and updates a map of
counters. If the counter for the particular method hits a threshold, we
JIT it.

Test: test.py
Test: imgdiag looking at boot.art
Bug: 162110941

Change-Id: I6493332eafc51856494ef20592ca83bc716c994e
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 331b8b7..795d621 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -3355,6 +3355,11 @@
         nullptr, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
   }
 
+  if (!orig->IsRuntimeMethod() &&
+      (compiler_options_.IsBootImage() || compiler_options_.IsBootImageExtension())) {
+    orig->SetMemorySharedMethod();
+  }
+
   memcpy(copy, orig, ArtMethod::Size(target_ptr_size_));
 
   CopyAndFixupReference(copy->GetDeclaringClassAddressWithoutBarrier(),
diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h
index f9cd8c8..72949b0 100644
--- a/libdexfile/dex/modifiers.h
+++ b/libdexfile/dex/modifiers.h
@@ -104,9 +104,9 @@
 static constexpr uint32_t kAccPublicApi =             0x10000000;  // field, method
 static constexpr uint32_t kAccCorePlatformApi =       0x20000000;  // field, method
 
-// Not currently used, except for intrinsic methods where these bits
-// are part of the intrinsic ordinal.
-static constexpr uint32_t kAccMayBeUnusedBits =       0x40000000;
+// For methods which we'd like to share memory between zygote and apps.
+// Uses an intrinsic bit but that's OK as intrinsics are always in the boot image.
+static constexpr uint32_t kAccMemorySharedMethod =       0x40000000;
 
 // Set by the compiler driver when compiling boot classes with instrinsic methods.
 static constexpr uint32_t kAccIntrinsic  =            0x80000000;  // method (runtime)
@@ -125,7 +125,7 @@
 // which overlap are not valid when kAccIntrinsic is set.
 static constexpr uint32_t kAccIntrinsicBits = kAccHiddenapiBits |
     kAccSingleImplementation | kAccMustCountLocks | kAccCompileDontBother | kAccCopied |
-    kAccPreviouslyWarm | kAccMayBeUnusedBits;
+    kAccPreviouslyWarm | kAccMemorySharedMethod;
 
 // Valid (meaningful) bits for a field.
 static constexpr uint32_t kAccValidFieldFlags = kAccPublic | kAccPrivate | kAccProtected |
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 5d90b9c..844a0ff 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -441,6 +441,9 @@
   if (IsAbstract()) {
     return;
   }
+  if (IsMemorySharedMethod()) {
+    return;
+  }
   DCHECK_EQ(new_value, Runtime::Current()->GetJITOptions()->GetWarmupThreshold());
   // Avoid dirtying the value if possible.
   if (hotness_count_ != new_value) {
@@ -460,6 +463,9 @@
   DCHECK(!IsAbstract());
   DCHECK_GT(new_samples, 0);
   DCHECK_LE(new_samples, std::numeric_limits<uint16_t>::max());
+  if (IsMemorySharedMethod()) {
+    return;
+  }
   uint16_t old_hotness_count = hotness_count_;
   uint16_t new_count = (old_hotness_count <= new_samples) ? 0u : old_hotness_count - new_samples;
   // Avoid dirtying the value if possible.
diff --git a/runtime/art_method.h b/runtime/art_method.h
index ac6988a..d3005cd 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -250,6 +250,17 @@
     AddAccessFlags(kAccPreCompiled | kAccCompileDontBother);
   }
 
+  bool IsMemorySharedMethod() {
+    return (GetAccessFlags() & kAccMemorySharedMethod) != 0;
+  }
+
+  void SetMemorySharedMethod() REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (!IsIntrinsic() && !IsAbstract()) {
+      AddAccessFlags(kAccMemorySharedMethod);
+      SetHotCounter();
+    }
+  }
+
   void ClearPreCompiled() REQUIRES_SHARED(Locks::mutator_lock_) {
     ClearAccessFlags(kAccPreCompiled | kAccCompileDontBother);
   }
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 3f0de3b..7f2d926 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3751,6 +3751,10 @@
        std::none_of(shorty.begin() + slow_args_search_start, shorty.end(), is_slow_arg))) {
     dst->SetNterpInvokeFastPathFlag();
   }
+
+  if (Runtime::Current()->IsZygote()) {
+    dst->SetMemorySharedMethod();
+  }
 }
 
 void ClassLinker::AppendToBootClassPath(Thread* self, const DexFile* dex_file) {
diff --git a/runtime/interpreter/mterp/arm64ng/main.S b/runtime/interpreter/mterp/arm64ng/main.S
index 4364df0..8ada63c 100644
--- a/runtime/interpreter/mterp/arm64ng/main.S
+++ b/runtime/interpreter/mterp/arm64ng/main.S
@@ -308,12 +308,7 @@
     cbz w2, NterpHandleHotnessOverflow
     add x2, x2, #-1
     strh w2, [x0, #ART_METHOD_HOTNESS_COUNT_OFFSET]
-    // Otherwise, do a suspend check.
-    ldr w0, [xSELF, #THREAD_FLAGS_OFFSET]
-    tst w0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
-    b.eq 1b
-    EXPORT_PC
-    bl    art_quick_test_suspend
+    DO_SUSPEND_CHECK continue_label=1b
     b 1b
 .endm
 
@@ -432,25 +427,22 @@
 #error Expected 0 for hotness value
 #endif
     // If the counter is at zero, handle this in the runtime.
-    cbz w2, 2f
+    cbz w2, 3f
     add x2, x2, #-1
     strh w2, [x0, #ART_METHOD_HOTNESS_COUNT_OFFSET]
-    ldr w0, [xSELF, #THREAD_FLAGS_OFFSET]
-    tst w0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
-    b.ne 3f
 1:
+    DO_SUSPEND_CHECK continue_label=2f
+2:
     FETCH_INST
     GET_INST_OPCODE ip
     GOTO_OPCODE ip
-2:
+3:
+    CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot=4f, if_not_hot=1b
+4:
     mov x1, xzr
     mov x2, xFP
     bl nterp_hot_method
-    b 1b
-3:
-    EXPORT_PC
-    bl art_quick_test_suspend
-    b 1b
+    b 2b
 .endm
 
 .macro SPILL_ALL_CALLEE_SAVES
@@ -1571,6 +1563,24 @@
     cbnz \ins, 1b
 .endm
 
+.macro CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot, if_not_hot
+    ldr wip, [x0, #ART_METHOD_ACCESS_FLAGS_OFFSET]
+    tbz wip, #ART_METHOD_IS_MEMORY_SHARED_FLAG_BIT, \if_hot
+    ldr wip, [xSELF, #THREAD_SHARED_METHOD_HOTNESS_OFFSET]
+    cbz wip, \if_hot
+    add wip, wip, #-1
+    str wip, [xSELF, #THREAD_SHARED_METHOD_HOTNESS_OFFSET]
+    b \if_not_hot
+.endm
+
+.macro DO_SUSPEND_CHECK continue_label
+    ldr wip, [xSELF, #THREAD_FLAGS_OFFSET]
+    tst wip, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
+    b.eq \continue_label
+    EXPORT_PC
+    bl    art_quick_test_suspend
+.endm
+
 %def entry():
 /*
  * ArtMethod entry point.
@@ -1754,14 +1764,17 @@
    COMMON_INVOKE_RANGE is_string_init=1, suffix="stringInit"
 
 NterpHandleHotnessOverflow:
+    CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot=1f, if_not_hot=5f
+1:
     mov x1, xPC
     mov x2, xFP
     bl nterp_hot_method
-    cbnz x0, 1f
+    cbnz x0, 3f
+2:
     FETCH wINST, 0                      // load wINST
     GET_INST_OPCODE ip                  // extract opcode from wINST
     GOTO_OPCODE ip                      // jump to next instruction
-1:
+3:
     // Drop the current frame.
     ldr ip, [xREFS, #-8]
     mov sp, ip
@@ -1801,11 +1814,11 @@
     sub sp, sp, x1
 
     add x2, x0, #OSR_DATA_MEMORY
-2:
+4:
     sub x1, x1, #8
     ldr ip, [x2, x1]
     str ip, [sp, x1]
-    cbnz x1, 2b
+    cbnz x1, 4b
 
     // Fetch the native PC to jump to and save it in a callee-save register.
     ldr xFP, [x0, #OSR_DATA_NATIVE_PC]
@@ -1815,6 +1828,9 @@
 
     // Jump to the compiled code.
     br xFP
+5:
+    DO_SUSPEND_CHECK continue_label=2b
+    b 2b
 
 // This is the logical end of ExecuteNterpImpl, where the frame info applies.
 // EndExecuteNterpImpl includes the methods below as we want the runtime to
diff --git a/runtime/interpreter/mterp/armng/main.S b/runtime/interpreter/mterp/armng/main.S
index 7fe507c..5a086f5 100644
--- a/runtime/interpreter/mterp/armng/main.S
+++ b/runtime/interpreter/mterp/armng/main.S
@@ -316,12 +316,7 @@
     beq NterpHandleHotnessOverflow
     add r2, r2, #-1
     strh r2, [r0, #ART_METHOD_HOTNESS_COUNT_OFFSET]
-    // Otherwise, do a suspend check.
-    ldr r0, [rSELF, #THREAD_FLAGS_OFFSET]
-    tst r0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
-    beq 1b
-    EXPORT_PC
-    bl    art_quick_test_suspend
+    DO_SUSPEND_CHECK continue_label=1b
     b 1b
 .endm
 
@@ -455,25 +450,22 @@
     ldr r0, [sp]
     ldrh r2, [r0, #ART_METHOD_HOTNESS_COUNT_OFFSET]
     cmp r2, #NTERP_HOTNESS_VALUE
-    beq 2f
+    beq 3f
     add r2, r2, #-1
     strh r2, [r0, #ART_METHOD_HOTNESS_COUNT_OFFSET]
-    ldr r0, [rSELF, #THREAD_FLAGS_OFFSET]
-    tst r0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
-    bne 3f
 1:
+    DO_SUSPEND_CHECK continue_label=2f
+2:
     FETCH_INST
     GET_INST_OPCODE ip
     GOTO_OPCODE ip
-2:
+3:
+    CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot=4f, if_not_hot=1b
+4:
     mov r1, #0
     mov r2, rFP
     bl nterp_hot_method
-    b 1b
-3:
-    EXPORT_PC
-    bl art_quick_test_suspend
-    b 1b
+    b 2b
 .endm
 
 .macro SPILL_ALL_CALLEE_SAVES
@@ -1584,6 +1576,28 @@
     bne 1b
 .endm
 
+.macro DO_SUSPEND_CHECK continue_label
+    // Otherwise, do a suspend check.
+    ldr ip, [rSELF, #THREAD_FLAGS_OFFSET]
+    tst ip, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
+    beq \continue_label
+    EXPORT_PC
+    bl    art_quick_test_suspend
+.endm
+
+.macro CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot, if_not_hot
+    ldr ip, [r0, #ART_METHOD_ACCESS_FLAGS_OFFSET]
+    tst ip, #ART_METHOD_IS_MEMORY_SHARED_FLAG
+    beq \if_hot
+    ldr ip, [rSELF, #THREAD_SHARED_METHOD_HOTNESS_OFFSET]
+    cmp ip, #0
+    beq \if_hot
+    add ip, ip, #-1
+    str ip, [rSELF, #THREAD_SHARED_METHOD_HOTNESS_OFFSET]
+    b \if_not_hot
+.endm
+
+
 %def entry():
 /*
  * ArtMethod entry point.
@@ -1770,15 +1784,18 @@
 
 
 NterpHandleHotnessOverflow:
+    CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot=1f, if_not_hot=5f
+1:
     mov r1, rPC
     mov r2, rFP
     bl nterp_hot_method
     cmp r0, #0
-    bne 1f
+    bne 3f
+2:
     FETCH_INST                          // load rINST
     GET_INST_OPCODE ip                  // extract opcode from rINST
     GOTO_OPCODE ip                      // jump to next instruction
-1:
+3:
     // Drop the current frame.
     ldr ip, [rREFS, #-4]
     mov sp, ip
@@ -1815,12 +1832,12 @@
     sub sp, sp, r1
 
     add r2, r0, #OSR_DATA_MEMORY
-2:
+4:
     sub r1, r1, #4
     ldr ip, [r2, r1]
     str ip, [sp, r1]
     cmp r1, #0
-    bne 2b
+    bne 4b
 
     // Fetch the native PC to jump to and save it in a callee-save register.
     ldr rFP, [r0, #OSR_DATA_NATIVE_PC]
@@ -1830,6 +1847,10 @@
 
     // Jump to the compiled code.
     bx rFP
+5:
+    DO_SUSPEND_CHECK continue_label=2b
+    b 2b
+
 // This is the logical end of ExecuteNterpImpl, where the frame info applies.
 // EndExecuteNterpImpl includes the methods below as we want the runtime to
 // see them as part of the Nterp PCs.
diff --git a/runtime/interpreter/mterp/nterp.cc b/runtime/interpreter/mterp/nterp.cc
index c58c8a0..d70a846 100644
--- a/runtime/interpreter/mterp/nterp.cc
+++ b/runtime/interpreter/mterp/nterp.cc
@@ -693,7 +693,12 @@
   // method.
   ScopedAssertNoThreadSuspension sants("In nterp");
   Runtime* runtime = Runtime::Current();
-  method->ResetCounter(runtime->GetJITOptions()->GetWarmupThreshold());
+  if (method->IsMemorySharedMethod()) {
+    DCHECK_EQ(Thread::Current()->GetSharedMethodHotness(), 0u);
+    Thread::Current()->ResetSharedMethodHotness();
+  } else {
+    method->ResetCounter(runtime->GetJITOptions()->GetWarmupThreshold());
+  }
   jit::Jit* jit = runtime->GetJit();
   if (jit != nullptr && jit->UseJitCompilation()) {
     // Nterp passes null on entry where we don't want to OSR.
@@ -707,7 +712,7 @@
         return osr_data;
       }
     }
-    jit->EnqueueCompilation(method, Thread::Current());
+    jit->MaybeEnqueueCompilation(method, Thread::Current());
   }
   return nullptr;
 }
diff --git a/runtime/interpreter/mterp/x86_64ng/main.S b/runtime/interpreter/mterp/x86_64ng/main.S
index bfbd398..bd191c0 100644
--- a/runtime/interpreter/mterp/x86_64ng/main.S
+++ b/runtime/interpreter/mterp/x86_64ng/main.S
@@ -255,7 +255,7 @@
     // Update method counter and do a suspend check if the branch is negative or zero.
     testq rINSTq, rINSTq
     jle 3f
-2:
+2:  // We use 2 and not 1 for this local label as the users of the BRANCH macro have a 1 label.
     FETCH_INST
     GOTO_NEXT
 3:
@@ -270,11 +270,7 @@
     // Update counter.
     addl $$-1, %esi
     movw %si, ART_METHOD_HOTNESS_COUNT_OFFSET(%rdi)
-    // Otherwise, do a suspend check.
-    testl   $$(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), rSELF:THREAD_FLAGS_OFFSET
-    jz      2b
-    EXPORT_PC
-    call    SYMBOL(art_quick_test_suspend)
+    DO_SUSPEND_CHECK continue_label=2b
     jmp 2b
 .endm
 
@@ -831,23 +827,23 @@
 #error Expected 0 for hotness value
 #endif
    // If the counter is at zero, handle this in the runtime.
-   testw %si, %si
-   je 2f
+   testl %esi, %esi
+   je 3f
    // Update counter.
    addl $$-1, %esi
    movw %si, ART_METHOD_HOTNESS_COUNT_OFFSET(%rdi)
-   testl $$(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), rSELF:THREAD_FLAGS_OFFSET
-   jz 1f
-   EXPORT_PC
-   call SYMBOL(art_quick_test_suspend)
 1:
+   DO_SUSPEND_CHECK continue_label=2f
+2:
    FETCH_INST
    GOTO_NEXT
-2:
+3:
+   CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot=4f, if_not_hot=1b
+4:
    movq $$0, %rsi
    movq rFP, %rdx
    call nterp_hot_method
-   jmp 1b
+   jmp 2b
 .endm
 
 .macro SPILL_ALL_CALLEE_SAVES
@@ -1671,6 +1667,24 @@
     jne 1b
 .endm
 
+.macro CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot, if_not_hot
+    testl $$ART_METHOD_IS_MEMORY_SHARED_FLAG, ART_METHOD_ACCESS_FLAGS_OFFSET(%rdi)
+    jz \if_hot
+    movzwl rSELF:THREAD_SHARED_METHOD_HOTNESS_OFFSET, %esi
+    testl %esi, %esi
+    je \if_hot
+    addl $$-1, %esi
+    movw %si, rSELF:THREAD_SHARED_METHOD_HOTNESS_OFFSET
+    jmp \if_not_hot
+.endm
+
+.macro DO_SUSPEND_CHECK continue_label
+    testl   $$(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), rSELF:THREAD_FLAGS_OFFSET
+    jz      \continue_label
+    EXPORT_PC
+    call    SYMBOL(art_quick_test_suspend)
+.endm
+
 %def entry():
 /*
  * ArtMethod entry point.
@@ -2168,14 +2182,17 @@
   OP_IGET load="movl", wide=0
 
 NterpHandleHotnessOverflow:
+    CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot=1f, if_not_hot=4f
+1:
     movq rPC, %rsi
     movq rFP, %rdx
     call nterp_hot_method
     testq %rax, %rax
-    jne 1f
+    jne 3f
+2:
     FETCH_INST
     GOTO_NEXT
-1:
+3:
     // Drop the current frame.
     movq -8(rREFS), %rsp
     CFI_DEF_CFA(rsp, CALLEE_SAVES_SIZE)
@@ -2203,6 +2220,9 @@
 
     // Jump to the compiled code.
     jmp *%rbx
+4:
+    DO_SUSPEND_CHECK continue_label=2b
+    jmp 2b
 
 NterpHandleInvokeInterfaceOnObjectMethodRange:
    shrl $$16, %eax
diff --git a/runtime/interpreter/mterp/x86ng/main.S b/runtime/interpreter/mterp/x86ng/main.S
index 744d9ce..1077bcf 100644
--- a/runtime/interpreter/mterp/x86ng/main.S
+++ b/runtime/interpreter/mterp/x86ng/main.S
@@ -312,12 +312,7 @@
     // Update counter.
     addl $$-1, %ecx
     movw %cx, ART_METHOD_HOTNESS_COUNT_OFFSET(%eax)
-    // If the counter overflows, handle this in the runtime.
-    jz NterpHandleHotnessOverflow
-    // Otherwise, do a suspend check.
-    testl   $$(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), rSELF:THREAD_FLAGS_OFFSET
-    jz      2b
-    jmp NterpCallSuspend
+    DO_SUSPEND_CHECK continue_label=2b
 .endm
 
 // Expects:
@@ -978,25 +973,28 @@
 #error Expected 0 for hotness value
 #endif
    // If the counter is at zero, handle this in the runtime.
-   testw %cx, %cx
+   testl %ecx, %ecx
    je 2f
    // Update counter.
    addl $$-1, %ecx
    movw %cx, ART_METHOD_HOTNESS_COUNT_OFFSET(%eax)
-   jz 2f
+   jz 3f
+1:
    testl $$(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), rSELF:THREAD_FLAGS_OFFSET
-   jz 1f
+   jz 2f
    EXPORT_PC
    call SYMBOL(art_quick_test_suspend)
    RESTORE_IBASE
-1:
+2:
    FETCH_INST
    GOTO_NEXT
-2:
+3:
+   CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot=4f, if_not_hot=1b
+4:
    movl $$0, ARG1
    movl rFP, ARG2
    call nterp_hot_method
-   jmp 1b
+   jmp 2b
 .endm
 
 .macro SPILL_ALL_CALLEE_SAVES
@@ -1733,6 +1731,24 @@
     jne 1b
 .endm
 
+.macro DO_SUSPEND_CHECK continue_label
+    testl   $$(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), rSELF:THREAD_FLAGS_OFFSET
+    jz      \continue_label
+    jmp     NterpCallSuspendAndGotoNext
+.endm
+
+.macro CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot, if_not_hot
+    testl $$ART_METHOD_IS_MEMORY_SHARED_FLAG, ART_METHOD_ACCESS_FLAGS_OFFSET(%eax)
+    jz \if_hot
+    movzwl rSELF:THREAD_SHARED_METHOD_HOTNESS_OFFSET, %ecx
+    testl %ecx, %ecx
+    je \if_hot
+    addl $$-1, %ecx
+    movw %cx, rSELF:THREAD_SHARED_METHOD_HOTNESS_OFFSET
+    jmp \if_not_hot
+.endm
+
+
 %def entry():
 /*
  * ArtMethod entry point.
@@ -2230,7 +2246,7 @@
 NterpGetInstanceField:
   OP_IGET load="movl", wide=0
 
-NterpCallSuspend:
+NterpCallSuspendAndGotoNext:
     EXPORT_PC
     // Save branch offset.
     movl rINST, LOCAL0(%esp)
@@ -2241,18 +2257,21 @@
     GOTO_NEXT
 
 NterpHandleHotnessOverflow:
+    CHECK_AND_UPDATE_SHARED_MEMORY_METHOD if_hot=1f, if_not_hot=4f
+1:
     movl rPC, %ecx
     movl rFP, ARG2
     // Save next PC.
     movl %ecx, LOCAL0(%esp)
     call nterp_hot_method
     testl %eax, %eax
-    jne 1f
+    jne 3f
     // Fetch next PC.
     mov LOCAL0(%esp), rPC
+2:
     FETCH_INST
     GOTO_NEXT
-1:
+3:
     // Drop the current frame.
     movl -4(rREFS), %esp
     CFI_DEF_CFA(esp, PARAMETERS_SAVES_SIZE+CALLEE_SAVES_SIZE)
@@ -2288,6 +2307,9 @@
 
     // Jump to the compiled code.
     ret
+4:
+    DO_SUSPEND_CHECK continue_label=2b
+
 
 NterpHandleInvokeInterfaceOnObjectMethodRange:
    shrl $$16, %eax
diff --git a/runtime/jit/jit-inl.h b/runtime/jit/jit-inl.h
index 32465f2..237f63c 100644
--- a/runtime/jit/jit-inl.h
+++ b/runtime/jit/jit-inl.h
@@ -28,12 +28,17 @@
 namespace jit {
 
 inline void Jit::AddSamples(Thread* self, ArtMethod* method) {
-  if (IgnoreSamplesForMethod(method)) {
-    return;
-  }
   if (method->CounterIsHot()) {
-    method->ResetCounter(Runtime::Current()->GetJITOptions()->GetWarmupThreshold());
-    EnqueueCompilation(method, self);
+    if (method->IsMemorySharedMethod()) {
+      if (self->DecrementSharedMethodHotness() == 0) {
+        self->ResetSharedMethodHotness();
+      } else {
+        return;
+      }
+    } else {
+      method->ResetCounter(Runtime::Current()->GetJITOptions()->GetWarmupThreshold());
+    }
+    MaybeEnqueueCompilation(method, self);
   } else {
     method->UpdateCounter(1);
   }
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 549718d..c9e6bbf 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -1728,7 +1728,7 @@
   }
 }
 
-void Jit::EnqueueCompilation(ArtMethod* method, Thread* self) {
+void Jit::MaybeEnqueueCompilation(ArtMethod* method, Thread* self) {
   if (thread_pool_ == nullptr) {
     return;
   }
@@ -1742,6 +1742,10 @@
     return;
   }
 
+  if (IgnoreSamplesForMethod(method)) {
+    return;
+  }
+
   if (GetCodeCache()->ContainsPc(method->GetEntryPointFromQuickCompiledCode())) {
     if (!method->IsNative() && !code_cache_->IsOsrCompiled(method)) {
       // If we already have compiled code for it, nterp may be stuck in a loop.
@@ -1765,8 +1769,20 @@
     return;
   }
 
-  if (IgnoreSamplesForMethod(method)) {
-    return;
+  static constexpr size_t kIndividualSharedMethodHotnessThreshold = 0xff;
+  if (method->IsMemorySharedMethod()) {
+    MutexLock mu(self, lock_);
+    auto it = shared_method_counters_.find(method);
+    if (it == shared_method_counters_.end()) {
+      shared_method_counters_[method] = kIndividualSharedMethodHotnessThreshold;
+      return;
+    } else if (it->second != 0) {
+      DCHECK_LE(it->second, kIndividualSharedMethodHotnessThreshold);
+      shared_method_counters_[method] = it->second - 1;
+      return;
+    } else {
+      shared_method_counters_[method] = kIndividualSharedMethodHotnessThreshold;
+    }
   }
 
   if (!method->IsNative() && GetCodeCache()->CanAllocateProfilingInfo()) {
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index 8c1b688..b439c8e 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -438,7 +438,7 @@
 
   void EnqueueOptimizedCompilation(ArtMethod* method, Thread* self);
 
-  void EnqueueCompilation(ArtMethod* method, Thread* self)
+  void MaybeEnqueueCompilation(ArtMethod* method, Thread* self)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
@@ -503,6 +503,10 @@
   // recomputing it.
   size_t fd_methods_size_;
 
+  // Map of hotness counters for methods which we want to share the memory
+  // between the zygote and apps.
+  std::map<ArtMethod*, uint16_t> shared_method_counters_;
+
   DISALLOW_COPY_AND_ASSIGN(Jit);
 };
 
diff --git a/runtime/thread.h b/runtime/thread.h
index 0fe6c4c..dd8b061 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -188,6 +188,8 @@
 // This should match RosAlloc::kNumThreadLocalSizeBrackets.
 static constexpr size_t kNumRosAllocThreadLocalSizeBracketsInThread = 16;
 
+static constexpr size_t kSharedMethodHotnessThreshold = 0xffff;
+
 // Thread's stack layout for implicit stack overflow checks:
 //
 //   +---------------------+  <- highest address of stack memory
@@ -768,6 +770,13 @@
     return sizeof(tls32_.is_gc_marking);
   }
 
+  template<PointerSize pointer_size>
+  static constexpr ThreadOffset<pointer_size> SharedMethodHotnessOffset() {
+    return ThreadOffset<pointer_size>(
+        OFFSETOF_MEMBER(Thread, tls32_) +
+        OFFSETOF_MEMBER(tls_32bit_sized_values, shared_method_hotness));
+  }
+
   // Deoptimize the Java stack.
   void DeoptimizeWithDeoptimizationException(JValue* result) REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1388,6 +1397,19 @@
     return StateAndFlags::EncodeState(state);
   }
 
+  void ResetSharedMethodHotness() {
+    tls32_.shared_method_hotness = kSharedMethodHotnessThreshold;
+  }
+
+  uint32_t GetSharedMethodHotness() const {
+    return tls32_.shared_method_hotness;
+  }
+
+  uint32_t DecrementSharedMethodHotness() {
+    tls32_.shared_method_hotness = (tls32_.shared_method_hotness - 1) & 0xffff;
+    return tls32_.shared_method_hotness;
+  }
+
  private:
   explicit Thread(bool daemon);
   ~Thread() REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_);
@@ -1696,7 +1718,9 @@
           force_interpreter_count(0),
           make_visibly_initialized_counter(0),
           define_class_counter(0),
-          num_name_readers(0) {}
+          num_name_readers(0),
+          shared_method_hotness(kSharedMethodHotnessThreshold)
+        {}
 
     // The state and flags field must be changed atomically so that flag values aren't lost.
     // See `StateAndFlags` for bit assignments of `ThreadFlag` and `ThreadState` values.
@@ -1790,6 +1814,14 @@
     // retrieved.
     mutable std::atomic<uint32_t> num_name_readers;
     static_assert(std::atomic<uint32_t>::is_always_lock_free);
+
+    // Thread-local hotness counter for shared memory methods. Initialized with
+    // `kSharedMethodHotnessThreshold`. The interpreter decrements it and goes
+    // into the runtime when hitting zero. Note that all previous decrements
+    // could have been executed by another method than the one seeing zero.
+    // There is a second level counter in `Jit::shared_method_counters_` to make
+    // sure we at least have a few samples before compiling a method.
+    uint32_t shared_method_hotness;
   } tls32_;
 
   struct PACKED(8) tls_64bit_sized_values {
diff --git a/tools/cpp-define-generator/art_method.def b/tools/cpp-define-generator/art_method.def
index a73bbed..d5ba599 100644
--- a/tools/cpp-define-generator/art_method.def
+++ b/tools/cpp-define-generator/art_method.def
@@ -21,6 +21,10 @@
 
 ASM_DEFINE(ART_METHOD_ACCESS_FLAGS_OFFSET,
            art::ArtMethod::AccessFlagsOffset().Int32Value())
+ASM_DEFINE(ART_METHOD_IS_MEMORY_SHARED_FLAG,
+           art::kAccMemorySharedMethod)
+ASM_DEFINE(ART_METHOD_IS_MEMORY_SHARED_FLAG_BIT,
+           art::MostSignificantBit(art::kAccMemorySharedMethod))
 ASM_DEFINE(ART_METHOD_IS_STATIC_FLAG,
            art::kAccStatic)
 ASM_DEFINE(ART_METHOD_IS_STATIC_FLAG_BIT,
diff --git a/tools/cpp-define-generator/thread.def b/tools/cpp-define-generator/thread.def
index 7df7650..bae9200 100644
--- a/tools/cpp-define-generator/thread.def
+++ b/tools/cpp-define-generator/thread.def
@@ -67,3 +67,5 @@
                .Int32Value())
 ASM_DEFINE(THREAD_READ_BARRIER_MARK_REG00_OFFSET,
            art::Thread::ReadBarrierMarkEntryPointsOffset<art::kRuntimePointerSize>(0))
+ASM_DEFINE(THREAD_SHARED_METHOD_HOTNESS_OFFSET,
+           art::Thread::SharedMethodHotnessOffset<art::kRuntimePointerSize>().Int32Value())