Merge "ART: Follow-up to 507cc6f83bf6379728f2dd20391f2ed5fbfe6371"
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index 28bd754..a57b619 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -472,6 +472,7 @@
bool verify_pre_gc_rosalloc_ = kIsDebugBuild;
bool verify_pre_sweeping_rosalloc_ = false;
bool verify_post_gc_rosalloc_ = false;
+ bool gcstress_ = false;
};
template <>
@@ -509,6 +510,10 @@
xgc.verify_post_gc_rosalloc_ = true;
} else if (gc_option == "nopostverify_rosalloc") {
xgc.verify_post_gc_rosalloc_ = false;
+ } else if (gc_option == "gcstress") {
+ xgc.gcstress_ = true;
+ } else if (gc_option == "nogcstress") {
+ xgc.gcstress_ = false;
} else if ((gc_option == "precise") ||
(gc_option == "noprecise") ||
(gc_option == "verifycardtable") ||
diff --git a/compiler/buffered_output_stream.h b/compiler/buffered_output_stream.h
index bbc49df..15fc033 100644
--- a/compiler/buffered_output_stream.h
+++ b/compiler/buffered_output_stream.h
@@ -28,6 +28,7 @@
explicit BufferedOutputStream(OutputStream* out);
virtual ~BufferedOutputStream() {
+ Flush();
delete out_;
}
diff --git a/compiler/dex/quick/codegen_util.cc b/compiler/dex/quick/codegen_util.cc
index c803e65..8629f39 100644
--- a/compiler/dex/quick/codegen_util.cc
+++ b/compiler/dex/quick/codegen_util.cc
@@ -1370,7 +1370,9 @@
DCHECK(first_bb->data_flow_info->vreg_to_ssa_map_exit != nullptr);
const int32_t* first_vreg_to_ssa_map = first_bb->data_flow_info->vreg_to_ssa_map_exit;
references->ClearAllBits();
- for (uint32_t vreg = 0, num_vregs = mir_graph_->GetNumOfCodeVRs(); vreg != num_vregs; ++vreg) {
+ for (uint32_t vreg = 0,
+ num_vregs = mir_graph_->GetNumOfCodeVRs() + mir_graph_->GetNumUsedCompilerTemps();
+ vreg != num_vregs; ++vreg) {
int32_t sreg = first_vreg_to_ssa_map[vreg];
if (sreg != INVALID_SREG && mir_graph_->reg_location_[sreg].ref &&
!mir_graph_->IsConstantNullRef(mir_graph_->reg_location_[sreg])) {
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index bd0bfcd..9abe2e7 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -2616,7 +2616,9 @@
case Primitive::kPrimInt: {
locations->SetInAt(0, Location::RequiresRegister());
locations->SetInAt(1, Location::RegisterOrConstant(op->InputAt(1)));
- locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
+ // Make the output overlap, as it will be used to hold the masked
+ // second input.
+ locations->SetOut(Location::RequiresRegister(), Location::kOutputOverlap);
break;
}
case Primitive::kPrimLong: {
@@ -2647,13 +2649,13 @@
// Arm doesn't mask the shift count so we need to do it ourselves.
if (second.IsRegister()) {
Register second_reg = second.AsRegister<Register>();
- __ and_(second_reg, second_reg, ShifterOperand(kMaxIntShiftValue));
+ __ and_(out_reg, second_reg, ShifterOperand(kMaxIntShiftValue));
if (op->IsShl()) {
- __ Lsl(out_reg, first_reg, second_reg);
+ __ Lsl(out_reg, first_reg, out_reg);
} else if (op->IsShr()) {
- __ Asr(out_reg, first_reg, second_reg);
+ __ Asr(out_reg, first_reg, out_reg);
} else {
- __ Lsr(out_reg, first_reg, second_reg);
+ __ Lsr(out_reg, first_reg, out_reg);
}
} else {
int32_t cst = second.GetConstant()->AsIntConstant()->GetValue();
@@ -2682,44 +2684,44 @@
Register second_reg = second.AsRegister<Register>();
if (op->IsShl()) {
+ __ and_(o_l, second_reg, ShifterOperand(kMaxLongShiftValue));
// Shift the high part
- __ and_(second_reg, second_reg, ShifterOperand(63));
- __ Lsl(o_h, high, second_reg);
+ __ Lsl(o_h, high, o_l);
// Shift the low part and `or` what overflew on the high part
- __ rsb(temp, second_reg, ShifterOperand(32));
+ __ rsb(temp, o_l, ShifterOperand(kArmBitsPerWord));
__ Lsr(temp, low, temp);
__ orr(o_h, o_h, ShifterOperand(temp));
// If the shift is > 32 bits, override the high part
- __ subs(temp, second_reg, ShifterOperand(32));
+ __ subs(temp, o_l, ShifterOperand(kArmBitsPerWord));
__ it(PL);
__ Lsl(o_h, low, temp, false, PL);
// Shift the low part
- __ Lsl(o_l, low, second_reg);
+ __ Lsl(o_l, low, o_l);
} else if (op->IsShr()) {
+ __ and_(o_h, second_reg, ShifterOperand(kMaxLongShiftValue));
// Shift the low part
- __ and_(second_reg, second_reg, ShifterOperand(63));
- __ Lsr(o_l, low, second_reg);
+ __ Lsr(o_l, low, o_h);
// Shift the high part and `or` what underflew on the low part
- __ rsb(temp, second_reg, ShifterOperand(32));
+ __ rsb(temp, o_h, ShifterOperand(kArmBitsPerWord));
__ Lsl(temp, high, temp);
__ orr(o_l, o_l, ShifterOperand(temp));
// If the shift is > 32 bits, override the low part
- __ subs(temp, second_reg, ShifterOperand(32));
+ __ subs(temp, o_h, ShifterOperand(kArmBitsPerWord));
__ it(PL);
__ Asr(o_l, high, temp, false, PL);
// Shift the high part
- __ Asr(o_h, high, second_reg);
+ __ Asr(o_h, high, o_h);
} else {
+ __ and_(o_h, second_reg, ShifterOperand(kMaxLongShiftValue));
// same as Shr except we use `Lsr`s and not `Asr`s
- __ and_(second_reg, second_reg, ShifterOperand(63));
- __ Lsr(o_l, low, second_reg);
- __ rsb(temp, second_reg, ShifterOperand(32));
+ __ Lsr(o_l, low, o_h);
+ __ rsb(temp, o_h, ShifterOperand(kArmBitsPerWord));
__ Lsl(temp, high, temp);
__ orr(o_l, o_l, ShifterOperand(temp));
- __ subs(temp, second_reg, ShifterOperand(32));
+ __ subs(temp, o_h, ShifterOperand(kArmBitsPerWord));
__ it(PL);
__ Lsr(o_l, high, temp, false, PL);
- __ Lsr(o_h, high, second_reg);
+ __ Lsr(o_h, high, o_h);
}
break;
}
@@ -3400,7 +3402,8 @@
case Primitive::kPrimInt:
case Primitive::kPrimNot: {
- DCHECK_EQ(sizeof(mirror::HeapReference<mirror::Object>), sizeof(int32_t));
+ static_assert(sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
+ "art::mirror::HeapReference<mirror::Object> and int32_t have different sizes.");
uint32_t data_offset = mirror::Array::DataOffset(sizeof(int32_t)).Uint32Value();
Register out = locations->Out().AsRegister<Register>();
if (index.IsConstant()) {
diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h
index 5b4b375..953e733 100644
--- a/compiler/optimizing/code_generator_arm.h
+++ b/compiler/optimizing/code_generator_arm.h
@@ -32,6 +32,7 @@
// Use a local definition to prevent copying mistakes.
static constexpr size_t kArmWordSize = kArmPointerSize;
+static constexpr size_t kArmBitsPerWord = kArmWordSize * kBitsPerByte;
static constexpr Register kParameterCoreRegisters[] = { R1, R2, R3 };
static constexpr size_t kParameterCoreRegistersLength = arraysize(kParameterCoreRegisters);
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index e55de8f..a8f57cc 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -3521,7 +3521,8 @@
case Primitive::kPrimInt:
case Primitive::kPrimNot: {
- DCHECK_EQ(sizeof(mirror::HeapReference<mirror::Object>), sizeof(int32_t));
+ static_assert(sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
+ "art::mirror::HeapReference<mirror::Object> and int32_t have different sizes.");
uint32_t data_offset = mirror::Array::DataOffset(sizeof(int32_t)).Uint32Value();
CpuRegister out = locations->Out().AsRegister<CpuRegister>();
if (index.IsConstant()) {
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index 2b85c7c..7d723ef 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -96,7 +96,7 @@
class HGraphVisualizerDisassembler {
public:
HGraphVisualizerDisassembler(InstructionSet instruction_set, const uint8_t* base_address)
- : instruction_set_(instruction_set) {
+ : instruction_set_(instruction_set), disassembler_(nullptr) {
libart_disassembler_handle_ =
dlopen(kIsDebugBuild ? "libartd-disassembler.so" : "libart-disassembler.so", RTLD_NOW);
if (libart_disassembler_handle_ == nullptr) {
@@ -128,6 +128,10 @@
}
void Disassemble(std::ostream& output, size_t start, size_t end) const {
+ if (disassembler_ == nullptr) {
+ return;
+ }
+
const uint8_t* base = disassembler_->GetDisassemblerOptions()->base_address_;
if (instruction_set_ == kThumb2) {
// ARM and Thumb-2 use the same disassembler. The bottom bit of the
diff --git a/compiler/output_stream_test.cc b/compiler/output_stream_test.cc
index fbc9d0d..6104ccd 100644
--- a/compiler/output_stream_test.cc
+++ b/compiler/output_stream_test.cc
@@ -47,11 +47,12 @@
CheckOffset(6);
EXPECT_TRUE(output_stream_->WriteFully(buf, 4));
CheckOffset(10);
+ EXPECT_TRUE(output_stream_->WriteFully(buf, 6));
}
void CheckTestOutput(const std::vector<uint8_t>& actual) {
uint8_t expected[] = {
- 0, 0, 1, 2, 0, 0, 1, 2, 3, 4
+ 0, 0, 1, 2, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6
};
EXPECT_EQ(sizeof(expected), actual.size());
EXPECT_EQ(0, memcmp(expected, &actual[0], actual.size()));
@@ -75,11 +76,13 @@
TEST_F(OutputStreamTest, Buffered) {
ScratchFile tmp;
- std::unique_ptr<FileOutputStream> file_output_stream(new FileOutputStream(tmp.GetFile()));
- CHECK(file_output_stream.get() != nullptr);
- BufferedOutputStream buffered_output_stream(file_output_stream.release());
- SetOutputStream(buffered_output_stream);
- GenerateTestOutput();
+ {
+ std::unique_ptr<FileOutputStream> file_output_stream(new FileOutputStream(tmp.GetFile()));
+ CHECK(file_output_stream.get() != nullptr);
+ BufferedOutputStream buffered_output_stream(file_output_stream.release());
+ SetOutputStream(buffered_output_stream);
+ GenerateTestOutput();
+ }
std::unique_ptr<File> in(OS::OpenFileForReading(tmp.GetFilename().c_str()));
EXPECT_TRUE(in.get() != nullptr);
std::vector<uint8_t> actual(in->GetLength());
diff --git a/runtime/arch/x86/thread_x86.cc b/runtime/arch/x86/thread_x86.cc
index b97c143..3d19f06 100644
--- a/runtime/arch/x86/thread_x86.cc
+++ b/runtime/arch/x86/thread_x86.cc
@@ -79,7 +79,8 @@
}
#else
// Read current LDT entries.
- CHECK_EQ((size_t)LDT_ENTRY_SIZE, sizeof(uint64_t));
+ static_assert(static_cast<size_t>(LDT_ENTRY_SIZE) == sizeof(uint64_t),
+ "LDT_ENTRY_SIZE is different from sizeof(uint64_t).");
std::vector<uint64_t> ldt(LDT_ENTRIES);
size_t ldt_size(sizeof(uint64_t) * ldt.size());
memset(&ldt[0], 0, ldt_size);
diff --git a/runtime/base/hash_set.h b/runtime/base/hash_set.h
index 8daf6d4..f2c8355 100644
--- a/runtime/base/hash_set.h
+++ b/runtime/base/hash_set.h
@@ -469,8 +469,6 @@
}
// Resize based on the minimum load factor.
Resize(min_index);
- // When we hit elements_until_expand_, we are at the max load factor and must expand again.
- elements_until_expand_ = NumBuckets() * max_load_factor_;
}
// Expand / shrink the table to the new specified size.
@@ -493,11 +491,18 @@
if (owned_data) {
allocfn_.deallocate(old_data, old_num_buckets);
}
+
+ // When we hit elements_until_expand_, we are at the max load factor and must expand again.
+ elements_until_expand_ = NumBuckets() * max_load_factor_;
}
ALWAYS_INLINE size_t FirstAvailableSlot(size_t index) const {
+ DCHECK_LT(index, NumBuckets()); // Don't try to get a slot out of range.
+ size_t non_empty_count = 0;
while (!emptyfn_.IsEmpty(data_[index])) {
index = NextIndex(index);
+ non_empty_count++;
+ DCHECK_LE(non_empty_count, NumBuckets()); // Don't loop forever.
}
return index;
}
@@ -526,7 +531,7 @@
Pred pred_; // Equals function.
size_t num_elements_; // Number of inserted elements.
size_t num_buckets_; // Number of hash table buckets.
- size_t elements_until_expand_; // Maxmimum number of elements until we expand the table.
+ size_t elements_until_expand_; // Maximum number of elements until we expand the table.
bool owns_data_; // If we own data_ and are responsible for freeing it.
T* data_; // Backing storage.
double min_load_factor_;
diff --git a/runtime/base/hash_set_test.cc b/runtime/base/hash_set_test.cc
index e88637f..fd9eb45 100644
--- a/runtime/base/hash_set_test.cc
+++ b/runtime/base/hash_set_test.cc
@@ -156,6 +156,38 @@
}
}
+TEST_F(HashSetTest, TestShrink) {
+ HashSet<std::string, IsEmptyFnString> hash_set;
+ std::vector<std::string> strings = {"a", "b", "c", "d", "e", "f", "g"};
+ for (size_t i = 0; i < strings.size(); ++i) {
+ // Insert some strings into the beginning of our hash set to establish an initial size
+ hash_set.Insert(strings[i]);
+ }
+
+ hash_set.ShrinkToMaximumLoad();
+ const double initial_load = hash_set.CalculateLoadFactor();
+
+ // Insert a bunch of random strings to guarantee that we grow the capacity.
+ std::vector<std::string> random_strings;
+ static constexpr size_t count = 1000;
+ for (size_t i = 0; i < count; ++i) {
+ random_strings.push_back(RandomString(10));
+ hash_set.Insert(random_strings[i]);
+ }
+
+ // Erase all the extra strings which guarantees that our load factor will be really bad.
+ for (size_t i = 0; i < count; ++i) {
+ hash_set.Erase(hash_set.Find(random_strings[i]));
+ }
+
+ const double bad_load = hash_set.CalculateLoadFactor();
+ EXPECT_GT(initial_load, bad_load);
+
+ // Shrink again, the load factor should be good again.
+ hash_set.ShrinkToMaximumLoad();
+ EXPECT_DOUBLE_EQ(initial_load, hash_set.CalculateLoadFactor());
+}
+
TEST_F(HashSetTest, TestStress) {
HashSet<std::string, IsEmptyFnString> hash_set;
std::unordered_multiset<std::string> std_set;
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 0ab148e..aa91ca1 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -61,6 +61,7 @@
kAbortLock,
kJdwpSocketLock,
kRegionSpaceRegionLock,
+ kTransactionLogLock,
kReferenceQueueSoftReferencesLock,
kReferenceQueuePhantomReferencesLock,
kReferenceQueueFinalizerReferencesLock,
@@ -77,7 +78,6 @@
kDexFileMethodInlinerLock,
kDexFileToMethodInlinerMapLock,
kMarkSweepMarkStackLock,
- kTransactionLogLock,
kInternTableLock,
kOatFileSecondaryLookupLock,
kDefaultMutexLevel,
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 98fa897..91b5000 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1310,7 +1310,7 @@
// reinit references to when reinitializing a ClassLinker from a
// mapped image.
void ClassLinker::VisitRoots(RootVisitor* visitor, VisitRootFlags flags) {
- class_roots_.VisitRoot(visitor, RootInfo(kRootVMInternal));
+ class_roots_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
Thread* const self = Thread::Current();
{
ReaderMutexLock mu(self, dex_lock_);
@@ -1333,9 +1333,9 @@
}
}
VisitClassRoots(visitor, flags);
- array_iftable_.VisitRoot(visitor, RootInfo(kRootVMInternal));
- for (size_t i = 0; i < kFindArrayCacheSize; ++i) {
- find_array_class_cache_[i].VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
+ array_iftable_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
+ for (GcRoot<mirror::Class>& root : find_array_class_cache_) {
+ root.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
}
}
@@ -4928,8 +4928,7 @@
}
}
if (miranda_method == nullptr) {
- size_t size = ArtMethod::ObjectSize(image_pointer_size_);
- miranda_method = reinterpret_cast<ArtMethod*>(allocator.Alloc(size));
+ miranda_method = reinterpret_cast<ArtMethod*>(allocator.Alloc(method_size));
CHECK(miranda_method != nullptr);
// Point the interface table at a phantom slot.
new(miranda_method) ArtMethod(*interface_method, image_pointer_size_);
@@ -4970,50 +4969,49 @@
}
StrideIterator<ArtMethod> out(
reinterpret_cast<uintptr_t>(virtuals) + old_method_count * method_size, method_size);
- // Copy the mirada methods before making a copy of the vtable so that moving GC doesn't miss
- // any roots. This is necessary since these miranda methods wont get their roots visited from
- // the class table root visiting until they are copied to the new virtuals array.
- const size_t old_vtable_count = vtable->GetLength();
- const size_t new_vtable_count = old_vtable_count + miranda_methods.size();
- size_t method_idx = old_vtable_count;
- for (auto* mir_method : miranda_methods) {
- ArtMethod* out_method = &*out;
- // Leave the declaring class alone as type indices are relative to it
- out_method->CopyFrom(mir_method, image_pointer_size_);
- out_method->SetAccessFlags(out_method->GetAccessFlags() | kAccMiranda);
- out_method->SetMethodIndex(0xFFFF & method_idx);
- move_table.emplace(mir_method, out_method);
+ // Copy over miranda methods before copying vtable since CopyOf may cause thread suspension and
+ // we want the roots of the miranda methods to get visited.
+ for (ArtMethod* mir_method : miranda_methods) {
+ out->CopyFrom(mir_method, image_pointer_size_);
+ out->SetAccessFlags(out->GetAccessFlags() | kAccMiranda);
+ move_table.emplace(mir_method, &*out);
++out;
- ++method_idx;
}
- DCHECK_EQ(new_vtable_count, method_idx);
UpdateClassVirtualMethods(klass.Get(), virtuals, new_method_count);
- // Done copying methods, they are all reachable from the class now, so we can end the no thread
+ // Done copying methods, they are all roots in the class now, so we can end the no thread
// suspension assert.
self->EndAssertNoThreadSuspension(old_cause);
+
+ const size_t old_vtable_count = vtable->GetLength();
+ const size_t new_vtable_count = old_vtable_count + miranda_methods.size();
+ miranda_methods.clear();
vtable.Assign(down_cast<mirror::PointerArray*>(vtable->CopyOf(self, new_vtable_count)));
if (UNLIKELY(vtable.Get() == nullptr)) {
self->AssertPendingOOMException();
return false;
}
+ out = StrideIterator<ArtMethod>(
+ reinterpret_cast<uintptr_t>(virtuals) + old_method_count * method_size, method_size);
+ size_t vtable_pos = old_vtable_count;
+ for (size_t i = old_method_count; i < new_method_count; ++i) {
+ // Leave the declaring class alone as type indices are relative to it
+ out->SetMethodIndex(0xFFFF & vtable_pos);
+ vtable->SetElementPtrSize(vtable_pos, &*out, image_pointer_size_);
+ ++out;
+ ++vtable_pos;
+ }
+ CHECK_EQ(vtable_pos, new_vtable_count);
// Update old vtable methods.
- for (method_idx = 0; method_idx < old_vtable_count; ++method_idx) {
- auto* m = vtable->GetElementPtrSize<ArtMethod*>(method_idx, image_pointer_size_);
+ for (size_t i = 0; i < old_vtable_count; ++i) {
+ auto* m = vtable->GetElementPtrSize<ArtMethod*>(i, image_pointer_size_);
DCHECK(m != nullptr) << PrettyClass(klass.Get());
auto it = move_table.find(m);
if (it != move_table.end()) {
auto* new_m = it->second;
DCHECK(new_m != nullptr) << PrettyClass(klass.Get());
- vtable->SetElementPtrSize(method_idx, new_m, image_pointer_size_);
+ vtable->SetElementPtrSize(i, new_m, image_pointer_size_);
}
}
- // Update miranda methods.
- out = StrideIterator<ArtMethod>(
- reinterpret_cast<uintptr_t>(virtuals) + old_method_count * method_size, method_size);
- for (; method_idx < new_vtable_count; ++method_idx) {
- vtable->SetElementPtrSize(method_idx, &*out, image_pointer_size_);
- ++out;
- }
klass->SetVTable(vtable.Get());
// Go fix up all the stale miranda pointers.
@@ -5214,7 +5212,7 @@
ArtField* field = &fields[i];
VLOG(class_linker) << "LinkFields: " << (is_static ? "static" : "instance")
<< " class=" << PrettyClass(klass.Get()) << " field=" << PrettyField(field) << " offset="
- << field->GetOffset();
+ << field->GetOffsetDuringLinking();
if (i != 0) {
ArtField* const prev_field = &fields[i - 1];
// NOTE: The field names can be the same. This is not possible in the Java language
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 5918c10..f04f356 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -54,10 +54,6 @@
#include "verifier/method_verifier-inl.h"
#include "well_known_classes.h"
-#ifdef HAVE_ANDROID_OS
-#include "cutils/properties.h"
-#endif
-
namespace art {
// The key identifying the debugger to update instrumentation.
@@ -65,22 +61,7 @@
// Limit alloc_record_count to the 2BE value (64k-1) that is the limit of the current protocol.
static uint16_t CappedAllocRecordCount(size_t alloc_record_count) {
- size_t cap = 0xffff;
-#ifdef HAVE_ANDROID_OS
- // Check whether there's a system property overriding the number of recent records.
- const char* propertyName = "dalvik.vm.recentAllocMax";
- char recentAllocMaxString[PROPERTY_VALUE_MAX];
- if (property_get(propertyName, recentAllocMaxString, "") > 0) {
- char* end;
- size_t value = strtoul(recentAllocMaxString, &end, 10);
- if (*end != '\0') {
- LOG(ERROR) << "Ignoring " << propertyName << " '" << recentAllocMaxString
- << "' --- invalid";
- } else {
- cap = value;
- }
- }
-#endif
+ const size_t cap = 0xffff;
if (alloc_record_count > cap) {
return cap;
}
@@ -3942,7 +3923,7 @@
<< " arg_count=" << pReq->arg_count;
CHECK(m != nullptr);
- CHECK_EQ(sizeof(jvalue), sizeof(uint64_t));
+ static_assert(sizeof(jvalue) == sizeof(uint64_t), "jvalue and uint64_t have different sizes.");
// Invoke the method.
ScopedLocalRef<jobject> ref(soa.Env(), soa.AddLocalReference<jobject>(pReq->receiver.Read()));
@@ -4725,7 +4706,7 @@
gc::AllocRecordObjectMap* records = Runtime::Current()->GetHeap()->GetAllocationRecords();
CHECK(records != nullptr);
- const uint16_t capped_count = CappedAllocRecordCount(records->Size());
+ const uint16_t capped_count = CappedAllocRecordCount(records->GetRecentAllocationSize());
uint16_t count = capped_count;
LOG(INFO) << "Tracked allocations, (count=" << count << ")";
@@ -4863,7 +4844,7 @@
StringTable method_names;
StringTable filenames;
- const uint16_t capped_count = CappedAllocRecordCount(records->Size());
+ const uint16_t capped_count = CappedAllocRecordCount(records->GetRecentAllocationSize());
uint16_t count = capped_count;
for (auto it = records->RBegin(), end = records->REnd();
count > 0 && it != end; count--, it++) {
diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc
index 5fa58f7..90b8fdb 100644
--- a/runtime/dex_file_verifier.cc
+++ b/runtime/dex_file_verifier.cc
@@ -694,25 +694,52 @@
std::unordered_set<uint32_t> direct_method_indexes;
// These calls use the raw access flags to check whether the whole dex field is valid.
-
+ uint32_t prev_index = 0;
for (; it.HasNextStaticField(); it.Next()) {
- if (!CheckClassDataItemField(it.GetMemberIndex(), it.GetRawMemberAccessFlags(), true)) {
+ uint32_t curr_index = it.GetMemberIndex();
+ if (curr_index < prev_index) {
+ ErrorStringPrintf("out-of-order static field indexes %d and %d", prev_index, curr_index);
+ return false;
+ }
+ prev_index = curr_index;
+ if (!CheckClassDataItemField(curr_index, it.GetRawMemberAccessFlags(), true)) {
return false;
}
}
+ prev_index = 0;
for (; it.HasNextInstanceField(); it.Next()) {
- if (!CheckClassDataItemField(it.GetMemberIndex(), it.GetRawMemberAccessFlags(), false)) {
+ uint32_t curr_index = it.GetMemberIndex();
+ if (curr_index < prev_index) {
+ ErrorStringPrintf("out-of-order instance field indexes %d and %d", prev_index, curr_index);
+ return false;
+ }
+ prev_index = curr_index;
+ if (!CheckClassDataItemField(curr_index, it.GetRawMemberAccessFlags(), false)) {
return false;
}
}
+ prev_index = 0;
for (; it.HasNextDirectMethod(); it.Next()) {
- if (!CheckClassDataItemMethod(it.GetMemberIndex(), it.GetRawMemberAccessFlags(),
+ uint32_t curr_index = it.GetMemberIndex();
+ if (curr_index < prev_index) {
+ ErrorStringPrintf("out-of-order direct method indexes %d and %d", prev_index, curr_index);
+ return false;
+ }
+ prev_index = curr_index;
+ if (!CheckClassDataItemMethod(curr_index, it.GetRawMemberAccessFlags(),
it.GetMethodCodeItemOffset(), direct_method_indexes, true)) {
return false;
}
}
+ prev_index = 0;
for (; it.HasNextVirtualMethod(); it.Next()) {
- if (!CheckClassDataItemMethod(it.GetMemberIndex(), it.GetRawMemberAccessFlags(),
+ uint32_t curr_index = it.GetMemberIndex();
+ if (curr_index < prev_index) {
+ ErrorStringPrintf("out-of-order virtual method indexes %d and %d", prev_index, curr_index);
+ return false;
+ }
+ prev_index = curr_index;
+ if (!CheckClassDataItemMethod(curr_index, it.GetRawMemberAccessFlags(),
it.GetMethodCodeItemOffset(), direct_method_indexes, false)) {
return false;
}
diff --git a/runtime/gc/allocation_record.cc b/runtime/gc/allocation_record.cc
index a385363..ac7de63 100644
--- a/runtime/gc/allocation_record.cc
+++ b/runtime/gc/allocation_record.cc
@@ -45,10 +45,29 @@
<< "' --- invalid";
} else {
alloc_record_max_ = value;
+ if (recent_record_max_ > value) {
+ recent_record_max_ = value;
+ }
+ }
+ }
+ // Check whether there's a system property overriding the number of recent records.
+ propertyName = "dalvik.vm.recentAllocMax";
+ char recentAllocMaxString[PROPERTY_VALUE_MAX];
+ if (property_get(propertyName, recentAllocMaxString, "") > 0) {
+ char* end;
+ size_t value = strtoul(recentAllocMaxString, &end, 10);
+ if (*end != '\0') {
+ LOG(ERROR) << "Ignoring " << propertyName << " '" << recentAllocMaxString
+ << "' --- invalid";
+ } else if (value > alloc_record_max_) {
+ LOG(ERROR) << "Ignoring " << propertyName << " '" << recentAllocMaxString
+ << "' --- should be less than " << alloc_record_max_;
+ } else {
+ recent_record_max_ = value;
}
}
// Check whether there's a system property overriding the max depth of stack trace.
- propertyName = "dalvik.vm.allocStackDepth";
+ propertyName = "debug.allocTracker.stackDepth";
char stackDepthString[PROPERTY_VALUE_MAX];
if (property_get(propertyName, stackDepthString, "") > 0) {
char* end;
@@ -56,6 +75,10 @@
if (*end != '\0') {
LOG(ERROR) << "Ignoring " << propertyName << " '" << stackDepthString
<< "' --- invalid";
+ } else if (value > kMaxSupportedStackDepth) {
+ LOG(WARNING) << propertyName << " '" << stackDepthString << "' too large, using "
+ << kMaxSupportedStackDepth;
+ max_stack_depth_ = kMaxSupportedStackDepth;
} else {
max_stack_depth_ = value;
}
@@ -67,6 +90,20 @@
STLDeleteValues(&entries_);
}
+void AllocRecordObjectMap::VisitRoots(RootVisitor* visitor) {
+ CHECK_LE(recent_record_max_, alloc_record_max_);
+ BufferedRootVisitor<kDefaultBufferedRootCount> buffered_visitor(visitor, RootInfo(kRootDebugger));
+ size_t count = recent_record_max_;
+ // Only visit the last recent_record_max_ number of objects in entries_.
+ // They need to be retained for DDMS's recent allocation tracking.
+ // TODO: This will cause 098-ddmc test to run out of memory for GC stress test.
+ // There should be an option that do not keep these objects live if allocation tracking is only
+ // for the purpose of an HPROF dump. b/20037135
+ for (auto it = entries_.rbegin(), end = entries_.rend(); count > 0 && it != end; count--, ++it) {
+ buffered_visitor.VisitRoot(it->first);
+ }
+}
+
void AllocRecordObjectMap::SweepAllocationRecords(IsMarkedCallback* callback, void* arg) {
VLOG(heap) << "Start SweepAllocationRecords()";
size_t count_deleted = 0, count_moved = 0;
@@ -139,6 +176,7 @@
if (self_name == "JDWP") {
records->alloc_ddm_thread_id_ = self->GetTid();
}
+ records->scratch_trace_.SetDepth(records->max_stack_depth_);
size_t sz = sizeof(AllocRecordStackTraceElement) * records->max_stack_depth_ +
sizeof(AllocRecord) + sizeof(AllocRecordStackTrace);
LOG(INFO) << "Enabling alloc tracker (" << records->alloc_record_max_ << " entries of "
@@ -181,19 +219,14 @@
DCHECK_LE(records->Size(), records->alloc_record_max_);
- // Remove oldest record.
- if (records->Size() == records->alloc_record_max_) {
- records->RemoveOldest();
- }
-
// Get stack trace.
- const size_t max_depth = records->max_stack_depth_;
- AllocRecordStackTrace* trace = new AllocRecordStackTrace(self->GetTid(), max_depth);
- // add scope to make "visitor" destroyed promptly, in order to set the trace->depth_
+ // add scope to make "visitor" destroyed promptly, in order to set the scratch_trace_->depth_
{
- AllocRecordStackVisitor visitor(self, trace, max_depth);
+ AllocRecordStackVisitor visitor(self, &records->scratch_trace_, records->max_stack_depth_);
visitor.WalkStack();
}
+ records->scratch_trace_.SetTid(self->GetTid());
+ AllocRecordStackTrace* trace = new AllocRecordStackTrace(records->scratch_trace_);
// Fill in the basics.
AllocRecord* record = new AllocRecord(byte_count, trace);
diff --git a/runtime/gc/allocation_record.h b/runtime/gc/allocation_record.h
index 45b3406..5214b6b 100644
--- a/runtime/gc/allocation_record.h
+++ b/runtime/gc/allocation_record.h
@@ -71,8 +71,15 @@
public:
static constexpr size_t kHashMultiplier = 17;
- AllocRecordStackTrace(pid_t tid, size_t max_depth)
- : tid_(tid), depth_(0), stack_(new AllocRecordStackTraceElement[max_depth]) {}
+ explicit AllocRecordStackTrace(size_t max_depth)
+ : tid_(0), depth_(0), stack_(new AllocRecordStackTraceElement[max_depth]) {}
+
+ AllocRecordStackTrace(const AllocRecordStackTrace& r)
+ : tid_(r.tid_), depth_(r.depth_), stack_(new AllocRecordStackTraceElement[r.depth_]) {
+ for (size_t i = 0; i < depth_; ++i) {
+ stack_[i] = r.stack_[i];
+ }
+ }
~AllocRecordStackTrace() {
delete[] stack_;
@@ -82,6 +89,10 @@
return tid_;
}
+ void SetTid(pid_t t) {
+ tid_ = t;
+ }
+
size_t GetDepth() const {
return depth_;
}
@@ -102,6 +113,7 @@
bool operator==(const AllocRecordStackTrace& other) const {
if (this == &other) return true;
+ if (tid_ != other.tid_) return false;
if (depth_ != other.depth_) return false;
for (size_t i = 0; i < depth_; ++i) {
if (!(stack_[i] == other.stack_[i])) return false;
@@ -110,7 +122,7 @@
}
private:
- const pid_t tid_;
+ pid_t tid_;
size_t depth_;
AllocRecordStackTraceElement* const stack_;
};
@@ -200,7 +212,9 @@
AllocRecordObjectMap() EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_)
: alloc_record_max_(kDefaultNumAllocRecords),
+ recent_record_max_(kDefaultNumRecentRecords),
max_stack_depth_(kDefaultAllocStackDepth),
+ scratch_trace_(kMaxSupportedStackDepth),
alloc_ddm_thread_id_(0) {}
~AllocRecordObjectMap();
@@ -208,6 +222,10 @@
void Put(mirror::Object* obj, AllocRecord* record)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) {
+ if (entries_.size() == alloc_record_max_) {
+ delete entries_.front().second;
+ entries_.pop_front();
+ }
entries_.emplace_back(GcRoot<mirror::Object>(obj), record);
}
@@ -215,17 +233,19 @@
return entries_.size();
}
- void SweepAllocationRecords(IsMarkedCallback* callback, void* arg)
+ size_t GetRecentAllocationSize() const SHARED_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) {
+ CHECK_LE(recent_record_max_, alloc_record_max_);
+ size_t sz = entries_.size();
+ return std::min(recent_record_max_, sz);
+ }
+
+ void VisitRoots(RootVisitor* visitor)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_);
- void RemoveOldest()
+ void SweepAllocationRecords(IsMarkedCallback* callback, void* arg)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
- EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_) {
- DCHECK(!entries_.empty());
- delete entries_.front().second;
- entries_.pop_front();
- }
+ EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_);
// TODO: Is there a better way to hide the entries_'s type?
EntryList::iterator Begin()
@@ -254,12 +274,13 @@
private:
static constexpr size_t kDefaultNumAllocRecords = 512 * 1024;
- static constexpr size_t kDefaultAllocStackDepth = 4;
+ static constexpr size_t kDefaultNumRecentRecords = 64 * 1024 - 1;
+ static constexpr size_t kDefaultAllocStackDepth = 16;
+ static constexpr size_t kMaxSupportedStackDepth = 128;
size_t alloc_record_max_ GUARDED_BY(Locks::alloc_tracker_lock_);
- // The implementation always allocates max_stack_depth_ number of frames for each stack trace.
- // As long as the max depth is not very large, this is not a waste of memory since most stack
- // traces will fill up the max depth number of the frames.
+ size_t recent_record_max_ GUARDED_BY(Locks::alloc_tracker_lock_);
size_t max_stack_depth_ GUARDED_BY(Locks::alloc_tracker_lock_);
+ AllocRecordStackTrace scratch_trace_ GUARDED_BY(Locks::alloc_tracker_lock_);
pid_t alloc_ddm_thread_id_ GUARDED_BY(Locks::alloc_tracker_lock_);
EntryList entries_ GUARDED_BY(Locks::alloc_tracker_lock_);
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index ee4568e..0ed3b6d 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -175,6 +175,13 @@
} else {
DCHECK(!IsAllocTrackingEnabled());
}
+ if (kInstrumented) {
+ if (gc_stress_mode_) {
+ CheckGcStressMode(self, &obj);
+ }
+ } else {
+ DCHECK(!gc_stress_mode_);
+ }
// IsConcurrentGc() isn't known at compile time so we can optimize by not checking it for
// the BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
// optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant since
@@ -392,7 +399,7 @@
// Zygote resulting in it being prematurely freed.
// We can only do this for primitive objects since large objects will not be within the card table
// range. This also means that we rely on SetClass not dirtying the object's card.
- return byte_count >= large_object_threshold_ && c->IsPrimitiveArray();
+ return byte_count >= large_object_threshold_ && (c->IsPrimitiveArray() || c->IsStringClass());
}
template <bool kGrow>
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 3c020e2..f0ba0bd 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -21,6 +21,7 @@
#include <limits>
#include <memory>
+#include <unwind.h> // For GC verification.
#include <vector>
#include "art_field-inl.h"
@@ -125,7 +126,8 @@
bool ignore_max_footprint, bool use_tlab,
bool verify_pre_gc_heap, bool verify_pre_sweeping_heap, bool verify_post_gc_heap,
bool verify_pre_gc_rosalloc, bool verify_pre_sweeping_rosalloc,
- bool verify_post_gc_rosalloc, bool use_homogeneous_space_compaction_for_oom,
+ bool verify_post_gc_rosalloc, bool gc_stress_mode,
+ bool use_homogeneous_space_compaction_for_oom,
uint64_t min_interval_homogeneous_space_compaction_by_oom)
: non_moving_space_(nullptr),
rosalloc_space_(nullptr),
@@ -170,6 +172,7 @@
verify_pre_gc_rosalloc_(verify_pre_gc_rosalloc),
verify_pre_sweeping_rosalloc_(verify_pre_sweeping_rosalloc),
verify_post_gc_rosalloc_(verify_post_gc_rosalloc),
+ gc_stress_mode_(gc_stress_mode),
/* For GC a lot mode, we limit the allocations stacks to be kGcAlotInterval allocations. This
* causes a lot of GC since we do a GC for alloc whenever the stack is full. When heap
* verification is enabled, we limit the size of allocation stacks to speed up their
@@ -210,13 +213,17 @@
gc_count_rate_histogram_("gc count rate histogram", 1U, kGcCountRateMaxBucketCount),
blocking_gc_count_rate_histogram_("blocking gc count rate histogram", 1U,
kGcCountRateMaxBucketCount),
- alloc_tracking_enabled_(false) {
+ alloc_tracking_enabled_(false),
+ backtrace_lock_(nullptr),
+ seen_backtrace_count_(0u),
+ unique_backtrace_count_(0u) {
if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
LOG(INFO) << "Heap() entering";
}
+ Runtime* const runtime = Runtime::Current();
// If we aren't the zygote, switch to the default non zygote allocator. This may update the
// entrypoints.
- const bool is_zygote = Runtime::Current()->IsZygote();
+ const bool is_zygote = runtime->IsZygote();
if (!is_zygote) {
// Background compaction is currently not supported for command line runs.
if (background_collector_type_ != foreground_collector_type_) {
@@ -508,8 +515,12 @@
LOG(FATAL) << "There's a gap between the image space and the non-moving space";
}
}
- if (running_on_valgrind_) {
- Runtime::Current()->GetInstrumentation()->InstrumentQuickAllocEntryPoints();
+ instrumentation::Instrumentation* const instrumentation = runtime->GetInstrumentation();
+ if (gc_stress_mode_) {
+ backtrace_lock_ = new Mutex("GC complete lock");
+ }
+ if (running_on_valgrind_ || gc_stress_mode_) {
+ instrumentation->InstrumentQuickAllocEntryPoints();
}
if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
LOG(INFO) << "Heap() exiting";
@@ -1074,6 +1085,12 @@
STLDeleteElements(&discontinuous_spaces_);
delete gc_complete_lock_;
delete pending_task_lock_;
+ delete backtrace_lock_;
+ if (unique_backtrace_count_.LoadRelaxed() != 0 || seen_backtrace_count_.LoadRelaxed() != 0) {
+ LOG(INFO) << "gc stress unique=" << unique_backtrace_count_.LoadRelaxed()
+ << " total=" << seen_backtrace_count_.LoadRelaxed() +
+ unique_backtrace_count_.LoadRelaxed();
+ }
VLOG(heap) << "Finished ~Heap()";
}
@@ -3681,6 +3698,15 @@
allocation_records_.reset(records);
}
+void Heap::VisitAllocationRecords(RootVisitor* visitor) const {
+ if (IsAllocTrackingEnabled()) {
+ MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_);
+ if (IsAllocTrackingEnabled()) {
+ GetAllocationRecords()->VisitRoots(visitor);
+ }
+ }
+}
+
void Heap::SweepAllocationRecords(IsMarkedCallback* visitor, void* arg) const {
if (IsAllocTrackingEnabled()) {
MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_);
@@ -3690,5 +3716,73 @@
}
}
+// Based on debug malloc logic from libc/bionic/debug_stacktrace.cpp.
+class StackCrawlState {
+ public:
+ StackCrawlState(uintptr_t* frames, size_t max_depth, size_t skip_count)
+ : frames_(frames), frame_count_(0), max_depth_(max_depth), skip_count_(skip_count) {
+ }
+ size_t GetFrameCount() const {
+ return frame_count_;
+ }
+ static _Unwind_Reason_Code Callback(_Unwind_Context* context, void* arg) {
+ auto* const state = reinterpret_cast<StackCrawlState*>(arg);
+ const uintptr_t ip = _Unwind_GetIP(context);
+ // The first stack frame is get_backtrace itself. Skip it.
+ if (ip != 0 && state->skip_count_ > 0) {
+ --state->skip_count_;
+ return _URC_NO_REASON;
+ }
+ // ip may be off for ARM but it shouldn't matter since we only use it for hashing.
+ state->frames_[state->frame_count_] = ip;
+ state->frame_count_++;
+ return state->frame_count_ >= state->max_depth_ ? _URC_END_OF_STACK : _URC_NO_REASON;
+ }
+
+ private:
+ uintptr_t* const frames_;
+ size_t frame_count_;
+ const size_t max_depth_;
+ size_t skip_count_;
+};
+
+static size_t get_backtrace(uintptr_t* frames, size_t max_depth) {
+ StackCrawlState state(frames, max_depth, 0u);
+ _Unwind_Backtrace(&StackCrawlState::Callback, &state);
+ return state.GetFrameCount();
+}
+
+void Heap::CheckGcStressMode(Thread* self, mirror::Object** obj) {
+ auto* const runtime = Runtime::Current();
+ if (gc_stress_mode_ && runtime->GetClassLinker()->IsInitialized() &&
+ !runtime->IsActiveTransaction() && mirror::Class::HasJavaLangClass()) {
+ // Check if we should GC.
+ bool new_backtrace = false;
+ {
+ static constexpr size_t kMaxFrames = 16u;
+ uintptr_t backtrace[kMaxFrames];
+ const size_t frames = get_backtrace(backtrace, kMaxFrames);
+ uint64_t hash = 0;
+ for (size_t i = 0; i < frames; ++i) {
+ hash = hash * 2654435761 + backtrace[i];
+ hash += (hash >> 13) ^ (hash << 6);
+ }
+ MutexLock mu(self, *backtrace_lock_);
+ new_backtrace = seen_backtraces_.find(hash) == seen_backtraces_.end();
+ if (new_backtrace) {
+ seen_backtraces_.insert(hash);
+ }
+ }
+ if (new_backtrace) {
+ StackHandleScope<1> hs(self);
+ auto h = hs.NewHandleWrapper(obj);
+ CollectGarbage(false);
+ unique_backtrace_count_.FetchAndAddSequentiallyConsistent(1);
+ } else {
+ seen_backtrace_count_.FetchAndAddSequentiallyConsistent(1);
+ }
+ }
+}
+
} // namespace gc
} // namespace art
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 18244c8..54a3235 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -19,6 +19,7 @@
#include <iosfwd>
#include <string>
+#include <unordered_set>
#include <vector>
#include "allocator_type.h"
@@ -181,7 +182,8 @@
bool ignore_max_footprint, bool use_tlab,
bool verify_pre_gc_heap, bool verify_pre_sweeping_heap, bool verify_post_gc_heap,
bool verify_pre_gc_rosalloc, bool verify_pre_sweeping_rosalloc,
- bool verify_post_gc_rosalloc, bool use_homogeneous_space_compaction,
+ bool verify_post_gc_rosalloc, bool gc_stress_mode,
+ bool use_homogeneous_space_compaction,
uint64_t min_interval_homogeneous_space_compaction_by_oom);
~Heap();
@@ -703,6 +705,9 @@
void SetAllocationRecords(AllocRecordObjectMap* records)
EXCLUSIVE_LOCKS_REQUIRED(Locks::alloc_tracker_lock_);
+ void VisitAllocationRecords(RootVisitor* visitor) const
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
void SweepAllocationRecords(IsMarkedCallback* visitor, void* arg) const
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
@@ -909,6 +914,10 @@
void UpdateGcCountRateHistograms() EXCLUSIVE_LOCKS_REQUIRED(gc_complete_lock_);
+ // GC stress mode attempts to do one GC per unique backtrace.
+ void CheckGcStressMode(Thread* self, mirror::Object** obj)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
// All-known continuous spaces, where objects lie within fixed bounds.
std::vector<space::ContinuousSpace*> continuous_spaces_;
@@ -1064,6 +1073,7 @@
bool verify_pre_gc_rosalloc_;
bool verify_pre_sweeping_rosalloc_;
bool verify_post_gc_rosalloc_;
+ const bool gc_stress_mode_;
// RAII that temporarily disables the rosalloc verification during
// the zygote fork.
@@ -1219,6 +1229,14 @@
std::unique_ptr<AllocRecordObjectMap> allocation_records_
GUARDED_BY(Locks::alloc_tracker_lock_);
+ // GC stress related data structures.
+ Mutex* backtrace_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ // Debugging variables, seen backtraces vs unique backtraces.
+ Atomic<uint64_t> seen_backtrace_count_;
+ Atomic<uint64_t> unique_backtrace_count_;
+ // Stack trace hashes that we already saw,
+ std::unordered_set<uint64_t> seen_backtraces_ GUARDED_BY(backtrace_lock_);
+
friend class CollectorTransitionTask;
friend class collector::GarbageCollector;
friend class collector::MarkCompact;
diff --git a/runtime/gc_root.h b/runtime/gc_root.h
index d6146f3..bb604f0 100644
--- a/runtime/gc_root.h
+++ b/runtime/gc_root.h
@@ -46,7 +46,9 @@
kRootMonitorUsed,
kRootThreadObject,
kRootInternedString,
+ kRootFinalizing, // used for HPROF's conversion to HprofHeapTag
kRootDebugger,
+ kRootReferenceCleanup, // used for HPROF's conversion to HprofHeapTag
kRootVMInternal,
kRootJNIMonitor,
};
diff --git a/runtime/indenter.h b/runtime/indenter.h
index d055d4e..38b398d 100644
--- a/runtime/indenter.h
+++ b/runtime/indenter.h
@@ -27,45 +27,76 @@
class Indenter : public std::streambuf {
public:
Indenter(std::streambuf* out, char text, size_t count)
- : indent_next_(true), out_sbuf_(out), text_(text), count_(count) {}
+ : indent_next_(true), out_sbuf_(out),
+ text_{text, text, text, text, text, text, text, text}, // NOLINT(whitespace/braces)
+ count_(count) {}
private:
- int_type overflow(int_type c) {
+ std::streamsize xsputn(const char* s, std::streamsize n) OVERRIDE {
+ std::streamsize result = n; // Aborts on failure.
+ const char* eol = static_cast<const char*>(memchr(s, '\n', n));
+ while (eol != nullptr) {
+ size_t to_write = eol + 1 - s;
+ Write(s, to_write);
+ s += to_write;
+ n -= to_write;
+ indent_next_ = true;
+ eol = static_cast<const char*>(memchr(s, '\n', n));
+ }
+ if (n != 0u) {
+ Write(s, n);
+ }
+ return result;
+ }
+
+ int_type overflow(int_type c) OVERRIDE {
if (UNLIKELY(c == std::char_traits<char>::eof())) {
out_sbuf_->pubsync();
return c;
}
- if (indent_next_) {
- for (size_t i = 0; i < count_; ++i) {
- int_type r = out_sbuf_->sputc(text_);
- if (UNLIKELY(r != text_)) {
- out_sbuf_->pubsync();
- r = out_sbuf_->sputc(text_);
- CHECK_EQ(r, text_) << "Error writing to buffer. Disk full?";
- }
- }
- }
+ char data[1] = { static_cast<char>(c) };
+ Write(data, 1u);
indent_next_ = (c == '\n');
- int_type r = out_sbuf_->sputc(c);
- if (UNLIKELY(r != c)) {
- out_sbuf_->pubsync();
- r = out_sbuf_->sputc(c);
- CHECK_EQ(r, c) << "Error writing to buffer. Disk full?";
- }
- return r;
+ return c;
}
int sync() {
return out_sbuf_->pubsync();
}
+ void Write(const char* s, std::streamsize n) {
+ if (indent_next_) {
+ size_t remaining = count_;
+ while (remaining != 0u) {
+ size_t to_write = std::min(remaining, sizeof(text_));
+ RawWrite(text_, to_write);
+ remaining -= to_write;
+ }
+ indent_next_ = false;
+ }
+ RawWrite(s, n);
+ }
+
+ void RawWrite(const char* s, std::streamsize n) {
+ size_t written = out_sbuf_->sputn(s, n);
+ s += written;
+ n -= written;
+ while (n != 0u) {
+ out_sbuf_->pubsync();
+ written = out_sbuf_->sputn(s, n);
+ CHECK_NE(written, 0u) << "Error writing to buffer. Disk full?";
+ s += written;
+ n -= written;
+ }
+ }
+
bool indent_next_;
// Buffer to write output to.
std::streambuf* const out_sbuf_;
// Text output as indent.
- const char text_;
+ const char text_[8];
// Number of times text is output.
const size_t count_;
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index 8c9222f..7f89b1d 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -552,7 +552,8 @@
template<VerifyObjectFlags kVerifyFlags>
inline Primitive::Type Class::GetPrimitiveType() {
- DCHECK_EQ(sizeof(Primitive::Type), sizeof(int32_t));
+ static_assert(sizeof(Primitive::Type) == sizeof(int32_t),
+ "art::Primitive::Type and int32_t have different sizes.");
int32_t v32 = GetField32<kVerifyFlags>(OFFSET_OF_OBJECT_MEMBER(Class, primitive_type_));
Primitive::Type type = static_cast<Primitive::Type>(v32 & 0xFFFF);
DCHECK_EQ(static_cast<size_t>(v32 >> 16), Primitive::ComponentSizeShift(type));
@@ -561,7 +562,8 @@
template<VerifyObjectFlags kVerifyFlags>
inline size_t Class::GetPrimitiveTypeSizeShift() {
- DCHECK_EQ(sizeof(Primitive::Type), sizeof(int32_t));
+ static_assert(sizeof(Primitive::Type) == sizeof(int32_t),
+ "art::Primitive::Type and int32_t have different sizes.");
int32_t v32 = GetField32<kVerifyFlags>(OFFSET_OF_OBJECT_MEMBER(Class, primitive_type_));
size_t size_shift = static_cast<Primitive::Type>(v32 >> 16);
DCHECK_EQ(size_shift, Primitive::ComponentSizeShift(static_cast<Primitive::Type>(v32 & 0xFFFF)));
@@ -757,7 +759,7 @@
}
inline void Class::SetSlowPath(bool enabled) {
- SetFieldBoolean<false>(GetSlowPathFlagOffset(), enabled);
+ SetFieldBoolean<false, false>(GetSlowPathFlagOffset(), enabled);
}
inline void Class::InitializeClassVisitor::operator()(
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 551e7e2..ba0a9fc 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -1030,10 +1030,14 @@
}
static Class* GetJavaLangClass() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- DCHECK(!java_lang_Class_.IsNull());
+ DCHECK(HasJavaLangClass());
return java_lang_Class_.Read();
}
+ static bool HasJavaLangClass() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return !java_lang_Class_.IsNull();
+ }
+
// Can't call this SetClass or else gets called instead of Object::SetClass in places.
static void SetClassClass(Class* java_lang_Class) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
static void ResetClass();
diff --git a/runtime/mirror/object_array-inl.h b/runtime/mirror/object_array-inl.h
index bef4af6..4a7e7b3 100644
--- a/runtime/mirror/object_array-inl.h
+++ b/runtime/mirror/object_array-inl.h
@@ -129,7 +129,8 @@
}
}
// Perform the memmove using int memmove then perform the write barrier.
- CHECK_EQ(sizeof(HeapReference<T>), sizeof(uint32_t));
+ static_assert(sizeof(HeapReference<T>) == sizeof(uint32_t),
+ "art::mirror::HeapReference<T> and uint32_t have different sizes.");
IntArray* dstAsIntArray = reinterpret_cast<IntArray*>(this);
IntArray* srcAsIntArray = reinterpret_cast<IntArray*>(src);
if (kUseReadBarrier) {
@@ -172,7 +173,8 @@
}
}
// Perform the memmove using int memcpy then perform the write barrier.
- CHECK_EQ(sizeof(HeapReference<T>), sizeof(uint32_t));
+ static_assert(sizeof(HeapReference<T>) == sizeof(uint32_t),
+ "art::mirror::HeapReference<T> and uint32_t have different sizes.");
IntArray* dstAsIntArray = reinterpret_cast<IntArray*>(this);
IntArray* srcAsIntArray = reinterpret_cast<IntArray*>(src);
if (kUseReadBarrier) {
diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h
index 8d9c08d..b689057 100644
--- a/runtime/mirror/string-inl.h
+++ b/runtime/mirror/string-inl.h
@@ -162,8 +162,8 @@
}
gc::Heap* heap = Runtime::Current()->GetHeap();
return down_cast<String*>(
- heap->AllocObjectWithAllocator<kIsInstrumented, false>(self, string_class, size,
- allocator_type, pre_fence_visitor));
+ heap->AllocObjectWithAllocator<kIsInstrumented, true>(self, string_class, size,
+ allocator_type, pre_fence_visitor));
}
template <bool kIsInstrumented>
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 7a78928..d0f01b3 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -144,7 +144,10 @@
};
Runtime::Runtime()
- : instruction_set_(kNone),
+ : resolution_method_(nullptr),
+ imt_conflict_method_(nullptr),
+ imt_unimplemented_method_(nullptr),
+ instruction_set_(kNone),
compiler_callbacks_(nullptr),
is_zygote_(false),
must_relocate_(false),
@@ -873,6 +876,7 @@
xgc_option.verify_pre_gc_rosalloc_,
xgc_option.verify_pre_sweeping_rosalloc_,
xgc_option.verify_post_gc_rosalloc_,
+ xgc_option.gcstress_,
runtime_options.GetOrDefault(Opt::EnableHSpaceCompactForOOM),
runtime_options.GetOrDefault(Opt::HSpaceCompactForOOMMinIntervalsMs));
ATRACE_END();
@@ -1378,6 +1382,7 @@
void Runtime::VisitConcurrentRoots(RootVisitor* visitor, VisitRootFlags flags) {
intern_table_->VisitRoots(visitor, flags);
class_linker_->VisitRoots(visitor, flags);
+ heap_->VisitAllocationRecords(visitor);
if ((flags & kVisitRootFlagNewRoots) == 0) {
// Guaranteed to have no new roots in the constant roots.
VisitConstantRoots(visitor);
diff --git a/runtime/stride_iterator.h b/runtime/stride_iterator.h
index a680302..d8d21aa 100644
--- a/runtime/stride_iterator.h
+++ b/runtime/stride_iterator.h
@@ -22,7 +22,7 @@
namespace art {
template<typename T>
-class StrideIterator : public std::iterator<std::random_access_iterator_tag, T> {
+class StrideIterator : public std::iterator<std::forward_iterator_tag, T> {
public:
StrideIterator(const StrideIterator&) = default;
StrideIterator(StrideIterator&&) = default;
diff --git a/runtime/thread.cc b/runtime/thread.cc
index fe8b0d8..37a86f1 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1277,7 +1277,8 @@
tlsPtr_.name = new std::string(kThreadNameDuringStartup);
tlsPtr_.nested_signal_state = static_cast<jmp_buf*>(malloc(sizeof(jmp_buf)));
- CHECK_EQ((sizeof(Thread) % 4), 0U) << sizeof(Thread);
+ static_assert((sizeof(Thread) % 4) == 0U,
+ "art::Thread has a size which is not a multiple of 4.");
tls32_.state_and_flags.as_struct.flags = 0;
tls32_.state_and_flags.as_struct.state = kNative;
memset(&tlsPtr_.held_mutexes[0], 0, sizeof(tlsPtr_.held_mutexes));
@@ -2361,8 +2362,7 @@
// Can't be null or how would we compile its instructions?
DCHECK(code_item != nullptr) << PrettyMethod(m);
NativePcOffsetToReferenceMap map(native_gc_map);
- size_t num_regs = std::min(map.RegWidth() * 8,
- static_cast<size_t>(code_item->registers_size_));
+ size_t num_regs = map.RegWidth() * 8;
if (num_regs > 0) {
Runtime* runtime = Runtime::Current();
const void* entry_point = runtime->GetInstrumentation()->GetQuickCodeFor(m, sizeof(void*));
diff --git a/test/098-ddmc/src/Main.java b/test/098-ddmc/src/Main.java
index 4914ba2..f41ff2a 100644
--- a/test/098-ddmc/src/Main.java
+++ b/test/098-ddmc/src/Main.java
@@ -43,24 +43,14 @@
System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248");
final int overflowAllocations = 64 * 1024; // Won't fit in unsigned 16-bit value.
- // TODO: Temporary fix. Keep the new objects live so they are not garbage collected.
- // This will cause OOM exception for GC stress tests. The root cause is changed behaviour of
- // getRecentAllocations(). Working on restoring its old behaviour. b/20037135
- Object[] objects = new Object[overflowAllocations];
for (int i = 0; i < overflowAllocations; i++) {
- objects[i] = new Object();
+ new Object();
}
Allocations after = new Allocations(DdmVmInternal.getRecentAllocations());
System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations));
System.out.println("after > before=" + (after.numberOfEntries > before.numberOfEntries));
System.out.println("after.numberOfEntries=" + after.numberOfEntries);
- // TODO: Temporary fix as above. b/20037135
- objects = null;
- Runtime.getRuntime().gc();
- final int fillerStrings = 16 * 1024;
- String[] strings = new String[fillerStrings];
-
System.out.println("Disable and confirm back to empty");
DdmVmInternal.enableRecentAllocations(false);
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
@@ -76,8 +66,8 @@
System.out.println("Confirm we can reenable twice in a row without losing allocations");
DdmVmInternal.enableRecentAllocations(true);
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
- for (int i = 0; i < fillerStrings; i++) {
- strings[i] = new String("fnord");
+ for (int i = 0; i < 16 * 1024; i++) {
+ new String("fnord");
}
Allocations first = new Allocations(DdmVmInternal.getRecentAllocations());
DdmVmInternal.enableRecentAllocations(true);
diff --git a/test/496-checker-inlining-and-class-loader/expected.txt b/test/496-checker-inlining-and-class-loader/expected.txt
index c6fcb51..312c28f 100644
--- a/test/496-checker-inlining-and-class-loader/expected.txt
+++ b/test/496-checker-inlining-and-class-loader/expected.txt
@@ -1,4 +1,4 @@
Request for LoadedByMyClassLoader
-Request for Main
+Request for FirstSeenByMyClassLoader
In between the two calls.
In $noinline$bar
diff --git a/test/496-checker-inlining-and-class-loader/src/FirstSeenByMyClassLoader.java b/test/496-checker-inlining-and-class-loader/src/FirstSeenByMyClassLoader.java
new file mode 100644
index 0000000..e97b4e3
--- /dev/null
+++ b/test/496-checker-inlining-and-class-loader/src/FirstSeenByMyClassLoader.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+public class FirstSeenByMyClassLoader {
+ public static void $inline$bar() {
+ }
+
+ public static void $noinline$bar() {
+ try {
+ System.out.println("In $noinline$bar");
+ } catch (Throwable t) { /* Ignore */ }
+ }
+}
diff --git a/test/496-checker-inlining-and-class-loader/src/Main.java b/test/496-checker-inlining-and-class-loader/src/Main.java
index 4f23eec..39c031a 100644
--- a/test/496-checker-inlining-and-class-loader/src/Main.java
+++ b/test/496-checker-inlining-and-class-loader/src/Main.java
@@ -82,7 +82,7 @@
/// CHECK-START: void LoadedByMyClassLoader.bar() inliner (after)
/// CHECK: LoadClass
/// CHECK-NEXT: ClinitCheck
- /* We inlined Main.$inline$bar */
+ /* We inlined FirstSeenByMyClassLoader.$inline$bar */
/// CHECK-NEXT: LoadClass
/// CHECK-NEXT: ClinitCheck
/// CHECK-NEXT: StaticFieldGet
@@ -91,7 +91,7 @@
/// CHECK-NEXT: InvokeVirtual
/// CHECK-START: void LoadedByMyClassLoader.bar() register (before)
- /* Load and initialize Main */
+ /* Load and initialize FirstSeenByMyClassLoader */
/// CHECK: LoadClass gen_clinit_check:true
/* Load and initialize System */
/// CHECK-NEXT: LoadClass gen_clinit_check:true
@@ -100,9 +100,9 @@
/// CHECK-NEXT: NullCheck
/// CHECK-NEXT: InvokeVirtual
public static void bar() {
- Main.$inline$bar();
+ FirstSeenByMyClassLoader.$inline$bar();
System.out.println("In between the two calls.");
- Main.$noinline$bar();
+ FirstSeenByMyClassLoader.$noinline$bar();
}
}
@@ -113,13 +113,4 @@
Method m = foo.getDeclaredMethod("bar");
m.invoke(null);
}
-
- public static void $inline$bar() {
- }
-
- public static void $noinline$bar() {
- try {
- System.out.println("In $noinline$bar");
- } catch (Throwable t) { /* Ignore */ }
- }
}
diff --git a/test/514-shifts/expected.txt b/test/514-shifts/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/514-shifts/expected.txt
diff --git a/test/514-shifts/info.txt b/test/514-shifts/info.txt
new file mode 100644
index 0000000..eb93c5f
--- /dev/null
+++ b/test/514-shifts/info.txt
@@ -0,0 +1,2 @@
+Regression test for optimizing that used to miscompile
+shifts on ARM.
diff --git a/test/514-shifts/src/Main.java b/test/514-shifts/src/Main.java
new file mode 100644
index 0000000..6c44eab
--- /dev/null
+++ b/test/514-shifts/src/Main.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+class Main {
+ public static void main(String[] args) {
+ testIntShiftRight();
+ testIntShiftLeft();
+ testIntUnsignedShiftRight();
+ testLongShiftRight();
+ testLongShiftLeft();
+ testLongUnsignedShiftRight();
+ }
+
+ public static void testIntShiftLeft() {
+ int a = myField;
+ int b = myOtherField << a;
+ if (b != -2147483648) {
+ throw new Error("Expected -2147483648, got " + b);
+ }
+ if (a != 0xFFF) {
+ throw new Error("Expected 0xFFF, got " + a);
+ }
+ }
+
+ public static void testIntShiftRight() {
+ int a = myField;
+ int b = myOtherField >> a;
+ if (b != 0) {
+ throw new Error("Expected 0, got " + b);
+ }
+ if (a != 0xFFF) {
+ throw new Error("Expected 0xFFF, got " + a);
+ }
+ }
+
+ public static void testIntUnsignedShiftRight() {
+ int a = myField;
+ int b = myOtherField >>> a;
+ if (b != 0) {
+ throw new Error("Expected 0, got " + b);
+ }
+ if (a != 0xFFF) {
+ throw new Error("Expected 0xFFF, got " + a);
+ }
+ }
+
+ public static void testLongShiftLeft() {
+ long a = myLongField;
+ long b = myOtherLongField << a;
+ if (b != 0x2468ACF13579BDEL) {
+ throw new Error("Expected 0x2468ACF13579BDEL, got " + b);
+ }
+ // The int conversion will be GVN'ed with the one required
+ // by Java specification of long shift left.
+ if ((int)a != 0x41) {
+ throw new Error("Expected 0x41, got " + a);
+ }
+ }
+
+ public static void testLongShiftRight() {
+ long a = myLongField;
+ long b = myOtherLongField >> a;
+ if (b != 0x91A2B3C4D5E6F7L) {
+ throw new Error("Expected 0x91A2B3C4D5E6F7L, got " + b);
+ }
+ // The int conversion will be GVN'ed with the one required
+ // by Java specification of long shift right.
+ if ((int)a != 0x41) {
+ throw new Error("Expected 0x41, got " + a);
+ }
+ }
+
+ public static void testLongUnsignedShiftRight() {
+ long a = myLongField;
+ long b = myOtherLongField >>> a;
+ if (b != 0x91A2B3C4D5E6F7L) {
+ throw new Error("Expected 0x91A2B3C4D5E6F7L, got " + b);
+ }
+ // The int conversion will be GVN'ed with the one required
+ // by Java specification of long shift right.
+ if ((int)a != 0x41) {
+ throw new Error("Expected 0x41, got " + a);
+ }
+ }
+
+ static int myField = 0xFFF;
+ static int myOtherField = 0x1;
+
+ // Use a value that will need to be masked before doing the shift.
+ // The maximum shift is 0x3F.
+ static long myLongField = 0x41;
+ static long myOtherLongField = 0x123456789abcdefL;
+}
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 60165d9..ac9656b 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -229,10 +229,19 @@
TEST_ART_BROKEN_NO_RELOCATE_TESTS :=
-# 098-ddmc is broken until we restore the old behavior of getRecentAllocation() of DDMS. b/20037135
-ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
- $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \
- $(IMAGE_TYPES), $(PICTEST_TYPES), $(DEBUGGABLE_TYPES), 098-ddmc, $(ALL_ADDRESS_SIZES))
+# Tests that are broken with GC stress.
+# 098-ddmc is broken until the allocation tracker does not mark recently allocated objects as roots.
+# Marking them roots is for consistent behavior with DDMS's getRecentAllocations(). b/20037135
+TEST_ART_BROKEN_GCSTRESS_RUN_TESTS := \
+ 098-ddmc
+
+ifneq (,$(filter gcstress,$(GC_TYPES)))
+ ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
+ $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),gcstress,$(JNI_TYPES), \
+ $(IMAGE_TYPES), $(PICTEST_TYPES), $(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_GCSTRESS_RUN_TESTS), $(ALL_ADDRESS_SIZES))
+endif
+
+TEST_ART_BROKEN_GCSTRESS_RUN_TESTS :=
# 115-native-bridge setup is complicated. Need to implement it correctly for the target.
ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES),$(COMPILER_TYPES), \