Files
sdk/runtime/vm/heap/become.cc
Ryan Macnak d36adbacaf [vm] Remove the VM isolate.
The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL).

The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls.

The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap.

Class serialization no longer distinguishes predefined classes.

The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.)

Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code.

The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime.

The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration.

Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot.

TEST=ci
Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
2026-05-18 11:35:03 -07:00

372 lines
12 KiB
C++

// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/heap/become.h"
#include "platform/assert.h"
#include "platform/utils.h"
#include "vm/dart_api_state.h"
#include "vm/heap/safepoint.h"
#include "vm/isolate_reload.h"
#include "vm/object.h"
#include "vm/raw_object.h"
#include "vm/timeline.h"
#include "vm/visitor.h"
namespace dart {
ForwardingCorpse* ForwardingCorpse::AsForwarder(uword addr, intptr_t size) {
ASSERT(size >= kObjectAlignment);
ASSERT(Utils::IsAligned(size, kObjectAlignment));
ForwardingCorpse* result = reinterpret_cast<ForwardingCorpse*>(addr);
uword tags = result->tags_; // Carry-over any identity hash.
tags = UntaggedObject::SizeTag::update(size, tags);
tags = UntaggedObject::ClassIdTag::update(kForwardingCorpse, tags);
bool is_old = (addr & kNewObjectAlignmentOffset) == kOldObjectAlignmentOffset;
tags = UntaggedObject::NotMarkedBit::update(true, tags);
tags = UntaggedObject::OldAndNotRememberedBit::update(is_old, tags);
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(!is_old, tags);
result->tags_ = tags;
if (size > UntaggedObject::SizeTag::kMaxSizeTag) {
*result->SizeAddress() = size;
}
result->set_target(Object::null());
return result;
}
void ForwardingCorpse::Init() {
ASSERT(sizeof(ForwardingCorpse) == kObjectAlignment);
ASSERT(OFFSET_OF(ForwardingCorpse, tags_) == Object::tags_offset());
}
// Free list elements are used as a marker for forwarding objects. This is
// safe because we cannot reach free list elements from live objects. Ideally
// forwarding objects would have their own class id. See TODO below.
static bool IsForwardingObject(ObjectPtr object) {
return object->IsHeapObject() && object->IsForwardingCorpse();
}
static ObjectPtr GetForwardedObject(ObjectPtr object) {
ASSERT(IsForwardingObject(object));
uword addr = static_cast<uword>(object) - kHeapObjectTag;
ForwardingCorpse* forwarder = reinterpret_cast<ForwardingCorpse*>(addr);
return forwarder->target();
}
static void ForwardObjectTo(ObjectPtr before_obj, ObjectPtr after_obj) {
const intptr_t size_before = before_obj->untag()->HeapSize();
uword corpse_addr = static_cast<uword>(before_obj) - kHeapObjectTag;
ForwardingCorpse* forwarder =
ForwardingCorpse::AsForwarder(corpse_addr, size_before);
forwarder->set_target(after_obj);
if (!IsForwardingObject(before_obj)) {
FATAL("become: ForwardObjectTo failure.");
}
// Still need to be able to iterate over the forwarding corpse.
const intptr_t size_after = before_obj->untag()->HeapSize();
if (size_before != size_after) {
FATAL("become: Before and after sizes do not match.");
}
}
class ForwardPointersVisitor : public ObjectPointerVisitor {
public:
explicit ForwardPointersVisitor(Thread* thread)
: ObjectPointerVisitor(thread->isolate_group()),
thread_(thread),
visiting_object_(nullptr) {}
void VisitPointers(ObjectPtr* first, ObjectPtr* last) override {
for (ObjectPtr* p = first; p <= last; p++) {
ObjectPtr old_target = *p;
ObjectPtr new_target;
if (IsForwardingObject(old_target)) {
new_target = GetForwardedObject(old_target);
} else {
// Though we do not need to update the slot's value when it is not
// forwarded, we do need to recheck the generational barrier. In
// particular, the remembered bit may be incorrectly false if this
// become was the result of aborting a scavenge while visiting the
// remembered set.
new_target = old_target;
}
if (visiting_object_ == nullptr) {
*p = new_target;
} else if (visiting_object_->untag()->IsCardRemembered()) {
visiting_object_->untag()->StoreArrayPointer(p, new_target, thread_);
} else {
visiting_object_->untag()->StorePointer(p, new_target, thread_);
}
}
}
#if defined(DART_COMPRESSED_POINTERS)
void VisitCompressedPointers(uword heap_base,
CompressedObjectPtr* first,
CompressedObjectPtr* last) override {
for (CompressedObjectPtr* p = first; p <= last; p++) {
ObjectPtr old_target = p->Decompress(heap_base);
ObjectPtr new_target;
if (IsForwardingObject(old_target)) {
new_target = GetForwardedObject(old_target);
} else {
// Though we do not need to update the slot's value when it is not
// forwarded, we do need to recheck the generational barrier. In
// particular, the remembered bit may be incorrectly false if this
// become was the result of aborting a scavenge while visiting the
// remembered set.
new_target = old_target;
}
if (visiting_object_ == nullptr) {
*p = new_target;
} else if (visiting_object_->untag()->IsCardRemembered()) {
visiting_object_->untag()->StoreCompressedArrayPointer(p, new_target,
thread_);
} else {
visiting_object_->untag()->StoreCompressedPointer(p, new_target,
thread_);
}
}
}
#endif
void VisitingObject(ObjectPtr obj) {
visiting_object_ = obj;
// The incoming remembered bit may be unreliable. Clear it so we can
// consistently reapply the barrier to all slots.
if ((obj != nullptr) && obj->IsOldObject() &&
obj->untag()->IsRemembered()) {
ASSERT(!obj->IsForwardingCorpse());
ASSERT(!obj->IsFreeListElement());
obj->untag()->ClearRememberedBit();
}
}
private:
Thread* thread_;
ObjectPtr visiting_object_;
DISALLOW_COPY_AND_ASSIGN(ForwardPointersVisitor);
};
class ForwardHeapPointersVisitor : public ObjectVisitor {
public:
explicit ForwardHeapPointersVisitor(ForwardPointersVisitor* pointer_visitor)
: pointer_visitor_(pointer_visitor) {}
void VisitObject(ObjectPtr obj) override {
pointer_visitor_->VisitingObject(obj);
obj->untag()->VisitPointers(pointer_visitor_);
}
private:
ForwardPointersVisitor* pointer_visitor_;
DISALLOW_COPY_AND_ASSIGN(ForwardHeapPointersVisitor);
};
class ForwardHeapPointersHandleVisitor : public HandleVisitor {
public:
ForwardHeapPointersHandleVisitor() : HandleVisitor() {}
void VisitHandle(uword addr) override {
FinalizablePersistentHandle* handle =
reinterpret_cast<FinalizablePersistentHandle*>(addr);
if (IsForwardingObject(handle->ptr())) {
*handle->ptr_addr() = GetForwardedObject(handle->ptr());
}
}
private:
DISALLOW_COPY_AND_ASSIGN(ForwardHeapPointersHandleVisitor);
};
// On IA32, object pointers are embedded directly in the instruction stream,
// which is normally write-protected, so we need to make it temporarily writable
// to forward the pointers. On all other architectures, object pointers are
// accessed through ObjectPools.
#if defined(TARGET_ARCH_IA32)
class WritableCodeLiteralsScope : public ValueObject {
public:
explicit WritableCodeLiteralsScope(Heap* heap) : heap_(heap) {
if (FLAG_write_protect_code) {
heap_->WriteProtectCode(false);
}
}
~WritableCodeLiteralsScope() {
if (FLAG_write_protect_code) {
heap_->WriteProtectCode(true);
}
}
private:
Heap* heap_;
};
#else
class WritableCodeLiteralsScope : public ValueObject {
public:
explicit WritableCodeLiteralsScope(Heap* heap) {}
~WritableCodeLiteralsScope() {}
};
#endif
Become::Become() {
IsolateGroup* group = Thread::Current()->isolate_group();
ASSERT(group->become() == nullptr); // Only one outstanding become at a time.
group->set_become(this);
}
Become::~Become() {
Thread::Current()->isolate_group()->set_become(nullptr);
}
void Become::Add(const Object& before, const Object& after) {
pointers_.Add(before.ptr());
pointers_.Add(after.ptr());
}
void Become::VisitObjectPointers(ObjectPointerVisitor* visitor) {
if (pointers_.length() != 0) {
visitor->VisitPointers(&pointers_[0], pointers_.length());
}
}
void Become::MakeDummyObject(const Instance& instance) {
// Make the forward pointer point to itself.
// This is needed to distinguish it from a real forward object.
ForwardObjectTo(instance.ptr(), instance.ptr());
}
static bool IsDummyObject(ObjectPtr object) {
if (!object->IsForwardingCorpse()) return false;
return GetForwardedObject(object) == object;
}
DART_NOINLINE
DART_NORETURN
static void InvalidForwarding(ObjectPtr before,
ObjectPtr after,
const char* message) {
// Separate prints so we can at least get partial information if header
// dereference or ToCString crashes.
OS::PrintErr("become: %s\n", message);
OS::PrintErr("before: %" Px "\n", static_cast<uword>(before));
OS::PrintErr("after: %" Px "\n", static_cast<uword>(after));
OS::PrintErr("before header: %" Px "\n",
before->IsHeapObject() ? before->untag()->tags() : 0);
OS::PrintErr("after header: %" Px "\n",
after->IsHeapObject() ? after->untag()->tags() : 0);
// Create both handles before either ToCString.
Object& before_handle = Object::Handle(before);
Object& after_handle = Object::Handle(after);
OS::PrintErr("before: %s\n", before_handle.ToCString());
OS::PrintErr("after: %s\n", after_handle.ToCString());
FATAL("become: %s", message);
}
struct PtrIntTrait {
typedef ObjectPtr Key;
typedef intptr_t Value;
typedef struct {
ObjectPtr key;
intptr_t value;
} Pair;
static Key KeyOf(Pair kv) { return kv.key; }
static Value ValueOf(Pair kv) { return kv.value; }
static uword Hash(Key key) {
return (static_cast<uword>(key) * 92821) ^ (static_cast<uword>(key) >> 8);
}
static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; }
};
void Become::Forward() {
if (pointers_.length() == 0) {
return;
}
Thread* thread = Thread::Current();
auto heap = thread->isolate_group()->heap();
TIMELINE_FUNCTION_GC_DURATION(thread, "Become::ElementsForwardIdentity");
HeapIterationScope his(thread);
// Setup forwarding pointers.
for (intptr_t i = 0; i < pointers_.length(); i += 2) {
ObjectPtr before = pointers_[i];
ObjectPtr after = pointers_[i + 1];
if (before == after) {
InvalidForwarding(before, after, "Cannot self-forward");
}
if (before->IsImmediateObject()) {
InvalidForwarding(before, after, "Cannot forward immediates");
}
if (after->IsImmediateObject()) {
InvalidForwarding(before, after, "Cannot target immediates");
}
if (before->IsForwardingCorpse() && !IsDummyObject(before)) {
InvalidForwarding(before, after, "Cannot forward to multiple targets");
}
if (after->IsForwardingCorpse()) {
// The Smalltalk become does allow this, and for very special cases
// it is important (shape changes to Class or Mixin), but as these
// cases do not arise in Dart, better to prohibit it.
InvalidForwarding(before, after, "No indirect chains of forwarding");
}
ForwardObjectTo(before, after);
heap->ForwardWeakEntries(before, after);
#if defined(HASH_IN_OBJECT_HEADER)
Object::SetCachedHashIfNotSet(after, Object::GetCachedHash(before));
#endif
}
FollowForwardingPointers(thread);
#if defined(DEBUG)
for (intptr_t i = 0; i < pointers_.length(); i += 2) {
ASSERT(pointers_[i] == pointers_[i + 1]);
}
#endif
pointers_.Clear();
}
void Become::FollowForwardingPointers(Thread* thread) {
// N.B.: We forward the heap before forwarding the stack. This limits the
// amount of following of forwarding pointers needed to get at stack maps.
auto isolate_group = thread->isolate_group();
Heap* heap = isolate_group->heap();
// Clear the store buffer; will be rebuilt as we forward the heap.
isolate_group->ReleaseStoreBuffers();
isolate_group->store_buffer()->Reset();
ForwardPointersVisitor pointer_visitor(thread);
{
// Heap pointers.
WritableCodeLiteralsScope writable_code(heap);
ForwardHeapPointersVisitor object_visitor(&pointer_visitor);
heap->VisitObjects(&object_visitor);
pointer_visitor.VisitingObject(nullptr);
}
// C++ pointers.
isolate_group->VisitObjectPointers(&pointer_visitor,
ValidationPolicy::kValidateFrames);
// Weak persistent handles.
ForwardHeapPointersHandleVisitor handle_visitor;
isolate_group->VisitWeakPersistentHandles(&handle_visitor);
}
} // namespace dart