Snap for 8580505 from 6898d018f6a48bbc2a8e471850e84e4611c7815c to main-cg-testing-release
Change-Id: Ic925903944f3777a1f514e3c30e4c1f0d6f49a73
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d606d7c..91a19d2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -401,6 +401,12 @@
"name": "art-run-test-2042-checker-dce-always-throw[com.google.android.art.apex]"
},
{
+ "name": "art-run-test-2042-reference-processing[com.google.android.art.apex]"
+ },
+ {
+ "name": "art-run-test-2043-reference-pauses[com.google.android.art.apex]"
+ },
+ {
"name": "art-run-test-2231-checker-heap-poisoning[com.google.android.art.apex]"
},
{
@@ -1699,6 +1705,12 @@
"name": "art-run-test-2042-checker-dce-always-throw"
},
{
+ "name": "art-run-test-2042-reference-processing"
+ },
+ {
+ "name": "art-run-test-2043-reference-pauses"
+ },
+ {
"name": "art-run-test-2231-checker-heap-poisoning"
},
{
@@ -2589,9 +2601,6 @@
],
"hwasan-presubmit": [
{
- "name": "BootImageProfileTest"
- },
- {
"name": "ArtServiceTests"
},
{
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index 0a1f017..d05324b 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -414,7 +414,7 @@
CHECK(!caller->IsCriticalNative());
CHECK(caller->IsSynchronized());
ObjPtr<mirror::Object> lock;
- if (self->GetManagedStack()->GetTopQuickFrameTag()) {
+ if (self->GetManagedStack()->GetTopQuickFrameGenericJniTag()) {
// Generic JNI.
lock = GetGenericJniSynchronizationObject(self, caller);
} else if (caller->IsStatic()) {
diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc
index 6cb5021..b88ebaf 100644
--- a/compiler/jni/quick/jni_compiler.cc
+++ b/compiler/jni/quick/jni_compiler.cc
@@ -95,6 +95,13 @@
const InstructionSetFeatures* instruction_set_features =
compiler_options.GetInstructionSetFeatures();
+ // When walking the stack the top frame doesn't have a pc associated with it. We then depend on
+ // the invariant that we don't have JITed code when AOT code is available. In debuggable runtimes
+ // this invariant doesn't hold. So we tag the SP for JITed code to indentify if we are executing
+ // JITed code or AOT code. Since tagging involves additional instructions we tag only in
+ // debuggable runtimes.
+ bool should_tag_sp = compiler_options.GetDebuggable() && compiler_options.IsJitCompiler();
+
// i.e. if the method was annotated with @FastNative
const bool is_fast_native = (access_flags & kAccFastNative) != 0u;
@@ -219,7 +226,7 @@
// because garbage collections are disabled within the execution of a
// @CriticalNative method.
if (LIKELY(!is_critical_native)) {
- __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>());
+ __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>(), should_tag_sp);
}
// 2. Lock the object (if synchronized) and transition out of Runnable (if normal native).
@@ -605,7 +612,7 @@
if (reference_return) {
// Suspend check entry point overwrites top of managed stack and leaves it clobbered.
// We need to restore the top for subsequent runtime call to `JniDecodeReferenceResult()`.
- __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>());
+ __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>(), should_tag_sp);
}
if (reference_return && main_out_arg_size != 0) {
__ IncreaseFrameSize(main_out_arg_size);
diff --git a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
index 6e6d40d..61151fe 100644
--- a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
+++ b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
@@ -428,8 +428,15 @@
asm_.StoreToOffset(kStoreWord, scratch, tr, thr_offs.Int32Value());
}
-void ArmVIXLJNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs) {
- asm_.StoreToOffset(kStoreWord, sp, tr, thr_offs.Int32Value());
+void ArmVIXLJNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) {
+ if (tag_sp) {
+ UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
+ vixl32::Register reg = temps.Acquire();
+ ___ Orr(reg, sp, 0x2);
+ asm_.StoreToOffset(kStoreWord, reg, tr, thr_offs.Int32Value());
+ } else {
+ asm_.StoreToOffset(kStoreWord, sp, tr, thr_offs.Int32Value());
+ }
}
void ArmVIXLJNIMacroAssembler::SignExtend(ManagedRegister mreg ATTRIBUTE_UNUSED,
diff --git a/compiler/utils/arm/jni_macro_assembler_arm_vixl.h b/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
index ed453ae..980de41 100644
--- a/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
+++ b/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
@@ -70,7 +70,7 @@
void StoreStackOffsetToThread(ThreadOffset32 thr_offs, FrameOffset fr_offs) override;
- void StoreStackPointerToThread(ThreadOffset32 thr_offs) override;
+ void StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) override;
void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.cc b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
index 50ca468..323a01e 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.cc
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
@@ -218,10 +218,13 @@
___ Str(scratch, MEM_OP(reg_x(TR), tr_offs.Int32Value()));
}
-void Arm64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 tr_offs) {
+void Arm64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 tr_offs, bool tag_sp) {
UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
Register scratch = temps.AcquireX();
___ Mov(scratch, reg_x(SP));
+ if (tag_sp) {
+ ___ Orr(scratch, scratch, 0x2);
+ }
___ Str(scratch, MEM_OP(reg_x(TR), tr_offs.Int32Value()));
}
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.h b/compiler/utils/arm64/jni_macro_assembler_arm64.h
index 2c04184..daea95d 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.h
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.h
@@ -72,7 +72,7 @@
void StoreRawPtr(FrameOffset dest, ManagedRegister src) override;
void StoreImmediateToFrame(FrameOffset dest, uint32_t imm) override;
void StoreStackOffsetToThread(ThreadOffset64 thr_offs, FrameOffset fr_offs) override;
- void StoreStackPointerToThread(ThreadOffset64 thr_offs) override;
+ void StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) override;
void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
// Load routines.
diff --git a/compiler/utils/assembler_thumb_test.cc b/compiler/utils/assembler_thumb_test.cc
index b2d4dcd..f867a05 100644
--- a/compiler/utils/assembler_thumb_test.cc
+++ b/compiler/utils/assembler_thumb_test.cc
@@ -159,7 +159,8 @@
__ StoreRef(FrameOffset(48), scratch_register);
__ StoreSpanning(FrameOffset(48), method_register, FrameOffset(48));
__ StoreStackOffsetToThread(ThreadOffset32(512), FrameOffset(4096));
- __ StoreStackPointerToThread(ThreadOffset32(512));
+ __ StoreStackPointerToThread(ThreadOffset32(512), false);
+ __ StoreStackPointerToThread(ThreadOffset32(512), true);
// Other
__ Call(method_register, FrameOffset(48));
diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc
index 1775014..dac21ae 100644
--- a/compiler/utils/assembler_thumb_test_expected.cc.inc
+++ b/compiler/utils/assembler_thumb_test_expected.cc.inc
@@ -41,46 +41,46 @@
" 7e: 0d f5 80 5c add.w r12, sp, #4096\n"
" 82: c9 f8 00 c2 str.w r12, [r9, #512]\n"
" 86: c9 f8 00 d2 str.w sp, [r9, #512]\n"
- " 8a: d0 f8 30 e0 ldr.w lr, [r0, #48]\n"
- " 8e: f0 47 blx lr\n"
- " 90: dd f8 2c c0 ldr.w r12, [sp, #44]\n"
- " 94: cd f8 30 c0 str.w r12, [sp, #48]\n"
- " 98: d9 f8 00 c2 ldr.w r12, [r9, #512]\n"
- " 9c: cd f8 2c c0 str.w r12, [sp, #44]\n"
- " a0: dd f8 2c c0 ldr.w r12, [sp, #44]\n"
- " a4: cd f8 30 c0 str.w r12, [sp, #48]\n"
- " a8: 48 46 mov r0, r9\n"
- " aa: cd f8 30 90 str.w r9, [sp, #48]\n"
- " ae: 04 46 mov r4, r0\n"
- " b0: 0d f1 30 0c add.w r12, sp, #48\n"
- " b4: bb f1 00 0f cmp.w r11, #0\n"
- " b8: 18 bf it ne\n"
- " ba: e3 46 movne r11, r12\n"
- " bc: 0d f1 30 0b add.w r11, sp, #48\n"
- " c0: 5f ea 0b 00 movs.w r0, r11\n"
- " c4: 18 bf it ne\n"
- " c6: 0c a8 addne r0, sp, #48\n"
- " c8: dd f8 40 c0 ldr.w r12, [sp, #64]\n"
- " cc: bc f1 00 0f cmp.w r12, #0\n"
- " d0: 18 bf it ne\n"
- " d2: 0d f1 40 0c addne.w r12, sp, #64\n"
- " d6: cd f8 30 c0 str.w r12, [sp, #48]\n"
- " da: 5f ea 0b 00 movs.w r0, r11\n"
- " de: 18 bf it ne\n"
- " e0: 00 a8 addne r0, sp, #0\n"
- " e2: 0d f2 04 40 addw r0, sp, #1028\n"
- " e6: bb f1 00 0f cmp.w r11, #0\n"
- " ea: 08 bf it eq\n"
- " ec: 58 46 moveq r0, r11\n"
- " ee: 0d f2 04 4c addw r12, sp, #1028\n"
- " f2: bb f1 00 0f cmp.w r11, #0\n"
- " f6: 18 bf it ne\n"
- " f8: e3 46 movne r11, r12\n"
- " fa: d9 f8 9c c0 ldr.w r12, [r9, #156]\n"
- " fe: bc f1 00 0f cmp.w r12, #0\n"
- " 102: 71 d1 bne 0x1e8 @ imm = #226\n"
- " 104: cd f8 ff c7 str.w r12, [sp, #2047]\n"
- " 108: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 8a: 4d f0 02 0c orr r12, sp, #2\n"
+ " 8e: c9 f8 00 c2 str.w r12, [r9, #512]\n"
+ " 92: d0 f8 30 e0 ldr.w lr, [r0, #48]\n"
+ " 96: f0 47 blx lr\n"
+ " 98: dd f8 2c c0 ldr.w r12, [sp, #44]\n"
+ " 9c: cd f8 30 c0 str.w r12, [sp, #48]\n"
+ " a0: d9 f8 00 c2 ldr.w r12, [r9, #512]\n"
+ " a4: cd f8 2c c0 str.w r12, [sp, #44]\n"
+ " a8: dd f8 2c c0 ldr.w r12, [sp, #44]\n"
+ " ac: cd f8 30 c0 str.w r12, [sp, #48]\n"
+ " b0: 48 46 mov r0, r9\n"
+ " b2: cd f8 30 90 str.w r9, [sp, #48]\n"
+ " b6: 04 46 mov r4, r0\n"
+ " b8: 0d f1 30 0c add.w r12, sp, #48\n"
+ " bc: bb f1 00 0f cmp.w r11, #0\n"
+ " c0: 18 bf it ne\n"
+ " c2: e3 46 movne r11, r12\n"
+ " c4: 0d f1 30 0b add.w r11, sp, #48\n"
+ " c8: 5f ea 0b 00 movs.w r0, r11\n"
+ " cc: 18 bf it ne\n"
+ " ce: 0c a8 addne r0, sp, #48\n"
+ " d0: dd f8 40 c0 ldr.w r12, [sp, #64]\n"
+ " d4: bc f1 00 0f cmp.w r12, #0\n"
+ " d8: 18 bf it ne\n"
+ " da: 0d f1 40 0c addne.w r12, sp, #64\n"
+ " de: cd f8 30 c0 str.w r12, [sp, #48]\n"
+ " e2: 5f ea 0b 00 movs.w r0, r11\n"
+ " e6: 18 bf it ne\n"
+ " e8: 00 a8 addne r0, sp, #0\n"
+ " ea: 0d f2 04 40 addw r0, sp, #1028\n"
+ " ee: bb f1 00 0f cmp.w r11, #0\n"
+ " f2: 08 bf it eq\n"
+ " f4: 58 46 moveq r0, r11\n"
+ " f6: 0d f2 04 4c addw r12, sp, #1028\n"
+ " fa: bb f1 00 0f cmp.w r11, #0\n"
+ " fe: 18 bf it ne\n"
+ " 100: e3 46 movne r11, r12\n"
+ " 102: d9 f8 9c c0 ldr.w r12, [r9, #156]\n"
+ " 106: bc f1 00 0f cmp.w r12, #0\n"
+ " 10a: 71 d1 bne 0x1f0 @ imm = #226\n"
" 10c: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 110: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 114: cd f8 ff c7 str.w r12, [sp, #2047]\n"
@@ -135,26 +135,28 @@
" 1d8: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 1dc: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 1e0: cd f8 ff c7 str.w r12, [sp, #2047]\n"
- " 1e4: 00 f0 02 b8 b.w 0x1ec @ imm = #4\n"
- " 1e8: 00 f0 1b b8 b.w 0x222 @ imm = #54\n"
- " 1ec: cd f8 ff c7 str.w r12, [sp, #2047]\n"
- " 1f0: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 1e4: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 1e8: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 1ec: 00 f0 02 b8 b.w 0x1f4 @ imm = #4\n"
+ " 1f0: 00 f0 1b b8 b.w 0x22a @ imm = #54\n"
" 1f4: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 1f8: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 1fc: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 200: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 204: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 208: cd f8 ff c7 str.w r12, [sp, #2047]\n"
- " 20c: 0d f5 80 5d add.w sp, sp, #4096\n"
- " 210: 08 b0 add sp, #32\n"
- " 212: 01 b0 add sp, #4\n"
- " 214: bd ec 10 8a vpop {s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31}\n"
- " 218: bd e8 e0 4d pop.w {r5, r6, r7, r8, r10, r11, lr}\n"
- " 21c: d9 f8 24 80 ldr.w r8, [r9, #36]\n"
- " 220: 70 47 bx lr\n"
- " 222: d9 f8 9c 00 ldr.w r0, [r9, #156]\n"
- " 226: d9 f8 d0 e2 ldr.w lr, [r9, #720]\n"
- " 22a: f0 47 blx lr\n"
+ " 20c: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 210: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 214: 0d f5 80 5d add.w sp, sp, #4096\n"
+ " 218: 08 b0 add sp, #32\n"
+ " 21a: 01 b0 add sp, #4\n"
+ " 21c: bd ec 10 8a vpop {s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31}\n"
+ " 220: bd e8 e0 4d pop.w {r5, r6, r7, r8, r10, r11, lr}\n"
+ " 224: d9 f8 24 80 ldr.w r8, [r9, #36]\n"
+ " 228: 70 47 bx lr\n"
+ " 22a: d9 f8 9c 00 ldr.w r0, [r9, #156]\n"
+ " 22e: d9 f8 d0 e2 ldr.w lr, [r9, #720]\n"
+ " 232: f0 47 blx lr\n"
};
const char* const VixlLoadFromOffsetResults = {
diff --git a/compiler/utils/jni_macro_assembler.h b/compiler/utils/jni_macro_assembler.h
index 7022e3d..c8c713a 100644
--- a/compiler/utils/jni_macro_assembler.h
+++ b/compiler/utils/jni_macro_assembler.h
@@ -126,7 +126,11 @@
virtual void StoreStackOffsetToThread(ThreadOffset<kPointerSize> thr_offs,
FrameOffset fr_offs) = 0;
- virtual void StoreStackPointerToThread(ThreadOffset<kPointerSize> thr_offs) = 0;
+ // Stores stack pointer by tagging it if required so we can walk the stack. In debuggable runtimes
+ // we use tag to tell if we are using JITed code or AOT code. In non-debuggable runtimes we never
+ // use JITed code when AOT code is present. So checking for AOT code is sufficient to detect which
+ // code is being executed. We avoid tagging in non-debuggable runtimes to reduce instructions.
+ virtual void StoreStackPointerToThread(ThreadOffset<kPointerSize> thr_offs, bool tag_sp) = 0;
virtual void StoreSpanning(FrameOffset dest,
ManagedRegister src,
diff --git a/compiler/utils/x86/jni_macro_assembler_x86.cc b/compiler/utils/x86/jni_macro_assembler_x86.cc
index 685f5f1..55d5428 100644
--- a/compiler/utils/x86/jni_macro_assembler_x86.cc
+++ b/compiler/utils/x86/jni_macro_assembler_x86.cc
@@ -187,8 +187,18 @@
__ fs()->movl(Address::Absolute(thr_offs), scratch);
}
-void X86JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs) {
- __ fs()->movl(Address::Absolute(thr_offs), ESP);
+void X86JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) {
+ if (tag_sp) {
+ // There is no free register, store contents onto stack and restore back later.
+ Register scratch = ECX;
+ __ movl(Address(ESP, -32), scratch);
+ __ movl(scratch, ESP);
+ __ orl(scratch, Immediate(0x2));
+ __ fs()->movl(Address::Absolute(thr_offs), scratch);
+ __ movl(scratch, Address(ESP, -32));
+ } else {
+ __ fs()->movl(Address::Absolute(thr_offs), ESP);
+ }
}
void X86JNIMacroAssembler::StoreSpanning(FrameOffset /*dst*/,
diff --git a/compiler/utils/x86/jni_macro_assembler_x86.h b/compiler/utils/x86/jni_macro_assembler_x86.h
index 29fccfd..f8ce38b 100644
--- a/compiler/utils/x86/jni_macro_assembler_x86.h
+++ b/compiler/utils/x86/jni_macro_assembler_x86.h
@@ -66,7 +66,7 @@
void StoreStackOffsetToThread(ThreadOffset32 thr_offs, FrameOffset fr_offs) override;
- void StoreStackPointerToThread(ThreadOffset32 thr_offs) override;
+ void StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) override;
void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
diff --git a/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc b/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
index d5d1bba..adc431f 100644
--- a/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
+++ b/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
@@ -217,8 +217,15 @@
__ gs()->movq(Address::Absolute(thr_offs, true), scratch);
}
-void X86_64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 thr_offs) {
- __ gs()->movq(Address::Absolute(thr_offs, true), CpuRegister(RSP));
+void X86_64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) {
+ if (tag_sp) {
+ CpuRegister reg = GetScratchRegister();
+ __ movq(reg, CpuRegister(RSP));
+ __ orq(reg, Immediate(0x2));
+ __ gs()->movq(Address::Absolute(thr_offs, true), reg);
+ } else {
+ __ gs()->movq(Address::Absolute(thr_offs, true), CpuRegister(RSP));
+ }
}
void X86_64JNIMacroAssembler::StoreSpanning(FrameOffset /*dst*/,
diff --git a/compiler/utils/x86_64/jni_macro_assembler_x86_64.h b/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
index e080f0b..feaf27e 100644
--- a/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
+++ b/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
@@ -67,7 +67,7 @@
void StoreStackOffsetToThread(ThreadOffset64 thr_offs, FrameOffset fr_offs) override;
- void StoreStackPointerToThread(ThreadOffset64 thr_offs) override;
+ void StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) override;
void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
diff --git a/libartbase/base/hiddenapi_flags.h b/libartbase/base/hiddenapi_flags.h
index 7415b93..9d0a18e 100644
--- a/libartbase/base/hiddenapi_flags.h
+++ b/libartbase/base/hiddenapi_flags.h
@@ -98,11 +98,12 @@
kMaxTargetP = 4,
kMaxTargetQ = 5,
kMaxTargetR = 6,
+ kMaxTargetS = 7,
// Special values
kInvalid = (static_cast<uint32_t>(-1) & kValueBitMask),
kMin = kSdk,
- kMax = kMaxTargetR,
+ kMax = kMaxTargetS,
};
// Additional bit flags after the first kValueBitSize bits in dex flags.
@@ -139,6 +140,7 @@
"max-target-p",
"max-target-q",
"max-target-r",
+ "max-target-s",
};
// A magic marker used by tests to mimic a hiddenapi list which doesn't exist
@@ -160,6 +162,7 @@
/* max-target-p */ SdkVersion::kP,
/* max-target-q */ SdkVersion::kQ,
/* max-target-r */ SdkVersion::kR,
+ /* max-target-s */ SdkVersion::kS,
};
explicit ApiList(Value val, uint32_t domain_apis = 0u)
@@ -204,6 +207,7 @@
static ApiList MaxTargetP() { return ApiList(Value::kMaxTargetP); }
static ApiList MaxTargetQ() { return ApiList(Value::kMaxTargetQ); }
static ApiList MaxTargetR() { return ApiList(Value::kMaxTargetR); }
+ static ApiList MaxTargetS() { return ApiList(Value::kMaxTargetS); }
static ApiList CorePlatformApi() { return ApiList(DomainApi::kCorePlatformApi); }
static ApiList TestApi() { return ApiList(DomainApi::kTestApi); }
diff --git a/libartservice/Android.bp b/libartservice/Android.bp
index 985a2eb..b9632ea 100644
--- a/libartservice/Android.bp
+++ b/libartservice/Android.bp
@@ -68,27 +68,9 @@
"com.android.art",
"com.android.art.debug",
],
- sdk_version: "core_platform",
+ sdk_version: "system_server_current",
min_sdk_version: "31",
- public: {
- // Override the setting of "module_current" from the defaults as that is
- // not available in all manifests where this needs to be built.
- sdk_version: "core_current",
- },
-
- system_server: {
- // Override the setting of "module_current" from the defaults as that is
- // not available in all manifests where this needs to be built.
- sdk_version: "core_current",
- },
-
- // The API elements are the ones annotated with
- // libcore.api.CorePlatformApi(status=libcore.api.CorePlatformApi.Status.STABLE)
- droiddoc_options: [
- "--show-single-annotation libcore.api.CorePlatformApi\\(status=libcore.api.CorePlatformApi.Status.STABLE\\)",
- ],
-
// Temporarily disable compatibility with previous released APIs.
// TODO - remove once prototype has stabilized
// running "m update-api" will give instructions on what to do next
@@ -100,11 +82,10 @@
compile_dex: true,
srcs: [
- "service/java/com/android/server/art/ArtManagerLocal.java",
+ "service/java/**/*.java",
],
libs: [
- "art.module.api.annotations.for.system.modules",
],
plugins: ["java_api_finder"],
diff --git a/libartservice/api/current.txt b/libartservice/api/current.txt
index c7844e0..d802177 100644
--- a/libartservice/api/current.txt
+++ b/libartservice/api/current.txt
@@ -1,9 +1 @@
// Signature format: 2.0
-package com.android.server.art {
-
- public final class ArtManagerLocal {
- ctor public ArtManagerLocal();
- }
-
-}
-
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 04629cb..64aec7b 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -16,13 +16,16 @@
package com.android.server.art;
+import android.annotation.SystemApi;
+
/**
- * This class provides a system API for functionality provided by the ART
- * module.
+ * This class provides a system API for functionality provided by the ART module.
+ *
+ * @hide
*/
-@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public final class ArtManagerLocal {
- static final String LOG_TAG = "ArtService";
+ private static final String TAG = "ArtService";
public ArtManagerLocal() {}
}
diff --git a/libartservice/tests/Android.bp b/libartservice/tests/Android.bp
index dc110a1..1b18cc5 100644
--- a/libartservice/tests/Android.bp
+++ b/libartservice/tests/Android.bp
@@ -27,7 +27,7 @@
default_applicable_licenses: ["art_license"],
}
-java_test {
+android_test {
name: "ArtServiceTests",
// Include all test java files.
@@ -36,9 +36,15 @@
],
static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
"androidx.test.runner",
+ "mockito-target-minus-junit4",
"service-art.impl",
],
+ sdk_version: "system_server_current",
+ min_sdk_version: "31",
+
test_suites: ["general-tests"],
}
diff --git a/libartservice/tests/AndroidManifest.xml b/libartservice/tests/AndroidManifest.xml
new file mode 100644
index 0000000..921bde9
--- /dev/null
+++ b/libartservice/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.art.tests">
+
+ <application android:label="ArtServiceTests">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.art.tests"
+ android:label="Tests for ART Serices" />
+</manifest>
diff --git a/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java b/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java
index 515849b..b0323c4 100644
--- a/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java
+++ b/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java
@@ -16,22 +16,29 @@
package com.android.server.art;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.server.art.ArtManagerLocal;
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
-public class ArtManagerLocalTests extends TestCase {
+@SmallTest
+@RunWith(MockitoJUnitRunner.class)
+public class ArtManagerLocalTests {
private ArtManagerLocal mArtManagerLocal;
- public void setup() {
+ @Before
+ public void setUp() {
mArtManagerLocal = new ArtManagerLocal();
}
+ @Test
public void testScaffolding() {
- assertTrue(true);
+ assertThat(true).isTrue();
}
-}
\ No newline at end of file
+}
diff --git a/odrefresh/CacheInfo.xsd b/odrefresh/CacheInfo.xsd
index 1cbef8a..196caf7 100644
--- a/odrefresh/CacheInfo.xsd
+++ b/odrefresh/CacheInfo.xsd
@@ -27,6 +27,7 @@
<!-- True if the cache info is generated in the Compilation OS. -->
<xs:attribute name="compilationOsMode" type="xs:boolean" />
<xs:sequence>
+ <xs:element name="systemProperties" minOccurs="1" maxOccurs="1" type="t:keyValuePairList" />
<xs:element name="artModuleInfo" minOccurs="1" maxOccurs="1" type="t:moduleInfo" />
<xs:element name="moduleInfoList" minOccurs="1" maxOccurs="1" type="t:moduleInfoList" />
<xs:element name="bootClasspath" minOccurs="1" maxOccurs="1" type="t:classpath" />
@@ -36,6 +37,19 @@
</xs:complexType>
</xs:element>
+ <!-- List of key-value pairs. -->
+ <xs:complexType name="keyValuePairList">
+ <xs:sequence>
+ <xs:element name="item" type="t:keyValuePair" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- A key-value pair. -->
+ <xs:complexType name="keyValuePair">
+ <xs:attribute name="k" type="xs:string" use="required" />
+ <xs:attribute name="v" type="xs:string" use="required" />
+ </xs:complexType>
+
<!-- List of modules. -->
<xs:complexType name="moduleInfoList">
<xs:sequence>
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index d95ab96..6a4646a 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -19,9 +19,11 @@
#include <optional>
#include <string>
+#include <unordered_map>
#include <vector>
#include "android-base/file.h"
+#include "android-base/no_destructor.h"
#include "arch/instruction_set.h"
#include "base/file_utils.h"
#include "base/globals.h"
@@ -32,6 +34,17 @@
namespace art {
namespace odrefresh {
+struct SystemPropertyConfig {
+ const char* name;
+ const char* default_value;
+};
+
+// The system properties that odrefresh keeps track of. Odrefresh will recompile everything if any
+// property changes.
+const android::base::NoDestructor<std::vector<SystemPropertyConfig>> kSystemProperties{
+ {SystemPropertyConfig{.name = "persist.device_config.runtime_native_boot.enable_uffd_gc",
+ .default_value = "false"}}};
+
// An enumeration of the possible zygote configurations on Android.
enum class ZygoteKind : uint8_t {
// 32-bit primary zygote, no secondary zygote.
@@ -66,6 +79,9 @@
bool compilation_os_mode_ = false;
bool minimal_ = false;
+ // The current values of system properties listed in `kSystemProperties`.
+ std::unordered_map<std::string, std::string> system_properties_;
+
// Staging directory for artifacts. The directory must exist and will be automatically removed
// after compilation. If empty, use the default directory.
std::string staging_dir_;
@@ -145,6 +161,9 @@
}
bool GetCompilationOsMode() const { return compilation_os_mode_; }
bool GetMinimal() const { return minimal_; }
+ const std::unordered_map<std::string, std::string>& GetSystemProperties() const {
+ return system_properties_;
+ }
void SetApexInfoListFile(const std::string& file_path) { apex_info_list_file_ = file_path; }
void SetArtBinDir(const std::string& art_bin_dir) { art_bin_dir_ = art_bin_dir; }
@@ -196,6 +215,10 @@
void SetMinimal(bool value) { minimal_ = value; }
+ std::unordered_map<std::string, std::string>* MutableSystemProperties() {
+ return &system_properties_;
+ }
+
private:
// Returns a pair for the possible instruction sets for the configured instruction set
// architecture. The first item is the 32-bit architecture and the second item is the 64-bit
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index 1d12b94..70cf48e 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -648,6 +648,11 @@
return Errorf("Could not create directory {}", QuotePath(dir_name));
}
+ std::vector<art_apex::KeyValuePair> system_properties;
+ for (const auto& [key, value] : config_.GetSystemProperties()) {
+ system_properties.emplace_back(key, value);
+ }
+
std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList();
if (!apex_info_list.has_value()) {
return Errorf("Could not update {}: no APEX info", QuotePath(cache_info_filename_));
@@ -685,15 +690,16 @@
return Errorf("Cannot open {} for writing.", QuotePath(cache_info_filename_));
}
- art_apex::CacheInfo info(
+ std::unique_ptr<art_apex::CacheInfo> info(new art_apex::CacheInfo(
+ {art_apex::KeyValuePairList(system_properties)},
{art_module_info},
{art_apex::ModuleInfoList(module_info_list)},
{art_apex::Classpath(bcp_components.value())},
{art_apex::Classpath(bcp_compilable_components.value())},
{art_apex::SystemServerComponents(system_server_components.value())},
- config_.GetCompilationOsMode() ? std::make_optional(true) : std::nullopt);
+ config_.GetCompilationOsMode() ? std::make_optional(true) : std::nullopt));
- art_apex::write(out, info);
+ art_apex::write(out, *info);
out.close();
if (out.fail()) {
return Errorf("Cannot write to {}", QuotePath(cache_info_filename_));
@@ -836,16 +842,106 @@
return jars_missing_artifacts->empty();
}
+WARN_UNUSED bool OnDeviceRefresh::CheckSystemPropertiesAreDefault() const {
+ const std::unordered_map<std::string, std::string>& system_properties =
+ config_.GetSystemProperties();
+
+ for (const SystemPropertyConfig& system_property_config : *kSystemProperties.get()) {
+ auto property = system_properties.find(system_property_config.name);
+ DCHECK(property != system_properties.end());
+
+ if (property->second != system_property_config.default_value) {
+ LOG(INFO) << "System property " << system_property_config.name << " has a non-default value ("
+ << property->second << ").";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+WARN_UNUSED bool OnDeviceRefresh::CheckSystemPropertiesHaveNotChanged(
+ const art_apex::CacheInfo& cache_info) const {
+ std::unordered_map<std::string, std::string> cached_system_properties;
+ const art_apex::KeyValuePairList* list = cache_info.getFirstSystemProperties();
+ if (list == nullptr) {
+ // This should never happen. We have already checked the ART module version, and the cache
+ // info is generated by the latest version of the ART module if it exists.
+ LOG(ERROR) << "Missing system properties from cache-info.";
+ return false;
+ }
+
+ for (const art_apex::KeyValuePair& pair : list->getItem()) {
+ cached_system_properties[pair.getK()] = pair.getV();
+ }
+
+ const std::unordered_map<std::string, std::string>& system_properties =
+ config_.GetSystemProperties();
+
+ for (const SystemPropertyConfig& system_property_config : *kSystemProperties.get()) {
+ auto property = system_properties.find(system_property_config.name);
+ DCHECK(property != system_properties.end());
+
+ auto cached_property = cached_system_properties.find(system_property_config.name);
+ if (cached_property == cached_system_properties.end()) {
+ // This should never happen. We have already checked the ART module version, and the cache
+ // info is generated by the latest version of the ART module if it exists.
+ LOG(ERROR) << "Missing system property from cache-info (" << system_property_config.name
+ << ")";
+ return false;
+ }
+
+ if (property->second != cached_property->second) {
+ LOG(INFO) << "System property " << system_property_config.name
+ << " value changed (before: " << cached_property->second
+ << ", now: " << property->second << ").";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+WARN_UNUSED bool OnDeviceRefresh::BootClasspathArtifactsOnSystemUsable(
+ const apex::ApexInfo& art_apex_info) const {
+ if (!art_apex_info.getIsFactory()) {
+ return false;
+ }
+ LOG(INFO) << "Factory ART APEX mounted.";
+
+ if (!CheckSystemPropertiesAreDefault()) {
+ return false;
+ }
+ LOG(INFO) << "System properties are set to default values.";
+
+ return true;
+}
+
+WARN_UNUSED bool OnDeviceRefresh::SystemServerArtifactsOnSystemUsable(
+ const std::vector<apex::ApexInfo>& apex_info_list) const {
+ if (std::any_of(apex_info_list.begin(),
+ apex_info_list.end(),
+ [](const apex::ApexInfo& apex_info) { return !apex_info.getIsFactory(); })) {
+ return false;
+ }
+ LOG(INFO) << "Factory APEXes mounted.";
+
+ if (!CheckSystemPropertiesAreDefault()) {
+ return false;
+ }
+ LOG(INFO) << "System properties are set to default values.";
+
+ return true;
+}
+
WARN_UNUSED bool OnDeviceRefresh::CheckBootClasspathArtifactsAreUpToDate(
OdrMetrics& metrics,
const InstructionSet isa,
const apex::ApexInfo& art_apex_info,
const std::optional<art_apex::CacheInfo>& cache_info,
/*out*/ std::vector<std::string>* checked_artifacts) const {
- if (art_apex_info.getIsFactory()) {
- LOG(INFO) << "Factory ART APEX mounted.";
-
- // ART is not updated, so we can use the artifacts on /system. Check if they exist.
+ if (BootClasspathArtifactsOnSystemUsable(art_apex_info)) {
+ // We can use the artifacts on /system. Check if they exist.
std::string error_msg;
if (BootClasspathArtifactsExist(/*on_system=*/true, /*minimal=*/false, isa, &error_msg)) {
return true;
@@ -900,6 +996,14 @@
return false;
}
+ if (!CheckSystemPropertiesHaveNotChanged(cache_info.value())) {
+ // We don't have a trigger kind for system property changes. For now, we reuse
+ // `kApexVersionMismatch` as it implies the expected behavior: re-compile regardless of the last
+ // compilation attempt.
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ return false;
+ }
+
// Check boot class components.
//
// This checks the size and checksums of odrefresh compilable files on the DEX2OATBOOTCLASSPATH
@@ -961,12 +1065,8 @@
std::set<std::string> jars_missing_artifacts_on_system;
bool artifacts_on_system_up_to_date = false;
- if (std::all_of(apex_info_list.begin(),
- apex_info_list.end(),
- [](const apex::ApexInfo& apex_info) { return apex_info.getIsFactory(); })) {
- LOG(INFO) << "Factory APEXes mounted.";
-
- // APEXes are not updated, so we can use the artifacts on /system. Check if they exist.
+ if (SystemServerArtifactsOnSystemUsable(apex_info_list)) {
+ // We can use the artifacts on /system. Check if they exist.
std::string error_msg;
if (SystemServerArtifactsExist(
/*on_system=*/true, &error_msg, &jars_missing_artifacts_on_system)) {
@@ -1052,6 +1152,14 @@
}
}
+ if (!CheckSystemPropertiesHaveNotChanged(cache_info.value())) {
+ // We don't have a trigger kind for system property changes. For now, we reuse
+ // `kApexVersionMismatch` as it implies the expected behavior: re-compile regardless of the last
+ // compilation attempt.
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ return false;
+ }
+
// Check system server components.
//
// This checks the size and checksums of odrefresh compilable files on the
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index 58da57a..9dd4009 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -140,6 +140,25 @@
/*out*/ std::set<std::string>* jars_missing_artifacts,
/*out*/ std::vector<std::string>* checked_artifacts = nullptr) const;
+ // Returns true if all of the system properties listed in `kSystemProperties` are set to the
+ // default values.
+ WARN_UNUSED bool CheckSystemPropertiesAreDefault() const;
+
+ // Returns true if none of the system properties listed in `kSystemProperties` has changed since
+ // the last compilation.
+ WARN_UNUSED bool CheckSystemPropertiesHaveNotChanged(
+ const com::android::art::CacheInfo& cache_info) const;
+
+ // Returns true if boot classpath artifacts on /system are usable if they exist. Note that this
+ // function does not check file existence.
+ WARN_UNUSED bool BootClasspathArtifactsOnSystemUsable(
+ const com::android::apex::ApexInfo& art_apex_info) const;
+
+ // Returns true if system_server artifacts on /system are usable if they exist. Note that this
+ // function does not check file existence.
+ WARN_UNUSED bool SystemServerArtifactsOnSystemUsable(
+ const std::vector<com::android::apex::ApexInfo>& apex_info_list) const;
+
// Checks whether all boot classpath artifacts are up to date. Returns true if all are present,
// false otherwise.
// If `checked_artifacts` is present, adds checked artifacts to `checked_artifacts`.
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index c2298f0..01299e0 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -18,6 +18,7 @@
#include <string>
#include <string_view>
+#include <unordered_map>
#include "android-base/properties.h"
#include "android-base/stringprintf.h"
@@ -34,14 +35,17 @@
namespace {
+using ::android::base::GetProperty;
using ::art::odrefresh::CompilationOptions;
using ::art::odrefresh::ExitCode;
+using ::art::odrefresh::kSystemProperties;
using ::art::odrefresh::OdrCompilationLog;
using ::art::odrefresh::OdrConfig;
using ::art::odrefresh::OdrMetrics;
using ::art::odrefresh::OnDeviceRefresh;
using ::art::odrefresh::QuotePath;
using ::art::odrefresh::ShouldDisableRefresh;
+using ::art::odrefresh::SystemPropertyConfig;
using ::art::odrefresh::ZygoteKind;
void UsageMsgV(const char* fmt, va_list ap) {
@@ -155,7 +159,7 @@
if (zygote.empty()) {
// Use ro.zygote by default, if not overridden by --zygote-arch flag.
- zygote = android::base::GetProperty("ro.zygote", {});
+ zygote = GetProperty("ro.zygote", {});
}
ZygoteKind zygote_kind;
if (!ParseZygoteKind(zygote.c_str(), &zygote_kind)) {
@@ -164,19 +168,24 @@
config->SetZygoteKind(zygote_kind);
if (config->GetSystemServerCompilerFilter().empty()) {
- std::string filter =
- android::base::GetProperty("dalvik.vm.systemservercompilerfilter", "speed");
+ std::string filter = GetProperty("dalvik.vm.systemservercompilerfilter", "speed");
config->SetSystemServerCompilerFilter(filter);
}
- if (ShouldDisableRefresh(
- android::base::GetProperty("ro.build.version.sdk", /*default_value=*/""))) {
+ if (ShouldDisableRefresh(GetProperty("ro.build.version.sdk", /*default_value=*/""))) {
config->SetRefresh(false);
}
return n;
}
+void GetSystemProperties(std::unordered_map<std::string, std::string>* system_properties) {
+ for (const SystemPropertyConfig& system_property_config : *kSystemProperties.get()) {
+ (*system_properties)[system_property_config.name] =
+ GetProperty(system_property_config.name, system_property_config.default_value);
+ }
+}
+
NO_RETURN void UsageHelp(const char* argv0) {
std::string name(android::base::Basename(argv0));
UsageMsg("Usage: %s [OPTION...] ACTION", name.c_str());
@@ -229,6 +238,7 @@
if (argc != 1) {
ArgumentError("Expected 1 argument, but have %d.", argc);
}
+ GetSystemProperties(config.MutableSystemProperties());
OdrMetrics metrics(config.GetArtifactDirectory());
OnDeviceRefresh odr(config);
@@ -268,6 +278,6 @@
} else if (action == "--help") {
UsageHelp(argv[0]);
} else {
- ArgumentError("Unknown argument: ", action);
+ ArgumentError("Unknown argument: %s", action.data());
}
}
diff --git a/odrefresh/schema/current.txt b/odrefresh/schema/current.txt
index 958f2a0..c45870b 100644
--- a/odrefresh/schema/current.txt
+++ b/odrefresh/schema/current.txt
@@ -8,12 +8,14 @@
method public boolean getCompilationOsMode();
method public com.android.art.Classpath getDex2oatBootClasspath();
method public com.android.art.ModuleInfoList getModuleInfoList();
+ method public com.android.art.KeyValuePairList getSystemProperties();
method public com.android.art.SystemServerComponents getSystemServerComponents();
method public void setArtModuleInfo(com.android.art.ModuleInfo);
method public void setBootClasspath(com.android.art.Classpath);
method public void setCompilationOsMode(boolean);
method public void setDex2oatBootClasspath(com.android.art.Classpath);
method public void setModuleInfoList(com.android.art.ModuleInfoList);
+ method public void setSystemProperties(com.android.art.KeyValuePairList);
method public void setSystemServerComponents(com.android.art.SystemServerComponents);
}
@@ -33,6 +35,20 @@
method public void setSize(java.math.BigInteger);
}
+ public class KeyValuePair {
+ ctor public KeyValuePair();
+ method public String getK();
+ method public String getV();
+ method public void setK(String);
+ method public void setV(String);
+ }
+
+ public class KeyValuePairList {
+ ctor public KeyValuePairList();
+ method public com.android.art.KeyValuePair getItem();
+ method public void setItem(com.android.art.KeyValuePair);
+ }
+
public class ModuleInfo {
ctor public ModuleInfo();
method public long getLastUpdateMillis();
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 61a6865..134c327 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -1083,6 +1083,11 @@
ENTRY art_quick_aput_obj
cbz x2, .Laput_obj_null
+#if defined(USE_READ_BARRIER) && !defined(USE_BAKER_READ_BARRIER)
+ READ_BARRIER_SLOW x3, w3, x0, MIRROR_OBJECT_CLASS_OFFSET
+ READ_BARRIER_SLOW x3, w3, x3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET
+ READ_BARRIER_SLOW x4, w4, x2, MIRROR_OBJECT_CLASS_OFFSET
+#else // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
#ifdef USE_READ_BARRIER
cbnz wMR, .Laput_obj_gc_marking
#endif // USE_READ_BARRIER
@@ -1092,6 +1097,7 @@
UNPOISON_HEAP_REF w3
ldr w4, [x2, #MIRROR_OBJECT_CLASS_OFFSET] // Heap reference = 32b; zero-extends to x4.
UNPOISON_HEAP_REF w4
+#endif // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
cmp w3, w4 // value's type == array's component type - trivial assignability
bne .Laput_obj_check_assignability
.Laput_obj_store:
@@ -1139,20 +1145,18 @@
RESTORE_TWO_REGS x2, xLR, 16
RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, 32
-#ifdef USE_READ_BARRIER
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
.cfi_remember_state
-#endif // USE_READ_BARRIER
+#endif // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
mov x1, x2 // Pass value.
mov x2, xSELF // Pass Thread::Current.
bl artThrowArrayStoreException // (Object*, Object*, Thread*).
brk 0 // Unreachable.
-#ifdef USE_READ_BARRIER
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
CFI_RESTORE_STATE_AND_DEF_CFA sp, 0
.Laput_obj_gc_marking:
-
-#ifdef USE_BAKER_READ_BARRIER
BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
w3, x0, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_array_class
.Laput_obj_mark_array_class_continue:
@@ -1162,17 +1166,10 @@
BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
w4, x2, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_object_class
.Laput_obj_mark_object_class_continue:
-#else // USE_BAKER_READ_BARRIER
- READ_BARRIER_SLOW x3, w3, x0, MIRROR_OBJECT_CLASS_OFFSET
- READ_BARRIER_SLOW x3, w3, x3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET
- READ_BARRIER_SLOW x4, w4, x2, MIRROR_OBJECT_CLASS_OFFSET
-#endif // USE_BAKER_READ_BARRIER
-
cmp w3, w4 // value's type == array's component type - trivial assignability
bne .Laput_obj_check_assignability
b .Laput_obj_store
-#ifdef USE_BAKER_READ_BARRIER
.Laput_obj_mark_array_class:
BAKER_RB_LOAD_AND_MARK w3, x0, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg03
b .Laput_obj_mark_array_class_continue
@@ -1185,8 +1182,7 @@
.Laput_obj_mark_object_class:
BAKER_RB_LOAD_AND_MARK w4, x2, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg04
b .Laput_obj_mark_object_class_continue
-#endif // USE_BAKER_READ_BARRIER
-#endif // USE_READ_BARRIER
+#endif // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
END art_quick_aput_obj
// Macro to facilitate adding new allocation entrypoints.
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 867f75c..40b7a7b 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -617,6 +617,15 @@
}
OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromEntryPoint(oat_entry_point);
+ // We could have existing Oat code for native methods but we may not use it if the runtime is java
+ // debuggable or when profiling boot class path. There is no easy way to check if the pc
+ // corresponds to QuickGenericJniStub. Since we have eliminated all the other cases, if the pc
+ // doesn't correspond to the AOT code then we must be running QuickGenericJniStub.
+ if (IsNative() && !method_header->Contains(pc)) {
+ DCHECK_NE(pc, 0u) << "PC 0 for " << PrettyMethod();
+ return nullptr;
+ }
+
DCHECK(method_header->Contains(pc))
<< PrettyMethod()
<< " " << std::hex << pc << " " << oat_entry_point
diff --git a/runtime/art_method.h b/runtime/art_method.h
index d8bd380..c2de718 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -259,7 +259,9 @@
}
void SetMemorySharedMethod() REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!IsIntrinsic() && !IsAbstract()) {
+ // Disable until we make sure critical code is AOTed.
+ static constexpr bool kEnabledMemorySharedMethod = false;
+ if (kEnabledMemorySharedMethod && !IsIntrinsic() && !IsAbstract()) {
AddAccessFlags(kAccMemorySharedMethod);
SetHotCounter();
}
diff --git a/runtime/base/locks.h b/runtime/base/locks.h
index 829adff..c15e5de 100644
--- a/runtime/base/locks.h
+++ b/runtime/base/locks.h
@@ -68,12 +68,12 @@
// Can be held while GC related work is done, and thus must be above kMarkSweepMarkStackLock
kThreadWaitLock,
kCHALock,
- kJitCodeCacheLock,
kRosAllocGlobalLock,
kRosAllocBracketLock,
kRosAllocBulkFreeLock,
kAllocSpaceLock,
kTaggingLockLevel,
+ kJitCodeCacheLock,
kTransactionLogLock,
kCustomTlsLock,
kJniFunctionTableLock,
diff --git a/runtime/entrypoints/jni/jni_entrypoints.cc b/runtime/entrypoints/jni/jni_entrypoints.cc
index c78b604..80eb89f 100644
--- a/runtime/entrypoints/jni/jni_entrypoints.cc
+++ b/runtime/entrypoints/jni/jni_entrypoints.cc
@@ -87,11 +87,11 @@
}
// Replace the runtime method on the stack with the target method.
- DCHECK(!self->GetManagedStack()->GetTopQuickFrameTag());
+ DCHECK(!self->GetManagedStack()->GetTopQuickFrameGenericJniTag());
ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrameKnownNotTagged();
DCHECK(*sp == Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
*sp = target_method;
- self->SetTopOfStackTagged(sp); // Fake GenericJNI frame.
+ self->SetTopOfStackGenericJniTagged(sp); // Fake GenericJNI frame.
// Continue with the target method.
method = target_method;
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index b15ce69..31471d6 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -1034,18 +1034,7 @@
// This will get the entry point either from the oat file, the JIT or the appropriate bridge
// method if none of those can be found.
result = instrumentation->GetCodeForInvoke(method);
- jit::Jit* jit = Runtime::Current()->GetJit();
DCHECK_NE(result, GetQuickInstrumentationEntryPoint()) << method->PrettyMethod();
- DCHECK(jit == nullptr ||
- // Native methods come through here in Interpreter entrypoints. We might not have
- // disabled jit-gc but that is fine since we won't return jit-code for native methods.
- method->IsNative() ||
- !jit->GetCodeCache()->GetGarbageCollectCode());
- DCHECK(!method->IsNative() ||
- jit == nullptr ||
- !jit->GetCodeCache()->ContainsPc(result))
- << method->PrettyMethod() << " code will jump to possibly cleaned up jit code!";
-
bool interpreter_entry = Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(result);
bool is_static = method->IsStatic();
uint32_t shorty_len;
@@ -2096,7 +2085,7 @@
}
// Fix up managed-stack things in Thread. After this we can walk the stack.
- self->SetTopOfStackTagged(managed_sp);
+ self->SetTopOfStackGenericJniTagged(managed_sp);
self->VerifyStack();
@@ -2190,7 +2179,7 @@
// anything that requires a mutator lock before that would cause problems as GC may have the
// exclusive mutator lock and may be moving objects, etc.
ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
- DCHECK(self->GetManagedStack()->GetTopQuickFrameTag());
+ DCHECK(self->GetManagedStack()->GetTopQuickFrameGenericJniTag());
uint32_t* sp32 = reinterpret_cast<uint32_t*>(sp);
ArtMethod* called = *sp;
uint32_t cookie = *(sp32 - 1);
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 6cbf178..0de62fe 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -1080,7 +1080,35 @@
REQUIRES_SHARED(Locks::heap_bitmap_lock_) {
DCHECK_EQ(collector_->RegionSpace()->RegionIdxForRef(obj), obj_region_idx_);
DCHECK(kHandleInterRegionRefs || collector_->immune_spaces_.ContainsObject(obj));
- CheckReference(obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(offset));
+ mirror::Object* ref =
+ obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(offset);
+ // TODO(lokeshgidra): Remove the following condition once b/173676071 is fixed.
+ if (UNLIKELY(ref == nullptr && offset == mirror::Object::ClassOffset())) {
+ // It has been verified as a race condition (see b/173676071)! After a small
+ // wait when we reload the class pointer, it turns out to be a valid class
+ // object. So as a workaround, we can continue execution and log an error
+ // that this happened.
+ for (size_t i = 0; i < 1000; i++) {
+ // Wait for 1ms at a time. Don't wait for more than 1 second in total.
+ usleep(1000);
+ ref = obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ if (ref != nullptr) {
+ LOG(ERROR) << "klass pointer for obj: "
+ << obj << " (" << mirror::Object::PrettyTypeOf(obj)
+ << ") found to be null first. Reloading after a small wait fetched klass: "
+ << ref << " (" << mirror::Object::PrettyTypeOf(ref) << ")";
+ break;
+ }
+ }
+
+ if (UNLIKELY(ref == nullptr)) {
+ // It must be heap corruption. Remove memory protection and dump data.
+ collector_->region_space_->Unprotect();
+ LOG(FATAL_WITHOUT_ABORT) << "klass pointer for ref: " << obj << " found to be null.";
+ collector_->heap_->GetVerification()->LogHeapCorruption(obj, offset, ref, /* fatal */ true);
+ }
+ }
+ CheckReference(ref);
}
void operator()(ObjPtr<mirror::Class> klass, ObjPtr<mirror::Reference> ref) const
diff --git a/runtime/hidden_api_test.cc b/runtime/hidden_api_test.cc
index f5cd15e..e204c57 100644
--- a/runtime/hidden_api_test.cc
+++ b/runtime/hidden_api_test.cc
@@ -235,6 +235,7 @@
runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kJustWarn);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Sdk()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Unsupported()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetS()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetR()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetQ()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetP()), false);
@@ -248,6 +249,7 @@
SetChangeIdState(kHideMaxtargetsdkQHiddenApis, false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Sdk()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Unsupported()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetS()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetR()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetQ()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetP()), false);
@@ -261,6 +263,7 @@
SetChangeIdState(kHideMaxtargetsdkQHiddenApis, false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Sdk()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Unsupported()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetS()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetR()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetQ()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetP()), false);
@@ -269,6 +272,8 @@
SetChangeIdState(kHideMaxtargetsdkQHiddenApis, true);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Sdk()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Unsupported()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetS()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetR()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetQ()), true);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetP()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetO()), true);
@@ -281,6 +286,7 @@
SetChangeIdState(kHideMaxtargetsdkQHiddenApis, false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Sdk()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Unsupported()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetS()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetR()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetQ()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetP()), true);
@@ -294,6 +300,7 @@
SetChangeIdState(kHideMaxtargetsdkQHiddenApis, true);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Sdk()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Unsupported()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetS()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetR()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetQ()), true);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetP()), true);
@@ -307,6 +314,19 @@
SetChangeIdState(kHideMaxtargetsdkQHiddenApis, true);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Sdk()), false);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Unsupported()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetS()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetR()), true);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetQ()), true);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetP()), true);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetO()), true);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Blocked()), true);
+
+ runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled);
+ runtime_->SetTargetSdkVersion(
+ static_cast<uint32_t>(hiddenapi::ApiList::MaxTargetS().GetMaxAllowedSdkVersion()) + 1);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Sdk()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Unsupported()), false);
+ ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetS()), true);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetR()), true);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetQ()), true);
ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::MaxTargetP()), true);
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 4b3b318..b4dc9f6 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -230,32 +230,9 @@
method->GetDeclaringClass()->DescriptorEquals("Ljava/lang/reflect/Proxy;");
}
-static void UpdateEntryPoints(ArtMethod* method, const void* quick_code)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (kIsDebugBuild) {
- if (NeedsClinitCheckBeforeCall(method) &&
- !method->GetDeclaringClass()->IsVisiblyInitialized()) {
- CHECK(CanHandleInitializationCheck(quick_code));
- }
- jit::Jit* jit = Runtime::Current()->GetJit();
- if (jit != nullptr && jit->GetCodeCache()->ContainsPc(quick_code)) {
- // Ensure we always have the thumb entrypoint for JIT on arm32.
- if (kRuntimeISA == InstructionSet::kArm) {
- CHECK_EQ(reinterpret_cast<uintptr_t>(quick_code) & 1, 1u);
- }
- }
- if (IsProxyInit(method)) {
- CHECK_NE(quick_code, GetQuickInstrumentationEntryPoint());
- }
- }
- // If the method is from a boot image, don't dirty it if the entrypoint
- // doesn't change.
- if (method->GetEntryPointFromQuickCompiledCode() != quick_code) {
- method->SetEntryPointFromQuickCompiledCode(quick_code);
- }
-}
-
-bool Instrumentation::CodeNeedsEntryExitStub(const void* code, ArtMethod* method)
+// Returns true if we need entry exit stub to call entry hooks. JITed code
+// directly call entry / exit hooks and don't need the stub.
+static bool CodeNeedsEntryExitStub(const void* code, ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) {
// Proxy.init should never have entry/exit stubs.
if (IsProxyInit(method)) {
@@ -294,6 +271,36 @@
return true;
}
+static void UpdateEntryPoints(ArtMethod* method, const void* quick_code)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (kIsDebugBuild) {
+ if (NeedsClinitCheckBeforeCall(method) &&
+ !method->GetDeclaringClass()->IsVisiblyInitialized()) {
+ CHECK(CanHandleInitializationCheck(quick_code));
+ }
+ jit::Jit* jit = Runtime::Current()->GetJit();
+ if (jit != nullptr && jit->GetCodeCache()->ContainsPc(quick_code)) {
+ // Ensure we always have the thumb entrypoint for JIT on arm32.
+ if (kRuntimeISA == InstructionSet::kArm) {
+ CHECK_EQ(reinterpret_cast<uintptr_t>(quick_code) & 1, 1u);
+ }
+ }
+ if (IsProxyInit(method)) {
+ CHECK_NE(quick_code, GetQuickInstrumentationEntryPoint());
+ }
+ const Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ if (instr->EntryExitStubsInstalled()) {
+ DCHECK(quick_code == GetQuickInstrumentationEntryPoint() ||
+ !CodeNeedsEntryExitStub(quick_code, method));
+ }
+ }
+ // If the method is from a boot image, don't dirty it if the entrypoint
+ // doesn't change.
+ if (method->GetEntryPointFromQuickCompiledCode() != quick_code) {
+ method->SetEntryPointFromQuickCompiledCode(quick_code);
+ }
+}
+
bool Instrumentation::InterpretOnly(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
if (method->IsNative()) {
return false;
@@ -303,16 +310,11 @@
Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method);
}
-static bool CanUseAotCode(ArtMethod* method, const void* quick_code)
+static bool CanUseAotCode(const void* quick_code)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (quick_code == nullptr) {
return false;
}
- if (method->IsNative()) {
- // AOT code for native methods can always be used.
- return true;
- }
-
Runtime* runtime = Runtime::Current();
// For simplicity, we never use AOT code for debuggable.
if (runtime->IsJavaDebuggable()) {
@@ -348,7 +350,7 @@
// In debuggable mode, we can only use AOT code for native methods.
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
const void* aot_code = method->GetOatMethodQuickCode(class_linker->GetImagePointerSize());
- if (CanUseAotCode(method, aot_code)) {
+ if (CanUseAotCode(aot_code)) {
return aot_code;
}
@@ -407,7 +409,7 @@
}
// Use the provided AOT code if possible.
- if (CanUseAotCode(method, aot_code)) {
+ if (CanUseAotCode(aot_code)) {
UpdateEntryPoints(method, aot_code);
return;
}
@@ -1204,7 +1206,11 @@
UpdateEntryPoints(method, GetQuickToInterpreterBridge());
} else if (NeedsClinitCheckBeforeCall(method) &&
!method->GetDeclaringClass()->IsVisiblyInitialized()) {
- UpdateEntryPoints(method, GetQuickResolutionStub());
+ if (EntryExitStubsInstalled()) {
+ UpdateEntryPoints(method, GetQuickInstrumentationEntryPoint());
+ } else {
+ UpdateEntryPoints(method, GetQuickResolutionStub());
+ }
} else {
UpdateEntryPoints(method, GetMaybeInstrumentedCodeForInvoke(method));
}
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index c63e73e..33ff8ff 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -580,11 +580,6 @@
// False otherwise.
bool RequiresInstrumentationInstallation(InstrumentationLevel new_level) const;
- // Returns true if we need entry exit stub to call entry hooks. JITed code
- // directly call entry / exit hooks and don't need the stub.
- static bool CodeNeedsEntryExitStub(const void* code, ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// Update the current instrumentation_level_.
void UpdateInstrumentationLevel(InstrumentationLevel level);
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index ae3575a..facf66a 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -208,7 +208,6 @@
// Jit GC for now (b/147208992).
if (code_cache->GetGarbageCollectCode()) {
code_cache->SetGarbageCollectCode(!jit_compiler_->GenerateDebugInfo() &&
- !Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled() &&
!jit->JitAtFirstUse());
}
@@ -1662,7 +1661,6 @@
// Jit GC for now (b/147208992).
code_cache_->SetGarbageCollectCode(
!jit_compiler_->GenerateDebugInfo() &&
- !runtime->GetInstrumentation()->AreExitStubsInstalled() &&
!JitAtFirstUse());
if (is_system_server && runtime->HasImageWithProfile()) {
diff --git a/runtime/managed_stack.h b/runtime/managed_stack.h
index 04a27fe..2184f47 100644
--- a/runtime/managed_stack.h
+++ b/runtime/managed_stack.h
@@ -75,8 +75,12 @@
return tagged_top_quick_frame_.GetSp();
}
- bool GetTopQuickFrameTag() const {
- return tagged_top_quick_frame_.GetTag();
+ bool GetTopQuickFrameGenericJniTag() const {
+ return tagged_top_quick_frame_.GetGenericJniTag();
+ }
+
+ bool GetTopQuickFrameJitJniTag() const {
+ return tagged_top_quick_frame_.GetJitJniTag();
}
bool HasTopQuickFrame() const {
@@ -89,10 +93,10 @@
tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateNotTagged(top);
}
- void SetTopQuickFrameTagged(ArtMethod** top) {
+ void SetTopQuickFrameGenericJniTagged(ArtMethod** top) {
DCHECK(top_shadow_frame_ == nullptr);
DCHECK_ALIGNED(top, 4u);
- tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateTagged(top);
+ tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateGenericJniTagged(top);
}
static constexpr size_t TaggedTopQuickFrameOffset() {
@@ -129,26 +133,30 @@
return TaggedTopQuickFrame(reinterpret_cast<uintptr_t>(sp));
}
- static TaggedTopQuickFrame CreateTagged(ArtMethod** sp) {
+ static TaggedTopQuickFrame CreateGenericJniTagged(ArtMethod** sp) {
DCHECK_ALIGNED(sp, 4u);
return TaggedTopQuickFrame(reinterpret_cast<uintptr_t>(sp) | 1u);
}
// Get SP known to be not tagged and non-null.
ArtMethod** GetSpKnownNotTagged() const {
- DCHECK(!GetTag());
+ DCHECK(!GetGenericJniTag() && !GetJitJniTag());
DCHECK_NE(tagged_sp_, 0u);
return reinterpret_cast<ArtMethod**>(tagged_sp_);
}
ArtMethod** GetSp() const {
- return reinterpret_cast<ArtMethod**>(tagged_sp_ & ~static_cast<uintptr_t>(1u));
+ return reinterpret_cast<ArtMethod**>(tagged_sp_ & ~static_cast<uintptr_t>(3u));
}
- bool GetTag() const {
+ bool GetGenericJniTag() const {
return (tagged_sp_ & 1u) != 0u;
}
+ bool GetJitJniTag() const {
+ return (tagged_sp_ & 2u) != 0u;
+ }
+
uintptr_t GetTaggedSp() const {
return tagged_sp_;
}
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 9a6c8a5..d2eb3bd 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -3147,15 +3147,19 @@
auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
for (auto& m : klass->GetMethods(pointer_size)) {
const void* code = m.GetEntryPointFromQuickCompiledCode();
+ // For java debuggable runtimes we also deoptimize native methods. For other cases (boot
+ // image profiling) we don't need to deoptimize native methods. If this changes also
+ // update Instrumentation::CanUseAotCode.
+ bool deoptimize_native_methods = Runtime::Current()->IsJavaDebuggable();
if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) &&
- !m.IsNative() &&
+ (!m.IsNative() || deoptimize_native_methods) &&
!m.IsProxyMethod()) {
instrumentation_->InitializeMethodsCode(&m, /*aot_code=*/ nullptr);
}
if (Runtime::Current()->GetJit() != nullptr &&
Runtime::Current()->GetJit()->GetCodeCache()->IsInZygoteExecSpace(code) &&
- !m.IsNative()) {
+ (!m.IsNative() || deoptimize_native_methods)) {
DCHECK(!m.IsProxyMethod());
instrumentation_->InitializeMethodsCode(&m, /*aot_code=*/ nullptr);
}
diff --git a/runtime/stack.cc b/runtime/stack.cc
index 50a96d0..33d3668 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -800,10 +800,20 @@
// between GenericJNI frame and JIT-compiled JNI stub; the entrypoint may have
// changed since the frame was entered. The top quick frame tag indicates
// GenericJNI here, otherwise it's either AOT-compiled or JNI-compiled JNI stub.
- if (UNLIKELY(current_fragment->GetTopQuickFrameTag())) {
+ if (UNLIKELY(current_fragment->GetTopQuickFrameGenericJniTag())) {
// The generic JNI does not have any method header.
cur_oat_quick_method_header_ = nullptr;
+ } else if (UNLIKELY(current_fragment->GetTopQuickFrameJitJniTag())) {
+ // Should be JITed code.
+ Runtime* runtime = Runtime::Current();
+ const void* code = runtime->GetJit()->GetCodeCache()->GetJniStubCode(method);
+ CHECK(code != nullptr) << method->PrettyMethod();
+ cur_oat_quick_method_header_ = OatQuickMethodHeader::FromCodePointer(code);
} else {
+ // We are sure we are not running GenericJni here. Though the entry point could still be
+ // GenericJnistub. The entry point is usually JITed, AOT or instrumentation stub when
+ // instrumentation is enabled. It could be lso a resolution stub if the class isn't
+ // visibly initialized yet.
const void* existing_entry_point = method->GetEntryPointFromQuickCompiledCode();
CHECK(existing_entry_point != nullptr);
Runtime* runtime = Runtime::Current();
@@ -819,7 +829,11 @@
if (code != nullptr) {
cur_oat_quick_method_header_ = OatQuickMethodHeader::FromEntryPoint(code);
} else {
- // This must be a JITted JNI stub frame.
+ // This must be a JITted JNI stub frame. For non-debuggable runtimes we only generate
+ // JIT stubs if there are no AOT stubs for native methods. Since we checked for AOT
+ // code earlier, we must be running JITed code. For debuggable runtimes we might have
+ // JIT code even when AOT code is present but we tag SP in JITed JNI stubs
+ // in debuggable runtimes. This case is handled earlier.
CHECK(runtime->GetJit() != nullptr);
code = runtime->GetJit()->GetCodeCache()->GetJniStubCode(method);
CHECK(code != nullptr) << method->PrettyMethod();
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 97cfb7a..e629b4e 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1981,6 +1981,9 @@
if (thread->IsStillStarting()) {
os << " (still starting up)";
}
+ if (thread->tls32_.disable_thread_flip_count != 0) {
+ os << " DisableFlipCount = " << thread->tls32_.disable_thread_flip_count;
+ }
os << "\n";
} else {
os << '"' << ::art::GetThreadName(tid) << '"'
diff --git a/runtime/thread.h b/runtime/thread.h
index b32e3c2..7ac4007 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -573,8 +573,8 @@
tlsPtr_.managed_stack.SetTopQuickFrame(top_method);
}
- void SetTopOfStackTagged(ArtMethod** top_method) {
- tlsPtr_.managed_stack.SetTopQuickFrameTagged(top_method);
+ void SetTopOfStackGenericJniTagged(ArtMethod** top_method) {
+ tlsPtr_.managed_stack.SetTopQuickFrameGenericJniTagged(top_method);
}
void SetTopOfShadowStack(ShadowFrame* top) {
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 6482e72..bfde711 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -1291,6 +1291,10 @@
DCHECK_EQ(self, Thread::Current());
CHECK_NE(self->GetState(), ThreadState::kRunnable);
Locks::mutator_lock_->AssertNotHeld(self);
+ if (self->tls32_.disable_thread_flip_count != 0) {
+ LOG(FATAL) << "Incomplete PrimitiveArrayCritical section at exit: " << *self << "count = "
+ << self->tls32_.disable_thread_flip_count;
+ }
VLOG(threads) << "ThreadList::Unregister() " << *self;
diff --git a/test/2042-reference-processing/Android.bp b/test/2042-reference-processing/Android.bp
new file mode 100644
index 0000000..a73b8d0
--- /dev/null
+++ b/test/2042-reference-processing/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2042-reference-processing`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2042-reference-processing",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2042-reference-processing-expected-stdout",
+ ":art-run-test-2042-reference-processing-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2042-reference-processing-expected-stdout",
+ out: ["art-run-test-2042-reference-processing-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2042-reference-processing-expected-stderr",
+ out: ["art-run-test-2042-reference-processing-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2042-reference-processing/expected-stderr.txt b/test/2042-reference-processing/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2042-reference-processing/expected-stderr.txt
diff --git a/test/2042-reference-processing/expected-stdout.txt b/test/2042-reference-processing/expected-stdout.txt
new file mode 100644
index 0000000..8b3e832
--- /dev/null
+++ b/test/2042-reference-processing/expected-stdout.txt
@@ -0,0 +1,2 @@
+Starting
+Finished
diff --git a/test/2042-reference-processing/info.txt b/test/2042-reference-processing/info.txt
new file mode 100644
index 0000000..e2d7a99
--- /dev/null
+++ b/test/2042-reference-processing/info.txt
@@ -0,0 +1,6 @@
+A test for reference processing correctness.
+
+The emphasis here is on fundamental properties. In particular, references to
+unreachable referents should be enqueued, and this should ensure that uncleared
+References don't point to objects for which References were enqueued. We also
+check various other ordering properties for java.lang.ref.References.
diff --git a/test/2042-reference-processing/src/Main.java b/test/2042-reference-processing/src/Main.java
new file mode 100644
index 0000000..ed67052
--- /dev/null
+++ b/test/2042-reference-processing/src/Main.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+/**
+ * Test that objects get finalized and their references cleared in the right order.
+ *
+ * We maintain a list of nominally MAX_LIVE_OBJS numbered finalizable objects.
+ * We then alternately drop the last 50, and add 50 more. When we see an object finalized
+ * or its reference cleared, we make sure that the preceding objects in its group of 50
+ * have also had their references cleared. We also perform a number of other more
+ * straightforward checks, such as ensuring that all references are eventually cleared,
+ * and all objects are finalized.
+ */
+public class Main {
+ // TODO(b/216481630) Enable CHECK_PHANTOM_REFS. This currently occasionally reports a few
+ // PhantomReferences as not enqueued. If this report is correct, this needs to be tracked
+ // down and fixed.
+ static final boolean CHECK_PHANTOM_REFS = false;
+
+ static final int MAX_LIVE_OBJS = 150;
+ static final int DROP_OBJS = 50; // Number of linked objects dropped in each batch.
+ static final int MIN_LIVE_OBJS = MAX_LIVE_OBJS - DROP_OBJS;
+ static final int TOTAL_OBJS = 200_000; // Allocate this many finalizable objects in total.
+ static final boolean REPORT_DROPS = false;
+ static volatile boolean pleaseStop;
+
+ AtomicInteger totalFinalized = new AtomicInteger(0);
+ Object phantomRefsLock = new Object();
+ int maxDropped = 0;
+ int liveObjects = 0;
+
+ // Number of next finalizable object to be allocated.
+ int nextAllocated = 0;
+
+ // List of finalizable objects in descending order. We add to the front and drop
+ // from the rear.
+ FinalizableObject listHead;
+
+ // A possibly incomplete list of FinalizableObject indices that were finalized, but
+ // have yet to be checked for consistency with reference processing.
+ ArrayBlockingQueue<Integer> finalized = new ArrayBlockingQueue<>(20_000);
+
+ // Maps from object number to Reference; Cleared references are deleted when queues are
+ // processed.
+ TreeMap<Integer, MyWeakReference> weakRefs = new TreeMap<>();
+ HashMap<Integer, MyPhantomReference> phantomRefs = new HashMap<>();
+
+ class FinalizableObject {
+ int n;
+ FinalizableObject next;
+ FinalizableObject(int num, FinalizableObject nextObj) {
+ n = num;
+ next = nextObj;
+ }
+ protected void finalize() {
+ if (!inPhantomRefs(n)) {
+ System.out.println("PhantomRef enqueued before finalizer ran");
+ }
+ totalFinalized.incrementAndGet();
+ if (!finalized.offer(n) && REPORT_DROPS) {
+ System.out.println("Dropped finalization of " + n);
+ }
+ }
+ }
+ ReferenceQueue<FinalizableObject> refQueue = new ReferenceQueue<>();
+ class MyWeakReference extends WeakReference<FinalizableObject> {
+ int n;
+ MyWeakReference(FinalizableObject obj) {
+ super(obj, refQueue);
+ n = obj.n;
+ }
+ };
+ class MyPhantomReference extends PhantomReference<FinalizableObject> {
+ int n;
+ MyPhantomReference(FinalizableObject obj) {
+ super(obj, refQueue);
+ n = obj.n;
+ }
+ }
+ boolean inPhantomRefs(int n) {
+ synchronized(phantomRefsLock) {
+ MyPhantomReference ref = phantomRefs.get(n);
+ if (ref == null) {
+ return false;
+ }
+ if (ref.n != n) {
+ System.out.println("phantomRef retrieval failed");
+ }
+ return true;
+ }
+ }
+
+ void CheckOKToClearWeak(int num) {
+ if (num > maxDropped) {
+ System.out.println("WeakRef to live object " + num + " was cleared/enqueued.");
+ }
+ int batchEnd = (num / DROP_OBJS + 1) * DROP_OBJS;
+ for (MyWeakReference wr : weakRefs.subMap(num + 1, batchEnd).values()) {
+ if (wr.n <= num || wr.n / DROP_OBJS != num / DROP_OBJS) {
+ throw new AssertionError("MyWeakReference logic error!");
+ }
+ // wr referent was dropped in same batch and precedes it in list.
+ if (wr.get() != null) {
+ // This violates the WeakReference spec, and can result in strong references
+ // to objects that have been cleaned.
+ System.out.println("WeakReference to " + wr.n
+ + " was erroneously cleared after " + num);
+ }
+ }
+ }
+
+ void CheckOKToClearPhantom(int num) {
+ if (num > maxDropped) {
+ System.out.println("PhantomRef to live object " + num + " was enqueued.");
+ }
+ MyWeakReference wr = weakRefs.get(num);
+ if (wr != null && wr.get() != null) {
+ System.out.println("PhantomRef cleared before WeakRef for " + num);
+ }
+ }
+
+ void emptyAndCheckQueues() {
+ // Check recently finalized objects for consistency with cleared references.
+ while (true) {
+ Integer num = finalized.poll();
+ if (num == null) {
+ break;
+ }
+ MyWeakReference wr = weakRefs.get(num);
+ if (wr != null) {
+ if (wr.n != num) {
+ System.out.println("Finalization logic error!");
+ }
+ if (wr.get() != null) {
+ System.out.println("Finalizing object with uncleared reference");
+ }
+ }
+ CheckOKToClearWeak(num);
+ }
+ // Check recently enqueued references for consistency.
+ while (true) {
+ Reference<FinalizableObject> ref = (Reference<FinalizableObject>) refQueue.poll();
+ if (ref == null) {
+ break;
+ }
+ if (ref instanceof MyWeakReference) {
+ MyWeakReference wr = (MyWeakReference) ref;
+ if (wr.get() != null) {
+ System.out.println("WeakRef " + wr.n + " enqueued but not cleared");
+ }
+ CheckOKToClearWeak(wr.n);
+ if (weakRefs.remove(Integer.valueOf(wr.n)) != ref) {
+ System.out.println("Missing WeakReference: " + wr.n);
+ }
+ } else if (ref instanceof MyPhantomReference) {
+ MyPhantomReference pr = (MyPhantomReference) ref;
+ CheckOKToClearPhantom(pr.n);
+ if (phantomRefs.remove(Integer.valueOf(pr.n)) != ref) {
+ System.out.println("Missing PhantomReference: " + pr.n);
+ }
+ } else {
+ System.out.println("Found unrecognized reference in queue");
+ }
+ }
+ }
+
+
+ /**
+ * Add n objects to the head of the list. These will be assigned the next n consecutive
+ * numbers after the current head of the list.
+ */
+ void addObjects(int n) {
+ for (int i = 0; i < n; ++i) {
+ int me = nextAllocated++;
+ listHead = new FinalizableObject(me, listHead);
+ weakRefs.put(me, new MyWeakReference(listHead));
+ synchronized(phantomRefsLock) {
+ phantomRefs.put(me, new MyPhantomReference(listHead));
+ }
+ }
+ liveObjects += n;
+ }
+
+ /**
+ * Drop n finalizable objects from the tail of the list. These are the lowest-numbered objects
+ * in the list.
+ */
+ void dropObjects(int n) {
+ FinalizableObject list = listHead;
+ FinalizableObject last = null;
+ if (n > liveObjects) {
+ System.out.println("Removing too many elements");
+ }
+ if (liveObjects == n) {
+ maxDropped = list.n;
+ listHead = null;
+ } else {
+ final int skip = liveObjects - n;
+ for (int i = 0; i < skip; ++i) {
+ last = list;
+ list = list.next;
+ }
+ int expected = nextAllocated - skip - 1;
+ if (list.n != expected) {
+ System.out.println("dropObjects found " + list.n + " but expected " + expected);
+ }
+ maxDropped = expected;
+ last.next = null;
+ }
+ liveObjects -= n;
+ }
+
+ void testLoop() {
+ System.out.println("Starting");
+ addObjects(MIN_LIVE_OBJS);
+ final int ITERS = (TOTAL_OBJS - MIN_LIVE_OBJS) / DROP_OBJS;
+ for (int i = 0; i < ITERS; ++i) {
+ addObjects(DROP_OBJS);
+ if (liveObjects != MAX_LIVE_OBJS) {
+ System.out.println("Unexpected live object count");
+ }
+ dropObjects(DROP_OBJS);
+ emptyAndCheckQueues();
+ }
+ dropObjects(MIN_LIVE_OBJS);
+ if (liveObjects != 0 || listHead != null) {
+ System.out.println("Unexpected live objecs at end");
+ }
+ if (maxDropped != TOTAL_OBJS - 1) {
+ System.out.println("Unexpected dropped object count: " + maxDropped);
+ }
+ for (int i = 0; i < 2; ++i) {
+ Runtime.getRuntime().gc();
+ System.runFinalization();
+ emptyAndCheckQueues();
+ }
+ if (!weakRefs.isEmpty()) {
+ System.out.println("Weak Reference map nonempty size = " + weakRefs.size());
+ }
+ if (CHECK_PHANTOM_REFS && !phantomRefs.isEmpty()) {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ System.out.println("Unexpected interrupt");
+ }
+ if (!phantomRefs.isEmpty()) {
+ System.out.println("Phantom Reference map nonempty size = " + phantomRefs.size());
+ System.out.print("First elements:");
+ int i = 0;
+ for (MyPhantomReference pr : phantomRefs.values()) {
+ System.out.print(" " + pr.n);
+ if (++i > 10) {
+ break;
+ }
+ }
+ System.out.println("");
+ }
+ }
+ if (totalFinalized.get() != TOTAL_OBJS) {
+ System.out.println("Finalized only " + totalFinalized + " objects");
+ }
+ }
+
+ static Runnable causeGCs = new Runnable() {
+ public void run() {
+ // Allocate a lot.
+ BigInteger counter = BigInteger.ZERO;
+ while (!pleaseStop) {
+ counter = counter.add(BigInteger.TEN);
+ }
+ // Look at counter to reduce chance of optimizing out the allocation.
+ if (counter.longValue() % 10 != 0) {
+ System.out.println("Bad causeGCs counter value: " + counter);
+ }
+ }
+ };
+
+ public static void main(String[] args) throws Exception {
+ Main theTest = new Main();
+ Thread gcThread = new Thread(causeGCs);
+ gcThread.setDaemon(true); // Terminate if main thread dies.
+ gcThread.start();
+ theTest.testLoop();
+ pleaseStop = true;
+ gcThread.join();
+ System.out.println("Finished");
+ }
+}
diff --git a/test/2043-reference-pauses/Android.bp b/test/2043-reference-pauses/Android.bp
new file mode 100644
index 0000000..a84aea2
--- /dev/null
+++ b/test/2043-reference-pauses/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2043-reference-pauses`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2043-reference-pauses",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2043-reference-pauses-expected-stdout",
+ ":art-run-test-2043-reference-pauses-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2043-reference-pauses-expected-stdout",
+ out: ["art-run-test-2043-reference-pauses-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2043-reference-pauses-expected-stderr",
+ out: ["art-run-test-2043-reference-pauses-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2043-reference-pauses/expected-stderr.txt b/test/2043-reference-pauses/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2043-reference-pauses/expected-stderr.txt
diff --git a/test/2043-reference-pauses/expected-stdout.txt b/test/2043-reference-pauses/expected-stdout.txt
new file mode 100644
index 0000000..8b3e832
--- /dev/null
+++ b/test/2043-reference-pauses/expected-stdout.txt
@@ -0,0 +1,2 @@
+Starting
+Finished
diff --git a/test/2043-reference-pauses/info.txt b/test/2043-reference-pauses/info.txt
new file mode 100644
index 0000000..f76fa32
--- /dev/null
+++ b/test/2043-reference-pauses/info.txt
@@ -0,0 +1,5 @@
+Tests WeakReference processing and retention of objects needed by finalizers.
+
+Can be used as Reference.get() blocking benchmark by setting PRINT_TIMES to
+true. This will print maximum observed latencies for Reference.get() when
+significant memory is only reachable from SoftReferences and Finalizers.
diff --git a/test/2043-reference-pauses/src/Main.java b/test/2043-reference-pauses/src/Main.java
new file mode 100644
index 0000000..dc64d9a
--- /dev/null
+++ b/test/2043-reference-pauses/src/Main.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.SoftReference;
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+/**
+ * Basic test of WeakReferences with large amounts of memory that's only reachable through
+ * finalizers. Also makes sure that finalizer-reachable data is not collected.
+ * Can easily be modified to time Reference.get() blocking.
+ */
+public class Main {
+ static final boolean PRINT_TIMES = false; // true will cause benchmark failure.
+ // Data structures repeatedly allocated in background to trigger GC.
+ // Size of finalizer reachable trees.
+ static final int TREE_HEIGHT = 15; // Trees contain 2^TREE_HEIGHT -1 allocated objects.
+ // Number of finalizable tree-owning objects that exist at one point.
+ static final int N_RESURRECTING_OBJECTS = 10;
+ // Number of short-lived, not finalizer-reachable, objects allocated between trees.
+ static final int N_PLAIN_OBJECTS = 20_000;
+ // Number of SoftReferences to CBTs we allocate.
+ static final int N_SOFTREFS = 10;
+
+ static final boolean BACKGROUND_GC_THREAD = true;
+ static final int NBATCHES = 10;
+ static final int NREFS = PRINT_TIMES ? 1_000_000 : 300_000; // Multiple of NBATCHES.
+ static final int REFS_PER_BATCH = NREFS / NBATCHES;
+
+ static volatile boolean pleaseStop = false;
+
+ // Large array of WeakReferences filled and accessed by tests below.
+ ArrayList<WeakReference<Integer>> weakRefs = new ArrayList<>(NREFS);
+
+ /**
+ * Complete binary tree data structure. make(n) takes O(2^n) space.
+ */
+ static class CBT {
+ CBT left;
+ CBT right;
+ CBT(CBT l, CBT r) {
+ left = l;
+ right = r;
+ }
+ static CBT make(int n) {
+ if (n == 0) {
+ return null;
+ }
+ return new CBT(make(n - 1), make(n - 1));
+ }
+ /**
+ * Check that path described by bit-vector path has the correct length.
+ */
+ void check(int n, int path) {
+ CBT current = this;
+ for (int i = 0; i < n; i++, path = path >>> 1) {
+ // Unexpectedly short paths result in NPE.
+ if ((path & 1) == 0) {
+ current = current.left;
+ } else {
+ current = current.right;
+ }
+ }
+ if (current != null) {
+ System.out.println("Complete binary tree path too long");
+ }
+ }
+ }
+
+
+ /**
+ * A finalizable object that refers to O(2^TREE_HEIGHT) otherwise unreachable memory.
+ * When finalized, it creates a new identical object, making sure that one always stays
+ * around.
+ */
+ static class ResurrectingObject {
+ CBT stuff;
+ ResurrectingObject() {
+ stuff = CBT.make(TREE_HEIGHT);
+ }
+ static ResurrectingObject a[] = new ResurrectingObject[2];
+ static int i = 0;
+ static synchronized void allocOne() {
+ a[(++i) % 2] = new ResurrectingObject();
+ // Check the previous one to make it hard to optimize anything out.
+ if (i > 1) {
+ a[(i + 1) % 2].stuff.check(TREE_HEIGHT, i /* weirdly interpreted as path */);
+ }
+ }
+ protected void finalize() {
+ stuff.check(TREE_HEIGHT, 42 /* Some path descriptor */);
+ // Allocate a new one to replace this one.
+ allocOne();
+ }
+ }
+
+ void fillWeakRefs() {
+ for (int i = 0; i < NREFS; ++i) {
+ weakRefs.add(null);
+ }
+ }
+
+ /*
+ * Return maximum observed time in nanos to dereference a WeakReference to an unreachable
+ * object. weakRefs is presumed to be pre-filled to have the correct size.
+ */
+ long timeUnreachableInner() {
+ long maxNanos = 0;
+ // Fill weakRefs with WeakReferences to unreachable integers, a batch at a time.
+ // Then time and test .get() calls on carefully sampled array entries, some of which
+ // will have been cleared.
+ for (int i = 0; i < NBATCHES; ++i) {
+ for (int j = 0; j < REFS_PER_BATCH; ++j) {
+ weakRefs.set(i * REFS_PER_BATCH + j,
+ new WeakReference(new Integer(i * REFS_PER_BATCH + j)));
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ System.out.println("Unexpected exception");
+ }
+ // Iterate over the filled-in section of weakRefs, but look only at a subset of the
+ // elements, making sure the subsets for different top-level iterations are disjoint.
+ // Otherwise the get() calls here will extend the lifetimes of the referents, and we
+ // may never see any cleared WeakReferences.
+ for (int j = (i + 1) * REFS_PER_BATCH - i - 1; j >= 0; j -= NBATCHES) {
+ WeakReference<Integer> wr = weakRefs.get(j);
+ if (wr != null) {
+ long startNanos = System.nanoTime();
+ Integer referent = wr.get();
+ long totalNanos = System.nanoTime() - startNanos;
+ if (referent == null) {
+ // Optimization to reduce max space use and scanning time.
+ weakRefs.set(j, null);
+ }
+ maxNanos = Math.max(maxNanos, totalNanos);
+ if (referent != null && referent.intValue() != j) {
+ System.out.println("Unexpected referent; expected " + j + " got "
+ + referent.intValue());
+ }
+ }
+ }
+ }
+ return maxNanos;
+ }
+
+ /*
+ * Wrapper for the above that also checks that references were reclaimed.
+ * We do this separately to make sure any stack references from the core of the
+ * test are gone. Empirically, we otherwise sometimes see the zeroth WeakReference
+ * not reclaimed.
+ */
+ long timeUnreachable() {
+ long maxNanos = timeUnreachableInner();
+ Runtime.getRuntime().gc();
+ System.runFinalization(); // Presumed to wait for reference clearing.
+ for (int i = 0; i < NREFS; ++i) {
+ if (weakRefs.get(i) != null && weakRefs.get(i).get() != null) {
+ System.out.println("WeakReference to " + i + " wasn't cleared");
+ }
+ }
+ return maxNanos;
+ }
+
+ /**
+ * Return maximum observed time in nanos to dereference a WeakReference to a reachable
+ * object. Overwrites weakRefs, which is presumed to have NREFS entries already.
+ */
+ long timeReachable() {
+ long maxNanos = 0;
+ // Similar to the above, but we use WeakReferences to otherwise reachable objects,
+ // which should thus not get cleared.
+ Integer[] strongRefs = new Integer[NREFS];
+ for (int i = 0; i < NBATCHES; ++i) {
+ for (int j = i * REFS_PER_BATCH; j < (i + 1) * NREFS / NBATCHES; ++j) {
+ Integer newObj = new Integer(j);
+ strongRefs[j] = newObj;
+ weakRefs.set(j, new WeakReference(newObj));
+ }
+ for (int j = (i + 1) * REFS_PER_BATCH - 1; j >= 0; --j) {
+ WeakReference<Integer> wr = weakRefs.get(j);
+ long startNanos = System.nanoTime();
+ Integer referent = wr.get();
+ long totalNanos = System.nanoTime() - startNanos;
+ maxNanos = Math.max(maxNanos, totalNanos);
+ if (referent == null) {
+ System.out.println("Unexpectedly cleared referent at " + j);
+ } else if (referent.intValue() != j) {
+ System.out.println("Unexpected reachable referent; expected " + j + " got "
+ + referent.intValue());
+ }
+ }
+ }
+ Reference.reachabilityFence(strongRefs);
+ return maxNanos;
+ }
+
+ void runTest() {
+ System.out.println("Starting");
+ fillWeakRefs();
+ long unreachableNanos = timeUnreachable();
+ if (PRINT_TIMES) {
+ System.out.println("Finished timeUnrechable()");
+ }
+ long reachableNanos = timeReachable();
+ String unreachableMillis =
+ String. format("%,.3f", ((double) unreachableNanos) / 1_000_000);
+ String reachableMillis =
+ String. format("%,.3f", ((double) reachableNanos) / 1_000_000);
+ if (PRINT_TIMES) {
+ System.out.println(
+ "Max time for WeakReference.get (unreachable): " + unreachableMillis);
+ System.out.println(
+ "Max time for WeakReference.get (reachable): " + reachableMillis);
+ }
+ // Only report extremely egregious pauses to avoid spurious failures.
+ if (unreachableNanos > 10_000_000_000L) {
+ System.out.println("WeakReference.get (unreachable) time unreasonably long");
+ }
+ if (reachableNanos > 10_000_000_000L) {
+ System.out.println("WeakReference.get (reachable) time unreasonably long");
+ }
+ }
+
+ /**
+ * Allocate and GC a lot, while keeping significant amounts of finalizer and
+ * SoftReference-reachable memory around.
+ */
+ static Runnable allocFinalizable = new Runnable() {
+ public void run() {
+ // Allocate and drop some finalizable objects that take a long time
+ // to mark. Designed to be hard to optimize away. Each of these objects will
+ // build a new one in its finalizer before really going away.
+ ArrayList<SoftReference<CBT>> softRefs = new ArrayList<>(N_SOFTREFS);
+ for (int i = 0; i < N_SOFTREFS; ++i) {
+ // These should not normally get reclaimed, since we shouldn't run out of
+ // memory. They do increase tracing time.
+ softRefs.add(new SoftReference(CBT.make(TREE_HEIGHT)));
+ }
+ for (int i = 0; i < N_RESURRECTING_OBJECTS; ++i) {
+ ResurrectingObject.allocOne();
+ }
+ BigInteger counter = BigInteger.ZERO;
+ for (int i = 1; !pleaseStop; ++i) {
+ // Allocate a lot of short-lived objects, using BigIntegers to minimize the chance
+ // of the allocation getting optimized out. This makes things slightly more
+ // realistic, since not all objects will be finalizer reachable.
+ for (int j = 0; j < N_PLAIN_OBJECTS / 2; ++j) {
+ counter = counter.add(BigInteger.TEN);
+ }
+ // Look at counter to reduce chance of optimizing out the allocation.
+ if (counter.longValue() % 10 != 0) {
+ System.out.println("Bad allocFinalizable counter value: " + counter);
+ }
+ // Explicitly collect here, mostly to prevent heap growth. Otherwise we get
+ // ahead of the GC and eventually block on it.
+ Runtime.getRuntime().gc();
+ if (PRINT_TIMES && i % 100 == 0) {
+ System.out.println("Collected " + i + " times");
+ }
+ }
+ // To be safe, access softRefs.
+ final CBT sample = softRefs.get(N_SOFTREFS / 2).get();
+ if (sample != null) {
+ sample.check(TREE_HEIGHT, 47 /* some path descriptor */);
+ }
+ }
+ };
+
+ public static void main(String[] args) throws Exception {
+ Main theTest = new Main();
+ Thread allocThread = null;
+ if (BACKGROUND_GC_THREAD) {
+ allocThread = new Thread(allocFinalizable);
+ allocThread.setDaemon(true); // Terminate if main thread dies.
+ allocThread.start();
+ }
+ theTest.runTest();
+ if (BACKGROUND_GC_THREAD) {
+ pleaseStop = true;
+ allocThread.join();
+ }
+ System.out.println("Finished");
+ }
+}
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 28a737d..98ea906 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -191,6 +191,12 @@
return 0;
}
+ // Ignore system classes, which may come from the JIT compiling a method
+ // in these classes.
+ if (reference_kind == JVMTI_HEAP_REFERENCE_SYSTEM_CLASS) {
+ return 0;
+ }
+
// Only check tagged objects.
if (tag == 0) {
return JVMTI_VISIT_OBJECTS;
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index 0b72612..f39bca1 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -276,7 +276,20 @@
// this before checking if we will execute JIT code in case the request
// is for an 'optimized' compilation.
jit->CompileMethod(method, self, kind, /*prejit=*/ false);
- } while (!code_cache->ContainsPc(method->GetEntryPointFromQuickCompiledCode()));
+ const void* entry_point = method->GetEntryPointFromQuickCompiledCode();
+ if (code_cache->ContainsPc(entry_point)) {
+ // If we're running baseline or not requesting optimized, we're good to go.
+ if (jit->GetJitCompiler()->IsBaselineCompiler() || kind != CompilationKind::kOptimized) {
+ break;
+ }
+ // If we're requesting optimized, check that we did get the method
+ // compiled optimized.
+ OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromEntryPoint(entry_point);
+ if (!CodeInfo::IsBaseline(method_header->GetOptimizedCodeInfoPtr())) {
+ break;
+ }
+ }
+ } while (true);
}
extern "C" JNIEXPORT void JNICALL Java_Main_ensureMethodJitCompiled(JNIEnv*, jclass, jobject meth) {
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 3e485f6..083ff80 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1358,8 +1358,13 @@
"description": "Interpreting BigInteger.add() is too slow (timeouts)"
},
{
- "tests": ["2029-contended-monitors"],
+ "tests": ["2029-contended-monitors", "2043-reference-pauses"],
"variant": "interpreter | interp-ac | gcstress | trace",
+ "description": ["Slow tests. Prone to timeouts."]
+ },
+ {
+ "tests": ["2042-reference-processing"],
+ "variant": "interpreter | interp-ac | gcstress | trace | debuggable",
"description": ["Slow test. Prone to timeouts."]
},
{
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
index d27902f..de497c5 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
@@ -144,6 +144,63 @@
}
@Test
+ public void verifyEnableUffdGcChangeTriggersCompilation() throws Exception {
+ try {
+ // Disable phenotype flag syncing. Potentially, we can set
+ // `set_sync_disabled_for_tests` to `until_reboot`, but setting it to
+ // `persistent` prevents unrelated system crashes/restarts from affecting the
+ // test. `set_sync_disabled_for_tests` is reset in the `finally` block anyway.
+ getDevice().executeShellV2Command(
+ "device_config set_sync_disabled_for_tests persistent");
+
+ // Simulate that the phenotype flag is set to the default value.
+ getDevice().executeShellV2Command(
+ "device_config put runtime_native_boot enable_uffd_gc false");
+
+ long timeMs = mTestUtils.getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+
+ // Artifacts should not be re-compiled.
+ assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
+ assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+
+ // Simulate that the phenotype flag is set to true.
+ getDevice().executeShellV2Command(
+ "device_config put runtime_native_boot enable_uffd_gc true");
+
+ timeMs = mTestUtils.getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+
+ // Artifacts should be re-compiled.
+ assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
+ assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+
+ // Run odrefresh again with the flag unchanged.
+ timeMs = mTestUtils.getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+
+ // Artifacts should not be re-compiled.
+ assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
+ assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
+
+ // Simulate that the phenotype flag is set to false.
+ getDevice().executeShellV2Command(
+ "device_config put runtime_native_boot enable_uffd_gc false");
+
+ timeMs = mTestUtils.getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+
+ // Artifacts should be re-compiled.
+ assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
+ assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
+ } finally {
+ getDevice().executeShellV2Command("device_config set_sync_disabled_for_tests none");
+ getDevice().executeShellV2Command(
+ "device_config delete runtime_native_boot enable_uffd_gc");
+ }
+ }
+
+ @Test
public void verifyNoCompilationWhenCacheIsGood() throws Exception {
mTestUtils.removeCompilationLogToAvoidBackoff();
long timeMs = mTestUtils.getCurrentTimeMs();
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index 37cc177..eae2324 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -209,6 +209,7 @@
"CtsJdwpTestCases", # times out
"art_standalone_compiler_tests", # b/230392320
"art_standalone_dex2oat_tests", # b/228881278
+ "BootImageProfileTest", # b/232012605
])
# ART gtests that do not need root access to the device.
diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt
index 4718947..ccd8db5 100644
--- a/tools/libcore_gcstress_debug_failures.txt
+++ b/tools/libcore_gcstress_debug_failures.txt
@@ -51,7 +51,13 @@
"org.apache.harmony.tests.java.lang.ref.ReferenceQueueTest#test_removeJ",
"org.apache.harmony.tests.java.lang.ProcessManagerTest#testSleep",
"org.apache.harmony.tests.java.util.TimerTest#testOverdueTaskExecutesImmediately",
- "org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet_hasNext"
+ "org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet_hasNext",
+ "test.java.lang.StrictMath.HypotTests_testAgainstTranslit_shard1",
+ "test.java.lang.StrictMath.HypotTests_testAgainstTranslit_shard2",
+ "test.java.lang.StrictMath.HypotTests_testAgainstTranslit_shard3",
+ "test.java.lang.StrictMath.HypotTests_testAgainstTranslit_shard4",
+ "test.java.math.BigDecimal",
+ "test.java.math.BigInteger_testConstructor"
]
},
{