| // 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()); |
| if (head_ != 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, ¤t_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, ¤t_entry->untag()->detach_); |
| GCVisitorType::ForwardOrSetNullIfCollected( |
| heap_base, ¤t_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(); |
| |
| // If we're in the marker, we need to ensure that we release the store |
| // buffer afterwards. |
| // If we're in the scavenger and have the finalizer in old space and |
| // a new space entry, we don't need to release the store buffer. |
| if (!first_entry && previous_head->IsNewObject() && |
| current_entry->IsOldObject()) { |
| TRACE_FINALIZER("Entry %p (old) next is %p (new)", |
| current_entry->untag(), previous_head->untag()); |
| // We must release the thread's store buffer block. |
| } |
| |
| // 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_ |