Commit 934dd8d7 authored by Irina Yatsenko's avatar Irina Yatsenko Committed by Commit Bot

[tools] Add !rs command to print remembered sets.

Produces output similar to:

Remembered set in chunk 0x29d0cd40000
  <empty>
Remembered set in chunk 0x891f200000
  <empty>
Remembered set in chunk 0x2fb14780000
  bucket 0x1ff381b09d0:
    0x2fb14780128 -> 0x6d7e080119
    0x2fb14780130 -> 0x6d7e080129
    0x2fb14780138 -> 0x6d7e080139
    0x2fb14780140 -> 0x6d7e080149
    0x2fb14780148 -> 0x6d7e080159
    0x2fb14780150 -> 0x6d7e080169
    0x2fb14780158 -> 0x6d7e080179
    0x2fb14780160 -> 0x6d7e080189
    0x2fb14780168 -> 0x6d7e080199
    0x2fb14780170 -> 0x6d7e0801a9
  10 remembered pointers in chunk 0x2fb14780000
Remembered set in chunk 0x5360700000
  <empty>

0: 000> !rs
Change-Id: I783322a2648ccba8a27aae72a459c742357e8e11
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1801253
Commit-Queue: Irina Yatsenko <irinayat@microsoft.com>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarDominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63910}
parent 39cc400d
...@@ -59,6 +59,7 @@ ...@@ -59,6 +59,7 @@
V(Regress791582) \ V(Regress791582) \
V(Regress845060) \ V(Regress845060) \
V(RegressMissingWriteBarrierInAllocate) \ V(RegressMissingWriteBarrierInAllocate) \
V(RememberedSet_LargePage) \
V(WriteBarriersInCopyJSObject) V(WriteBarriersInCopyJSObject)
#define HEAP_TEST(Name) \ #define HEAP_TEST(Name) \
......
...@@ -49,12 +49,17 @@ Page* HeapTester::AllocateByteArraysOnPage( ...@@ -49,12 +49,17 @@ Page* HeapTester::AllocateByteArraysOnPage(
return page; return page;
} }
// Fill new space with objects that become garbage immediately. This ensures template <RememberedSetType direction>
// that the next Scavenge has work to do but after it there is still available static size_t GetRememberedSetSize(HeapObject obj) {
// new space. std::set<Address> slots;
static void SimulateReclaimableFullNewSpace(Isolate* isolate) { RememberedSet<direction>::Iterate(
HandleScope scope_temp(isolate); MemoryChunk::FromHeapObject(obj),
heap::SimulateFullSpace(isolate->heap()->new_space()); [&slots](MaybeObjectSlot slot) {
slots.insert(slot.address());
return KEEP_SLOT;
},
SlotSet::KEEP_EMPTY_BUCKETS);
return slots.size();
} }
HEAP_TEST(StoreBuffer_CreateFromOldToYoung) { HEAP_TEST(StoreBuffer_CreateFromOldToYoung) {
...@@ -62,6 +67,8 @@ HEAP_TEST(StoreBuffer_CreateFromOldToYoung) { ...@@ -62,6 +67,8 @@ HEAP_TEST(StoreBuffer_CreateFromOldToYoung) {
Isolate* isolate = CcTest::i_isolate(); Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory(); Factory* factory = isolate->factory();
Heap* heap = isolate->heap(); Heap* heap = isolate->heap();
heap::SealCurrentObjects(heap);
CHECK(heap->store_buffer()->Empty());
HandleScope scope(isolate); HandleScope scope(isolate);
const int n = 10; const int n = 10;
...@@ -91,13 +98,14 @@ HEAP_TEST(StoreBuffer_CreateFromOldToYoung) { ...@@ -91,13 +98,14 @@ HEAP_TEST(StoreBuffer_CreateFromOldToYoung) {
CHECK_EQ(expected_slots_count, added_slots_count); CHECK_EQ(expected_slots_count, added_slots_count);
} }
// The old to new refs serve as roots during scavenge. // GC should flush the store buffer into remembered sets and retain the target
SimulateReclaimableFullNewSpace(isolate); // young objects.
CHECK_EQ(0, GetRememberedSetSize<OLD_TO_NEW>(*old));
CcTest::CollectGarbage(i::NEW_SPACE); CcTest::CollectGarbage(i::NEW_SPACE);
CHECK(old->get(0).IsHeapNumber());
// GC flushes the store buffer into remembered sets.
CHECK(heap->store_buffer()->Empty()); CHECK(heap->store_buffer()->Empty());
CHECK_EQ(n / 2, GetRememberedSetSize<OLD_TO_NEW>(*old));
CHECK(Heap::InYoungGeneration(old->get(0)));
} }
HEAP_TEST(StoreBuffer_Overflow) { HEAP_TEST(StoreBuffer_Overflow) {
...@@ -123,19 +131,19 @@ HEAP_TEST(StoreBuffer_NotUsedOnAgingObjectWithRefsToYounger) { ...@@ -123,19 +131,19 @@ HEAP_TEST(StoreBuffer_NotUsedOnAgingObjectWithRefsToYounger) {
Isolate* isolate = CcTest::i_isolate(); Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory(); Factory* factory = isolate->factory();
Heap* heap = isolate->heap(); Heap* heap = isolate->heap();
heap::SealCurrentObjects(heap);
CHECK(heap->store_buffer()->Empty());
const int n = 10; const int n = 10;
HandleScope scope(isolate); HandleScope scope(isolate);
Handle<FixedArray> arr = factory->NewFixedArray(n); Handle<FixedArray> arr = factory->NewFixedArray(n);
// Transition the array into the older new tier. // Transition the array into the older new tier.
SimulateReclaimableFullNewSpace(isolate);
CcTest::CollectGarbage(i::NEW_SPACE); CcTest::CollectGarbage(i::NEW_SPACE);
CHECK(Heap::InYoungGeneration(*arr)); CHECK(Heap::InYoungGeneration(*arr));
// Fill the array with younger objects. // Fill the array with younger objects.
{ {
const auto prev_top = *(heap->store_buffer_top_address());
HandleScope scope_inner(isolate); HandleScope scope_inner(isolate);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
Handle<Object> number = factory->NewHeapNumber(i); Handle<Object> number = factory->NewHeapNumber(i);
...@@ -143,17 +151,50 @@ HEAP_TEST(StoreBuffer_NotUsedOnAgingObjectWithRefsToYounger) { ...@@ -143,17 +151,50 @@ HEAP_TEST(StoreBuffer_NotUsedOnAgingObjectWithRefsToYounger) {
} }
// The references aren't crossing generations yet so none should be tracked. // The references aren't crossing generations yet so none should be tracked.
CHECK_EQ(prev_top, *(heap->store_buffer_top_address())); CHECK(heap->store_buffer()->Empty());
} }
// Promote the array into old, its elements are still in new, the old to new // Promote the array into old, its elements are still in new, the old to new
// refs are inserted directly into the remembered sets during GC. // refs are inserted directly into the remembered sets during GC.
SimulateReclaimableFullNewSpace(isolate);
CcTest::CollectGarbage(i::NEW_SPACE); CcTest::CollectGarbage(i::NEW_SPACE);
CHECK(!Heap::InYoungGeneration(*arr)); CHECK(heap->InOldSpace(*arr));
CHECK(Heap::InYoungGeneration(arr->get(n / 2))); CHECK(Heap::InYoungGeneration(arr->get(n / 2)));
CHECK(heap->store_buffer()->Empty()); CHECK(heap->store_buffer()->Empty());
CHECK_EQ(n, GetRememberedSetSize<OLD_TO_NEW>(*arr));
}
HEAP_TEST(RememberedSet_LargePage) {
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
heap::SealCurrentObjects(heap);
CHECK(heap->store_buffer()->Empty());
v8::HandleScope scope(CcTest::isolate());
// Allocate an object in Large space.
const int count = Max(FixedArray::kMaxRegularLength + 1, 128 * KB);
Handle<FixedArray> arr = factory->NewFixedArray(count, AllocationType::kOld);
CHECK(heap->lo_space()->Contains(*arr));
// Create OLD_TO_NEW references from the large object.
{
v8::HandleScope short_lived(CcTest::isolate());
Handle<Object> number = factory->NewHeapNumber(42);
arr->set(0, *number);
arr->set(count - 1, *number);
CHECK(!heap->store_buffer()->Empty());
}
// GC should flush the store buffer into the remembered set of the large page,
// it should also keep the young targets alive.
CcTest::CollectAllGarbage();
CHECK(heap->store_buffer()->Empty());
CHECK(Heap::InYoungGeneration(arr->get(0)));
CHECK(Heap::InYoungGeneration(arr->get(count - 1)));
CHECK_EQ(2, GetRememberedSetSize<OLD_TO_NEW>(*arr));
} }
HEAP_TEST(InvalidatedSlotsNoInvalidatedRanges) { HEAP_TEST(InvalidatedSlotsNoInvalidatedRanges) {
......
...@@ -53,6 +53,11 @@ function help() { ...@@ -53,6 +53,11 @@ function help() {
print(" !where(address)"); print(" !where(address)");
print(" prints name of the space and address of the MemoryChunk the"); print(" prints name of the space and address of the MemoryChunk the");
print(" 'address' is from, e.g. !where(0x235cb869f9)"); print(" 'address' is from, e.g. !where(0x235cb869f9)");
print(" !rs(chunk_address, set_id = 0)");
print(" prints slots from the remembered set in the MemoryChunk. If");
print(" 'chunk_address' isn't specified, prints for all chunks in the");
print(" old space; 'set_id' should match RememberedSetType enum,");
print(" e.g. !rs, !rs 0x2fb14780000, !rs(0x2fb14780000, 1)");
print(""); print("");
print("--------------------------------------------------------------------"); print("--------------------------------------------------------------------");
...@@ -140,10 +145,23 @@ function hex(number) { ...@@ -140,10 +145,23 @@ function hex(number) {
/*============================================================================= /*=============================================================================
Utils (postmortem and live) Utils (postmortem and live)
=============================================================================*/ =============================================================================*/
// WinDbg wraps large integers into objects that fail isInteger test (and, // WinDbg wraps large integers (0x80000000+) into an object of library type that
// consequently fail isSafeInteger test even if the original value was a safe // fails isInteger test (and, consequently fail isSafeInteger test even if the
// integer). I cannot figure out how to extract the original value from the // original value was a safe integer).
// wrapper object so doing it via conversion to a string. Brrr. Ugly. // However, that library type does have a set of methods on it which you can use
// to force conversion:
// .asNumber() / .valueOf(): Performs conversion to JavaScript number.
// Throws if the ordinal part of the 64-bit number does not pack into JavaScript
// number without loss of precision.
// .convertToNumber(): Performs conversion to JavaScript number.
// Does NOT throw if the ordinal part of the 64-bit number does not pack into
// JavaScript number. This will simply result in loss of precision.
// The library will also add these methods to the prototype for the standard
// number prototype. Meaning you can always .asNumber() / .convertToNumber() to
// get either JavaScript number or the private Int64 type into a JavaScript
// number.
// We could use the conversion functions but it seems that doing the conversion
// via toString is just as good and slightly more generic...
function int(val) { function int(val) {
if (typeof val === 'number') { if (typeof val === 'number') {
return Number.isInteger(val) ? val : undefined; return Number.isInteger(val) ? val : undefined;
...@@ -209,9 +227,18 @@ function module_name(use_this_module) { ...@@ -209,9 +227,18 @@ function module_name(use_this_module) {
return m.Name.indexOf("\\v8.dll") !== -1; return m.Name.indexOf("\\v8.dll") !== -1;
}); });
if (v8) { let v8_test = host.namespace.Debugger.State.DebuggerVariables.curprocess
.Modules.Where(
function(m) {
return m.Name.indexOf("\\v8_for_testing.dll") !== -1;
});
if (v8.Count() > 0) {
module_name_cache = "v8"; module_name_cache = "v8";
} }
else if (v8_test.Count() > 0) {
module_name_cache = "v8_for_testing";
}
else { else {
for (let exe_name in known_exes) { for (let exe_name in known_exes) {
let exe = host.namespace.Debugger.State.DebuggerVariables.curprocess let exe = host.namespace.Debugger.State.DebuggerVariables.curprocess
...@@ -219,7 +246,7 @@ function module_name(use_this_module) { ...@@ -219,7 +246,7 @@ function module_name(use_this_module) {
function(m) { function(m) {
return m.Name.indexOf(`\\${exe_name}.exe`) !== -1; return m.Name.indexOf(`\\${exe_name}.exe`) !== -1;
}); });
if (exe) { if (exe.Count() > 0) {
module_name_cache = exe_name; module_name_cache = exe_name;
break; break;
} }
...@@ -741,6 +768,79 @@ function dp(addr, count = 10) { ...@@ -741,6 +768,79 @@ function dp(addr, count = 10) {
} }
} }
// set ids: 0 = OLD_TO_NEW, 1 = 0 = OLD_TO_OLD
function print_remembered_set(chunk_addr, set_id = 0) {
if (!chunk_addr) {
if (isolate_address == 0) {
print("Please call !set_iso(isolate_address) or provide chunk address.");
return;
}
let iso = cast(isolate_address, "v8::internal::Isolate");
let h = iso.heap_;
let chunks = [];
get_chunks_space('old', h.old_space_.memory_chunk_list_.front_, chunks);
get_chunks_space('lo', h.lo_space_.memory_chunk_list_.front_, chunks);
for (let c of chunks) {
try {
print_remembered_set(c.address);
}
catch (e) {
print(`failed to process chunk ${hex(c.address)} due to ${e.message}`);
}
}
return;
}
print(`Remembered set in chunk ${hex(chunk_addr)}`);
let chunk = cast(chunk_addr, "v8::internal::MemoryChunk");
// chunk.slot_set_ is an array of SlotSet's. For standard pages there is 0 or
// 1 item in the array, but for large pages there will be more.
const page_size = 256 * 1024;
const sets_count = Math.floor((chunk.size_ + page_size - 1) / page_size);
let rs = chunk.slot_set_[set_id];
if (rs.isNull) {
print(` <empty>`);
return;
}
if (rs[0].page_start_ != chunk_addr) {
print(`page_start_ [${hex(rs.page_start_)}] doesn't match chunk_addr!`);
return;
}
let count = 0;
for (let s = 0; s < sets_count; s++){
const buckets_count = rs[s].buckets_.Count();
for (let b = 0; b < buckets_count; b++) {
let bucket = rs[s].buckets_[b];
if (bucket.isNull) continue;
// there are 32 cells in each bucket, cell's size is 32 bits
print(` bucket ${hex(bucket.address.asNumber())}:`);
const first_cell = bucket.address.asNumber();
for (let c = 0; c < 32; c++) {
let cell = host.memory.readMemoryValues(
first_cell + c * 4, 1, 4 /*size to read*/)[0];
if (cell == 0) continue;
let mask = 1;
for (let bit = 0; bit < 32; bit++){
if (cell & mask) {
count++;
const slot_offset = (b * 32 * 32 + c * 32 + bit) * 8;
const slot = rs[s].page_start_ + slot_offset;
print(` ${hex(slot)} -> ${hex(poi(slot))}`);
}
mask = mask << 1;
}
}
}
}
if (count == 0) print(` <empty>`);
else print(` ${count} remembered pointers in chunk ${hex(chunk_addr)}`);
}
/*============================================================================= /*=============================================================================
Initialize short aliased names for the most common commands Initialize short aliased names for the most common commands
=============================================================================*/ =============================================================================*/
...@@ -757,6 +857,7 @@ function initializeScript() { ...@@ -757,6 +857,7 @@ function initializeScript() {
new host.functionAlias(print_memory, "mem"), new host.functionAlias(print_memory, "mem"),
new host.functionAlias(print_owning_space, "where"), new host.functionAlias(print_owning_space, "where"),
new host.functionAlias(print_handles_data, "handles"), new host.functionAlias(print_handles_data, "handles"),
new host.functionAlias(print_remembered_set, "rs"),
new host.functionAlias(print_object_prev, "jo_prev"), new host.functionAlias(print_object_prev, "jo_prev"),
new host.functionAlias(print_object_next, "jo_next"), new host.functionAlias(print_object_next, "jo_next"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment