%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/heap/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/heap/read-only-promotion.cc |
// Copyright 2023 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/read-only-promotion.h"
#include <unordered_set>
#include "src/common/assert-scope.h"
#include "src/execution/isolate.h"
#include "src/heap/combined-heap.h"
#include "src/heap/heap.h"
#include "src/objects/heap-object-inl.h"
#include "src/sandbox/external-pointer-table.h"
namespace v8 {
namespace internal {
namespace {
// Convenience aliases:
using HeapObjectSet = std::unordered_set<Tagged<HeapObject>, Object::Hasher,
Object::KeyEqualSafe>;
using HeapObjectMap = std::unordered_map<Tagged<HeapObject>, Tagged<HeapObject>,
Object::Hasher, Object::KeyEqualSafe>;
bool Contains(const HeapObjectSet& s, Tagged<HeapObject> o) {
return s.count(o) != 0;
}
bool Contains(const HeapObjectMap& s, Tagged<HeapObject> o) {
return s.count(o) != 0;
}
class Committee final {
public:
static std::vector<Tagged<HeapObject>> DeterminePromotees(
Isolate* isolate, const DisallowGarbageCollection& no_gc,
const SafepointScope& safepoint_scope) {
return Committee(isolate).DeterminePromotees(safepoint_scope);
}
private:
explicit Committee(Isolate* isolate) : isolate_(isolate) {}
std::vector<Tagged<HeapObject>> DeterminePromotees(
const SafepointScope& safepoint_scope) {
DCHECK(promo_accepted_.empty());
DCHECK(promo_rejected_.empty());
// We assume that a full and precise GC has reclaimed all dead objects
// and therefore that no filtering of unreachable objects is required here.
HeapObjectIterator it(isolate_->heap(), safepoint_scope);
for (Tagged<HeapObject> o = it.Next(); !o.is_null(); o = it.Next()) {
DCHECK(!o.InReadOnlySpace());
// Note that cycles prevent us from promoting/rejecting each subgraph as
// we visit it, since locally we cannot determine whether the deferred
// decision on the 'cycle object' will be 'promote' or 'reject'. This
// could be solved if necessary (with more complex code), but for now
// there are no performance issues.
HeapObjectSet accepted_subgraph; // Either all are accepted or none.
HeapObjectSet visited; // Cycle detection.
if (!EvaluateSubgraph(o, &accepted_subgraph, &visited)) continue;
if (accepted_subgraph.empty()) continue;
if (V8_UNLIKELY(v8_flags.trace_read_only_promotion)) {
LogAcceptedPromotionSet(accepted_subgraph);
}
promo_accepted_.insert(accepted_subgraph.begin(),
accepted_subgraph.end());
}
// Return promotees as a sorted list. Note that sorting uses object
// addresses; the list order is deterministic only if heap layout
// itself is deterministic (see v8_flags.predictable).
std::vector<Tagged<HeapObject>> promotees{promo_accepted_.begin(),
promo_accepted_.end()};
std::sort(promotees.begin(), promotees.end(), Object::Comparer());
return promotees;
}
// Returns `false` if the subgraph rooted at `o` is rejected.
// Returns `true` if it is accepted, or if we've reached a cycle and `o`
// will be processed further up the callchain.
bool EvaluateSubgraph(Tagged<HeapObject> o, HeapObjectSet* accepted_subgraph,
HeapObjectSet* visited) {
if (o.InReadOnlySpace()) return true;
if (Contains(promo_rejected_, o)) return false;
if (Contains(promo_accepted_, o)) return true;
if (Contains(*visited, o)) return true;
visited->insert(o);
if (!IsPromoCandidate(isolate_, o)) {
const auto& [it, inserted] = promo_rejected_.insert(o);
if (V8_UNLIKELY(v8_flags.trace_read_only_promotion) && inserted) {
LogRejectedPromotionForFailedPredicate(o);
}
return false;
}
// Recurse into outgoing pointers.
CandidateVisitor v(this, accepted_subgraph, visited);
o->Iterate(isolate_, &v);
if (!v.all_slots_are_promo_candidates()) {
const auto& [it, inserted] = promo_rejected_.insert(o);
if (V8_UNLIKELY(v8_flags.trace_read_only_promotion) && inserted) {
LogRejectedPromotionForInvalidSubgraph(o,
v.first_rejected_slot_offset());
}
return false;
}
accepted_subgraph->insert(o);
return true;
}
#define PROMO_CANDIDATE_TYPE_LIST(V) \
V(AccessCheckInfo) \
V(AccessorInfo) \
V(CallHandlerInfo) \
V(Code) \
V(InterceptorInfo) \
V(ScopeInfo) \
V(SharedFunctionInfo) \
V(Symbol)
// TODO(jgruber): Don't forget to extend ReadOnlyPromotionImpl::Verify when
// adding new object types here.
static bool IsPromoCandidate(Isolate* isolate, Tagged<HeapObject> o) {
const InstanceType itype = o->map(isolate)->instance_type();
#define V(TYPE) \
if (InstanceTypeChecker::Is##TYPE(itype)) { \
return IsPromoCandidate##TYPE(isolate, TYPE::cast(o)); \
/* NOLINTNEXTLINE(readability/braces) */ \
} else
PROMO_CANDIDATE_TYPE_LIST(V)
/* if { ... } else */ {
return false;
}
#undef V
UNREACHABLE();
}
#undef PROMO_CANDIDATE_TYPE_LIST
#define DEF_PROMO_CANDIDATE(Type) \
static bool IsPromoCandidate##Type(Isolate* isolate, Tagged<Type> o) { \
return true; \
}
DEF_PROMO_CANDIDATE(AccessCheckInfo)
DEF_PROMO_CANDIDATE(AccessorInfo)
DEF_PROMO_CANDIDATE(CallHandlerInfo)
static bool IsPromoCandidateCode(Isolate* isolate, Tagged<Code> o) {
return Builtins::kCodeObjectsAreInROSpace && o->is_builtin();
}
DEF_PROMO_CANDIDATE(InterceptorInfo)
DEF_PROMO_CANDIDATE(ScopeInfo)
static bool IsPromoCandidateSharedFunctionInfo(Isolate* isolate,
Tagged<SharedFunctionInfo> o) {
// Only internal SFIs are guaranteed to remain immutable.
if (o->has_script(kAcquireLoad)) return false;
// kIllegal is used for js_global_object_function, which is created during
// bootstrapping but never rooted. We currently assumed that all objects in
// the snapshot are live. But RO space is 1) not GC'd and 2) serialized
// verbatim, preserving dead objects. As a workaround, exclude this builtin
// id from RO allocation.
// TODO(jgruber): A better solution. Remove the liveness assumption (see
// test-heap-profiler.cc)? Overwrite dead RO objects with fillers
// pre-serialization? Implement a RO GC pass pre-serialization?
return o->HasBuiltinId() && o->builtin_id() != Builtin::kIllegal;
}
DEF_PROMO_CANDIDATE(Symbol)
#undef DEF_PROMO_CANDIDATE
// Recurses into all tagged slots of an object and tracks whether predicates
// failed on any part of the subgraph.
class CandidateVisitor : public ObjectVisitor {
public:
CandidateVisitor(Committee* committee, HeapObjectSet* accepted_subgraph,
HeapObjectSet* visited)
: committee_(committee),
accepted_subgraph_(accepted_subgraph),
visited_(visited) {}
int first_rejected_slot_offset() const {
return first_rejected_slot_offset_;
}
bool all_slots_are_promo_candidates() const {
return first_rejected_slot_offset_ == -1;
}
void VisitPointers(Tagged<HeapObject> host, MaybeObjectSlot start,
MaybeObjectSlot end) final {
if (!all_slots_are_promo_candidates()) return;
for (MaybeObjectSlot slot = start; slot < end; slot++) {
MaybeObject maybe_object = slot.load(committee_->isolate_);
Tagged<HeapObject> heap_object;
if (!maybe_object.GetHeapObject(&heap_object)) continue;
if (!committee_->EvaluateSubgraph(heap_object, accepted_subgraph_,
visited_)) {
first_rejected_slot_offset_ =
static_cast<int>(slot.address() - host.address());
DCHECK_GE(first_rejected_slot_offset_, 0);
return;
}
}
}
void VisitPointers(Tagged<HeapObject> host, ObjectSlot start,
ObjectSlot end) final {
VisitPointers(host, MaybeObjectSlot(start), MaybeObjectSlot(end));
}
void VisitInstructionStreamPointer(Tagged<Code> host,
InstructionStreamSlot slot) final {
DCHECK(host->is_builtin());
}
void VisitMapPointer(Tagged<HeapObject> host) final {
MaybeObjectSlot slot = host->RawMaybeWeakField(HeapObject::kMapOffset);
VisitPointers(host, slot, slot + 1);
}
private:
Committee* const committee_;
HeapObjectSet* const accepted_subgraph_;
HeapObjectSet* const visited_;
int first_rejected_slot_offset_ = -1;
};
static void LogAcceptedPromotionSet(const HeapObjectSet& os) {
std::cout << "ro-promotion: accepted set {";
for (Tagged<HeapObject> o : os) {
std::cout << reinterpret_cast<void*>(o.ptr()) << ", ";
}
std::cout << "}\n";
}
static void LogRejectedPromotionForFailedPredicate(Tagged<HeapObject> o) {
std::cout << "ro-promotion: rejected due to failed predicate "
<< reinterpret_cast<void*>(o.ptr()) << " ("
<< o->map()->instance_type() << ")"
<< "\n";
}
void LogRejectedPromotionForInvalidSubgraph(Tagged<HeapObject> o,
int first_rejected_slot_offset) {
std::cout << "ro-promotion: rejected due to rejected subgraph "
<< reinterpret_cast<void*>(o.ptr()) << " ("
<< o->map()->instance_type() << ")"
<< " at slot offset " << first_rejected_slot_offset << " ";
MaybeObjectSlot slot = o->RawMaybeWeakField(first_rejected_slot_offset);
MaybeObject maybe_object = slot.load(isolate_);
Tagged<HeapObject> heap_object;
if (maybe_object.GetHeapObject(&heap_object)) {
std::cout << reinterpret_cast<void*>(heap_object.ptr()) << " ("
<< heap_object->map()->instance_type() << ")"
<< "\n";
} else {
std::cout << "<cleared weak object>\n";
}
}
Isolate* const isolate_;
HeapObjectSet promo_accepted_;
HeapObjectSet promo_rejected_;
};
class ReadOnlyPromotionImpl final : public AllStatic {
public:
static void CopyToReadOnlyHeap(
Isolate* isolate, const std::vector<Tagged<HeapObject>>& promotees,
HeapObjectMap* moves) {
ReadOnlySpace* rospace = isolate->heap()->read_only_space();
for (Tagged<HeapObject> src : promotees) {
const int size = src->Size(isolate);
Tagged<HeapObject> dst =
rospace->AllocateRaw(size, kTaggedAligned).ToObjectChecked();
Heap::CopyBlock(dst.address(), src.address(), size);
moves->emplace(src, dst);
}
}
static void UpdatePointers(Isolate* isolate,
const SafepointScope& safepoint_scope,
const HeapObjectMap& moves) {
Heap* heap = isolate->heap();
#ifdef V8_COMPRESS_POINTERS
ExternalPointerTable::UnsealReadOnlySegmentScope unseal_scope(
&isolate->external_pointer_table());
#endif // V8_COMPRESS_POINTERS
UpdatePointersVisitor v(isolate, &moves);
// Iterate all roots.
EmbedderStackStateScope stack_scope(
isolate->heap(), EmbedderStackStateScope::kExplicitInvocation,
StackState::kNoHeapPointers);
heap->IterateRoots(&v, base::EnumSet<SkipRoot>{});
// Iterate all objects on the mutable heap.
// We assume that a full and precise GC has reclaimed all dead objects
// and therefore that no filtering of unreachable objects is required here.
HeapObjectIterator it(heap, safepoint_scope);
for (Tagged<HeapObject> o = it.Next(); !o.is_null(); o = it.Next()) {
o->Iterate(isolate, &v);
}
// Iterate all objects we just copied into RO space.
for (auto [src, dst] : moves) {
dst->Iterate(isolate, &v);
}
}
static void Verify(Isolate* isolate, const SafepointScope& safepoint_scope) {
#ifdef DEBUG
// Verify that certain objects were promoted as expected.
//
// Known objects.
Heap* heap = isolate->heap();
CHECK(heap->promise_all_resolve_element_shared_fun().InReadOnlySpace());
// TODO(jgruber): Extend here with more objects as they are added to
// the promotion algorithm.
// Builtin Code objects.
if (Builtins::kCodeObjectsAreInROSpace) {
Builtins* builtins = isolate->builtins();
for (int i = 0; i < Builtins::kBuiltinCount; i++) {
CHECK(builtins->code(static_cast<Builtin>(i)).InReadOnlySpace());
}
}
#endif // DEBUG
}
private:
class UpdatePointersVisitor final : public ObjectVisitor, public RootVisitor {
public:
UpdatePointersVisitor(Isolate* isolate, const HeapObjectMap* moves)
: isolate_(isolate), moves_(moves) {
for (auto [src, dst] : *moves_) {
moves_reverse_lookup_.emplace(dst, src);
}
}
// The RootVisitor interface.
void VisitRootPointers(Root root, const char* description,
FullObjectSlot start, FullObjectSlot end) final {
for (FullObjectSlot slot = start; slot < end; slot++) {
ProcessSlot(root, slot);
}
}
// The ObjectVisitor interface.
void VisitPointers(Tagged<HeapObject> host, MaybeObjectSlot start,
MaybeObjectSlot end) final {
for (MaybeObjectSlot slot = start; slot < end; slot++) {
ProcessSlot(host, slot);
}
}
void VisitPointers(Tagged<HeapObject> host, ObjectSlot start,
ObjectSlot end) final {
VisitPointers(host, MaybeObjectSlot(start), MaybeObjectSlot(end));
}
void VisitInstructionStreamPointer(Tagged<Code> host,
InstructionStreamSlot slot) final {
// InstructionStream objects never move to RO space.
}
void VisitMapPointer(Tagged<HeapObject> host) final {
ProcessSlot(host, host->RawMaybeWeakField(HeapObject::kMapOffset));
}
void VisitExternalPointer(Tagged<HeapObject> host,
ExternalPointerSlot slot) final {
#ifdef V8_ENABLE_SANDBOX
auto it = moves_reverse_lookup_.find(host);
if (it == moves_reverse_lookup_.end()) return;
// If we reach here, `host` is a moved object with external pointer slots
// located in RO space. To preserve the 1:1 relation between slots and
// table entries, allocate a new entry (in
// read_only_external_pointer_space) now.
RecordProcessedSlotIfDebug(slot.address());
Address slot_value = slot.load(isolate_);
slot.init(isolate_, slot_value);
if (V8_UNLIKELY(v8_flags.trace_read_only_promotion_verbose)) {
LogUpdatedExternalPointerTableEntry(host, slot, slot_value);
}
#endif // V8_ENABLE_SANDBOX
}
void VisitIndirectPointer(Tagged<HeapObject> host, IndirectPointerSlot slot,
IndirectPointerMode mode) final {}
void VisitIndirectPointerTableEntry(Tagged<HeapObject> host,
IndirectPointerSlot slot) final {
#ifdef V8_ENABLE_SANDBOX
// When an object owning an indirect pointer table entry is relocated, it
// needs to update the entry to point to its new location. Currently, only
// Code objects are referenced through indirect pointers, and they use the
// code pointer table.
CHECK(IsCode(host));
CHECK_EQ(slot.tag(), kCodeIndirectPointerTag);
// Due to the way we handle baseline code during serialization (we
// manually skip over them), we may encounter such live Code objects in
// mutable space during iteration. Do a lookup to make sure we only
// update CPT entries for moved objects.
auto it = moves_reverse_lookup_.find(host);
if (it == moves_reverse_lookup_.end()) return;
// If we reach here, `host` is a moved Code object located in RO space.
CHECK(host.InReadOnlySpace());
RecordProcessedSlotIfDebug(slot.address());
Tagged<Code> dead_code = Code::cast(it->second);
CHECK(IsCode(dead_code));
CHECK(!dead_code.InReadOnlySpace());
IndirectPointerHandle handle = slot.Relaxed_LoadHandle();
CodePointerTable* cpt = GetProcessWideCodePointerTable();
CHECK_EQ(dead_code, Tagged<Object>(cpt->GetCodeObject(handle)));
// The old Code object (in mutable space) is dead. To preserve the 1:1
// relation between Code objects and CPT entries, overwrite it immediately
// with the filler object.
isolate_->heap()->CreateFillerObjectAt(dead_code.address(), Code::kSize);
// Update the CPT entry to point at the moved RO Code object.
cpt->SetCodeObject(handle, host.ptr());
if (V8_UNLIKELY(v8_flags.trace_read_only_promotion_verbose)) {
LogUpdatedCodePointerTableEntry(host, slot, dead_code);
}
#else
UNREACHABLE();
#endif
}
void VisitRootPointers(Root root, const char* description,
OffHeapObjectSlot start,
OffHeapObjectSlot end) override {
// We shouldn't have moved any string table contents (which is what
// OffHeapObjectSlot currently refers to).
for (OffHeapObjectSlot slot = start; slot < end; slot++) {
Tagged<Object> o = slot.load(isolate_);
if (!IsHeapObject(o)) continue;
CHECK(!Contains(*moves_, HeapObject::cast(o)));
}
}
private:
void ProcessSlot(Root root, FullObjectSlot slot) {
Tagged<Object> old_slot_value_obj = slot.load(isolate_);
if (!IsHeapObject(old_slot_value_obj)) return;
Tagged<HeapObject> old_slot_value = HeapObject::cast(old_slot_value_obj);
auto it = moves_->find(old_slot_value);
if (it == moves_->end()) return;
Tagged<HeapObject> new_slot_value = it->second;
slot.store(new_slot_value);
if (V8_UNLIKELY(v8_flags.trace_read_only_promotion_verbose)) {
LogUpdatedPointer(root, slot, old_slot_value, new_slot_value);
}
}
void ProcessSlot(Tagged<HeapObject> host, MaybeObjectSlot slot) {
Tagged<HeapObject> old_slot_value;
if (!slot.load(isolate_).GetHeapObject(&old_slot_value)) return;
auto it = moves_->find(old_slot_value);
if (it == moves_->end()) return;
Tagged<HeapObject> new_slot_value = it->second;
slot.store(MaybeObject::FromObject(new_slot_value));
if (V8_UNLIKELY(v8_flags.trace_read_only_promotion_verbose)) {
LogUpdatedPointer(host, slot, old_slot_value, new_slot_value);
}
}
void LogUpdatedPointer(Root root, FullObjectSlot slot,
Tagged<HeapObject> old_slot_value,
Tagged<HeapObject> new_slot_value) {
std::cout << "ro-promotion: updated pointer {root "
<< static_cast<int>(root) << " slot "
<< reinterpret_cast<void*>(slot.address()) << " from "
<< reinterpret_cast<void*>(old_slot_value.ptr()) << " to "
<< reinterpret_cast<void*>(new_slot_value.ptr()) << "}\n";
}
void LogUpdatedPointer(Tagged<HeapObject> host, MaybeObjectSlot slot,
Tagged<HeapObject> old_slot_value,
Tagged<HeapObject> new_slot_value) {
std::cout << "ro-promotion: updated pointer {host "
<< reinterpret_cast<void*>(host.address()) << " slot "
<< reinterpret_cast<void*>(slot.address()) << " from "
<< reinterpret_cast<void*>(old_slot_value.ptr()) << " to "
<< reinterpret_cast<void*>(new_slot_value.ptr()) << "}\n";
}
void LogUpdatedExternalPointerTableEntry(Tagged<HeapObject> host,
ExternalPointerSlot slot,
Address slot_value) {
std::cout << "ro-promotion: updated external pointer slot {host "
<< reinterpret_cast<void*>(host.address()) << " slot "
<< reinterpret_cast<void*>(slot.address()) << " slot_value "
<< reinterpret_cast<void*>(slot_value) << "}\n";
}
void LogUpdatedCodePointerTableEntry(Tagged<HeapObject> host,
IndirectPointerSlot slot,
Tagged<Code> old_code_object) {
std::cout << "ro-promotion: updated code pointer table entry {host "
<< reinterpret_cast<void*>(host.address()) << " slot "
<< reinterpret_cast<void*>(slot.address()) << " from "
<< reinterpret_cast<void*>(old_code_object.ptr()) << "}\n";
}
#ifdef DEBUG
void RecordProcessedSlotIfDebug(Address slot_address) {
// If this fails, we're visiting some object multiple times by accident.
CHECK_EQ(processed_slots_.count(slot_address), 0);
processed_slots_.insert(slot_address);
}
std::unordered_set<Address> processed_slots_; // To avoid dupe processing.
#else
void RecordProcessedSlotIfDebug(Address slot_address) const {}
#endif // DEBUG
Isolate* const isolate_;
const HeapObjectMap* moves_;
HeapObjectMap moves_reverse_lookup_;
};
};
} // namespace
// static
void ReadOnlyPromotion::Promote(Isolate* isolate,
const SafepointScope& safepoint_scope,
const DisallowGarbageCollection& no_gc) {
// Visit the mutable heap and determine the set of objects that can be
// promoted to RO space.
std::vector<Tagged<HeapObject>> promotees =
Committee::DeterminePromotees(isolate, no_gc, safepoint_scope);
// Physically copy promotee objects to RO space and track all object moves.
HeapObjectMap moves;
ReadOnlyPromotionImpl::CopyToReadOnlyHeap(isolate, promotees, &moves);
// Update all references to moved objects to point at their new location in
// RO space.
ReadOnlyPromotionImpl::UpdatePointers(isolate, safepoint_scope, moves);
ReadOnlyPromotionImpl::Verify(isolate, safepoint_scope);
}
} // namespace internal
} // namespace v8