Merge "Abstract SetField functions with Transaction check"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 0ed230c..8a8df36 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,3 +1,4 @@
[Hook Scripts]
check_generated_files_up_to_date = tools/cpp-define-generator/presubmit-check-files-up-to-date
+check_generated_tests_up_to_date = tools/test_presubmit.py
check_cpplint_on_changed_files = tools/cpplint_presubmit.py
diff --git a/build/art.go b/build/art.go
index 1b9c646..c06be0a 100644
--- a/build/art.go
+++ b/build/art.go
@@ -97,6 +97,12 @@
"-DART_STACK_OVERFLOW_GAP_x86_64=8192")
}
+ if envTrue(ctx, "ART_ENABLE_ADDRESS_SANITIZER") {
+ // Used to enable full sanitization, i.e., user poisoning, under ASAN.
+ cflags = append(cflags, "-DART_ENABLE_ADDRESS_SANITIZER=1")
+ asflags = append(asflags, "-DART_ENABLE_ADDRESS_SANITIZER=1")
+ }
+
return cflags, asflags
}
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 1c73dfa..4f1fef9 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -737,16 +737,82 @@
return IsBootClassLoaderClass(klass) && !IsInBootImage(klass);
}
+// This visitor follows the references of an instance, recursively then prune this class
+// if a type of any field is pruned.
+class ImageWriter::PruneObjectReferenceVisitor {
+ public:
+ PruneObjectReferenceVisitor(ImageWriter* image_writer,
+ bool* early_exit,
+ std::unordered_set<mirror::Object*>* visited,
+ bool* result)
+ : image_writer_(image_writer), early_exit_(early_exit), visited_(visited), result_(result) {}
+
+ ALWAYS_INLINE void VisitRootIfNonNull(
+ mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+ ALWAYS_INLINE void VisitRoot(
+ mirror::CompressedReference<mirror::Object>* root ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+ ALWAYS_INLINE void operator() (ObjPtr<mirror::Object> obj,
+ MemberOffset offset,
+ bool is_static ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ mirror::Object* ref =
+ obj->GetFieldObject<mirror::Object, kVerifyNone, kWithoutReadBarrier>(offset);
+ if (ref == nullptr || visited_->find(ref) != visited_->end()) {
+ return;
+ }
+
+ ObjPtr<mirror::Class> klass = ref->IsClass() ? ref->AsClass() : ref->GetClass();
+ if (klass == mirror::Method::StaticClass() || klass == mirror::Constructor::StaticClass()) {
+ // Prune all classes using reflection because the content they held will not be fixup.
+ *result_ = true;
+ }
+
+ // Record the object visited in case of circular reference.
+ visited_->emplace(ref);
+ if (ref->IsClass()) {
+ *result_ = *result_ ||
+ image_writer_->PruneAppImageClassInternal(ref->AsClass(), early_exit_, visited_);
+ } else {
+ *result_ = *result_ ||
+ image_writer_->PruneAppImageClassInternal(klass, early_exit_, visited_);
+ ref->VisitReferences(*this, *this);
+ }
+ // Clean up before exit for next call of this function.
+ visited_->erase(ref);
+ }
+
+ ALWAYS_INLINE void operator() (ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED,
+ ObjPtr<mirror::Reference> ref) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ operator()(ref, mirror::Reference::ReferentOffset(), /* is_static */ false);
+ }
+
+ ALWAYS_INLINE bool GetResult() const {
+ return result_;
+ }
+
+ private:
+ ImageWriter* image_writer_;
+ bool* early_exit_;
+ std::unordered_set<mirror::Object*>* visited_;
+ bool* const result_;
+};
+
+
bool ImageWriter::PruneAppImageClass(ObjPtr<mirror::Class> klass) {
bool early_exit = false;
- std::unordered_set<mirror::Class*> visited;
+ std::unordered_set<mirror::Object*> visited;
return PruneAppImageClassInternal(klass, &early_exit, &visited);
}
bool ImageWriter::PruneAppImageClassInternal(
ObjPtr<mirror::Class> klass,
bool* early_exit,
- std::unordered_set<mirror::Class*>* visited) {
+ std::unordered_set<mirror::Object*>* visited) {
DCHECK(early_exit != nullptr);
DCHECK(visited != nullptr);
DCHECK(compile_app_image_);
@@ -807,9 +873,18 @@
&my_early_exit,
visited);
} else {
- result = result || PruneAppImageClassInternal(ref->GetClass(),
+ mirror::Class* type = ref->GetClass();
+ result = result || PruneAppImageClassInternal(type,
&my_early_exit,
visited);
+ if (!result) {
+ // For non-class case, also go through all the types mentioned by it's fields'
+ // references recursively to decide whether to keep this class.
+ bool tmp = false;
+ PruneObjectReferenceVisitor visitor(this, &my_early_exit, visited, &tmp);
+ ref->VisitReferences(visitor, visitor);
+ result = result || tmp;
+ }
}
}
field_offset = MemberOffset(field_offset.Uint32Value() +
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index 5e2db7d..c42523b 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -484,7 +484,7 @@
// early_exit is true if we had a cyclic dependency anywhere down the chain.
bool PruneAppImageClassInternal(ObjPtr<mirror::Class> klass,
bool* early_exit,
- std::unordered_set<mirror::Class*>* visited)
+ std::unordered_set<mirror::Object*>* visited)
REQUIRES_SHARED(Locks::mutator_lock_);
bool IsMultiImage() const {
@@ -621,6 +621,7 @@
class PruneClassLoaderClassesVisitor;
class RegisterBootClassPathClassesVisitor;
class VisitReferencesVisitor;
+ class PruneObjectReferenceVisitor;
DISALLOW_COPY_AND_ASSIGN(ImageWriter);
};
diff --git a/compiler/jni/jni_cfi_test.cc b/compiler/jni/jni_cfi_test.cc
index 28b7290..23106e5 100644
--- a/compiler/jni/jni_cfi_test.cc
+++ b/compiler/jni/jni_cfi_test.cc
@@ -112,18 +112,28 @@
#ifdef ART_ENABLE_CODEGEN_arm
TEST_ISA(kThumb2)
#endif
+
#ifdef ART_ENABLE_CODEGEN_arm64
+// Run the tests for ARM64 only with Baker read barriers, as the
+// expected generated code contains a Marking Register refresh
+// instruction.
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
TEST_ISA(kArm64)
#endif
+#endif
+
#ifdef ART_ENABLE_CODEGEN_x86
TEST_ISA(kX86)
#endif
+
#ifdef ART_ENABLE_CODEGEN_x86_64
TEST_ISA(kX86_64)
#endif
+
#ifdef ART_ENABLE_CODEGEN_mips
TEST_ISA(kMips)
#endif
+
#ifdef ART_ENABLE_CODEGEN_mips64
TEST_ISA(kMips64)
#endif
diff --git a/compiler/jni/jni_cfi_test_expected.inc b/compiler/jni/jni_cfi_test_expected.inc
index 2710ae9..acb8a57 100644
--- a/compiler/jni/jni_cfi_test_expected.inc
+++ b/compiler/jni/jni_cfi_test_expected.inc
@@ -89,7 +89,8 @@
0xF3, 0x53, 0x46, 0xA9, 0xF5, 0x5B, 0x47, 0xA9, 0xF7, 0x63, 0x48, 0xA9,
0xF9, 0x6B, 0x49, 0xA9, 0xFB, 0x73, 0x4A, 0xA9, 0xFD, 0x7B, 0x4B, 0xA9,
0xE8, 0x27, 0x42, 0x6D, 0xEA, 0x2F, 0x43, 0x6D, 0xEC, 0x37, 0x44, 0x6D,
- 0xEE, 0x3F, 0x45, 0x6D, 0xFF, 0x03, 0x03, 0x91, 0xC0, 0x03, 0x5F, 0xD6,
+ 0xEE, 0x3F, 0x45, 0x6D, 0x74, 0x36, 0x40, 0xB9, 0xFF, 0x03, 0x03, 0x91,
+ 0xC0, 0x03, 0x5F, 0xD6,
};
static constexpr uint8_t expected_cfi_kArm64[] = {
0x44, 0x0E, 0xC0, 0x01, 0x44, 0x93, 0x18, 0x94, 0x16, 0x44, 0x95, 0x14,
@@ -101,7 +102,7 @@
0xD3, 0xD4, 0x44, 0xD5, 0xD6, 0x44, 0xD7, 0xD8, 0x44, 0xD9, 0xDA, 0x44,
0xDB, 0xDC, 0x44, 0xDD, 0xDE, 0x44, 0x06, 0x48, 0x06, 0x49, 0x44, 0x06,
0x4A, 0x06, 0x4B, 0x44, 0x06, 0x4C, 0x06, 0x4D, 0x44, 0x06, 0x4E, 0x06,
- 0x4F, 0x44, 0x0E, 0x00, 0x44, 0x0B, 0x0E, 0xC0, 0x01,
+ 0x4F, 0x48, 0x0E, 0x00, 0x44, 0x0B, 0x0E, 0xC0, 0x01,
};
// 0x00000000: sub sp, sp, #0xc0 (192)
// 0x00000004: .cfi_def_cfa_offset: 192
@@ -175,11 +176,12 @@
// 0x0000006c: ldp d14, d15, [sp, #80]
// 0x00000070: .cfi_restore_extended: r78
// 0x00000070: .cfi_restore_extended: r79
-// 0x00000070: add sp, sp, #0xc0 (192)
-// 0x00000074: .cfi_def_cfa_offset: 0
-// 0x00000074: ret
-// 0x00000078: .cfi_restore_state
-// 0x00000078: .cfi_def_cfa_offset: 192
+// 0x00000070: ldr w20, [tr, #52] ; is_gc_marking
+// 0x00000074: add sp, sp, #0xc0 (192)
+// 0x00000078: .cfi_def_cfa_offset: 0
+// 0x00000078: ret
+// 0x0000007c: .cfi_restore_state
+// 0x0000007c: .cfi_def_cfa_offset: 192
static constexpr uint8_t expected_asm_kX86[] = {
0x57, 0x56, 0x55, 0x83, 0xC4, 0xE4, 0x50, 0x89, 0x4C, 0x24, 0x34, 0xF3,
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index b34d938..6ce7d75 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -49,6 +49,9 @@
return count + 1;
}
+// TODO: In the Baker read barrier configuration, add checks to ensure
+// the Marking Register's value is correct.
+
namespace art {
enum class JniKind {
diff --git a/compiler/jni/quick/arm64/calling_convention_arm64.cc b/compiler/jni/quick/arm64/calling_convention_arm64.cc
index 33f4d77..e086455 100644
--- a/compiler/jni/quick/arm64/calling_convention_arm64.cc
+++ b/compiler/jni/quick/arm64/calling_convention_arm64.cc
@@ -108,11 +108,25 @@
// Calling convention
ManagedRegister Arm64ManagedRuntimeCallingConvention::InterproceduralScratchRegister() {
- return Arm64ManagedRegister::FromXRegister(X20); // saved on entry restored on exit
+ // X20 is safe to use as a scratch register:
+ // - with Baker read barriers, it is reserved as Marking Register,
+ // and thus does not actually need to be saved/restored; it is
+ // refreshed on exit (see Arm64JNIMacroAssembler::RemoveFrame);
+ // - in other cases, it is saved on entry (in
+ // Arm64JNIMacroAssembler::BuildFrame) and restored on exit (in
+ // Arm64JNIMacroAssembler::RemoveFrame).
+ return Arm64ManagedRegister::FromXRegister(X20);
}
ManagedRegister Arm64JniCallingConvention::InterproceduralScratchRegister() {
- return Arm64ManagedRegister::FromXRegister(X20); // saved on entry restored on exit
+ // X20 is safe to use as a scratch register:
+ // - with Baker read barriers, it is reserved as Marking Register,
+ // and thus does not actually need to be saved/restored; it is
+ // refreshed on exit (see Arm64JNIMacroAssembler::RemoveFrame);
+ // - in other cases, it is saved on entry (in
+ // Arm64JNIMacroAssembler::BuildFrame) and restored on exit (in
+ // Arm64JNIMacroAssembler::RemoveFrame).
+ return Arm64ManagedRegister::FromXRegister(X20);
}
static ManagedRegister ReturnRegisterForShorty(const char* shorty) {
diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc
index bc21607..38c732b 100644
--- a/compiler/linker/arm64/relative_patcher_arm64.cc
+++ b/compiler/linker/arm64/relative_patcher_arm64.cc
@@ -381,6 +381,21 @@
// Note: The fake dependency is unnecessary for the slow path.
}
+// Load the read barrier introspection entrypoint in register `entrypoint`.
+static void LoadReadBarrierMarkIntrospectionEntrypoint(arm64::Arm64Assembler& assembler,
+ vixl::aarch64::Register entrypoint) {
+ using vixl::aarch64::MemOperand;
+ using vixl::aarch64::ip0;
+ // Thread Register.
+ const vixl::aarch64::Register tr = vixl::aarch64::x19;
+
+ // entrypoint = Thread::Current()->pReadBarrierMarkReg16, i.e. pReadBarrierMarkIntrospection.
+ DCHECK_EQ(ip0.GetCode(), 16u);
+ const int32_t entry_point_offset =
+ Thread::ReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ip0.GetCode());
+ __ Ldr(entrypoint, MemOperand(tr, entry_point_offset));
+}
+
void Arm64RelativePatcher::CompileBakerReadBarrierThunk(arm64::Arm64Assembler& assembler,
uint32_t encoded_data) {
using namespace vixl::aarch64; // NOLINT(build/namespaces)
@@ -412,6 +427,7 @@
__ Bind(&slow_path);
MemOperand ldr_address(lr, BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET);
__ Ldr(ip0.W(), ldr_address); // Load the LDR (immediate) unsigned offset.
+ LoadReadBarrierMarkIntrospectionEntrypoint(assembler, ip1);
__ Ubfx(ip0.W(), ip0.W(), 10, 12); // Extract the offset.
__ Ldr(ip0.W(), MemOperand(base_reg, ip0, LSL, 2)); // Load the reference.
// Do not unpoison. With heap poisoning enabled, the entrypoint expects a poisoned reference.
@@ -441,6 +457,7 @@
__ Bind(&slow_path);
MemOperand ldr_address(lr, BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET);
__ Ldr(ip0.W(), ldr_address); // Load the LDR (register) unsigned offset.
+ LoadReadBarrierMarkIntrospectionEntrypoint(assembler, ip1);
__ Ubfx(ip0, ip0, 16, 6); // Extract the index register, plus 32 (bit 21 is set).
__ Bfi(ip1, ip0, 3, 6); // Insert ip0 to the entrypoint address to create
// a switch case target based on the index register.
@@ -469,6 +486,7 @@
__ Bind(¬_marked);
__ Tst(ip0.W(), Operand(ip0.W(), LSL, 1));
__ B(&forwarding_address, mi);
+ LoadReadBarrierMarkIntrospectionEntrypoint(assembler, ip1);
// Adjust the art_quick_read_barrier_mark_introspection address in IP1 to
// art_quick_read_barrier_mark_introspection_gc_roots.
__ Add(ip1, ip1, Operand(BAKER_MARK_INTROSPECTION_GC_ROOT_ENTRYPOINT_OFFSET));
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index f7465c0..7cb3166 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -473,8 +473,8 @@
return true;
}
-dchecked_vector<const char*> OatWriter::GetSourceLocations() const {
- dchecked_vector<const char*> locations;
+dchecked_vector<std::string> OatWriter::GetSourceLocations() const {
+ dchecked_vector<std::string> locations;
locations.reserve(oat_dex_files_.size());
for (const OatDexFile& oat_dex_file : oat_dex_files_) {
locations.push_back(oat_dex_file.GetLocation());
diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h
index 9217701..024a3e8 100644
--- a/compiler/oat_writer.h
+++ b/compiler/oat_writer.h
@@ -153,7 +153,7 @@
const VdexFile& vdex_file,
const char* location,
CreateTypeLookupTable create_type_lookup_table = CreateTypeLookupTable::kDefault);
- dchecked_vector<const char*> GetSourceLocations() const;
+ dchecked_vector<std::string> GetSourceLocations() const;
// Write raw dex files to the vdex file, mmap the file and open the dex files from it.
// Supporting data structures are written into the .rodata section of the oat file.
diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h
index 7bf43f7..73202b4 100644
--- a/compiler/optimizing/code_generator.h
+++ b/compiler/optimizing/code_generator.h
@@ -404,17 +404,6 @@
// accessing the String's `value` field in String intrinsics.
static uint32_t GetArrayDataOffset(HArrayGet* array_get);
- // Return the entry point offset for ReadBarrierMarkRegX, where X is `reg`.
- template <PointerSize pointer_size>
- static int32_t GetReadBarrierMarkEntryPointsOffset(size_t reg) {
- // The entry point list defines 30 ReadBarrierMarkRegX entry points.
- DCHECK_LT(reg, 30u);
- // The ReadBarrierMarkRegX entry points are ordered by increasing
- // register number in Thread::tls_Ptr_.quick_entrypoints.
- return QUICK_ENTRYPOINT_OFFSET(pointer_size, pReadBarrierMarkReg00).Int32Value()
- + static_cast<size_t>(pointer_size) * reg;
- }
-
void EmitParallelMoves(Location from1,
Location to1,
Primitive::Type type1,
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 6b9f232..92467fe 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -729,7 +729,7 @@
} else {
// Entrypoint is not already loaded, load from the thread.
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref_reg);
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref_reg);
// This runtime call does not require a stack map.
arm_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
}
@@ -8428,7 +8428,7 @@
// Thread::Current()->pReadBarrierMarkReg12, i.e. pReadBarrierMarkIntrospection.
DCHECK_EQ(IP, 12);
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(IP);
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(IP);
__ LoadFromOffset(kLoadWord, kBakerCcEntrypointRegister, TR, entry_point_offset);
Label return_address;
@@ -8469,7 +8469,7 @@
// temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(root.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(root.reg());
// Loading the entrypoint does not require a load acquire since it is only changed when
// threads are suspended or running a checkpoint.
__ LoadFromOffset(kLoadWord, temp.AsRegister<Register>(), TR, entry_point_offset);
@@ -8572,7 +8572,7 @@
// Thread::Current()->pReadBarrierMarkReg12, i.e. pReadBarrierMarkIntrospection.
DCHECK_EQ(IP, 12);
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(IP);
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(IP);
__ LoadFromOffset(kLoadWord, kBakerCcEntrypointRegister, TR, entry_point_offset);
Label return_address;
@@ -8655,7 +8655,7 @@
// Thread::Current()->pReadBarrierMarkReg16, i.e. pReadBarrierMarkIntrospection.
DCHECK_EQ(IP, 12);
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(IP);
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(IP);
__ LoadFromOffset(kLoadWord, kBakerCcEntrypointRegister, TR, entry_point_offset);
__ AddConstant(data_reg, obj, data_offset);
@@ -8736,7 +8736,7 @@
// temp2 = Thread::Current()->pReadBarrierMarkReg ## ref.reg()
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref.reg());
// Loading the entrypoint does not require a load acquire since it is only changed when
// threads are suspended or running a checkpoint.
__ LoadFromOffset(kLoadWord, temp2.AsRegister<Register>(), TR, entry_point_offset);
@@ -8805,7 +8805,7 @@
// temp3 = Thread::Current()->pReadBarrierMarkReg ## ref.reg()
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref.reg());
// Loading the entrypoint does not require a load acquire since it is only changed when
// threads are suspended or running a checkpoint.
__ LoadFromOffset(kLoadWord, temp3.AsRegister<Register>(), TR, entry_point_offset);
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 2561ed0..7e5b1a0 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -672,7 +672,9 @@
// `ref`.
//
// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked.
+// barrier marking runtime entry point to be invoked or an empty
+// location; in the latter case, the read barrier marking runtime
+// entry point will be loaded by the slow path code itself.
class ReadBarrierMarkSlowPathBaseARM64 : public SlowPathCodeARM64 {
protected:
ReadBarrierMarkSlowPathBaseARM64(HInstruction* instruction, Location ref, Location entrypoint)
@@ -716,7 +718,7 @@
} else {
// Entrypoint is not already loaded, load from the thread.
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ref_.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ref_.reg());
// This runtime call does not require a stack map.
arm64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
}
@@ -743,9 +745,10 @@
// another thread, or if another thread installed another object
// reference (different from `ref`) in `obj.field`).
//
-// If `entrypoint` is a valid location it is assumed to already be
-// holding the entrypoint. The case where the entrypoint is passed in
-// is when the decision to mark is based on whether the GC is marking.
+// Argument `entrypoint` must be a register location holding the read
+// barrier marking runtime entry point to be invoked or an empty
+// location; in the latter case, the read barrier marking runtime
+// entry point will be loaded by the slow path code itself.
class ReadBarrierMarkSlowPathARM64 : public ReadBarrierMarkSlowPathBaseARM64 {
public:
ReadBarrierMarkSlowPathARM64(HInstruction* instruction,
@@ -791,7 +794,9 @@
// reference (different from `ref`) in `obj.field`).
//
// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked.
+// barrier marking runtime entry point to be invoked or an empty
+// location; in the latter case, the read barrier marking runtime
+// entry point will be loaded by the slow path code itself.
class LoadReferenceWithBakerReadBarrierSlowPathARM64 : public ReadBarrierMarkSlowPathBaseARM64 {
public:
LoadReferenceWithBakerReadBarrierSlowPathARM64(HInstruction* instruction,
@@ -803,7 +808,7 @@
bool needs_null_check,
bool use_load_acquire,
Register temp,
- Location entrypoint)
+ Location entrypoint = Location::NoLocation())
: ReadBarrierMarkSlowPathBaseARM64(instruction, ref, entrypoint),
obj_(obj),
offset_(offset),
@@ -947,20 +952,23 @@
// another object reference (different from `ref`) in `obj.field`).
//
// Argument `entrypoint` must be a register location holding the read
-// barrier marking runtime entry point to be invoked.
+// barrier marking runtime entry point to be invoked or an empty
+// location; in the latter case, the read barrier marking runtime
+// entry point will be loaded by the slow path code itself.
class LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64
: public ReadBarrierMarkSlowPathBaseARM64 {
public:
- LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64(HInstruction* instruction,
- Location ref,
- Register obj,
- uint32_t offset,
- Location index,
- size_t scale_factor,
- bool needs_null_check,
- bool use_load_acquire,
- Register temp,
- Location entrypoint)
+ LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64(
+ HInstruction* instruction,
+ Location ref,
+ Register obj,
+ uint32_t offset,
+ Location index,
+ size_t scale_factor,
+ bool needs_null_check,
+ bool use_load_acquire,
+ Register temp,
+ Location entrypoint = Location::NoLocation())
: ReadBarrierMarkSlowPathBaseARM64(instruction, ref, entrypoint),
obj_(obj),
offset_(offset),
@@ -1655,7 +1663,7 @@
// Blocked core registers:
// lr : Runtime reserved.
// tr : Runtime reserved.
- // xSuspend : Runtime reserved. TODO: Unblock this when the runtime stops using it.
+ // mr : Runtime reserved.
// ip1 : VIXL core temp.
// ip0 : VIXL core temp.
//
@@ -5921,20 +5929,17 @@
// Baker's read barrier are used.
if (kBakerReadBarrierLinkTimeThunksEnableForGcRoots &&
!Runtime::Current()->UseJitCompilation()) {
- // Note that we do not actually check the value of `GetIsGcMarking()`
- // to decide whether to mark the loaded GC root or not. Instead, we
- // load into `temp` (actually IP1) the read barrier mark introspection
- // entrypoint. If `temp` is null, it means that `GetIsGcMarking()` is
- // false, and vice versa.
+ // Query `art::Thread::Current()->GetIsGcMarking()` (stored in
+ // the Marking Register) to decide whether we need to enter
+ // the slow path to mark the GC root.
//
// We use link-time generated thunks for the slow path. That thunk
// checks the reference and jumps to the entrypoint if needed.
//
- // temp = Thread::Current()->pReadBarrierMarkIntrospection
// lr = &return_address;
// GcRoot<mirror::Object> root = *(obj+offset); // Original reference load.
- // if (temp != nullptr) {
- // goto gc_root_thunk<root_reg>(lr)
+ // if (mr) { // Thread::Current()->GetIsGcMarking()
+ // goto gc_root_thunk<root_reg>(lr)
// }
// return_address:
@@ -5946,11 +5951,6 @@
linker::Arm64RelativePatcher::EncodeBakerReadBarrierGcRootData(root_reg.GetCode());
vixl::aarch64::Label* cbnz_label = codegen_->NewBakerReadBarrierPatch(custom_data);
- // ip1 = Thread::Current()->pReadBarrierMarkReg16, i.e. pReadBarrierMarkIntrospection.
- DCHECK_EQ(ip0.GetCode(), 16u);
- const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ip0.GetCode());
- __ Ldr(ip1, MemOperand(tr, entry_point_offset));
EmissionCheckScope guard(GetVIXLAssembler(), 3 * vixl::aarch64::kInstructionSize);
vixl::aarch64::Label return_address;
__ adr(lr, &return_address);
@@ -5961,36 +5961,26 @@
"GC root LDR must be 2 instruction (8B) before the return address label.");
__ ldr(root_reg, MemOperand(obj.X(), offset));
__ Bind(cbnz_label);
- __ cbnz(ip1, static_cast<int64_t>(0)); // Placeholder, patched at link-time.
+ __ cbnz(mr, static_cast<int64_t>(0)); // Placeholder, patched at link-time.
__ Bind(&return_address);
} else {
- // Note that we do not actually check the value of
- // `GetIsGcMarking()` to decide whether to mark the loaded GC
- // root or not. Instead, we load into `temp` the read barrier
- // mark entry point corresponding to register `root`. If `temp`
- // is null, it means that `GetIsGcMarking()` is false, and vice
- // versa.
+ // Query `art::Thread::Current()->GetIsGcMarking()` (stored in
+ // the Marking Register) to decide whether we need to enter
+ // the slow path to mark the GC root.
//
- // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
// GcRoot<mirror::Object> root = *(obj+offset); // Original reference load.
- // if (temp != nullptr) { // <=> Thread::Current()->GetIsGcMarking()
+ // if (mr) { // Thread::Current()->GetIsGcMarking()
// // Slow path.
- // root = temp(root); // root = ReadBarrier::Mark(root); // Runtime entry point call.
+ // entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
+ // root = entrypoint(root); // root = ReadBarrier::Mark(root); // Entry point call.
// }
- // Slow path marking the GC root `root`. The entrypoint will already be loaded in `temp`.
- Register temp = lr;
- SlowPathCodeARM64* slow_path = new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathARM64(
- instruction, root, /* entrypoint */ LocationFrom(temp));
+ // Slow path marking the GC root `root`. The entrypoint will
+ // be loaded by the slow path code.
+ SlowPathCodeARM64* slow_path =
+ new (GetGraph()->GetArena()) ReadBarrierMarkSlowPathARM64(instruction, root);
codegen_->AddSlowPath(slow_path);
- // temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
- const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(root.reg());
- // Loading the entrypoint does not require a load acquire since it is only changed when
- // threads are suspended or running a checkpoint.
- __ Ldr(temp, MemOperand(tr, entry_point_offset));
-
// /* GcRoot<mirror::Object> */ root = *(obj + offset)
if (fixup_label == nullptr) {
__ Ldr(root_reg, MemOperand(obj, offset));
@@ -6005,9 +5995,7 @@
"art::mirror::CompressedReference<mirror::Object> and int32_t "
"have different sizes.");
- // The entrypoint is null when the GC is not marking, this prevents one load compared to
- // checking GetIsGcMarking.
- __ Cbnz(temp, slow_path->GetEntryLabel());
+ __ Cbnz(mr, slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel());
}
} else {
@@ -6048,20 +6036,19 @@
if (kBakerReadBarrierLinkTimeThunksEnableForFields &&
!use_load_acquire &&
!Runtime::Current()->UseJitCompilation()) {
- // Note that we do not actually check the value of `GetIsGcMarking()`
- // to decide whether to mark the loaded reference or not. Instead, we
- // load into `temp` (actually IP1) the read barrier mark introspection
- // entrypoint. If `temp` is null, it means that `GetIsGcMarking()` is
- // false, and vice versa.
+ // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
+ // Marking Register) to decide whether we need to enter the slow
+ // path to mark the reference. Then, in the slow path, check the
+ // gray bit in the lock word of the reference's holder (`obj`) to
+ // decide whether to mark `ref` or not.
//
// We use link-time generated thunks for the slow path. That thunk checks
// the holder and jumps to the entrypoint if needed. If the holder is not
// gray, it creates a fake dependency and returns to the LDR instruction.
//
- // temp = Thread::Current()->pReadBarrierMarkIntrospection
// lr = &gray_return_address;
- // if (temp != nullptr) {
- // goto field_thunk<holder_reg, base_reg>(lr)
+ // if (mr) { // Thread::Current()->GetIsGcMarking()
+ // goto field_thunk<holder_reg, base_reg>(lr)
// }
// not_gray_return_address:
// // Original reference load. If the offset is too large to fit
@@ -6087,17 +6074,12 @@
obj.GetCode());
vixl::aarch64::Label* cbnz_label = NewBakerReadBarrierPatch(custom_data);
- // ip1 = Thread::Current()->pReadBarrierMarkReg16, i.e. pReadBarrierMarkIntrospection.
- DCHECK_EQ(ip0.GetCode(), 16u);
- const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ip0.GetCode());
- __ Ldr(ip1, MemOperand(tr, entry_point_offset));
EmissionCheckScope guard(GetVIXLAssembler(),
(kPoisonHeapReferences ? 4u : 3u) * vixl::aarch64::kInstructionSize);
vixl::aarch64::Label return_address;
__ adr(lr, &return_address);
__ Bind(cbnz_label);
- __ cbnz(ip1, static_cast<int64_t>(0)); // Placeholder, patched at link-time.
+ __ cbnz(mr, static_cast<int64_t>(0)); // Placeholder, patched at link-time.
static_assert(BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4),
"Field LDR must be 1 instruction (4B) before the return address label; "
" 2 instructions (8B) for heap poisoning.");
@@ -6143,20 +6125,19 @@
if (kBakerReadBarrierLinkTimeThunksEnableForArrays &&
!Runtime::Current()->UseJitCompilation()) {
- // Note that we do not actually check the value of `GetIsGcMarking()`
- // to decide whether to mark the loaded reference or not. Instead, we
- // load into `temp` (actually IP1) the read barrier mark introspection
- // entrypoint. If `temp` is null, it means that `GetIsGcMarking()` is
- // false, and vice versa.
+ // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
+ // Marking Register) to decide whether we need to enter the slow
+ // path to mark the reference. Then, in the slow path, check the
+ // gray bit in the lock word of the reference's holder (`obj`) to
+ // decide whether to mark `ref` or not.
//
// We use link-time generated thunks for the slow path. That thunk checks
// the holder and jumps to the entrypoint if needed. If the holder is not
// gray, it creates a fake dependency and returns to the LDR instruction.
//
- // temp = Thread::Current()->pReadBarrierMarkIntrospection
// lr = &gray_return_address;
- // if (temp != nullptr) {
- // goto field_thunk<holder_reg, base_reg>(lr)
+ // if (mr) { // Thread::Current()->GetIsGcMarking()
+ // goto array_thunk<base_reg>(lr)
// }
// not_gray_return_address:
// // Original reference load. If the offset is too large to fit
@@ -6176,18 +6157,13 @@
linker::Arm64RelativePatcher::EncodeBakerReadBarrierArrayData(temp.GetCode());
vixl::aarch64::Label* cbnz_label = NewBakerReadBarrierPatch(custom_data);
- // ip1 = Thread::Current()->pReadBarrierMarkReg16, i.e. pReadBarrierMarkIntrospection.
- DCHECK_EQ(ip0.GetCode(), 16u);
- const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ip0.GetCode());
- __ Ldr(ip1, MemOperand(tr, entry_point_offset));
__ Add(temp.X(), obj.X(), Operand(data_offset));
EmissionCheckScope guard(GetVIXLAssembler(),
(kPoisonHeapReferences ? 4u : 3u) * vixl::aarch64::kInstructionSize);
vixl::aarch64::Label return_address;
__ adr(lr, &return_address);
__ Bind(cbnz_label);
- __ cbnz(ip1, static_cast<int64_t>(0)); // Placeholder, patched at link-time.
+ __ cbnz(mr, static_cast<int64_t>(0)); // Placeholder, patched at link-time.
static_assert(BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET == (kPoisonHeapReferences ? -8 : -4),
"Array LDR must be 1 instruction (4B) before the return address label; "
" 2 instructions (8B) for heap poisoning.");
@@ -6231,35 +6207,28 @@
// `instruction->IsArrayGet()` => `!use_load_acquire`.
DCHECK(!instruction->IsArrayGet() || !use_load_acquire);
- // Query `art::Thread::Current()->GetIsGcMarking()` to decide
- // whether we need to enter the slow path to mark the reference.
- // Then, in the slow path, check the gray bit in the lock word of
- // the reference's holder (`obj`) to decide whether to mark `ref` or
- // not.
+ // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
+ // Marking Register) to decide whether we need to enter the slow
+ // path to mark the reference. Then, in the slow path, check the
+ // gray bit in the lock word of the reference's holder (`obj`) to
+ // decide whether to mark `ref` or not.
//
- // Note that we do not actually check the value of `GetIsGcMarking()`;
- // instead, we load into `temp2` the read barrier mark entry point
- // corresponding to register `ref`. If `temp2` is null, it means
- // that `GetIsGcMarking()` is false, and vice versa.
- //
- // temp2 = Thread::Current()->pReadBarrierMarkReg ## root.reg()
- // if (temp2 != nullptr) { // <=> Thread::Current()->GetIsGcMarking()
+ // if (mr) { // Thread::Current()->GetIsGcMarking()
// // Slow path.
// uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
// lfence; // Load fence or artificial data dependency to prevent load-load reordering
// HeapReference<mirror::Object> ref = *src; // Original reference load.
// bool is_gray = (rb_state == ReadBarrier::GrayState());
// if (is_gray) {
- // ref = temp2(ref); // ref = ReadBarrier::Mark(ref); // Runtime entry point call.
+ // entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
+ // ref = entrypoint(ref); // ref = ReadBarrier::Mark(ref); // Runtime entry point call.
// }
// } else {
// HeapReference<mirror::Object> ref = *src; // Original reference load.
// }
// Slow path marking the object `ref` when the GC is marking. The
- // entrypoint will already be loaded in `temp2`.
- Register temp2 = lr;
- Location temp2_loc = LocationFrom(temp2);
+ // entrypoint will be loaded by the slow path code.
SlowPathCodeARM64* slow_path =
new (GetGraph()->GetArena()) LoadReferenceWithBakerReadBarrierSlowPathARM64(
instruction,
@@ -6270,19 +6239,10 @@
scale_factor,
needs_null_check,
use_load_acquire,
- temp,
- /* entrypoint */ temp2_loc);
+ temp);
AddSlowPath(slow_path);
- // temp2 = Thread::Current()->pReadBarrierMarkReg ## ref.reg()
- const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ref.reg());
- // Loading the entrypoint does not require a load acquire since it is only changed when
- // threads are suspended or running a checkpoint.
- __ Ldr(temp2, MemOperand(tr, entry_point_offset));
- // The entrypoint is null when the GC is not marking, this prevents one load compared to
- // checking GetIsGcMarking.
- __ Cbnz(temp2, slow_path->GetEntryLabel());
+ __ Cbnz(mr, slow_path->GetEntryLabel());
// Fast path: the GC is not marking: just load the reference.
GenerateRawReferenceLoad(
instruction, ref, obj, offset, index, scale_factor, needs_null_check, use_load_acquire);
@@ -6303,19 +6263,14 @@
// `instruction->IsArrayGet()` => `!use_load_acquire`.
DCHECK(!instruction->IsArrayGet() || !use_load_acquire);
- // Query `art::Thread::Current()->GetIsGcMarking()` to decide
- // whether we need to enter the slow path to update the reference
- // field within `obj`. Then, in the slow path, check the gray bit
- // in the lock word of the reference's holder (`obj`) to decide
- // whether to mark `ref` and update the field or not.
+ // Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
+ // Marking Register) to decide whether we need to enter the slow
+ // path to update the reference field within `obj`. Then, in the
+ // slow path, check the gray bit in the lock word of the reference's
+ // holder (`obj`) to decide whether to mark `ref` and update the
+ // field or not.
//
- // Note that we do not actually check the value of `GetIsGcMarking()`;
- // instead, we load into `temp2` the read barrier mark entry point
- // corresponding to register `ref`. If `temp2` is null, it means
- // that `GetIsGcMarking()` is false, and vice versa.
- //
- // temp2 = Thread::Current()->pReadBarrierMarkReg ## root.reg()
- // if (temp2 != nullptr) { // <=> Thread::Current()->GetIsGcMarking()
+ // if (mr) { // Thread::Current()->GetIsGcMarking()
// // Slow path.
// uint32_t rb_state = Lockword(obj->monitor_).ReadBarrierState();
// lfence; // Load fence or artificial data dependency to prevent load-load reordering
@@ -6323,15 +6278,14 @@
// bool is_gray = (rb_state == ReadBarrier::GrayState());
// if (is_gray) {
// old_ref = ref;
- // ref = temp2(ref); // ref = ReadBarrier::Mark(ref); // Runtime entry point call.
+ // entrypoint = Thread::Current()->pReadBarrierMarkReg ## root.reg()
+ // ref = entrypoint(ref); // ref = ReadBarrier::Mark(ref); // Runtime entry point call.
// compareAndSwapObject(obj, field_offset, old_ref, ref);
// }
// }
// Slow path updating the object reference at address `obj + field_offset`
- // when the GC is marking. The entrypoint will already be loaded in `temp2`.
- Register temp2 = lr;
- Location temp2_loc = LocationFrom(temp2);
+ // when the GC is marking. The entrypoint will be loaded by the slow path code.
SlowPathCodeARM64* slow_path =
new (GetGraph()->GetArena()) LoadReferenceWithBakerReadBarrierAndUpdateFieldSlowPathARM64(
instruction,
@@ -6342,19 +6296,10 @@
/* scale_factor */ 0u /* "times 1" */,
needs_null_check,
use_load_acquire,
- temp,
- /* entrypoint */ temp2_loc);
+ temp);
AddSlowPath(slow_path);
- // temp2 = Thread::Current()->pReadBarrierMarkReg ## ref.reg()
- const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(ref.reg());
- // Loading the entrypoint does not require a load acquire since it is only changed when
- // threads are suspended or running a checkpoint.
- __ Ldr(temp2, MemOperand(tr, entry_point_offset));
- // The entrypoint is null when the GC is not marking, this prevents one load compared to
- // checking GetIsGcMarking.
- __ Cbnz(temp2, slow_path->GetEntryLabel());
+ __ Cbnz(mr, slow_path->GetEntryLabel());
// Fast path: the GC is not marking: nothing to do (the field is
// up-to-date, and we don't need to load the reference).
__ Bind(slow_path->GetExitLabel());
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index d9c49d1..584eead 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -70,21 +70,32 @@
};
static constexpr size_t kParameterFPRegistersLength = arraysize(kParameterFPRegisters);
-// Thread Register
+// Thread Register.
const vixl::aarch64::Register tr = vixl::aarch64::x19;
+// Marking Register.
+const vixl::aarch64::Register mr = vixl::aarch64::x20;
// Method register on invoke.
static const vixl::aarch64::Register kArtMethodRegister = vixl::aarch64::x0;
const vixl::aarch64::CPURegList vixl_reserved_core_registers(vixl::aarch64::ip0,
vixl::aarch64::ip1);
const vixl::aarch64::CPURegList vixl_reserved_fp_registers(vixl::aarch64::d31);
-const vixl::aarch64::CPURegList runtime_reserved_core_registers(tr, vixl::aarch64::lr);
+const vixl::aarch64::CPURegList runtime_reserved_core_registers =
+ vixl::aarch64::CPURegList(
+ tr,
+ // Reserve X20 as Marking Register when emitting Baker read barriers.
+ ((kEmitCompilerReadBarrier && kUseBakerReadBarrier) ? mr : vixl::aarch64::NoCPUReg),
+ vixl::aarch64::lr);
-// Callee-saved registers AAPCS64 (without x19 - Thread Register)
-const vixl::aarch64::CPURegList callee_saved_core_registers(vixl::aarch64::CPURegister::kRegister,
- vixl::aarch64::kXRegSize,
- vixl::aarch64::x20.GetCode(),
- vixl::aarch64::x30.GetCode());
+// Callee-save registers AAPCS64, without x19 (Thread Register) (nor
+// x20 (Marking Register) when emitting Baker read barriers).
+const vixl::aarch64::CPURegList callee_saved_core_registers(
+ vixl::aarch64::CPURegister::kRegister,
+ vixl::aarch64::kXRegSize,
+ ((kEmitCompilerReadBarrier && kUseBakerReadBarrier)
+ ? vixl::aarch64::x21.GetCode()
+ : vixl::aarch64::x20.GetCode()),
+ vixl::aarch64::x30.GetCode());
const vixl::aarch64::CPURegList callee_saved_fp_registers(vixl::aarch64::CPURegister::kFPRegister,
vixl::aarch64::kDRegSize,
vixl::aarch64::d8.GetCode(),
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 9a2402b..7334678 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -786,7 +786,7 @@
} else {
// Entrypoint is not already loaded, load from the thread.
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref_reg.GetCode());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref_reg.GetCode());
// This runtime call does not require a stack map.
arm_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
}
@@ -8559,7 +8559,7 @@
// Thread::Current()->pReadBarrierMarkReg12, i.e. pReadBarrierMarkIntrospection.
DCHECK_EQ(ip.GetCode(), 12u);
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ip.GetCode());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ip.GetCode());
__ Ldr(kBakerCcEntrypointRegister, MemOperand(tr, entry_point_offset));
vixl::EmissionCheckScope guard(GetVIXLAssembler(),
@@ -8601,7 +8601,7 @@
// temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(root.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(root.reg());
// Loading the entrypoint does not require a load acquire since it is only changed when
// threads are suspended or running a checkpoint.
GetAssembler()->LoadFromOffset(kLoadWord, RegisterFrom(temp), tr, entry_point_offset);
@@ -8705,7 +8705,7 @@
// Thread::Current()->pReadBarrierMarkReg12, i.e. pReadBarrierMarkIntrospection.
DCHECK_EQ(ip.GetCode(), 12u);
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ip.GetCode());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ip.GetCode());
__ Ldr(kBakerCcEntrypointRegister, MemOperand(tr, entry_point_offset));
vixl::EmissionCheckScope guard(
@@ -8797,7 +8797,7 @@
// Thread::Current()->pReadBarrierMarkReg16, i.e. pReadBarrierMarkIntrospection.
DCHECK_EQ(ip.GetCode(), 12u);
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ip.GetCode());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ip.GetCode());
__ Ldr(kBakerCcEntrypointRegister, MemOperand(tr, entry_point_offset));
__ Add(data_reg, obj, Operand(data_offset));
@@ -8883,7 +8883,7 @@
// temp2 = Thread::Current()->pReadBarrierMarkReg ## ref.reg()
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref.reg());
// Loading the entrypoint does not require a load acquire since it is only changed when
// threads are suspended or running a checkpoint.
GetAssembler()->LoadFromOffset(kLoadWord, RegisterFrom(temp2), tr, entry_point_offset);
@@ -8951,7 +8951,7 @@
// temp3 = Thread::Current()->pReadBarrierMarkReg ## ref.reg()
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(ref.reg());
// Loading the entrypoint does not require a load acquire since it is only changed when
// threads are suspended or running a checkpoint.
GetAssembler()->LoadFromOffset(kLoadWord, RegisterFrom(temp3), tr, entry_point_offset);
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index abe1d70..be8f9e9 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -656,7 +656,7 @@
__ NopIfNoReordering();
} else {
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(ref_reg - 1);
+ Thread::ReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(ref_reg - 1);
// This runtime call does not require a stack map.
mips_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset,
instruction_,
@@ -750,7 +750,7 @@
// rX <- ReadBarrierMarkRegX(rX)
//
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(ref_reg - 1);
+ Thread::ReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(ref_reg - 1);
// This runtime call does not require a stack map.
mips_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset,
instruction_,
@@ -6497,7 +6497,7 @@
// temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(root.reg() - 1);
+ Thread::ReadBarrierMarkEntryPointsOffset<kMipsPointerSize>(root.reg() - 1);
// Loading the entrypoint does not require a load acquire since it is only changed when
// threads are suspended or running a checkpoint.
__ LoadFromOffset(kLoadWord, temp.AsRegister<Register>(), TR, entry_point_offset);
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 232241c..cf6b3d5 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -606,7 +606,7 @@
__ Nop();
} else {
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(ref_reg - 1);
+ Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(ref_reg - 1);
// This runtime call does not require a stack map.
mips64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset,
instruction_,
@@ -699,7 +699,7 @@
// rX <- ReadBarrierMarkRegX(rX)
//
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(ref_reg - 1);
+ Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(ref_reg - 1);
// This runtime call does not require a stack map.
mips64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset,
instruction_,
@@ -4421,7 +4421,7 @@
// temp = Thread::Current()->pReadBarrierMarkReg ## root.reg()
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(root.reg() - 1);
+ Thread::ReadBarrierMarkEntryPointsOffset<kMips64PointerSize>(root.reg() - 1);
// Loading the entrypoint does not require a load acquire since it is only changed when
// threads are suspended or running a checkpoint.
__ LoadFromOffset(kLoadDoubleword, temp.AsRegister<GpuRegister>(), TR, entry_point_offset);
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 79fccfe..af0e646 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -509,8 +509,7 @@
//
// rX <- ReadBarrierMarkRegX(rX)
//
- int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86PointerSize>(ref_reg);
+ int32_t entry_point_offset = Thread::ReadBarrierMarkEntryPointsOffset<kX86PointerSize>(ref_reg);
// This runtime call does not require a stack map.
x86_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
__ jmp(GetExitLabel());
@@ -595,8 +594,7 @@
//
// rX <- ReadBarrierMarkRegX(rX)
//
- int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86PointerSize>(ref_reg);
+ int32_t entry_point_offset = Thread::ReadBarrierMarkEntryPointsOffset<kX86PointerSize>(ref_reg);
// This runtime call does not require a stack map.
x86_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
@@ -7153,7 +7151,7 @@
// Test the entrypoint (`Thread::Current()->pReadBarrierMarkReg ## root.reg()`).
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86PointerSize>(root.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kX86PointerSize>(root.reg());
__ fs()->cmpl(Address::Absolute(entry_point_offset), Immediate(0));
// The entrypoint is null when the GC is not marking.
__ j(kNotEqual, slow_path->GetEntryLabel());
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 57319ce..86f6d51 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -524,7 +524,7 @@
// rX <- ReadBarrierMarkRegX(rX)
//
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(ref_reg);
+ Thread::ReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(ref_reg);
// This runtime call does not require a stack map.
x86_64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
__ jmp(GetExitLabel());
@@ -615,7 +615,7 @@
// rX <- ReadBarrierMarkRegX(rX)
//
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(ref_reg);
+ Thread::ReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(ref_reg);
// This runtime call does not require a stack map.
x86_64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
@@ -6540,7 +6540,7 @@
// Test the `Thread::Current()->pReadBarrierMarkReg ## root.reg()` entrypoint.
const int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(root.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(root.reg());
__ gs()->cmpl(Address::Absolute(entry_point_offset, /* no_rip */ true), Immediate(0));
// The entrypoint is null when the GC is not marking.
__ j(kNotEqual, slow_path->GetEntryLabel());
diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc
index ae5f8d1..3795866 100644
--- a/compiler/optimizing/intrinsics_arm.cc
+++ b/compiler/optimizing/intrinsics_arm.cc
@@ -154,8 +154,7 @@
DCHECK(0 <= tmp && tmp < kNumberOfCoreRegisters) << tmp;
// TODO: Load the entrypoint once before the loop, instead of
// loading it at every iteration.
- int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(tmp);
+ int32_t entry_point_offset = Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(tmp);
// This runtime call does not require a stack map.
arm_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
__ MaybePoisonHeapReference(tmp);
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index 37d7981..aec1ec7 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -205,7 +205,7 @@
// TODO: Load the entrypoint once before the loop, instead of
// loading it at every iteration.
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(tmp_.reg());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArm64PointerSize>(tmp_.reg());
// This runtime call does not require a stack map.
codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
codegen->GetAssembler()->MaybePoisonHeapReference(tmp_reg);
diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc
index 3c9b613..ced931b 100644
--- a/compiler/optimizing/intrinsics_arm_vixl.cc
+++ b/compiler/optimizing/intrinsics_arm_vixl.cc
@@ -226,7 +226,7 @@
// TODO: Load the entrypoint once before the loop, instead of
// loading it at every iteration.
int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kArmPointerSize>(tmp.GetCode());
+ Thread::ReadBarrierMarkEntryPointsOffset<kArmPointerSize>(tmp.GetCode());
// This runtime call does not require a stack map.
arm_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
assembler->MaybePoisonHeapReference(tmp);
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index 6b4851d..a18b0cc 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -143,8 +143,7 @@
// explanations.)
DCHECK_NE(temp2, ESP);
DCHECK(0 <= temp2 && temp2 < kNumberOfCpuRegisters) << temp2;
- int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86PointerSize>(temp2);
+ int32_t entry_point_offset = Thread::ReadBarrierMarkEntryPointsOffset<kX86PointerSize>(temp2);
// This runtime call does not require a stack map.
x86_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
__ MaybePoisonHeapReference(temp2);
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index ef98b7b..5abdb1d 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -105,8 +105,7 @@
// No need to save live registers; it's taken care of by the
// entrypoint. Also, there is no need to update the stack mask,
// as this runtime call will not trigger a garbage collection.
- int32_t entry_point_offset =
- CodeGenerator::GetReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(TMP);
+ int32_t entry_point_offset = Thread::ReadBarrierMarkEntryPointsOffset<kX86_64PointerSize>(TMP);
// This runtime call does not require a stack map.
x86_64_codegen->InvokeRuntimeWithoutRecordingPcInfo(entry_point_offset, instruction_, this);
__ MaybePoisonHeapReference(CpuRegister(TMP));
diff --git a/compiler/optimizing/optimizing_cfi_test.cc b/compiler/optimizing/optimizing_cfi_test.cc
index 490e50c..f59bfb6 100644
--- a/compiler/optimizing/optimizing_cfi_test.cc
+++ b/compiler/optimizing/optimizing_cfi_test.cc
@@ -171,18 +171,31 @@
#ifdef ART_ENABLE_CODEGEN_arm
TEST_ISA(kThumb2)
#endif
+
#ifdef ART_ENABLE_CODEGEN_arm64
+// Run the tests for ARM64 only with Baker read barriers, as the
+// expected generated code saves and restore X21 and X22 (instead of
+// X20 and X21), as X20 is used as Marking Register in the Baker read
+// barrier configuration, and as such is removed from the set of
+// callee-save registers in the ARM64 code generator of the Optimizing
+// compiler.
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
TEST_ISA(kArm64)
#endif
+#endif
+
#ifdef ART_ENABLE_CODEGEN_x86
TEST_ISA(kX86)
#endif
+
#ifdef ART_ENABLE_CODEGEN_x86_64
TEST_ISA(kX86_64)
#endif
+
#ifdef ART_ENABLE_CODEGEN_mips
TEST_ISA(kMips)
#endif
+
#ifdef ART_ENABLE_CODEGEN_mips64
TEST_ISA(kMips64)
#endif
diff --git a/compiler/optimizing/optimizing_cfi_test_expected.inc b/compiler/optimizing/optimizing_cfi_test_expected.inc
index 60af2b4..abab431 100644
--- a/compiler/optimizing/optimizing_cfi_test_expected.inc
+++ b/compiler/optimizing/optimizing_cfi_test_expected.inc
@@ -31,21 +31,21 @@
// 0x00000010: .cfi_def_cfa_offset: 64
static constexpr uint8_t expected_asm_kArm64[] = {
- 0xFF, 0x03, 0x01, 0xD1, 0xF4, 0x17, 0x00, 0xF9, 0xF5, 0x7B, 0x03, 0xA9,
- 0xE8, 0xA7, 0x01, 0x6D, 0xE8, 0xA7, 0x41, 0x6D, 0xF4, 0x17, 0x40, 0xF9,
- 0xF5, 0x7B, 0x43, 0xA9, 0xFF, 0x03, 0x01, 0x91, 0xC0, 0x03, 0x5F, 0xD6,
+ 0xFF, 0x03, 0x01, 0xD1, 0xF5, 0x17, 0x00, 0xF9, 0xF6, 0x7B, 0x03, 0xA9,
+ 0xE8, 0xA7, 0x01, 0x6D, 0xE8, 0xA7, 0x41, 0x6D, 0xF5, 0x17, 0x40, 0xF9,
+ 0xF6, 0x7B, 0x43, 0xA9, 0xFF, 0x03, 0x01, 0x91, 0xC0, 0x03, 0x5F, 0xD6,
};
static constexpr uint8_t expected_cfi_kArm64[] = {
- 0x44, 0x0E, 0x40, 0x44, 0x94, 0x06, 0x44, 0x95, 0x04, 0x9E, 0x02, 0x44,
+ 0x44, 0x0E, 0x40, 0x44, 0x95, 0x06, 0x44, 0x96, 0x04, 0x9E, 0x02, 0x44,
0x05, 0x48, 0x0A, 0x05, 0x49, 0x08, 0x0A, 0x44, 0x06, 0x48, 0x06, 0x49,
- 0x44, 0xD4, 0x44, 0xD5, 0xDE, 0x44, 0x0E, 0x00, 0x44, 0x0B, 0x0E, 0x40,
+ 0x44, 0xD5, 0x44, 0xD6, 0xDE, 0x44, 0x0E, 0x00, 0x44, 0x0B, 0x0E, 0x40,
};
// 0x00000000: sub sp, sp, #0x40 (64)
// 0x00000004: .cfi_def_cfa_offset: 64
-// 0x00000004: str x20, [sp, #40]
-// 0x00000008: .cfi_offset: r20 at cfa-24
-// 0x00000008: stp x21, lr, [sp, #48]
-// 0x0000000c: .cfi_offset: r21 at cfa-16
+// 0x00000004: str x21, [sp, #40]
+// 0x00000008: .cfi_offset: r21 at cfa-24
+// 0x00000008: stp x22, lr, [sp, #48]
+// 0x0000000c: .cfi_offset: r22 at cfa-16
// 0x0000000c: .cfi_offset: r30 at cfa-8
// 0x0000000c: stp d8, d9, [sp, #24]
// 0x00000010: .cfi_offset_extended: r72 at cfa-40
@@ -54,10 +54,10 @@
// 0x00000010: ldp d8, d9, [sp, #24]
// 0x00000014: .cfi_restore_extended: r72
// 0x00000014: .cfi_restore_extended: r73
-// 0x00000014: ldr x20, [sp, #40]
-// 0x00000018: .cfi_restore: r20
-// 0x00000018: ldp x21, lr, [sp, #48]
-// 0x0000001c: .cfi_restore: r21
+// 0x00000014: ldr x21, [sp, #40]
+// 0x00000018: .cfi_restore: r21
+// 0x00000018: ldp x22, lr, [sp, #48]
+// 0x0000001c: .cfi_restore: r22
// 0x0000001c: .cfi_restore: r30
// 0x0000001c: add sp, sp, #0x40 (64)
// 0x00000020: .cfi_def_cfa_offset: 0
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.cc b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
index 9cd6884..c436fd9 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.cc
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
@@ -772,6 +772,13 @@
asm_.UnspillRegisters(core_reg_list, frame_size - core_reg_size);
asm_.UnspillRegisters(fp_reg_list, frame_size - core_reg_size - fp_reg_size);
+ if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ // Refresh Mark Register.
+ // TODO: Refresh MR only if suspend is taken.
+ ___ Ldr(reg_w(MR),
+ MemOperand(reg_x(TR), Thread::IsGcMarkingOffset<kArm64PointerSize>().Int32Value()));
+ }
+
// Decrease frame size to start of callee saved regs.
DecreaseFrameSize(frame_size);
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index ca0bae1..6257c7c 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -49,6 +49,7 @@
#include "base/timing_logger.h"
#include "base/unix_file/fd_file.h"
#include "class_linker.h"
+#include "class_loader_context.h"
#include "compiler.h"
#include "compiler_callbacks.h"
#include "debug/elf_debug_writer.h"
@@ -400,6 +401,27 @@
UsageError("");
UsageError(" --classpath-dir=<directory-path>: directory used to resolve relative class paths.");
UsageError("");
+ UsageError(" --class-loader-context=<string spec>: a string specifying the intended");
+ UsageError(" runtime loading context for the compiled dex files.");
+ UsageError(" ");
+ UsageError(" It describes how the class loader chain should be built in order to ensure");
+ UsageError(" classes are resolved during dex2aot as they would be resolved at runtime.");
+ UsageError(" This spec will be encoded in the oat file. If at runtime the dex file is");
+ UsageError(" loaded in a different context, the oat file will be rejected.");
+ UsageError(" ");
+ UsageError(" The chain is interpreted in the natural 'parent order', meaning that class");
+ UsageError(" loader 'i+1' will be the parent of class loader 'i'.");
+ UsageError(" The compilation sources will be added to the classpath of the last class");
+ UsageError(" loader. This allows the compiled dex files to be loaded at runtime in");
+ UsageError(" a class loader that contains other dex files as well (e.g. shared libraries).");
+ UsageError(" ");
+ UsageError(" Note that the compiler will be tolerant if the source dex files specified");
+ UsageError(" with --dex-file are found in the classpath. The source dex files will be");
+ UsageError(" removed from any class loader's classpath possibly resulting in empty");
+ UsageError(" class loaders.");
+ UsageError(" ");
+ UsageError(" Example: --classloader-spec=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]");
+ UsageError("");
std::cerr << "See log for usage error information\n";
exit(EXIT_FAILURE);
}
@@ -1271,6 +1293,12 @@
force_determinism_ = true;
} else if (option.starts_with("--classpath-dir=")) {
classpath_dir_ = option.substr(strlen("--classpath-dir=")).data();
+ } else if (option.starts_with("--class-loader-context=")) {
+ class_loader_context_ = ClassLoaderContext::Create(
+ option.substr(strlen("--class-loader-context=")).data());
+ if (class_loader_context_== nullptr) {
+ Usage("Option --class-loader-context has an incorrect format: %s", option.data());
+ }
} else if (!compiler_options_->ParseCompilerOption(option, Usage)) {
Usage("Unknown argument %s", option.data());
}
@@ -1542,25 +1570,45 @@
}
// Open dex files for class path.
- std::vector<std::string> class_path_locations =
- GetClassPathLocations(runtime_->GetClassPathString());
- OpenClassPathFiles(class_path_locations,
- &class_path_files_,
- &opened_oat_files_,
- runtime_->GetInstructionSet(),
- classpath_dir_);
-
- // Store the classpath we have right now.
- std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(class_path_files_);
- std::string encoded_class_path;
- if (class_path_locations.size() == 1 &&
- class_path_locations[0] == OatFile::kSpecialSharedLibrary) {
- // When passing the special shared library as the classpath, it is the only path.
- encoded_class_path = OatFile::kSpecialSharedLibrary;
- } else {
- encoded_class_path = OatFile::EncodeDexFileDependencies(class_path_files, classpath_dir_);
+ if (class_loader_context_ == nullptr) {
+ // TODO(calin): Temporary workaround while we transition to use
+ // --class-loader-context instead of --runtime-arg -cp
+ if (runtime_->GetClassPathString().empty()) {
+ class_loader_context_ = std::unique_ptr<ClassLoaderContext>(
+ new ClassLoaderContext());
+ } else {
+ std::string spec = runtime_->GetClassPathString() == OatFile::kSpecialSharedLibrary
+ ? OatFile::kSpecialSharedLibrary
+ : "PCL[" + runtime_->GetClassPathString() + "]";
+ class_loader_context_ = ClassLoaderContext::Create(spec);
+ }
}
- key_value_store_->Put(OatHeader::kClassPathKey, encoded_class_path);
+ CHECK(class_loader_context_ != nullptr);
+ DCHECK_EQ(oat_writers_.size(), 1u);
+
+ // Note: Ideally we would reject context where the source dex files are also
+ // specified in the classpath (as it doesn't make sense). However this is currently
+ // needed for non-prebuild tests and benchmarks which expects on the fly compilation.
+ // Also, for secondary dex files we do not have control on the actual classpath.
+ // Instead of aborting, remove all the source location from the context classpaths.
+ if (class_loader_context_->RemoveLocationsFromClassPaths(
+ oat_writers_[0]->GetSourceLocations())) {
+ LOG(WARNING) << "The source files to be compiled are also in the classpath.";
+ }
+
+ // We need to open the dex files before encoding the context in the oat file.
+ // (because the encoding adds the dex checksum...)
+ // TODO(calin): consider redesigning this so we don't have to open the dex files before
+ // creating the actual class loader.
+ if (!class_loader_context_->OpenDexFiles(runtime_->GetInstructionSet(), classpath_dir_)) {
+ // Do not abort if we couldn't open files from the classpath. They might be
+ // apks without dex files and right now are opening flow will fail them.
+ LOG(WARNING) << "Failed to open classpath dex files";
+ }
+
+ // Store the class loader context in the oat header.
+ key_value_store_->Put(OatHeader::kClassPathKey,
+ class_loader_context_->EncodeContextForOatFile(classpath_dir_));
}
// Now that we have finalized key_value_store_, start writing the oat file.
@@ -1656,17 +1704,7 @@
if (kSaveDexInput) {
SaveDexInput();
}
-
- // Handle and ClassLoader creation needs to come after Runtime::Create.
- ScopedObjectAccess soa(self);
-
- // Classpath: first the class-path given.
- std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(class_path_files_);
-
- // Then the dex files we'll compile. Thus we'll resolve the class-path first.
- class_path_files.insert(class_path_files.end(), dex_files_.begin(), dex_files_.end());
-
- class_loader_ = class_linker->CreatePathClassLoader(self, class_path_files);
+ class_loader_ = class_loader_context_->CreateClassLoader(dex_files_);
}
// Ensure opened dex files are writable for dex-to-dex transformations.
@@ -1721,7 +1759,12 @@
if (!no_inline_filters.empty()) {
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(class_path_files_);
+ std::vector<const DexFile*> class_path_files;
+ if (!IsBootImage()) {
+ // The class loader context is used only for apps.
+ class_path_files = class_loader_context_->FlattenOpenedDexFiles();
+ }
+
std::vector<const std::vector<const DexFile*>*> dex_file_vectors = {
&class_linker->GetBootClassPath(),
&class_path_files,
@@ -2224,8 +2267,8 @@
DCHECK(!IsBootImage());
DCHECK_EQ(oat_writers_.size(), 1u);
std::vector<std::string> dex_files_canonical_locations;
- for (const char* location : oat_writers_[0]->GetSourceLocations()) {
- dex_files_canonical_locations.push_back(DexFile::GetDexCanonicalLocation(location));
+ for (const std::string& location : oat_writers_[0]->GetSourceLocations()) {
+ dex_files_canonical_locations.push_back(DexFile::GetDexCanonicalLocation(location.c_str()));
}
std::vector<std::string> parsed;
@@ -2240,48 +2283,6 @@
return parsed;
}
- // Opens requested class path files and appends them to opened_dex_files. If the dex files have
- // been stripped, this opens them from their oat files and appends them to opened_oat_files.
- static void OpenClassPathFiles(std::vector<std::string>& class_path_locations,
- std::vector<std::unique_ptr<const DexFile>>* opened_dex_files,
- std::vector<std::unique_ptr<OatFile>>* opened_oat_files,
- InstructionSet isa,
- std::string& classpath_dir) {
- DCHECK(opened_dex_files != nullptr) << "OpenClassPathFiles dex out-param is nullptr";
- DCHECK(opened_oat_files != nullptr) << "OpenClassPathFiles oat out-param is nullptr";
- for (std::string& location : class_path_locations) {
- // Stop early if we detect the special shared library, which may be passed as the classpath
- // for dex2oat when we want to skip the shared libraries check.
- if (location == OatFile::kSpecialSharedLibrary) {
- break;
- }
- // If path is relative, append it to the provided base directory.
- if (!classpath_dir.empty() && location[0] != '/') {
- location = classpath_dir + '/' + location;
- }
- static constexpr bool kVerifyChecksum = true;
- std::string error_msg;
- if (!DexFile::Open(
- location.c_str(), location.c_str(), kVerifyChecksum, &error_msg, opened_dex_files)) {
- // If we fail to open the dex file because it's been stripped, try to open the dex file
- // from its corresponding oat file.
- OatFileAssistant oat_file_assistant(location.c_str(), isa, false);
- std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile());
- if (oat_file == nullptr) {
- LOG(WARNING) << "Failed to open dex file and associated oat file for '" << location
- << "': " << error_msg;
- } else {
- std::vector<std::unique_ptr<const DexFile>> oat_dex_files =
- oat_file_assistant.LoadDexFiles(*oat_file, location.c_str());
- opened_oat_files->push_back(std::move(oat_file));
- opened_dex_files->insert(opened_dex_files->end(),
- std::make_move_iterator(oat_dex_files.begin()),
- std::make_move_iterator(oat_dex_files.end()));
- }
- }
- }
- }
-
bool PrepareImageClasses() {
// If --image-classes was specified, calculate the full list of classes to include in the image.
if (image_classes_filename_ != nullptr) {
@@ -2737,8 +2738,8 @@
std::unique_ptr<Runtime> runtime_;
- // Ownership for the class path files.
- std::vector<std::unique_ptr<const DexFile>> class_path_files_;
+ // The spec describing how the class loader should be setup for compilation.
+ std::unique_ptr<ClassLoaderContext> class_loader_context_;
size_t thread_count_;
uint64_t start_ns_;
@@ -2792,9 +2793,9 @@
std::unique_ptr<CompilerDriver> driver_;
std::vector<std::unique_ptr<MemMap>> opened_dex_files_maps_;
- std::vector<std::unique_ptr<OatFile>> opened_oat_files_;
std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
+ // Note that this might contain pointers owned by class_loader_context_.
std::vector<const DexFile*> no_inline_from_dex_files_;
bool dump_stats_;
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index b604e8b..1505eb5 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -89,7 +89,8 @@
CompilerFilter::Filter filter,
const std::vector<std::string>& extra_args = {},
bool expect_success = true,
- bool use_fd = false) {
+ bool use_fd = false,
+ std::function<void(const OatFile&)> check_oat = [](const OatFile&) {}) {
std::string error_msg;
int status = GenerateOdexForTestWithStatus(dex_location,
odex_location,
@@ -113,6 +114,7 @@
ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
CheckFilter(filter, odex_file->GetCompilerFilter());
+ check_oat(*(odex_file.get()));
} else {
ASSERT_FALSE(success) << output_;
@@ -895,4 +897,123 @@
EXPECT_EQ(static_cast<int>(dex2oat::ReturnCode::kCreateRuntime), WEXITSTATUS(status)) << output_;
}
+class Dex2oatClassLoaderContextTest : public Dex2oatTest {
+ protected:
+ void RunTest(const char* class_loader_context,
+ const char* expected_classpath_key,
+ bool expected_success,
+ bool use_second_source = false) {
+ std::string dex_location = GetUsedDexLocation();
+ std::string odex_location = GetUsedOatLocation();
+
+ Copy(use_second_source ? GetDexSrc2() : GetDexSrc1(), dex_location);
+
+ std::string error_msg;
+ std::vector<std::string> extra_args;
+ if (class_loader_context != nullptr) {
+ extra_args.push_back(std::string("--class-loader-context=") + class_loader_context);
+ }
+ auto check_oat = [expected_classpath_key](const OatFile& oat_file) {
+ ASSERT_TRUE(expected_classpath_key != nullptr);
+ const char* classpath = oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey);
+ ASSERT_TRUE(classpath != nullptr);
+ ASSERT_STREQ(expected_classpath_key, classpath);
+ };
+
+ GenerateOdexForTest(dex_location,
+ odex_location,
+ CompilerFilter::kQuicken,
+ extra_args,
+ expected_success,
+ /*use_fd*/ false,
+ check_oat);
+ }
+
+ std::string GetUsedDexLocation() {
+ return GetScratchDir() + "/Context.jar";
+ }
+
+ std::string GetUsedOatLocation() {
+ return GetOdexDir() + "/Context.odex";
+ }
+
+ const char* kEmptyClassPathKey = "";
+};
+
+TEST_F(Dex2oatClassLoaderContextTest, InvalidContext) {
+ RunTest("Invalid[]", /*expected_classpath_key*/ nullptr, /*expected_success*/ false);
+}
+
+TEST_F(Dex2oatClassLoaderContextTest, EmptyContext) {
+ RunTest("PCL[]", kEmptyClassPathKey, /*expected_success*/ true);
+}
+
+TEST_F(Dex2oatClassLoaderContextTest, SpecialContext) {
+ RunTest(OatFile::kSpecialSharedLibrary,
+ OatFile::kSpecialSharedLibrary,
+ /*expected_success*/ true);
+}
+
+TEST_F(Dex2oatClassLoaderContextTest, ContextWithTheSourceDexFiles) {
+ std::string context = "PCL[" + GetUsedDexLocation() + "]";
+ RunTest(context.c_str(), kEmptyClassPathKey, /*expected_success*/ true);
+}
+
+TEST_F(Dex2oatClassLoaderContextTest, ContextWithOtherDexFiles) {
+ std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Nested");
+ std::string expected_classpath_key =
+ OatFile::EncodeDexFileDependencies(MakeNonOwningPointerVector(dex_files), "");
+
+ std::string context = "PCL[" + dex_files[0]->GetLocation() + "]";
+ RunTest(context.c_str(), expected_classpath_key.c_str(), true);
+}
+
+TEST_F(Dex2oatClassLoaderContextTest, ContextWithStrippedDexFiles) {
+ std::string stripped_classpath = GetScratchDir() + "/stripped_classpath.jar";
+ Copy(GetStrippedDexSrc1(), stripped_classpath);
+
+ std::string context = "PCL[" + stripped_classpath + "]";
+ // Expect an empty context because stripped dex files cannot be open.
+ RunTest(context.c_str(), /*expected_classpath_key*/ "" , /*expected_success*/ true);
+}
+
+TEST_F(Dex2oatClassLoaderContextTest, ContextWithStrippedDexFilesBackedByOdex) {
+ std::string stripped_classpath = GetScratchDir() + "/stripped_classpath.jar";
+ std::string odex_for_classpath = GetOdexDir() + "/stripped_classpath.odex";
+
+ Copy(GetDexSrc1(), stripped_classpath);
+
+ GenerateOdexForTest(stripped_classpath,
+ odex_for_classpath,
+ CompilerFilter::kQuicken,
+ {},
+ true);
+
+ // Strip the dex file
+ Copy(GetStrippedDexSrc1(), stripped_classpath);
+
+ std::string context = "PCL[" + stripped_classpath + "]";
+ std::string expected_classpath;
+ {
+ // Open the oat file to get the expected classpath.
+ OatFileAssistant oat_file_assistant(stripped_classpath.c_str(), kRuntimeISA, false);
+ std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile());
+ std::vector<std::unique_ptr<const DexFile>> oat_dex_files =
+ OatFileAssistant::LoadDexFiles(*oat_file, stripped_classpath.c_str());
+ expected_classpath = OatFile::EncodeDexFileDependencies(
+ MakeNonOwningPointerVector(oat_dex_files), "");
+ }
+
+ RunTest(context.c_str(),
+ expected_classpath.c_str(),
+ /*expected_success*/ true,
+ /*use_second_source*/ true);
+}
+
+TEST_F(Dex2oatClassLoaderContextTest, ContextWithNotExistentDexFiles) {
+ std::string context = "PCL[does_not_exists.dex]";
+ // Expect an empty context because stripped dex files cannot be open.
+ RunTest(context.c_str(), kEmptyClassPathKey, /*expected_success*/ true);
+}
+
} // namespace art
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 676efc4..b909bda 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1331,7 +1331,7 @@
// r0: type r1: component_count r2: total_size r9: Thread::Current, r3, r12: free.
// Need to preserve r0 and r1 to the slow path.
.macro ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE slowPathLabel
- and r2, r2, #OBJECT_ALIGNMENT_MASK_TOGGLED // Apply alignemnt mask
+ and r2, r2, #OBJECT_ALIGNMENT_MASK_TOGGLED // Apply alignment mask
// (addr + 7) & ~7.
// Load thread_local_pos (r3) and
diff --git a/runtime/arch/arm64/asm_support_arm64.S b/runtime/arch/arm64/asm_support_arm64.S
index bcf55e3..715fc35 100644
--- a/runtime/arch/arm64/asm_support_arm64.S
+++ b/runtime/arch/arm64/asm_support_arm64.S
@@ -33,6 +33,12 @@
#define xIP1 x17
#define wIP1 w17
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+// Marking Register, holding Thread::Current()->GetIsGcMarking().
+// Only used with the Concurrent Copying (CC) garbage
+// collector, with the Baker read barrier configuration.
+#define wMR w20
+#endif
.macro ENTRY name
.type \name, #function
@@ -55,14 +61,14 @@
END \name
.endm
-// Macros to poison (negate) the reference for heap poisoning.
+// Macro to poison (negate) the reference for heap poisoning.
.macro POISON_HEAP_REF rRef
#ifdef USE_HEAP_POISONING
neg \rRef, \rRef
#endif // USE_HEAP_POISONING
.endm
-// Macros to unpoison (negate) the reference for heap poisoning.
+// Macro to unpoison (negate) the reference for heap poisoning.
.macro UNPOISON_HEAP_REF rRef
#ifdef USE_HEAP_POISONING
neg \rRef, \rRef
diff --git a/runtime/arch/arm64/context_arm64.cc b/runtime/arch/arm64/context_arm64.cc
index 0465c1e..0f0814a 100644
--- a/runtime/arch/arm64/context_arm64.cc
+++ b/runtime/arch/arm64/context_arm64.cc
@@ -137,7 +137,9 @@
for (size_t i = 0; i < kNumberOfDRegisters; ++i) {
fprs[i] = fprs_[i] != nullptr ? *fprs_[i] : Arm64Context::kBadFprBase + i;
}
+ // Ensure the Thread Register contains the address of the current thread.
DCHECK_EQ(reinterpret_cast<uintptr_t>(Thread::Current()), gprs[TR]);
+ // The Marking Register will be updated by art_quick_do_long_jump.
art_quick_do_long_jump(gprs, fprs);
}
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 138dbf9..e097a33 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -39,6 +39,18 @@
.cfi_restore \reg
.endm
+.macro SAVE_REG_INCREASE_FRAME reg, frame_adjustment
+ str \reg, [sp, #-(\frame_adjustment)]!
+ .cfi_adjust_cfa_offset (\frame_adjustment)
+ .cfi_rel_offset \reg, 0
+.endm
+
+.macro RESTORE_REG_DECREASE_FRAME reg, frame_adjustment
+ ldr \reg, [sp], #(\frame_adjustment)
+ .cfi_restore \reg
+ .cfi_adjust_cfa_offset -(\frame_adjustment)
+.endm
+
.macro SAVE_TWO_REGS reg1, reg2, offset
stp \reg1, \reg2, [sp, #(\offset)]
.cfi_rel_offset \reg1, (\offset)
@@ -140,6 +152,9 @@
SAVE_TWO_REGS x29, xLR, 80
// Store ArtMethod* Runtime::callee_save_methods_[kSaveRefsOnly].
+ // Note: We could avoid saving X20 in the case of Baker read
+ // barriers, as it is overwritten by REFRESH_MARKING_REGISTER
+ // later; but it's not worth handling this special case.
stp xIP0, x20, [sp]
.cfi_rel_offset x20, 8
@@ -151,6 +166,9 @@
// TODO: Probably no need to restore registers preserved by aapcs64.
.macro RESTORE_SAVE_REFS_ONLY_FRAME
// Callee-saves.
+ // Note: Likewise, we could avoid restoring X20 in the case of Baker
+ // read barriers, as it is overwritten by REFRESH_MARKING_REGISTER
+ // later; but it's not worth handling this special case.
RESTORE_REG x20, 8
RESTORE_TWO_REGS x21, x22, 16
RESTORE_TWO_REGS x23, x24, 32
@@ -165,11 +183,6 @@
DECREASE_FRAME 96
.endm
-.macro RESTORE_SAVE_REFS_ONLY_FRAME_AND_RETURN
- RESTORE_SAVE_REFS_ONLY_FRAME
- ret
-.endm
-
.macro SETUP_SAVE_REFS_AND_ARGS_FRAME_INTERNAL
INCREASE_FRAME 224
@@ -192,6 +205,9 @@
SAVE_TWO_REGS x5, x6, 112
// x7, Callee-saves.
+ // Note: We could avoid saving X20 in the case of Baker read
+ // barriers, as it is overwritten by REFRESH_MARKING_REGISTER
+ // later; but it's not worth handling this special case.
SAVE_TWO_REGS x7, x20, 128
SAVE_TWO_REGS x21, x22, 144
SAVE_TWO_REGS x23, x24, 160
@@ -250,6 +266,9 @@
RESTORE_TWO_REGS x5, x6, 112
// x7, Callee-saves.
+ // Note: Likewise, we could avoid restoring X20 in the case of Baker
+ // read barriers, as it is overwritten by REFRESH_MARKING_REGISTER
+ // later; but it's not worth handling this special case.
RESTORE_TWO_REGS x7, x20, 128
RESTORE_TWO_REGS x21, x22, 144
RESTORE_TWO_REGS x23, x24, 160
@@ -358,7 +377,7 @@
ldp d29, d30, [sp, #240]
ldr d31, [sp, #256]
- // Restore core registers.
+ // Restore core registers, except x0.
RESTORE_TWO_REGS x1, x2, 272
RESTORE_TWO_REGS x3, x4, 288
RESTORE_TWO_REGS x5, x6, 304
@@ -379,10 +398,21 @@
.endm
.macro RESTORE_SAVE_EVERYTHING_FRAME
- RESTORE_REG x0, 264
+ RESTORE_REG x0, 264
RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0
.endm
+// Macro to refresh the Marking Register (W20).
+//
+// This macro must be called at the end of functions implementing
+// entrypoints that possibly (directly or indirectly) perform a
+// suspend check (before they return).
+.macro REFRESH_MARKING_REGISTER
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+ ldr wMR, [xSELF, #THREAD_IS_GC_MARKING_OFFSET]
+#endif
+.endm
+
.macro RETURN_IF_RESULT_IS_ZERO
cbnz x0, 1f // result non-zero branch over
ret // return
@@ -562,6 +592,7 @@
bl \cxx_name // (method_idx, this, Thread*, SP)
mov xIP0, x1 // save Method*->code_
RESTORE_SAVE_REFS_AND_ARGS_FRAME
+ REFRESH_MARKING_REGISTER
cbz x0, 1f // did we find the target? if not go to exception delivery
br xIP0 // tail call to target
1:
@@ -661,13 +692,15 @@
.macro INVOKE_STUB_CALL_AND_RETURN
+ REFRESH_MARKING_REGISTER
+
// load method-> METHOD_QUICK_CODE_OFFSET
ldr x9, [x0, #ART_METHOD_QUICK_CODE_OFFSET_64]
// Branch to method.
blr x9
// Restore return value address and shorty address.
- ldp x4,x5, [xFP, #16]
+ ldp x4, x5, [xFP, #16]
.cfi_restore x4
.cfi_restore x5
@@ -1046,6 +1079,7 @@
stp x3, x4, [sp, #16] // Save result and shorty addresses.
stp xFP, xLR, [sp] // Store LR & FP.
mov xSELF, x5 // Move thread pointer into SELF register.
+ REFRESH_MARKING_REGISTER
sub sp, sp, #16
str xzr, [sp] // Store null for ArtMethod* slot
@@ -1152,7 +1186,7 @@
ldp x24, x25, [x0], #-16
ldp x22, x23, [x0], #-16
ldp x20, x21, [x0], #-16
- ldp x18, x19, [x0], #-16
+ ldp x18, x19, [x0], #-16 // X18 & xSELF
ldp x16, x17, [x0], #-16
ldp x14, x15, [x0], #-16
ldp x12, x13, [x0], #-16
@@ -1163,6 +1197,8 @@
ldp x2, x3, [x0], #-16
mov sp, x1
+ REFRESH_MARKING_REGISTER
+
// Need to load PC, it's at the end (after the space for the unused XZR). Use x1.
ldr x1, [x0, #33*8]
// And the value of x0.
@@ -1213,6 +1249,7 @@
mov x1, xSELF // pass Thread::Current
bl artLockObjectFromCode // (Object* obj, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
RETURN_IF_W0_IS_ZERO_OR_DELIVER
END art_quick_lock_object
@@ -1221,6 +1258,7 @@
mov x1, xSELF // pass Thread::Current
bl artLockObjectFromCode // (Object* obj, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
RETURN_IF_W0_IS_ZERO_OR_DELIVER
END art_quick_lock_object_no_inline
@@ -1275,6 +1313,7 @@
mov x1, xSELF // pass Thread::Current
bl artUnlockObjectFromCode // (Object* obj, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
RETURN_IF_W0_IS_ZERO_OR_DELIVER
END art_quick_unlock_object
@@ -1283,6 +1322,7 @@
mov x1, xSELF // pass Thread::Current
bl artUnlockObjectFromCode // (Object* obj, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
RETURN_IF_W0_IS_ZERO_OR_DELIVER
END art_quick_unlock_object_no_inline
@@ -1356,7 +1396,7 @@
*/
.macro READ_BARRIER xDest, wDest, xObj, xTemp, wTemp, offset, number
#ifdef USE_READ_BARRIER
-#ifdef USE_BAKER_READ_BARRIER
+# ifdef USE_BAKER_READ_BARRIER
ldr \wTemp, [\xObj, #MIRROR_OBJECT_LOCK_WORD_OFFSET]
tbnz \wTemp, #LOCK_WORD_READ_BARRIER_STATE_SHIFT, .Lrb_slowpath\number
// False dependency to avoid needing load/load fence.
@@ -1364,7 +1404,7 @@
ldr \wDest, [\xObj, #\offset] // Heap reference = 32b. This also zero-extends to \xDest.
UNPOISON_HEAP_REF \wDest
b .Lrb_exit\number
-#endif
+# endif // USE_BAKER_READ_BARRIER
.Lrb_slowpath\number:
// Store registers used in art_quick_aput_obj (x0-x4, LR), stack is 16B aligned.
SAVE_TWO_REGS_INCREASE_FRAME x0, x1, 48
@@ -1471,6 +1511,7 @@
mov x1, xSELF // pass Thread::Current
bl \entrypoint // (uint32_t type_idx, Method* method, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
\return
END \name
.endm
@@ -1483,6 +1524,7 @@
mov x2, xSELF // pass Thread::Current
bl \entrypoint // (uint32_t type_idx, Method* method, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
\return
END \name
.endm
@@ -1495,6 +1537,7 @@
mov x3, xSELF // pass Thread::Current
bl \entrypoint
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
\return
END \name
.endm
@@ -1507,8 +1550,8 @@
mov x4, xSELF // pass Thread::Current
bl \entrypoint //
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
\return
- DELIVER_PENDING_EXCEPTION
END \name
.endm
@@ -1520,6 +1563,7 @@
mov x1, xSELF // pass Thread::Current
bl \entrypoint // (uint32_t type_idx, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
\return
END \name
.endm
@@ -1531,6 +1575,7 @@
mov x2, xSELF // pass Thread::Current
bl \entrypoint
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
\return
END \name
.endm
@@ -1542,6 +1587,7 @@
mov x3, xSELF // pass Thread::Current
bl \entrypoint
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
\return
END \name
.endm
@@ -1556,6 +1602,7 @@
cbz w0, 1f // If result is null, deliver the OOME.
.cfi_remember_state
RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0
+ REFRESH_MARKING_REGISTER
ret // return
.cfi_restore_state
.cfi_def_cfa_offset FRAME_SIZE_SAVE_EVERYTHING // workaround for clang bug: 31975598
@@ -1588,6 +1635,9 @@
ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_initialize_type_and_verify_access, artInitializeTypeAndVerifyAccessFromCode
ONE_ARG_SAVE_EVERYTHING_DOWNCALL art_quick_resolve_string, artResolveStringFromCode
+// Note: Functions `art{Get,Set}<Kind>{Static,Instance>FromCompiledCode` are
+// defined by macros in runtime/entrypoints/quick/quick_field_entrypoints.cc.
+
ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
@@ -1752,6 +1802,7 @@
mov x1, xSELF // pass Thread::Current
bl \cxx_name
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
END \c_name
.endm
@@ -1815,6 +1866,7 @@
mov x1, xSELF // Pass Thread::Current.
bl \entrypoint // (mirror::Class*, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
END \name
.endm
@@ -1825,7 +1877,7 @@
GENERATE_ALLOC_OBJECT_RESOLVED_TLAB art_quick_alloc_object_initialized_tlab, artAllocObjectFromCodeInitializedTLAB, /* isInitialized */ 1
.macro ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED_WITH_SIZE slowPathLabel, xClass, wClass, xCount, wCount, xTemp0, wTemp0, xTemp1, wTemp1, xTemp2, wTemp2
- and \xTemp1, \xTemp1, #OBJECT_ALIGNMENT_MASK_TOGGLED64 // Apply alignemnt mask
+ and \xTemp1, \xTemp1, #OBJECT_ALIGNMENT_MASK_TOGGLED64 // Apply alignment mask
// (addr + 7) & ~7. The mask must
// be 64 bits to keep high bits in
// case of overflow.
@@ -1887,6 +1939,7 @@
mov x2, xSELF // pass Thread::Current
bl \entrypoint
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
END \name
.endm
@@ -1937,8 +1990,8 @@
add \xTemp1, \xTemp1, #(MIRROR_WIDE_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK)
.endm
-# TODO(ngeoffray): art_quick_alloc_array_resolved_region_tlab is not used for arm64, remove
-# the entrypoint once all backends have been updated to use the size variants.
+// TODO(ngeoffray): art_quick_alloc_array_resolved_region_tlab is not used for arm64, remove
+// the entrypoint once all backends have been updated to use the size variants.
GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_UNKNOWN
GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved8_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_8
GENERATE_ALLOC_ARRAY_TLAB art_quick_alloc_array_resolved16_region_tlab, artAllocArrayFromCodeResolvedRegionTLAB, COMPUTE_ARRAY_SIZE_16
@@ -1959,6 +2012,7 @@
mov x0, xSELF
bl artTestSuspendFromCode // (Thread*)
RESTORE_SAVE_EVERYTHING_FRAME
+ REFRESH_MARKING_REGISTER
ret
END art_quick_test_suspend
@@ -1966,7 +2020,9 @@
mov x0, xSELF
SETUP_SAVE_REFS_ONLY_FRAME // save callee saves for stack crawl
bl artTestSuspendFromCode // (Thread*)
- RESTORE_SAVE_REFS_ONLY_FRAME_AND_RETURN
+ RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
+ ret
END art_quick_implicit_suspend
/*
@@ -1983,6 +2039,7 @@
ldr x2, [xSELF, THREAD_EXCEPTION_OFFSET]
cbnz x2, .Lexception_in_proxy // success if no exception is pending
RESTORE_SAVE_REFS_AND_ARGS_FRAME // Restore frame
+ REFRESH_MARKING_REGISTER
fmov d0, x0 // Store result in d0 in case it was float or double
ret // return on success
.Lexception_in_proxy:
@@ -2035,6 +2092,7 @@
mov xIP0, x0 // Remember returned code pointer in xIP0.
ldr x0, [sp, #0] // artQuickResolutionTrampoline puts called method in *SP.
RESTORE_SAVE_REFS_AND_ARGS_FRAME
+ REFRESH_MARKING_REGISTER
br xIP0
1:
RESTORE_SAVE_REFS_AND_ARGS_FRAME
@@ -2170,6 +2228,7 @@
// Tear down the callee-save frame.
RESTORE_SAVE_REFS_AND_ARGS_FRAME
+ REFRESH_MARKING_REGISTER
// store into fpr, for when it's a fpr return...
fmov d0, x0
@@ -2202,6 +2261,7 @@
bl artQuickToInterpreterBridge
RESTORE_SAVE_REFS_AND_ARGS_FRAME // TODO: no need to restore arguments in this case.
+ REFRESH_MARKING_REGISTER
fmov d0, x0
@@ -2231,6 +2291,7 @@
mov x0, x20 // Reload method reference.
RESTORE_SAVE_REFS_AND_ARGS_FRAME // Note: will restore xSELF
+ REFRESH_MARKING_REGISTER
cbz xIP0, 1f // Deliver the pending exception if method is null.
adr xLR, art_quick_instrumentation_exit
br xIP0 // Tail-call method with lr set to art_quick_instrumentation_exit.
@@ -2263,6 +2324,7 @@
.cfi_adjust_cfa_offset -16
RESTORE_SAVE_REFS_ONLY_FRAME
+ REFRESH_MARKING_REGISTER
cbz xIP0, 1f // Handle error
br xIP0 // Tail-call out.
1:
@@ -2831,6 +2893,7 @@
.Lcleanup_and_return:
DECREASE_FRAME 16
RESTORE_SAVE_REFS_AND_ARGS_FRAME
+ REFRESH_MARKING_REGISTER
RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
.section .rodata // Place handler table in read-only section away from text.
diff --git a/runtime/arch/arm64/registers_arm64.h b/runtime/arch/arm64/registers_arm64.h
index 4683fc3..d4c9192 100644
--- a/runtime/arch/arm64/registers_arm64.h
+++ b/runtime/arch/arm64/registers_arm64.h
@@ -61,6 +61,7 @@
kNumberOfXRegisters = 33,
// Aliases.
TR = X19, // ART Thread Register - Managed Runtime (Callee Saved Reg)
+ MR = X20, // ART Marking Register - Managed Runtime (Callee Saved Reg)
IP0 = X16, // Used as scratch by VIXL.
IP1 = X17, // Used as scratch by ART JNI Assembler.
FP = X29,
diff --git a/runtime/arch/quick_alloc_entrypoints.S b/runtime/arch/quick_alloc_entrypoints.S
index 2b3525b..fbfa756 100644
--- a/runtime/arch/quick_alloc_entrypoints.S
+++ b/runtime/arch/quick_alloc_entrypoints.S
@@ -53,7 +53,7 @@
.endm
// Generate the allocation entrypoints for each allocator. This is used as an alternative to
-// GNERATE_ALL_ALLOC_ENTRYPOINTS for selectively implementing allocation fast paths in
+// GENERATE_ALL_ALLOC_ENTRYPOINTS for selectively implementing allocation fast paths in
// hand-written assembly.
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(c_suffix, cxx_suffix) \
ONE_ARG_DOWNCALL art_quick_alloc_object_resolved ## c_suffix, artAllocObjectFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
diff --git a/runtime/base/arena_allocator.h b/runtime/base/arena_allocator.h
index ebde82d..a484c5c 100644
--- a/runtime/base/arena_allocator.h
+++ b/runtime/base/arena_allocator.h
@@ -336,7 +336,8 @@
auto* end = reinterpret_cast<uint8_t*>(ptr) + aligned_ptr_size;
// If we haven't allocated anything else, we can safely extend.
if (end == ptr_) {
- DCHECK(!IsRunningOnMemoryTool()); // Red zone prevents end == ptr_.
+ // Red zone prevents end == ptr_ (unless input = allocator state = null).
+ DCHECK(!IsRunningOnMemoryTool() || ptr_ == nullptr);
const size_t aligned_new_size = RoundUp(new_size, kAlignment);
const size_t size_delta = aligned_new_size - aligned_ptr_size;
// Check remain space.
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index de1fefd..00ae758 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -671,6 +671,10 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_);
+ // Visit all of the class loaders in the class linker.
+ void VisitClassLoaders(ClassLoaderVisitor* visitor) const
+ REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_);
+
struct DexCacheData {
// Construct an invalid data object.
DexCacheData()
@@ -720,9 +724,6 @@
static void DeleteClassLoader(Thread* self, const ClassLoaderData& data)
REQUIRES_SHARED(Locks::mutator_lock_);
- void VisitClassLoaders(ClassLoaderVisitor* visitor) const
- REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_);
-
void VisitClassesInternal(ClassVisitor* visitor)
REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_);
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 458e830..8d3c62f 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -166,7 +166,7 @@
}
if (kUseBakerReadBarrier && kGrayDirtyImmuneObjects) {
// Switch to read barrier mark entrypoints before we gray the objects. This is required in case
- // a mutator sees a gray bit and dispatches on the entrpoint. (b/37876887).
+ // a mutator sees a gray bit and dispatches on the entrypoint. (b/37876887).
ActivateReadBarrierEntrypoints();
// Gray dirty immune objects concurrently to reduce GC pause times. We re-process gray cards in
// the pause.
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 1bf9285..b229b6c 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1008,6 +1008,20 @@
}
}
+ if (obj->IsClass()) {
+ mirror::Class* klass = obj->AsClass<kVerifyNone, kWithoutReadBarrier>();
+ // Fixup super class before visiting instance fields which require
+ // information from their super class to calculate offsets.
+ mirror::Class* super_class = klass->GetSuperClass();
+ if (super_class != nullptr) {
+ mirror::Class* new_super_class = down_cast<mirror::Class*>(ForwardObject(super_class));
+ if (new_super_class != super_class && IsInAppImage(new_super_class)) {
+ // Recursively fix all dependencies.
+ operator()(new_super_class);
+ }
+ }
+ }
+
obj->VisitReferences</*visit native roots*/false, kVerifyNone, kWithoutReadBarrier>(
*this,
*this);
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 38edc7a..74fec48 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -241,7 +241,7 @@
}
CHECK(receiver->GetClass()->ShouldHaveEmbeddedVTable());
ArtMethod* const called_method = receiver->GetClass()->GetEmbeddedVTableEntry(
- vtable_idx, kRuntimePointerSize);
+ vtable_idx, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
if (UNLIKELY(called_method == nullptr)) {
CHECK(self->IsExceptionPending());
result->SetJ(0);
diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc
index c314f3c..c2ef724 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -386,8 +386,9 @@
Thread* self = Thread::Current();
ScopedObjectAccess soa(self);
mirror::Class* klass = mirror::String::GetJavaLangString();
- ArtMethod* method = klass->FindDeclaredDirectMethod("<init>", "(Ljava/lang/String;)V",
- kRuntimePointerSize);
+ ArtMethod* method =
+ klass->FindDeclaredDirectMethod("<init>", "(Ljava/lang/String;)V",
+ Runtime::Current()->GetClassLinker()->GetImagePointerSize());
// create instruction data for invoke-direct {v0, v1} of method with fake index
uint16_t inst_data[3] = { 0x2070, 0x0000, 0x0010 };
@@ -1335,10 +1336,16 @@
ArtMethod* throw_cons = throw_class->FindDeclaredDirectMethod(
"<init>", "(Ljava/lang/String;)V", class_linker->GetImagePointerSize());
ASSERT_TRUE(throw_cons != nullptr);
-
- Handle<mirror::Constructor> cons = hs.NewHandle(
- mirror::Constructor::CreateFromArtMethod<kRuntimePointerSize, false>(self, throw_cons));
- ASSERT_TRUE(cons != nullptr);
+ Handle<mirror::Constructor> cons;
+ if (class_linker->GetImagePointerSize() == PointerSize::k64) {
+ cons = hs.NewHandle(
+ mirror::Constructor::CreateFromArtMethod<PointerSize::k64, false>(self, throw_cons));
+ ASSERT_TRUE(cons != nullptr);
+ } else {
+ cons = hs.NewHandle(
+ mirror::Constructor::CreateFromArtMethod<PointerSize::k32, false>(self, throw_cons));
+ ASSERT_TRUE(cons != nullptr);
+ }
Handle<mirror::ObjectArray<mirror::Object>> args = hs.NewHandle(
mirror::ObjectArray<mirror::Object>::Alloc(
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index 10dddae..556fe66 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -29,6 +29,7 @@
#include "base/stl_util.h"
#include "base/systrace.h"
#include "base/time_utils.h"
+#include "class_table-inl.h"
#include "compiler_filter.h"
#include "dex_reference_collection.h"
#include "gc/collector_type.h"
@@ -121,7 +122,7 @@
}
total_ms_of_sleep_ += options_.GetSaveResolvedClassesDelayMs();
}
- FetchAndCacheResolvedClassesAndMethods();
+ FetchAndCacheResolvedClassesAndMethods(/*startup*/ true);
// Loop for the profiled methods.
while (!ShuttingDown(self)) {
@@ -210,64 +211,6 @@
}
}
-using MethodReferenceCollection = DexReferenceCollection<uint16_t, ScopedArenaAllocatorAdapter>;
-using TypeReferenceCollection = DexReferenceCollection<dex::TypeIndex,
- ScopedArenaAllocatorAdapter>;
-
-// Get resolved methods that have a profile info or more than kStartupMethodSamples samples.
-// Excludes native methods and classes in the boot image.
-class GetClassesAndMethodsVisitor : public ClassVisitor {
- public:
- GetClassesAndMethodsVisitor(MethodReferenceCollection* hot_methods,
- MethodReferenceCollection* sampled_methods,
- TypeReferenceCollection* resolved_classes,
- uint32_t hot_method_sample_threshold,
- bool profile_boot_class_path)
- : hot_methods_(hot_methods),
- sampled_methods_(sampled_methods),
- resolved_classes_(resolved_classes),
- hot_method_sample_threshold_(hot_method_sample_threshold),
- profile_boot_class_path_(profile_boot_class_path) {}
-
- virtual bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (klass->IsProxyClass() ||
- klass->IsArrayClass() ||
- klass->IsPrimitive() ||
- !klass->IsResolved() ||
- klass->IsErroneousResolved() ||
- (!profile_boot_class_path_ && klass->GetClassLoader() == nullptr)) {
- return true;
- }
- CHECK(klass->GetDexCache() != nullptr) << klass->PrettyClass();
- resolved_classes_->AddReference(&klass->GetDexFile(), klass->GetDexTypeIndex());
- for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) {
- if (!method.IsNative()) {
- DCHECK(!method.IsProxyMethod());
- const uint16_t counter = method.GetCounter();
- // Mark startup methods as hot if they have more than hot_method_sample_threshold_ samples.
- // This means they will get compiled by the compiler driver.
- if (method.GetProfilingInfo(kRuntimePointerSize) != nullptr ||
- (method.GetAccessFlags() & kAccPreviouslyWarm) != 0 ||
- counter >= hot_method_sample_threshold_) {
- hot_methods_->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
- } else if (counter != 0) {
- sampled_methods_->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
- }
- } else {
- CHECK_EQ(method.GetCounter(), 0u);
- }
- }
- return true;
- }
-
- private:
- MethodReferenceCollection* const hot_methods_;
- MethodReferenceCollection* const sampled_methods_;
- TypeReferenceCollection* const resolved_classes_;
- uint32_t hot_method_sample_threshold_;
- const bool profile_boot_class_path_;
-};
-
class ScopedDefaultPriority {
public:
explicit ScopedDefaultPriority(pthread_t thread) : thread_(thread) {
@@ -282,7 +225,140 @@
const pthread_t thread_;
};
-void ProfileSaver::FetchAndCacheResolvedClassesAndMethods() {
+// GetClassLoadersVisitor takes a snapshot of the class loaders and stores them in the out
+// class_loaders argument. Not affected by class unloading since there are no suspend points in
+// the caller.
+class GetClassLoadersVisitor : public ClassLoaderVisitor {
+ public:
+ explicit GetClassLoadersVisitor(VariableSizedHandleScope* hs,
+ std::vector<Handle<mirror::ClassLoader>>* class_loaders)
+ : hs_(hs),
+ class_loaders_(class_loaders) {}
+
+ void Visit(ObjPtr<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) OVERRIDE {
+ class_loaders_->push_back(hs_->NewHandle(class_loader));
+ }
+
+ private:
+ VariableSizedHandleScope* const hs_;
+ std::vector<Handle<mirror::ClassLoader>>* const class_loaders_;
+};
+
+// GetClassesVisitor takes a snapshot of the loaded classes that we may want to visit and stores
+// them in the out argument. Not affected by class unloading since there are no suspend points in
+// the caller.
+class GetClassesVisitor : public ClassVisitor {
+ public:
+ explicit GetClassesVisitor(bool profile_boot_class_path,
+ ScopedArenaVector<ObjPtr<mirror::Class>>* out)
+ : profile_boot_class_path_(profile_boot_class_path),
+ out_(out) {}
+
+ virtual bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (klass->IsProxyClass() ||
+ klass->IsArrayClass() ||
+ klass->IsPrimitive() ||
+ !klass->IsResolved() ||
+ klass->IsErroneousResolved() ||
+ (!profile_boot_class_path_ && klass->GetClassLoader() == nullptr)) {
+ return true;
+ }
+ out_->push_back(klass);
+ return true;
+ }
+
+ private:
+ const bool profile_boot_class_path_;
+ ScopedArenaVector<ObjPtr<mirror::Class>>* const out_;
+};
+
+using MethodReferenceCollection = DexReferenceCollection<uint16_t, ScopedArenaAllocatorAdapter>;
+using TypeReferenceCollection = DexReferenceCollection<dex::TypeIndex,
+ ScopedArenaAllocatorAdapter>;
+
+// Iterate over all of the loaded classes and visit each one. For each class, add it to the
+// resolved_classes out argument if startup is true.
+// Add methods to the hot_methods out argument if the number of samples is greater or equal to
+// hot_method_sample_threshold, add it to sampled_methods if it has at least one sample.
+static void SampleClassesAndExecutedMethods(pthread_t profiler_pthread,
+ bool profile_boot_class_path,
+ ScopedArenaAllocator* allocator,
+ uint32_t hot_method_sample_threshold,
+ bool startup,
+ TypeReferenceCollection* resolved_classes,
+ MethodReferenceCollection* hot_methods,
+ MethodReferenceCollection* sampled_methods) {
+ Thread* const self = Thread::Current();
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ // Restore profile saver thread priority during the GC critical section. This helps prevent
+ // priority inversions blocking the GC for long periods of time.
+ ScopedDefaultPriority sdp(profiler_pthread);
+ // Do ScopedGCCriticalSection before acquiring mutator lock to prevent the GC running and
+ // blocking threads during thread root flipping. Since the GC is a background thread, blocking it
+ // is not a problem.
+ ScopedObjectAccess soa(self);
+ gc::ScopedGCCriticalSection sgcs(self,
+ gc::kGcCauseProfileSaver,
+ gc::kCollectorTypeCriticalSection);
+ VariableSizedHandleScope hs(soa.Self());
+ std::vector<Handle<mirror::ClassLoader>> class_loaders;
+ if (profile_boot_class_path) {
+ // First add the boot class loader since visit classloaders doesn't visit it.
+ class_loaders.push_back(hs.NewHandle<mirror::ClassLoader>(nullptr));
+ }
+ GetClassLoadersVisitor class_loader_visitor(&hs, &class_loaders);
+ {
+ // Read the class loaders into a temporary array to prevent contention problems on the
+ // class_linker_classes_lock.
+ ScopedTrace trace2("Get class loaders");
+ ReaderMutexLock mu(soa.Self(), *Locks::classlinker_classes_lock_);
+ class_linker->VisitClassLoaders(&class_loader_visitor);
+ }
+ ScopedArenaVector<ObjPtr<mirror::Class>> classes(allocator->Adapter());
+ for (Handle<mirror::ClassLoader> class_loader : class_loaders) {
+ ClassTable* table = class_linker->ClassTableForClassLoader(class_loader.Get());
+ if (table == nullptr) {
+ // If the class loader has not loaded any classes, it may have a null table.
+ continue;
+ }
+ GetClassesVisitor get_classes_visitor(profile_boot_class_path, &classes);
+ {
+ // Collect the classes into a temporary array to prevent lock contention on the class
+ // table lock. We want to avoid blocking class loading in other threads as much as
+ // possible.
+ ScopedTrace trace3("Visiting class table");
+ table->Visit(get_classes_visitor);
+ }
+ for (ObjPtr<mirror::Class> klass : classes) {
+ if (startup) {
+ // We only record classes for the startup case. This may change in the future.
+ resolved_classes->AddReference(&klass->GetDexFile(), klass->GetDexTypeIndex());
+ }
+ // Visit all of the methods in the class to see which ones were executed.
+ for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) {
+ if (!method.IsNative()) {
+ DCHECK(!method.IsProxyMethod());
+ const uint16_t counter = method.GetCounter();
+ // Mark startup methods as hot if they have more than hot_method_sample_threshold
+ // samples. This means they will get compiled by the compiler driver.
+ if (method.GetProfilingInfo(kRuntimePointerSize) != nullptr ||
+ (method.GetAccessFlags() & kAccPreviouslyWarm) != 0 ||
+ counter >= hot_method_sample_threshold) {
+ hot_methods->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
+ } else if (counter != 0) {
+ sampled_methods->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
+ }
+ } else {
+ CHECK_EQ(method.GetCounter(), 0u);
+ }
+ }
+ }
+ classes.clear();
+ }
+}
+
+void ProfileSaver::FetchAndCacheResolvedClassesAndMethods(bool startup) {
ScopedTrace trace(__PRETTY_FUNCTION__);
const uint64_t start_time = NanoTime();
@@ -294,34 +370,25 @@
ArenaStack stack(runtime->GetArenaPool());
ScopedArenaAllocator allocator(&stack);
MethodReferenceCollection hot_methods(allocator.Adapter(), allocator.Adapter());
- MethodReferenceCollection startup_methods(allocator.Adapter(), allocator.Adapter());
+ MethodReferenceCollection sampled_methods(allocator.Adapter(), allocator.Adapter());
TypeReferenceCollection resolved_classes(allocator.Adapter(), allocator.Adapter());
const bool is_low_ram = Runtime::Current()->GetHeap()->IsLowMemoryMode();
- const size_t hot_threshold = options_.GetHotStartupMethodSamples(is_low_ram);
pthread_t profiler_pthread;
{
MutexLock mu(self, *Locks::profiler_lock_);
profiler_pthread = profiler_pthread_;
}
- {
- // Restore profile saver thread priority during the GC critical section. This helps prevent
- // priority inversions blocking the GC for long periods of time.
- ScopedDefaultPriority sdp(profiler_pthread);
- ScopedObjectAccess soa(self);
- gc::ScopedGCCriticalSection sgcs(self,
- gc::kGcCauseProfileSaver,
- gc::kCollectorTypeCriticalSection);
- {
- ScopedTrace trace2("Get hot methods");
- GetClassesAndMethodsVisitor visitor(&hot_methods,
- &startup_methods,
- &resolved_classes,
- hot_threshold,
- options_.GetProfileBootClassPath());
- runtime->GetClassLinker()->VisitClasses(&visitor);
- }
- }
-
+ const uint32_t hot_method_sample_threshold = startup ?
+ options_.GetHotStartupMethodSamples(is_low_ram) :
+ std::numeric_limits<uint32_t>::max();
+ SampleClassesAndExecutedMethods(profiler_pthread,
+ options_.GetProfileBootClassPath(),
+ &allocator,
+ hot_method_sample_threshold,
+ startup,
+ &resolved_classes,
+ &hot_methods,
+ &sampled_methods);
MutexLock mu(self, *Locks::profiler_lock_);
uint64_t total_number_of_profile_entries_cached = 0;
using Hotness = ProfileCompilationInfo::MethodHotness;
@@ -329,9 +396,12 @@
for (const auto& it : tracked_dex_base_locations_) {
std::set<DexCacheResolvedClasses> resolved_classes_for_location;
const std::string& filename = it.first;
- auto info_it = profile_cache_.Put(
- filename,
- new ProfileCompilationInfo(Runtime::Current()->GetArenaPool()));
+ auto info_it = profile_cache_.find(filename);
+ if (info_it == profile_cache_.end()) {
+ info_it = profile_cache_.Put(
+ filename,
+ new ProfileCompilationInfo(Runtime::Current()->GetArenaPool()));
+ }
ProfileCompilationInfo* cached_info = info_it->second;
const std::set<std::string>& locations = it.second;
@@ -339,18 +409,20 @@
const DexFile* const dex_file = pair.first;
if (locations.find(dex_file->GetBaseLocation()) != locations.end()) {
const MethodReferenceCollection::IndexVector& indices = pair.second;
+ uint8_t flags = Hotness::kFlagHot;
+ flags |= startup ? Hotness::kFlagStartup : Hotness::kFlagPostStartup;
cached_info->AddMethodsForDex(
- static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagStartup),
+ static_cast<Hotness::Flag>(flags),
dex_file,
indices.begin(),
indices.end());
}
}
- for (const auto& pair : startup_methods.GetMap()) {
+ for (const auto& pair : sampled_methods.GetMap()) {
const DexFile* const dex_file = pair.first;
if (locations.find(dex_file->GetBaseLocation()) != locations.end()) {
const MethodReferenceCollection::IndexVector& indices = pair.second;
- cached_info->AddMethodsForDex(Hotness::kFlagStartup,
+ cached_info->AddMethodsForDex(startup ? Hotness::kFlagStartup : Hotness::kFlagPostStartup,
dex_file,
indices.begin(),
indices.end());
@@ -375,8 +447,9 @@
max_number_of_profile_entries_cached_,
total_number_of_profile_entries_cached);
VLOG(profiler) << "Profile saver recorded " << hot_methods.NumReferences() << " hot methods and "
- << startup_methods.NumReferences() << " startup methods with threshold "
- << hot_threshold << " in " << PrettyDuration(NanoTime() - start_time);
+ << sampled_methods.NumReferences() << " sampled methods with threshold "
+ << hot_method_sample_threshold << " in "
+ << PrettyDuration(NanoTime() - start_time);
}
bool ProfileSaver::ProcessProfilingInfo(bool force_save, /*out*/uint16_t* number_of_new_methods) {
@@ -397,6 +470,10 @@
*number_of_new_methods = 0;
}
+ // We only need to do this once, not once per dex location.
+ // TODO: Figure out a way to only do it when stuff has changed? It takes 30-50ms.
+ FetchAndCacheResolvedClassesAndMethods(/*startup*/ false);
+
for (const auto& it : tracked_locations) {
if (!force_save && ShuttingDown(Thread::Current())) {
// The ProfileSaver is in shutdown mode, meaning a stop request was made and
@@ -442,6 +519,7 @@
total_number_of_skipped_writes_++;
continue;
}
+
if (number_of_new_methods != nullptr) {
*number_of_new_methods =
std::max(static_cast<uint16_t>(delta_number_of_methods),
@@ -473,11 +551,12 @@
total_number_of_failed_writes_++;
}
}
- // Trim the maps to madvise the pages used for profile info.
- // It is unlikely we will need them again in the near feature.
- Runtime::Current()->GetArenaPool()->TrimMaps();
}
+ // Trim the maps to madvise the pages used for profile info.
+ // It is unlikely we will need them again in the near feature.
+ Runtime::Current()->GetArenaPool()->TrimMaps();
+
return profile_file_saved;
}
@@ -713,16 +792,15 @@
}
}
-bool ProfileSaver::HasSeenMethod(const std::string& profile,
- const DexFile* dex_file,
- uint16_t method_idx) {
+bool ProfileSaver::HasSeenMethod(const std::string& profile, bool hot, MethodReference ref) {
MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
if (instance_ != nullptr) {
ProfileCompilationInfo info(Runtime::Current()->GetArenaPool());
if (!info.Load(profile, /*clear_if_invalid*/false)) {
return false;
}
- return info.GetMethodHotness(MethodReference(dex_file, method_idx)).IsInProfile();
+ ProfileCompilationInfo::MethodHotness hotness = info.GetMethodHotness(ref);
+ return hot ? hotness.IsHot() : hotness.IsInProfile();
}
return false;
}
diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h
index 01d72fe..ce8233b 100644
--- a/runtime/jit/profile_saver.h
+++ b/runtime/jit/profile_saver.h
@@ -19,6 +19,7 @@
#include "base/mutex.h"
#include "jit_code_cache.h"
+#include "method_reference.h"
#include "profile_compilation_info.h"
#include "profile_saver_options.h"
#include "safe_map.h"
@@ -55,10 +56,8 @@
// For testing or manual purposes (SIGUSR1).
static void ForceProcessProfiles();
- // Just for testing purpose.
- static bool HasSeenMethod(const std::string& profile,
- const DexFile* dex_file,
- uint16_t method_idx);
+ // Just for testing purposes.
+ static bool HasSeenMethod(const std::string& profile, bool hot, MethodReference ref);
private:
ProfileSaver(const ProfileSaverOptions& options,
@@ -97,7 +96,7 @@
// Fetches the current resolved classes and methods from the ClassLinker and stores them in the
// profile_cache_ for later save.
- void FetchAndCacheResolvedClassesAndMethods();
+ void FetchAndCacheResolvedClassesAndMethods(bool startup);
void DumpInfo(std::ostream& os);
diff --git a/runtime/signal_catcher.cc b/runtime/signal_catcher.cc
index 8c934d5..f0b6ee4 100644
--- a/runtime/signal_catcher.cc
+++ b/runtime/signal_catcher.cc
@@ -168,7 +168,7 @@
}
#if defined(ART_TARGET_ANDROID)
- if (!tombstoned_notify_completion(tombstone_fd)) {
+ if (use_tombstoned_stack_trace_fd_ && !tombstoned_notify_completion(tombstone_fd)) {
LOG(WARNING) << "Unable to notify tombstoned of dump completion.";
}
#endif
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 36ecd33..b01d50a 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2134,6 +2134,10 @@
ScopedObjectAccess soa(self);
// We may need to call user-supplied managed code, do this before final clean-up.
HandleUncaughtExceptions(soa);
+ Runtime* runtime = Runtime::Current();
+ if (runtime != nullptr) {
+ runtime->GetRuntimeCallbacks()->ThreadDeath(self);
+ }
RemoveFromThreadGroup(soa);
// this.nativePeer = 0;
@@ -2144,11 +2148,6 @@
jni::DecodeArtField(WellKnownClasses::java_lang_Thread_nativePeer)
->SetLong<false>(tlsPtr_.opeer, 0);
}
- Runtime* runtime = Runtime::Current();
- if (runtime != nullptr) {
- runtime->GetRuntimeCallbacks()->ThreadDeath(self);
- }
-
// Thread.join() is implemented as an Object.wait() on the Thread.lock object. Signal anyone
// who is waiting.
@@ -2862,6 +2861,7 @@
DO_THREAD_OFFSET(SelfOffset<ptr_size>(), "self")
DO_THREAD_OFFSET(StackEndOffset<ptr_size>(), "stack_end")
DO_THREAD_OFFSET(ThinLockIdOffset<ptr_size>(), "thin_lock_thread_id")
+ DO_THREAD_OFFSET(IsGcMarkingOffset<ptr_size>(), "is_gc_marking")
DO_THREAD_OFFSET(TopOfManagedStackOffset<ptr_size>(), "top_quick_frame_method")
DO_THREAD_OFFSET(TopShadowFrameOffset<ptr_size>(), "top_shadow_frame")
DO_THREAD_OFFSET(TopHandleScopeOffset<ptr_size>(), "top_handle_scope")
diff --git a/runtime/thread.h b/runtime/thread.h
index e785ddc..24d126f 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -656,6 +656,17 @@
OFFSETOF_MEMBER(tls_ptr_sized_values, jni_entrypoints) + jni_entrypoint_offset);
}
+ // Return the entry point offset integer value for ReadBarrierMarkRegX, where X is `reg`.
+ template <PointerSize pointer_size>
+ static int32_t ReadBarrierMarkEntryPointsOffset(size_t reg) {
+ // The entry point list defines 30 ReadBarrierMarkRegX entry points.
+ DCHECK_LT(reg, 30u);
+ // The ReadBarrierMarkRegX entry points are ordered by increasing
+ // register number in Thread::tls_Ptr_.quick_entrypoints.
+ return QUICK_ENTRYPOINT_OFFSET(pointer_size, pReadBarrierMarkReg00).Int32Value()
+ + static_cast<size_t>(pointer_size) * reg;
+ }
+
template<PointerSize pointer_size>
static ThreadOffset<pointer_size> SelfOffset() {
return ThreadOffsetFromTlsPtr<pointer_size>(OFFSETOF_MEMBER(tls_ptr_sized_values, self));
diff --git a/test/079-phantom/src/Bitmap.java b/test/079-phantom/src/Bitmap.java
index ff43749..0d6e2d8 100644
--- a/test/079-phantom/src/Bitmap.java
+++ b/test/079-phantom/src/Bitmap.java
@@ -17,6 +17,7 @@
import java.lang.ref.ReferenceQueue;
import java.lang.ref.PhantomReference;
import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
public class Bitmap {
String mName; /* for debugging */
@@ -76,11 +77,14 @@
PhantomWrapper phan = new PhantomWrapper(wrapper, sPhantomQueue,
nativeData);
sPhantomList.add(phan);
+ wrapper.mPhantomWrapper = phan;
return wrapper;
}
- static void freeNativeStorage(int nativeDataPtr) {
+ static void freeNativeStorage(int nativeDataPtr, CountDownLatch freeSignal) {
System.out.println("freeNativeStorage: " + nativeDataPtr);
+ // Wake up the main thread that is [or will be] blocked until this native data is freed.
+ freeSignal.countDown();
}
/*
@@ -93,6 +97,9 @@
}
public int mNativeData;
+ // The PhantomWrapper corresponding to this NativeWrapper.
+ public PhantomWrapper mPhantomWrapper;
+
/*
@Override
protected void finalize() throws Throwable {
@@ -118,6 +125,8 @@
}
public int mNativeData;
+ // This will be signaled once mNativeData has been freed.
+ public CountDownLatch mFreeSignal = new CountDownLatch(1);
}
/*
@@ -137,8 +146,7 @@
PhantomWrapper ref = (PhantomWrapper) mQueue.remove();
//System.out.println("dequeued ref " + ref.mNativeData +
// " - " + ref);
- Bitmap.freeNativeStorage(ref.mNativeData);
- //ref.clear();
+ Bitmap.freeNativeStorage(ref.mNativeData, ref.mFreeSignal);
} catch (InterruptedException ie) {
System.out.println("intr");
break;
diff --git a/test/079-phantom/src/Main.java b/test/079-phantom/src/Main.java
index daead2e..ae2c688 100644
--- a/test/079-phantom/src/Main.java
+++ b/test/079-phantom/src/Main.java
@@ -14,8 +14,11 @@
* limitations under the License.
*/
+import java.util.concurrent.CountDownLatch;
+
public class Main {
Bitmap mBitmap1, mBitmap2, mBitmap3, mBitmap4;
+ CountDownLatch mFreeSignalA, mFreeSignalB;
public static void sleep(int ms) {
try {
@@ -31,7 +34,6 @@
Main main = new Main();
main.run();
- sleep(1000);
System.out.println("done");
}
@@ -46,22 +48,30 @@
System.out.println("nulling 1");
mBitmap1 = null;
Runtime.getRuntime().gc();
- sleep(500);
+ try {
+ mFreeSignalA.await(); // Block until dataA is definitely freed.
+ } catch (InterruptedException e) {
+ System.out.println("got unexpected InterruptedException e: " + e);
+ }
System.out.println("nulling 2");
mBitmap2 = null;
Runtime.getRuntime().gc();
- sleep(500);
+ sleep(200);
System.out.println("nulling 3");
mBitmap3 = null;
Runtime.getRuntime().gc();
- sleep(500);
+ sleep(200);
System.out.println("nulling 4");
mBitmap4 = null;
Runtime.getRuntime().gc();
- sleep(500);
+ try {
+ mFreeSignalB.await(); // Block until dataB is definitely freed.
+ } catch (InterruptedException e) {
+ System.out.println("got unexpected InterruptedException e: " + e);
+ }
Bitmap.shutDown();
}
@@ -77,7 +87,10 @@
*/
public void createBitmaps() {
Bitmap.NativeWrapper dataA = Bitmap.allocNativeStorage(10, 10);
+ mFreeSignalA = dataA.mPhantomWrapper.mFreeSignal;
Bitmap.NativeWrapper dataB = Bitmap.allocNativeStorage(20, 20);
+ mFreeSignalB = dataB.mPhantomWrapper.mFreeSignal;
+
mBitmap1 = new Bitmap("one", 10, 10, dataA);
mBitmap2 = new Bitmap("two", 20, 20, dataB);
mBitmap3 = mBitmap4 = new Bitmap("three/four", 20, 20, dataB);
diff --git a/test/595-profile-saving/profile-saving.cc b/test/595-profile-saving/profile-saving.cc
index 0bdbade..b2b7ef2 100644
--- a/test/595-profile-saving/profile-saving.cc
+++ b/test/595-profile-saving/profile-saving.cc
@@ -45,8 +45,9 @@
ProfileSaver::ForceProcessProfiles();
}
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_presentInProfile(JNIEnv* env,
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_profileHasMethod(JNIEnv* env,
jclass,
+ jboolean hot,
jstring filename,
jobject method) {
ScopedUtfChars filename_chars(env, filename);
@@ -55,8 +56,9 @@
ObjPtr<mirror::Executable> exec = soa.Decode<mirror::Executable>(method);
ArtMethod* art_method = exec->GetArtMethod();
return ProfileSaver::HasSeenMethod(std::string(filename_chars.c_str()),
- art_method->GetDexFile(),
- art_method->GetDexMethodIndex());
+ hot != JNI_FALSE,
+ MethodReference(art_method->GetDexFile(),
+ art_method->GetDexMethodIndex()));
}
} // namespace
diff --git a/test/595-profile-saving/src/Main.java b/test/595-profile-saving/src/Main.java
index 18c0598..197c4e7 100644
--- a/test/595-profile-saving/src/Main.java
+++ b/test/595-profile-saving/src/Main.java
@@ -42,6 +42,14 @@
System.out.println("Class loader does not match boot class");
}
testAddMethodToProfile(file, bootMethod);
+
+ // Test a sampled method that is only warm and not hot.
+ Method reflectMethod = Main.class.getDeclaredMethod("testReflectionInvoke");
+ reflectMethod.invoke(null);
+ testSampledMethodInProfile(file, reflectMethod);
+ if (staticObj == null) {
+ throw new AssertionError("Object was not set");
+ }
} finally {
if (file != null) {
file.delete();
@@ -49,23 +57,38 @@
}
}
+ static Object staticObj = null;
+
+ static void testReflectionInvoke() {
+ staticObj = new Object();
+ }
+
static void testAddMethodToProfile(File file, Method m) {
// Make sure we have a profile info for this method without the need to loop.
ensureProfilingInfo(m);
- // Make sure the profile gets saved.
+ // Make sure the profile gets processed.
ensureProfileProcessing();
// Verify that the profile was saved and contains the method.
- if (!presentInProfile(file.getPath(), m)) {
+ if (!profileHasMethod(true, file.getPath(), m)) {
throw new RuntimeException("Method with index " + m + " not in the profile");
}
}
+ static void testSampledMethodInProfile(File file, Method m) {
+ // Make sure the profile gets processed.
+ ensureProfileProcessing();
+ // Verify that the profile was saved and contains the method.
+ if (!profileHasMethod(false, file.getPath(), m)) {
+ throw new RuntimeException("Method with index " + m + " not sampled in the profile");
+ }
+ }
+
// Ensure a method has a profiling info.
public static native void ensureProfilingInfo(Method method);
// Ensures the profile saver does its usual processing.
public static native void ensureProfileProcessing();
- // Checks if the profiles saver knows about the method.
- public static native boolean presentInProfile(String profile, Method method);
+ // Checks if the profile saver has the method as a warm/sampled method.
+ public static native boolean profileHasMethod(boolean hot, String profile, Method method);
private static final String TEMP_FILE_NAME_PREFIX = "dummy";
private static final String TEMP_FILE_NAME_SUFFIX = "-file";
diff --git a/test/912-classes/src-art/art/Test912.java b/test/912-classes/src-art/art/Test912.java
index 9896eac..fbf8794 100644
--- a/test/912-classes/src-art/art/Test912.java
+++ b/test/912-classes/src-art/art/Test912.java
@@ -228,7 +228,8 @@
// The JIT may deeply inline and load some classes. Preload these for test determinism.
final String PRELOAD_FOR_JIT[] = {
"java.nio.charset.CoderMalfunctionError",
- "java.util.NoSuchElementException"
+ "java.util.NoSuchElementException",
+ "java.io.FileNotFoundException", // b/63581208
};
for (String s : PRELOAD_FOR_JIT) {
Class.forName(s);
diff --git a/test/990-method-handle-and-mr/build b/test/990-method-handle-and-mr/build
new file mode 100755
index 0000000..5e5f36e
--- /dev/null
+++ b/test/990-method-handle-and-mr/build
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+# Exit on failure.
+set -e
+
+if [[ $@ != *"--jvm"* ]]; then
+ # Don't do anything with jvm.
+ export USE_JACK=true
+fi
+
+./default-build "$@" --experimental method-handles
diff --git a/test/990-method-handle-and-mr/expected.txt b/test/990-method-handle-and-mr/expected.txt
new file mode 100644
index 0000000..8483fb5
--- /dev/null
+++ b/test/990-method-handle-and-mr/expected.txt
@@ -0,0 +1,4 @@
+Test
+Test
+Test
+passed
diff --git a/test/990-method-handle-and-mr/info.txt b/test/990-method-handle-and-mr/info.txt
new file mode 100644
index 0000000..85a957c
--- /dev/null
+++ b/test/990-method-handle-and-mr/info.txt
@@ -0,0 +1,2 @@
+Test stressing code generated for invoke-polymorphic instructions with
+respect to Marking Register (on architectures supporting MR).
diff --git a/test/990-method-handle-and-mr/src/Main.java b/test/990-method-handle-and-mr/src/Main.java
new file mode 100644
index 0000000..739b8eb
--- /dev/null
+++ b/test/990-method-handle-and-mr/src/Main.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// This test was inspired by benchmarks.MicroMethodHandles.java.MicroMethodHandles.
+
+import java.io.PrintStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+class A {
+ public Long binaryFunction(int x, double y) {
+ return 1000l;
+ }
+}
+
+class Test {
+ Test() throws Throwable {
+ this.handle = MethodHandles.lookup().findVirtual(A.class, "binaryFunction",
+ MethodType.methodType(Long.class, int.class,
+ double.class));
+ this.a = new A();
+ this.x = new Integer(72);
+ this.y = new Double(-1.39e-31);
+ }
+
+ void execute() {
+ try {
+ executeFor(2000);
+ System.out.println(getName());
+ } catch (Throwable t) {
+ System.err.println("Exception during the execution of " + getName());
+ System.err.println(t);
+ t.printStackTrace(new PrintStream(System.err));
+ System.exit(1);
+ }
+ }
+
+ void executeFor(long timeMinimumMillis) throws Throwable {
+ long startTime = System.currentTimeMillis();
+ long elapsed = 0;
+ while (elapsed < timeMinimumMillis) {
+ exercise();
+ elapsed = System.currentTimeMillis() - startTime;
+ }
+ }
+
+ void exercise() throws Throwable {
+ for (int i = 0; i < EXERCISE_ITERATIONS; ++i) {
+ run();
+ }
+ }
+
+ void run() throws Throwable {
+ long result = (long) handle.invoke(a, x, y);
+ }
+
+ String getName() {
+ return getClass().getSimpleName();
+ }
+
+ private static final int EXERCISE_ITERATIONS = 500;
+
+ private MethodHandle handle;
+ private A a;
+ private Integer x;
+ private Double y;
+}
+
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ Test[] tests = new Test[] { new Test(), new Test(), new Test() };
+ for (Test test : tests) {
+ test.execute();
+ }
+ System.out.println("passed");
+ }
+}
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 8a4c2df..c6553f8 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -223,5 +223,10 @@
result: EXEC_FAILED,
bug: 62408076,
names: ["libcore.java.lang.reflect.annotations.AnnotatedElementParameterTest#testImplicitConstructorParameters_singleAnnotation"]
+},
+{
+ description: "java.io.IOException: Error writing ASN.1 encoding",
+ result: EXEC_FAILED,
+ names: ["libcore.javax.crypto.spec.AlgorithmParametersTestGCM#testEncoding"]
}
]
diff --git a/tools/runtime_memusage/sanitizer_logcat_analysis.sh b/tools/runtime_memusage/sanitizer_logcat_analysis.sh
index 75cb9a9..66b48fa 100755
--- a/tools/runtime_memusage/sanitizer_logcat_analysis.sh
+++ b/tools/runtime_memusage/sanitizer_logcat_analysis.sh
@@ -18,13 +18,22 @@
#
# This script takes in a logcat containing Sanitizer traces and outputs several
# files, prints information regarding the traces, and plots information as well.
+ALL_PIDS=false
USE_TEMP=true
DO_REDO=false
+PACKAGE_NAME=""
# EXACT_ARG and MIN_ARG are passed to prune_sanitizer_output.py
EXACT_ARG=""
MIN_ARG=""
+OFFSET_ARGS=""
+TIME_ARGS=""
usage() {
echo "Usage: $0 [options] [LOGCAT_FILE] [CATEGORIES...]"
+ echo " -a"
+ echo " Forces all pids associated with registered dex"
+ echo " files in the logcat to be processed."
+ echo " default: only the last pid is processed"
+ echo
echo " -d OUT_DIRECTORY"
echo " Puts all output in specified directory."
echo " If not given, output will be put in a local"
@@ -37,7 +46,7 @@
echo " the -m argument or by prune_sanitizer_output.py"
echo
echo " -f"
- echo " forces redo of all commands even if output"
+ echo " Forces redo of all commands even if output"
echo " files exist. Steps are skipped if their output"
echo " exist already and this is not enabled."
echo
@@ -46,6 +55,22 @@
echo " at least MINIMUM_CALLS_PER_TRACE lines."
echo " default: specified by prune_sanitizer_output.py"
echo
+ echo " -o [OFFSET],[OFFSET]"
+ echo " Filters out all Dex File offsets outside the"
+ echo " range between provided offsets. 'inf' can be"
+ echo " provided for infinity."
+ echo " default: 0,inf"
+ echo
+ echo " -p [PACKAGE_NAME]"
+ echo " Using the package name, uses baksmali to get"
+ echo " a dump of the Dex File format for the package."
+ echo
+ echo " -t [TIME_OFFSET],[TIME_OFFSET]"
+ echo " Filters out all time offsets outside the"
+ echo " range between provided offsets. 'inf' can be"
+ echo " provided for infinity."
+ echo " default: 0,inf"
+ echo
echo " CATEGORIES are words that are expected to show in"
echo " a large subset of symbolized traces. Splits"
echo " output based on each word."
@@ -55,33 +80,61 @@
}
-while [[ $# -gt 1 ]]; do
-case $1 in
- -d)
- shift
- USE_TEMP=false
- OUT_DIR=$1
- shift
- break
- ;;
- -e)
- shift
- EXACT_ARG='-e'
- ;;
- -f)
- shift
- DO_REDO=true
- ;;
- -m)
- shift
- MIN_ARG='-m '"$1"''
- shift
- ;;
- *)
- usage
- exit
+while getopts ":ad:efm:o:p:t:" opt ; do
+case ${opt} in
+ a)
+ ALL_PIDS=true
+ ;;
+ d)
+ USE_TEMP=false
+ OUT_DIR=$OPTARG
+ ;;
+ e)
+ EXACT_ARG='-e'
+ ;;
+ f)
+ DO_REDO=true
+ ;;
+ m)
+ if ! [ "$OPTARG" -eq "$OPTARG" ]; then
+ usage
+ exit
+ fi
+ MIN_ARG='-m '"$OPTARG"
+ ;;
+ o)
+ set -f
+ OLD_IFS=$IFS
+ IFS=","
+ OFFSET_ARGS=( $OPTARG )
+ if [ "${#OFFSET_ARGS[@]}" -ne 2 ]; then
+ usage
+ exit
+ fi
+ OFFSET_ARGS=( "--offsets" "${OFFSET_ARGS[@]}" )
+ IFS=$OLD_IFS
+ ;;
+ t)
+ set -f
+ OLD_IFS=$IFS
+ IFS=","
+ TIME_ARGS=( $OPTARG )
+ if [ "${#TIME_ARGS[@]}" -ne 2 ]; then
+ usage
+ exit
+ fi
+ TIME_ARGS=( "--times" "${TIME_ARGS[@]}" )
+ IFS=$OLD_IFS
+ ;;
+ p)
+ PACKAGE_NAME=$OPTARG
+ ;;
+ \?)
+ usage
+ exit
esac
done
+shift $((OPTIND -1))
if [ $# -lt 1 ]; then
usage
@@ -103,78 +156,147 @@
fi
# Note: Steps are skipped if their output exists until -f flag is enabled
-# Step 1 - Only output lines related to Sanitizer
-# Folder that holds all file output
echo "Output folder: $OUT_DIR"
-ASAN_OUT=$OUT_DIR/asan_output
-if [ ! -f $ASAN_OUT ] || [ $DO_REDO = true ]; then
- DO_REDO=true
- echo "Extracting ASAN output"
- grep "app_process64" $LOGCAT_FILE > $ASAN_OUT
-else
- echo "Skipped: Extracting ASAN output"
+unique_pids=( $(grep "RegisterDexFile" "$LOGCAT_FILE" | grep -v "zygote64" | tr -s ' ' | cut -f3 -d' ' | awk '!a[$0]++') )
+echo "List of pids: ${unique_pids[@]}"
+if [ $ALL_PIDS = false ]; then
+ unique_pids=( ${unique_pids[-1]} )
fi
-# Step 2 - Only output lines containing Dex File Start Addresses
-DEX_START=$OUT_DIR/dex_start
-if [ ! -f $DEX_START ] || [ $DO_REDO = true ]; then
- DO_REDO=true
- echo "Extracting Start of Dex File(s)"
- grep "RegisterDexFile" $LOGCAT_FILE > $DEX_START
-else
- echo "Skipped: Extracting Start of Dex File(s)"
-fi
+for pid in "${unique_pids[@]}"
+do
+ echo
+ echo "Current pid: $pid"
+ echo
+ PID_DIR=$OUT_DIR/$pid
+ if [ ! -d "$PID_DIR" ]; then
+ mkdir $PID_DIR
+ DO_REDO[$pid]=true
+ fi
-# Step 3 - Clean Sanitizer output from Step 2 since logcat cannot
-# handle large amounts of output.
-ASAN_OUT_FILTERED=$OUT_DIR/asan_output_filtered
-if [ ! -f $ASAN_OUT_FILTERED ] || [ $DO_REDO = true ]; then
- DO_REDO=true
- echo "Filtering/Cleaning ASAN output"
- python $ANDROID_BUILD_TOP/art/tools/runtime_memusage/prune_sanitizer_output.py \
- $EXACT_ARG $MIN_ARG -d $OUT_DIR $ASAN_OUT
-else
- echo "Skipped: Filtering/Cleaning ASAN output"
-fi
+ INTERMEDIATES_DIR=$PID_DIR/intermediates
+ RESULTS_DIR=$PID_DIR/results
+ LOGCAT_PID_FILE=$PID_DIR/logcat
-# Step 4 - Retrieve symbolized stack traces from Step 3 output
-SYM_FILTERED=$OUT_DIR/sym_filtered
-if [ ! -f $SYM_FILTERED ] || [ $DO_REDO = true ]; then
- DO_REDO=true
- echo "Retrieving symbolized traces"
- $ANDROID_BUILD_TOP/development/scripts/stack $ASAN_OUT_FILTERED > $SYM_FILTERED
-else
- echo "Skipped: Retrieving symbolized traces"
-fi
+ if [ ! -f "$PID_DIR/logcat" ] || [ "${DO_REDO[$pid]}" = true ] || [ $DO_REDO = true ]; then
+ DO_REDO[$pid]=true
+ awk '{if($3 == '$pid') print $0}' $LOGCAT_FILE > $LOGCAT_PID_FILE
+ fi
-# Step 5 - Using Steps 2, 3, 4 outputs in order to output graph data
-# and trace data
-# Only the category names are needed for the commands giving final output
-shift
-TIME_OUTPUT=($OUT_DIR/time_output_*.dat)
-if [ ! -e ${TIME_OUTPUT[0]} ] || [ $DO_REDO = true ]; then
- DO_REDO=true
- echo "Creating Categorized Time Table"
- python $ANDROID_BUILD_TOP/art/tools/runtime_memusage/symbol_trace_info.py \
- -d $OUT_DIR $ASAN_OUT_FILTERED $SYM_FILTERED $DEX_START $@
-else
- echo "Skipped: Creating Categorized Time Table"
-fi
+ if [ ! -d "$INTERMEDIATES_DIR" ]; then
+ mkdir $INTERMEDIATES_DIR
+ DO_REDO[$pid]=true
+ fi
-# Step 6 - Use graph data from Step 5 to plot graph
-# Contains the category names used for legend of gnuplot
-PLOT_CATS=`echo \"Uncategorized $@\"`
-echo "Plotting Categorized Time Table"
-# Plots the information from logcat
-gnuplot --persist -e \
- 'filename(n) = sprintf("'"$OUT_DIR"'/time_output_%d.dat", n);
- catnames = '"$PLOT_CATS"';
- set title "Dex File Offset vs. Time accessed since App Start";
- set xlabel "Time (milliseconds)";
- set ylabel "Dex File Offset (bytes)";
- plot for [i=0:'"$NUM_CAT"'] filename(i) using 1:2 title word(catnames, i + 1);'
+ # Step 1 - Only output lines related to Sanitizer
+ # Folder that holds all file output
+ ASAN_OUT=$INTERMEDIATES_DIR/asan_output
+ if [ ! -f $ASAN_OUT ] || [ "${DO_REDO[$pid]}" = true ] || [ $DO_REDO = true ]; then
+ DO_REDO[$pid]=true
+ echo "Extracting ASAN output"
+ grep "app_process64" $LOGCAT_PID_FILE > $ASAN_OUT
+ else
+ echo "Skipped: Extracting ASAN output"
+ fi
-if [ $USE_TEMP = true ]; then
- echo "Removing temp directory and files"
- rm -rf $OUT_DIR
-fi
+ # Step 2 - Only output lines containing Dex File Start Addresses
+ DEX_START=$INTERMEDIATES_DIR/dex_start
+ if [ ! -f $DEX_START ] || [ "${DO_REDO[$pid]}" = true ] || [ $DO_REDO = true ]; then
+ DO_REDO[$pid]=true
+ echo "Extracting Start of Dex File(s)"
+ grep "RegisterDexFile" $LOGCAT_PID_FILE > $DEX_START
+ else
+ echo "Skipped: Extracting Start of Dex File(s)"
+ fi
+
+ # Step 3 - Clean Sanitizer output from Step 2 since logcat cannot
+ # handle large amounts of output.
+ ASAN_OUT_FILTERED=$INTERMEDIATES_DIR/asan_output_filtered
+ if [ ! -f $ASAN_OUT_FILTERED ] || [ "${DO_REDO[$pid]}" = true ] || [ $DO_REDO = true ]; then
+ DO_REDO[$pid]=true
+ echo "Filtering/Cleaning ASAN output"
+ python $ANDROID_BUILD_TOP/art/tools/runtime_memusage/prune_sanitizer_output.py \
+ $EXACT_ARG $MIN_ARG -d $INTERMEDIATES_DIR $ASAN_OUT
+ else
+ echo "Skipped: Filtering/Cleaning ASAN output"
+ fi
+
+ # Step 4 - Retrieve symbolized stack traces from Step 3 output
+ SYM_FILTERED=$INTERMEDIATES_DIR/sym_filtered
+ if [ ! -f $SYM_FILTERED ] || [ "${DO_REDO[$pid]}" = true ] || [ $DO_REDO = true ]; then
+ DO_REDO[$pid]=true
+ echo "Retrieving symbolized traces"
+ $ANDROID_BUILD_TOP/development/scripts/stack $ASAN_OUT_FILTERED > $SYM_FILTERED
+ else
+ echo "Skipped: Retrieving symbolized traces"
+ fi
+
+ # Step 4.5 - Obtain Dex File Format of dex file related to package
+ BAKSMALI_DMP_OUT="$INTERMEDIATES_DIR""/baksmali_dex_file"
+ BAKSMALI_DMP_ARG="--dex-file="$BAKSMALI_DMP_OUT
+ if [ ! -f $BAKSMALI_DMP_OUT ] || [ "${DO_REDO[$pid]}" = true ] || [ $DO_REDO = true ]; then
+ if [ $PACKAGE_NAME != "" ]; then
+ # Extracting Dex File path on device from Dex File related to package
+ apk_directory=$(dirname $(grep $PACKAGE_NAME $DEX_START | tail -n1 | awk '{print $8}'))
+ apk_dex_files=$(adb shell find $apk_directory -name "*.?dex" -type f 2> /dev/null)
+ for apk_file in $apk_dex_files; do
+ base_name=$(basename $apk_file)
+ adb pull $apk_file $INTERMEDIATES_DIR/base."${base_name#*.}"
+ done
+ oatdump --oat-file=$INTERMEDIATES_DIR/base.odex --export-dex-to=$INTERMEDIATES_DIR --output=/dev/null
+ export_dex=( $INTERMEDIATES_DIR/*apk_export* )
+ baksmali -JXmx1024M dump $export_dex > $BAKSMALI_DMP_OUT 2> /dev/null
+ if ! [ -s $BAKSMALI_DMP_OUT ]; then
+ rm $BAKSMALI_DMP_OUT
+ BAKSMALI_DMP_ARG=""
+ echo "Failed to retrieve Dex File format"
+ fi
+ else
+ BAKSMALI_DMP_ARG=""
+ echo "Failed to retrieve Dex File format"
+ fi
+ else
+ echo "Skipped: Retrieving Dex File format from baksmali"
+ fi
+
+ if [ ! -d "$RESULTS_DIR" ]; then
+ mkdir $RESULTS_DIR
+ DO_REDO[$pid]=true
+ fi
+
+ # Step 5 - Using Steps 2, 3, 4 outputs in order to output graph data
+ # and trace data
+ # Only the category names are needed for the commands giving final output
+ shift
+ TIME_OUTPUT=($RESULTS_DIR/time_output_*.dat)
+ if [ ! -e ${TIME_OUTPUT[0]} ] || [ "${DO_REDO[$pid]}" = true ] || [ $DO_REDO = true ]; then
+ DO_REDO[$pid]=true
+ echo "Creating Categorized Time Table"
+ python $ANDROID_BUILD_TOP/art/tools/runtime_memusage/symbol_trace_info.py \
+ -d $RESULTS_DIR ${OFFSET_ARGS[@]} ${TIME_ARGS[@]} $BAKSMALI_DMP_ARG $ASAN_OUT_FILTERED $SYM_FILTERED $DEX_START $@
+ else
+ echo "Skipped: Creating Categorized Time Table"
+ fi
+
+ # Step 6 - Use graph data from Step 5 to plot graph
+ # Contains the category names used for legend of gnuplot
+ PLOT_CATS=`echo \"Uncategorized $@\"`
+ PACKAGE_STRING=""
+ if [ $PACKAGE_NAME != "" ]; then
+ PACKAGE_STRING="Package name: "$PACKAGE_NAME" "
+ fi
+ echo "Plotting Categorized Time Table"
+ # Plots the information from logcat
+ gnuplot --persist -e \
+ 'filename(n) = sprintf("'"$RESULTS_DIR"'/time_output_%d.dat", n);
+ catnames = '"$PLOT_CATS"';
+ set title "'"$PACKAGE_STRING"'PID: '"$pid"'";
+ set xlabel "Time (milliseconds)";
+ set ylabel "Dex File Offset (bytes)";
+ plot for [i=0:'"$NUM_CAT"'] filename(i) using 1:2 title word(catnames, i + 1);'
+
+ if [ $USE_TEMP = true ]; then
+ echo "Removing temp directory and files"
+ rm -rf $OUT_DIR
+ fi
+done
diff --git a/tools/runtime_memusage/symbol_trace_info.py b/tools/runtime_memusage/symbol_trace_info.py
index e539be2..a5ced38 100755
--- a/tools/runtime_memusage/symbol_trace_info.py
+++ b/tools/runtime_memusage/symbol_trace_info.py
@@ -25,7 +25,7 @@
import argparse
import bisect
import os
-import sys
+import re
def find_match(list_substrings, big_string):
@@ -36,8 +36,13 @@
return list_substrings.index("Uncategorized")
-def absolute_to_relative(plot_list, dex_start_list, cat_list):
+def absolute_to_relative(data_lists, symbol_traces):
"""Address changed to Dex File offset and shifting time to 0 min in ms."""
+ plot_list = data_lists["plot_list"]
+ dex_start_list = data_lists["dex_start_list"]
+ cat_list = data_lists["cat_list"]
+ offsets = data_lists["offsets"]
+ time_offsets = data_lists["time_offsets"]
time_format_str = "%H:%M:%S.%f"
first_access_time = datetime.strptime(plot_list[0][0],
time_format_str)
@@ -52,9 +57,22 @@
dex_file_start = dex_start_list[bisect.bisect(dex_start_list,
address_access) - 1
]
- elem.insert(1, address_access - dex_file_start)
- # Category that a data point belongs to
- elem.insert(2, cat_list[ind])
+ dex_offset = address_access - dex_file_start
+ # Meant to nullify data that does not meet offset criteria if specified
+ # Assumes that offsets is already sorted
+ if (dex_offset >= offsets[0] and dex_offset < offsets[1] and
+ elem[0] >= time_offsets[0] and elem[0] < time_offsets[1]):
+
+ elem.insert(1, dex_offset)
+ # Category that a data point belongs to
+ elem.insert(2, cat_list[ind])
+ else:
+ elem[0] = None
+ elem[1] = None
+ elem.append(None)
+ elem.append(None)
+ symbol_traces[ind] = None
+ cat_list[ind] = None
def print_category_info(cat_split, outname, out_dir_name, title):
@@ -67,7 +85,7 @@
str(len(trace_counts_list_ordered)))
print("\tSum of trace counts: " +
str(sum([trace[1] for trace in trace_counts_list_ordered])))
- print("\n\tCount: How many traces appeared with count\n\t")
+ print("\n\tCount: How many traces appeared with count\n\t", end="")
print(Counter([trace[1] for trace in trace_counts_list_ordered]))
with open(os.path.join(out_dir_name, outname), "w") as output_file:
for trace in trace_counts_list_ordered:
@@ -79,6 +97,8 @@
def print_categories(categories, symbol_file_split, out_dir_name):
"""Prints details of all categories."""
+ symbol_file_split = [trace for trace in symbol_file_split
+ if trace is not None]
# Info of traces containing a call to current category
for cat_num, cat_name in enumerate(categories[1:]):
print("\nCategory #%d" % (cat_num + 1))
@@ -123,6 +143,26 @@
parser.add_argument("-d", action="store",
default="", dest="out_dir_name", type=is_directory,
help="Output Directory")
+ parser.add_argument("--dex-file", action="store",
+ default=None, dest="dex_file",
+ type=argparse.FileType("r"),
+ help="Baksmali Dex File Dump")
+ parser.add_argument("--offsets", action="store", nargs=2,
+ default=[float(0), float("inf")],
+ dest="offsets",
+ metavar="OFFSET",
+ type=float,
+ help="Filters out accesses not between provided"
+ " offsets if provided. Can provide 'inf'"
+ " for infinity")
+ parser.add_argument("--times", action="store", nargs=2,
+ default=[float(0), float("inf")],
+ dest="times",
+ metavar="TIME",
+ type=float,
+ help="Filters out accesses not between provided"
+ " time offsets if provided. Can provide 'inf'"
+ " for infinity")
parser.add_argument("sanitizer_trace", action="store",
type=argparse.FileType("r"),
help="File containing sanitizer traces filtered by "
@@ -141,6 +181,14 @@
return parser.parse_args(argv)
+def get_dex_offset_data(line, dex_file_item):
+ """ Returns a tuple of dex file offset, item name, and data of a line."""
+ return (int(line[:line.find(":")], 16),
+ (dex_file_item,
+ line.split("|")[1].strip())
+ )
+
+
def read_data(parsed_argv):
"""Reads data from filepath arguments and parses them into lists."""
# Using a dictionary to establish relation between lists added
@@ -149,6 +197,12 @@
# Makes sure each trace maps to some category
categories.insert(0, "Uncategorized")
+ data_lists["offsets"] = parsed_argv.offsets
+ data_lists["offsets"].sort()
+
+ data_lists["times"] = parsed_argv.times
+ data_lists["times"].sort()
+
logcat_file_data = parsed_argv.sanitizer_trace.readlines()
parsed_argv.sanitizer_trace.close()
@@ -159,6 +213,25 @@
dex_start_file_data = parsed_argv.dex_starts.readlines()
parsed_argv.dex_starts.close()
+ if parsed_argv.dex_file != None:
+ dex_file_data = parsed_argv.dex_file.read()
+ parsed_argv.dex_file.close()
+ # Splits baksmali dump by each item
+ item_split = [s.splitlines() for s in re.split(r"\|\[[0-9]+\] ",
+ dex_file_data)]
+ # Splits each item by line and creates a list of offsets and a
+ # corresponding list of the data associated with that line
+ offset_list, offset_data = zip(*[get_dex_offset_data(line, item[0])
+ for item in item_split
+ for line in item[1:]
+ if re.search("[0-9a-f]{6}:", line)
+ is not None
+ and line.find("|") != -1])
+ data_lists["offset_list"] = offset_list
+ data_lists["offset_data"] = offset_data
+ else:
+ dex_file_data = None
+
# Each element is a tuple of time and address accessed
data_lists["plot_list"] = [[elem[1] for elem in enumerate(line.split())
if elem[0] in (1, 11)
@@ -184,23 +257,26 @@
return data_lists, categories, symbol_file_split
-def main(argv=None):
+def main():
"""Takes in trace information and outputs details about them."""
- if argv is None:
- argv = sys.argv
- parsed_argv = parse_args(argv[1:])
+ parsed_argv = parse_args(None)
data_lists, categories, symbol_file_split = read_data(parsed_argv)
+
# Formats plot_list such that each element is a data point
- absolute_to_relative(data_lists["plot_list"], data_lists["dex_start_list"],
- data_lists["cat_list"])
+ #absolute_to_relative(data_lists["plot_list"], data_lists["dex_start_list"],
+ # data_lists["cat_list"], data_lists["offsets"],
+ # data_lists["times"], symbol_file_split)
+ absolute_to_relative(data_lists, symbol_file_split)
for file_ext, cat_name in enumerate(categories):
out_file_name = os.path.join(parsed_argv.out_dir_name, "time_output_" +
str(file_ext) +
".dat")
with open(out_file_name, "w") as output_file:
output_file.write("# Category: " + cat_name + "\n")
- output_file.write("# Time, Dex File Offset, Address \n")
+ output_file.write("# Time, Dex File Offset_10, Dex File Offset_16,"
+ " Address, Item Accessed, Item Member Accessed"
+ " Unaligned\n")
for time, dex_offset, category, address in data_lists["plot_list"]:
if category == cat_name:
output_file.write(
@@ -208,9 +284,23 @@
" " +
str(dex_offset) +
" #" +
- str(address) +
- "\n")
-
+ hex(dex_offset) +
+ " " +
+ str(address))
+ if data_lists.has_key("offset_list"):
+ dex_offset_index = bisect.bisect(
+ data_lists["offset_list"],
+ dex_offset) - 1
+ aligned_dex_offset = (data_lists["offset_list"]
+ [dex_offset_index])
+ dex_offset_data = (data_lists["offset_data"]
+ [dex_offset_index])
+ output_file.write(
+ " " +
+ "|".join(dex_offset_data) +
+ " " +
+ str(aligned_dex_offset != dex_offset))
+ output_file.write("\n")
print_categories(categories, symbol_file_split, parsed_argv.out_dir_name)
diff --git a/tools/test_presubmit.py b/tools/test_presubmit.py
new file mode 100755
index 0000000..f6e6df9
--- /dev/null
+++ b/tools/test_presubmit.py
@@ -0,0 +1,159 @@
+#!/usr/bin/python3
+#
+# Copyright 2017, 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.
+
+#
+# There are many run-tests which generate their sources automatically.
+# It is desirable to keep the checked-in source code, as we re-run generators very rarely.
+#
+# This script will re-run the generators only if their dependent files have changed and then
+# complain if the outputs no longer matched what's in the source tree.
+#
+
+import os
+import pathlib
+import subprocess
+import sys
+import tempfile
+
+THIS_PATH = os.path.dirname(os.path.realpath(__file__))
+
+TOOLS_GEN_SRCS = [
+ # tool -> path to a script to generate a file
+ # reference_files -> list of files that the script can generate
+ # args -> lambda(path) that generates arguments the 'tool' in order to output to 'path'
+ # interesting_files -> which files much change in order to re-run the tool.
+ # interesting_to_reference_files: lambda(x,reference_files)
+ # given the interesting file 'x' and a list of reference_files,
+ # return exactly one reference file that corresponds to it.
+ { 'tool' : 'test/988-method-trace/gen_srcs.py',
+ 'reference_files' : ['test/988-method-trace/src/art/Test988Intrinsics.java'],
+ 'args' : lambda output_path: [output_path],
+ 'interesting_files' : ['compiler/intrinsics_list.h'],
+ 'interesting_to_reference_file' : lambda interesting, references: references[0],
+ },
+]
+
+DEBUG = False
+
+def debug_print(msg):
+ if DEBUG:
+ print("[DEBUG]: " + msg, file=sys.stderr)
+
+def is_interesting(f, tool_dict):
+ """
+ Returns true if this is a file we want to run this tool before uploading. False otherwise.
+ """
+ path = pathlib.Path(f)
+ return str(path) in tool_dict['interesting_files']
+
+def get_changed_files(commit):
+ """
+ Gets the files changed in the given commit.
+ """
+ return subprocess.check_output(
+ ["git", 'diff-tree', '--no-commit-id', '--name-only', '-r', commit],
+ stderr=subprocess.STDOUT,
+ universal_newlines=True).split()
+
+def command_line_for_tool(tool_dict, output):
+ """
+ Calculate the command line for this tool when ran against the output file 'output'.
+ """
+ proc_args = [tool_dict['tool']] + tool_dict['args'](output)
+ return proc_args
+
+def run_tool(tool_dict, output):
+ """
+ Execute this tool by passing the tool args to the tool.
+ """
+ proc_args = command_line_for_tool(tool_dict, output)
+ debug_print("PROC_ARGS: %s" %(proc_args))
+ succ = subprocess.call(proc_args)
+ return succ
+
+def get_reference_file(changed_file, tool_dict):
+ """
+ Lookup the file that the tool is generating in response to changing an interesting file
+ """
+ return tool_dict['interesting_to_reference_file'](changed_file, tool_dict['reference_files'])
+
+def run_diff(changed_file, tool_dict, original_file):
+ ref_file = get_reference_file(changed_file, tool_dict)
+
+ return subprocess.call(["diff", ref_file, original_file]) != 0
+
+def run_gen_srcs(files):
+ """
+ Runs test tools only for interesting files that were changed in this commit.
+ """
+ if len(files) == 0:
+ return
+
+ success = 0 # exit code 0 = success, >0 error.
+ had_diffs = False
+
+ for tool_dict in TOOLS_GEN_SRCS:
+ tool_ran_at_least_once = False
+ for f in files:
+ if is_interesting(f, tool_dict):
+ tmp_file = tempfile.mktemp()
+ reference_file = get_reference_file(f, tool_dict)
+
+ # Generate the source code with a temporary file as the output.
+ success = run_tool(tool_dict, tmp_file)
+ if success != 0:
+ # Immediately abort if the tool fails with a non-0 exit code, do not go any further.
+ print("[FATAL] Error when running tool (return code %s)" %(success), file=sys.stderr)
+ print("$> %s" %(" ".join(command_line_for_tool(tool_dict, tmp_file))), file=sys.stderr)
+ sys.exit(success)
+ if run_diff(f, tool_dict, tmp_file):
+ # If the tool succeeded, but there was a diff, then the generated code has diverged.
+ # Output the diff information and continue to the next files/tools.
+ had_diffs = True
+ print("-----------------------------------------------------------", file=sys.stderr)
+ print("File '%s' diverged from generated file; please re-run tools:" %(reference_file), file=sys.stderr)
+ print("$> %s" %(" ".join(command_line_for_tool(tool_dict, reference_file))), file=sys.stderr)
+ else:
+ debug_print("File %s is consistent with tool %s" %(reference_file, tool_dict['tool']))
+
+ tool_ran_at_least_once = True
+
+ if not tool_ran_at_least_once:
+ debug_print("Interesting files %s unchanged, skipping tool '%s'" %(tool_dict['interesting_files'], tool_dict['tool']))
+
+ if had_diffs:
+ success = 1
+ # Always return non-0 exit code when there were diffs so that the presubmit hooks are FAILED.
+
+ return success
+
+
+def main():
+ if 'PREUPLOAD_COMMIT' in os.environ:
+ commit = os.environ['PREUPLOAD_COMMIT']
+ else:
+ print("WARNING: Not running as a pre-upload hook. Assuming commit to check = 'HEAD'", file=sys.stderr)
+ commit = "HEAD"
+
+ os.chdir(os.path.join(THIS_PATH, '..')) # run tool relative to 'art' directory
+ debug_print("CWD: %s" %(os.getcwd()))
+
+ changed_files = get_changed_files(commit)
+ debug_print("Changed files: %s" %(changed_files))
+ return run_gen_srcs(changed_files)
+
+if __name__ == '__main__':
+ sys.exit(main())