blob: 1a0d98d79b9e90e61ded7a90327c9c2a67f06e3d [file] [log] [blame]
// Copyright (c) 2022, 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.
// Logic shared between the Scavenger and Marker.
#ifndef RUNTIME_VM_HEAP_GC_SHARED_H_
#define RUNTIME_VM_HEAP_GC_SHARED_H_
#include "vm/compiler/runtime_api.h"
#if defined(SHOULD_NOT_INCLUDE_RUNTIME)
#error "Should not include runtime"
#endif
#include "vm/dart_api_state.h"
#include "vm/heap/scavenger.h"
#include "vm/log.h"
#include "vm/message_handler.h"
#include "vm/object.h"
namespace dart {
// These object types have a linked list chaining all pending objects when
// processing these in the GC.
// The field should not be visited by pointer visitors.
// The field should only be set during a GC.
//
// Macro params:
// - type
// - variable name
#define GC_LINKED_LIST(V) \
V(WeakProperty, weak_properties) \
V(WeakReference, weak_references) \
V(FinalizerEntry, finalizer_entries)
template <typename Type, typename PtrType>
class GCLinkedList {
public:
void Enqueue(PtrType ptr) {
ASSERT(ptr->untag()->next_seen_by_gc().IsRawNull());
ptr->untag()->next_seen_by_gc_ = head_;
if (head_ == Type::null()) {
tail_ = ptr;
}
head_ = ptr;
}
void FlushInto(GCLinkedList<Type, PtrType>* to) {
if (to->head_ == Type::null()) {
ASSERT(to->tail_ == Type::null());
to->head_ = head_;
to->tail_ = tail_;
} else {
ASSERT(to->tail_ != Type::null());
ASSERT(to->tail_->untag()->next_seen_by_gc() == Type::null());
to->tail_->untag()->next_seen_by_gc_ = head_;
to->tail_ = tail_;
}
Release();
}
PtrType Release() {
PtrType return_value = head_;
head_ = Type::null();
tail_ = Type::null();
return return_value;
}
bool IsEmpty() { return head_ == Type::null() && tail_ == Type::null(); }
private:
PtrType head_ = Type::null();
PtrType tail_ = Type::null();
};
struct GCLinkedLists {
void Release();
bool IsEmpty();
void FlushInto(GCLinkedLists* to);
#define FOREACH(type, var) GCLinkedList<type, type##Ptr> var;
GC_LINKED_LIST(FOREACH)
#undef FOREACH
};
#ifdef DEBUG
#define TRACE_FINALIZER(format, ...) \
if (FLAG_trace_finalizers) { \
THR_Print("%s %p " format "\n", GCVisitorType::kName, visitor, \
__VA_ARGS__); \
}
#else
#define TRACE_FINALIZER(format, ...)
#endif
// The space in which `raw_entry`'s `value` is.
Heap::Space SpaceForExternal(FinalizerEntryPtr raw_entry);
// Runs the finalizer if not detached, detaches the value and set external size
// to 0.
// TODO(http://dartbug.com/47777): Can this be merged with
// NativeFinalizer::RunCallback?
template <typename GCVisitorType>
void RunNativeFinalizerCallback(NativeFinalizerPtr raw_finalizer,
FinalizerEntryPtr raw_entry,
Heap::Space before_gc_space,
GCVisitorType* visitor) {
PointerPtr callback_pointer = raw_finalizer->untag()->callback();
const auto callback = reinterpret_cast<NativeFinalizer::Callback>(
callback_pointer->untag()->data());
ObjectPtr token_object = raw_entry->untag()->token();
const bool is_detached = token_object == raw_entry;
const intptr_t external_size = raw_entry->untag()->external_size();
if (is_detached) {
// Detached from Dart code.
ASSERT(token_object == raw_entry);
ASSERT(external_size == 0);
if (FLAG_trace_finalizers) {
TRACE_FINALIZER("Not running native finalizer %p callback %p, detached",
raw_finalizer->untag(), callback);
}
} else {
// TODO(http://dartbug.com/48615): Unbox pointer address in entry.
ASSERT(token_object.IsPointer());
PointerPtr token = static_cast<PointerPtr>(token_object);
void* peer = reinterpret_cast<void*>(token->untag()->data());
if (FLAG_trace_finalizers) {
TRACE_FINALIZER("Running native finalizer %p callback %p with token %p",
raw_finalizer->untag(), callback, peer);
}
raw_entry.untag()->set_token(raw_entry);
(*callback)(peer);
if (external_size > 0) {
if (FLAG_trace_finalizers) {
TRACE_FINALIZER("Clearing external size %" Pd " bytes in %s space",
external_size, before_gc_space == 0 ? "new" : "old");
}
visitor->isolate_group()->heap()->FreedExternal(external_size,
before_gc_space);
raw_entry->untag()->set_external_size(0);
}
}
}
// This function processes all finalizer entries discovered by a scavenger or
// marker. If an entry is referencing an object that is going to die, such entry
// is cleared and enqueued in the respective finalizer.
//
// Finalizer entries belonging to unreachable finalizer entries do not get
// processed, so the callback will not be called for these finalizers.
//
// For more documentation see runtime/docs/gc.md.
//
// |GCVisitorType| is a concrete type implementing either marker or scavenger.
// It is expected to provide |ForwardOrSetNullIfCollected| method for clearing
// fields referring to dead objects and |kName| field which contains visitor
// name for tracing output.
template <typename GCVisitorType>
void MournFinalized(GCVisitorType* visitor) {
FinalizerEntryPtr current_entry =
visitor->delayed_.finalizer_entries.Release();
while (current_entry != FinalizerEntry::null()) {
TRACE_FINALIZER("Processing Entry %p", current_entry->untag());
FinalizerEntryPtr next_entry =
current_entry->untag()->next_seen_by_gc_.Decompress(
current_entry->heap_base());
current_entry->untag()->next_seen_by_gc_ = FinalizerEntry::null();
uword heap_base = current_entry->heap_base();
const Heap::Space before_gc_space = SpaceForExternal(current_entry);
const bool value_collected_this_gc =
GCVisitorType::ForwardOrSetNullIfCollected(
heap_base, &current_entry->untag()->value_);
if (!value_collected_this_gc && before_gc_space == Heap::kNew) {
const Heap::Space after_gc_space = SpaceForExternal(current_entry);
if (after_gc_space == Heap::kOld) {
const intptr_t external_size = current_entry->untag()->external_size_;
TRACE_FINALIZER("Promoting external size %" Pd
" bytes from new to old space",
external_size);
visitor->isolate_group()->heap()->PromotedExternal(external_size);
}
}
GCVisitorType::ForwardOrSetNullIfCollected(
heap_base, &current_entry->untag()->detach_);
GCVisitorType::ForwardOrSetNullIfCollected(
heap_base, &current_entry->untag()->finalizer_);
ObjectPtr token_object = current_entry->untag()->token();
// See sdk/lib/_internal/vm/lib/internal_patch.dart FinalizerBase.detach.
const bool is_detached = token_object == current_entry;
if (value_collected_this_gc && !is_detached) {
FinalizerBasePtr finalizer = current_entry->untag()->finalizer();
if (finalizer.IsRawNull()) {
TRACE_FINALIZER("Value collected entry %p finalizer null",
current_entry->untag());
// Do nothing, the finalizer has been GCed.
} else {
TRACE_FINALIZER("Value collected entry %p finalizer %p",
current_entry->untag(), finalizer->untag());
FinalizerPtr finalizer_dart = static_cast<FinalizerPtr>(finalizer);
// Move entry to entries collected and current head of that list as
// the next element. Using a atomic exchange satisfies concurrency
// between the parallel GC tasks.
// We rely on the fact that the mutator thread is not running to avoid
// races between GC and mutator modifying Finalizer.entries_collected.
//
// We only run in serial marker or in the finalize step in the marker,
// both are in safepoint.
// The main scavenger worker is at safepoint, the other scavenger
// workers are are not, but they bypass safepoint because the main
// worker is at a safepoint already.
ASSERT(Thread::Current()->IsAtSafepoint() ||
Thread::Current()->BypassSafepoints());
if (finalizer.IsNativeFinalizer()) {
NativeFinalizerPtr native_finalizer =
static_cast<NativeFinalizerPtr>(finalizer);
// Immediately call native callback.
RunNativeFinalizerCallback(native_finalizer, current_entry,
before_gc_space, visitor);
// Fall-through sending a message to clear the entries and remove
// from detachments.
}
FinalizerEntryPtr previous_head =
finalizer_dart->untag()->exchange_entries_collected(current_entry);
current_entry->untag()->set_next(previous_head);
const bool first_entry = previous_head.IsRawNull();
// Schedule calling Dart finalizer.
if (first_entry) {
Isolate* isolate = finalizer->untag()->isolate_;
if (isolate == nullptr) {
TRACE_FINALIZER(
"Not scheduling finalizer %p callback on isolate null",
finalizer->untag());
} else {
TRACE_FINALIZER("Scheduling finalizer %p callback on isolate %p",
finalizer->untag(), isolate);
PersistentHandle* handle =
isolate->group()->api_state()->AllocatePersistentHandle();
handle->set_ptr(finalizer);
MessageHandler* message_handler = isolate->message_handler();
message_handler->PostMessage(
Message::New(handle, Message::kNormalPriority),
/*before_events*/ false);
}
}
}
}
current_entry = next_entry;
}
}
#undef TRACE_FINALIZER
} // namespace dart
#endif // RUNTIME_VM_HEAP_GC_SHARED_H_