Commit 1d2c043a authored by Steve Blackburn's avatar Steve Blackburn Committed by Commit Bot

Serialization without heap assumptions.

Currently back references to regular objects are encoded in terms of
a relative address, index by chunk index and chunk offset.   This
approach has the advantage of avoiding the need for a table of
back-references at deserialization time, but makes strong assumptions
about the way objects are organized in memory (for example, this will
not work if the allocator uses a free list rather than a bump pointer).

I did some quick measurements and found that the absolute number of
objects and back-references is low, suggesting that simply indexing
objects would work with little (or no) observable impact on peak
memory use during deserialization.   Indexing only back referenced
objects is not implemented in this simple CL, but could fairly easily
be added.

Given that the existing mechanism will remain in place, I have
implemented the object index by simply making chunk size one, so
every object lives on its own chunk (with offset zero).   This is
the moral equivalent to indexing each object but is a more minimal
change.  Directly encoding an object index will be more efficient,
the trade off made here is just to keep the change absolutely minimal.

If using an object index becomes the default, this can be optimized
first by only using an index for each object that is actually back-
referenced (about half of all objects in my measurements), and more
aggressively, a technique like register allocation could be used at
serialization time to limit the number of indices to the maximum
number of outstanding back-references at any time (basically a live-
range analysis of back-references).

Bug: v8:9533
Change-Id: I1b7ae87e954f67f6405c2bbdf3b4a4f385af8579
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2030742
Commit-Queue: Steve Blackburn <steveblackburn@google.com>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66154}
parent 12fb997a
......@@ -167,6 +167,8 @@ struct MaybeBoolFlag {
FLAG(MAYBE_BOOL, MaybeBoolFlag, nam, {false COMMA false}, cmt)
#define DEFINE_INT(nam, def, cmt) FLAG(INT, int, nam, def, cmt)
#define DEFINE_UINT(nam, def, cmt) FLAG(UINT, unsigned int, nam, def, cmt)
#define DEFINE_UINT_READONLY(nam, def, cmt) \
FLAG_READONLY(UINT, unsigned int, nam, def, cmt)
#define DEFINE_UINT64(nam, def, cmt) FLAG(UINT64, uint64_t, nam, def, cmt)
#define DEFINE_FLOAT(nam, def, cmt) FLAG(FLOAT, double, nam, def, cmt)
#define DEFINE_SIZE_T(nam, def, cmt) FLAG(SIZE_T, size_t, nam, def, cmt)
......@@ -1291,9 +1293,13 @@ DEFINE_BOOL(profile_deserialization, false,
"Print the time it takes to deserialize the snapshot.")
DEFINE_BOOL(serialization_statistics, false,
"Collect statistics on serialized objects.")
#ifdef V8_ENABLE_THIRD_PARTY_HEAP
DEFINE_UINT_READONLY(serialization_chunk_size, 1,
"Custom size for serialization chunks")
#else
DEFINE_UINT(serialization_chunk_size, 4096,
"Custom size for serialization chunks")
#endif
// Regexp
DEFINE_BOOL(regexp_optimization, true, "generate optimized regexp code")
DEFINE_BOOL(regexp_mode_modifiers, false, "enable inline flags in regexp.")
......
......@@ -294,8 +294,8 @@ HeapObject Heap::AllocateRawWith(int size, AllocationType allocation,
Address Heap::DeserializerAllocate(AllocationType type, int size_in_bytes) {
if (V8_ENABLE_THIRD_PARTY_HEAP_BOOL) {
AllocationResult allocation = tp_heap_->Allocate(
size_in_bytes, type, AllocationAlignment::kWordAligned);
return allocation.ToObjectChecked().address();
size_in_bytes, type, AllocationAlignment::kDoubleAligned);
return allocation.ToObjectChecked().ptr();
} else {
UNIMPLEMENTED(); // unimplemented
}
......
......@@ -1835,8 +1835,13 @@ bool Heap::ReserveSpace(Reservation* reservations, std::vector<Address>* maps) {
DCHECK_EQ(0, reserved_size % Map::kSize);
int num_maps = reserved_size / Map::kSize;
for (int i = 0; i < num_maps; i++) {
AllocationResult allocation =
map_space()->AllocateRawUnaligned(Map::kSize);
AllocationResult allocation;
#if V8_ENABLE_THIRD_PARTY_HEAP_BOOL
allocation = AllocateRaw(Map::kSize, AllocationType::kMap,
AllocationOrigin::kRuntime, kWordAligned);
#else
allocation = map_space()->AllocateRawUnaligned(Map::kSize);
#endif
HeapObject free_space;
if (allocation.To(&free_space)) {
// Mark with a free list node, in case we have a GC before
......@@ -1863,12 +1868,24 @@ bool Heap::ReserveSpace(Reservation* reservations, std::vector<Address>* maps) {
DCHECK_LE(static_cast<size_t>(size),
MemoryChunkLayout::AllocatableMemoryInMemoryChunk(
static_cast<AllocationSpace>(space)));
#if V8_ENABLE_THIRD_PARTY_HEAP_BOOL
AllocationType type = (space == CODE_SPACE)
? AllocationType::kCode
: (space == RO_SPACE)
? AllocationType::kReadOnly
: AllocationType::kYoung;
AllocationAlignment align =
(space == CODE_SPACE) ? kCodeAligned : kWordAligned;
allocation =
AllocateRaw(size, type, AllocationOrigin::kRuntime, align);
#else
if (space == NEW_SPACE) {
allocation = new_space()->AllocateRawUnaligned(size);
} else {
// The deserializer will update the skip list.
allocation = paged_space(space)->AllocateRawUnaligned(size);
}
#endif
HeapObject free_space;
if (allocation.To(&free_space)) {
// Mark with a free list node, in case we have a GC before
......
......@@ -45,10 +45,12 @@ Address DeserializerAllocator::AllocateRaw(SnapshotSpace space, int size) {
int chunk_index = current_chunk_[space_number];
DCHECK_LE(high_water_[space_number], reservation[chunk_index].end);
#endif
#ifndef V8_ENABLE_THIRD_PARTY_HEAP
if (space == SnapshotSpace::kCode)
MemoryChunk::FromAddress(address)
->GetCodeObjectRegistry()
->RegisterNewlyAllocatedCodeObject(address);
#endif
return address;
}
}
......@@ -56,16 +58,22 @@ Address DeserializerAllocator::AllocateRaw(SnapshotSpace space, int size) {
Address DeserializerAllocator::Allocate(SnapshotSpace space, int size) {
Address address;
HeapObject obj;
if (V8_ENABLE_THIRD_PARTY_HEAP_BOOL) {
AllocationType type = (space == SnapshotSpace::kCode)
? AllocationType::kCode
: AllocationType::kYoung;
return heap_->DeserializerAllocate(type, size);
}
// TODO(steveblackburn) The following logic and AllocateRaw() above should
// be lifted into Heap, pushing the logic to heap_->DeserializerAllocate().
// The implementation below and AllocateRaw() above leak heap abstractions
// such as particular structure of heap spaces.
// TODO(steveblackburn) Note that the third party heap allocates objects
// at reservation time, which means alignment must be acted on at
// reservation time, not here. Since the current encoding does not
// inform the reservation of the alignment, it must be conservatively
// aligned.
//
// A more general approach will be to avoid reservation altogether, and
// instead of chunk index/offset encoding, simply encode backreferences
// by index (this can be optimized by applying something like register
// allocation to keep the metadata needed to record the in-flight
// backreferences minimal). This has the significant advantage of
// abstracting away the details of the memory allocator from this code.
// At each allocation, the regular allocator performs allocation,
// and a fixed-sized table is used to track and fix all back references.
if (V8_ENABLE_THIRD_PARTY_HEAP_BOOL) return AllocateRaw(space, size);
if (next_alignment_ != kWordAligned) {
const int reserved = size + Heap::GetMaximumFillToAlign(next_alignment_);
address = AllocateRaw(space, reserved);
......@@ -140,7 +148,6 @@ void DeserializerAllocator::DecodeReservation(
}
bool DeserializerAllocator::ReserveSpace() {
DCHECK(!V8_ENABLE_THIRD_PARTY_HEAP_BOOL);
#ifdef DEBUG
for (int i = 0; i < kNumberOfSpaces; ++i) {
DCHECK_GT(reservations_[i].size(), 0);
......
......@@ -33,9 +33,7 @@ ObjectDeserializer::DeserializeSharedFunctionInfo(
MaybeHandle<HeapObject> ObjectDeserializer::Deserialize(Isolate* isolate) {
Initialize(isolate);
#if !V8_ENABLE_THIRD_PARTY_HEAP_BOOL
if (!allocator()->ReserveSpace()) return MaybeHandle<HeapObject>();
#endif
DCHECK(deserializing_user_code());
HandleScope scope(isolate);
......
......@@ -31,11 +31,9 @@ MaybeHandle<Object> PartialDeserializer::Deserialize(
Isolate* isolate, Handle<JSGlobalProxy> global_proxy,
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer) {
Initialize(isolate);
#if !V8_ENABLE_THIRD_PARTY_HEAP_BOOL
if (!allocator()->ReserveSpace()) {
V8::FatalProcessOutOfMemory(isolate, "PartialDeserializer");
}
#endif
AddAttachedObject(global_proxy);
......
......@@ -16,11 +16,9 @@ namespace internal {
void StartupDeserializer::DeserializeInto(Isolate* isolate) {
Initialize(isolate);
#if !V8_ENABLE_THIRD_PARTY_HEAP_BOOL
if (!allocator()->ReserveSpace()) {
V8::FatalProcessOutOfMemory(isolate, "StartupDeserializer");
}
#endif
// No active threads.
DCHECK_NULL(isolate->thread_manager()->FirstThreadStateInUse());
......
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