dart-sdk/runtime/vm/message.cc
Daco Harkes e151a81108 Reland "[vm] Implement Finalizer"
Original CL in patchset 1.
Split-off https://dart-review.googlesource.com/c/sdk/+/238341
And pulled in fix https://dart-review.googlesource.com/c/sdk/+/238582
(Should merge cleanly when this lands later.)

This CL implements the `Finalizer` in the GC.

The GC is specially aware of two types of objects for the purposes of
running finalizers.

1) `FinalizerEntry`
2) `Finalizer` (`FinalizerBase`, `_FinalizerImpl`)

A `FinalizerEntry` contains the `value`, the optional `detach` key, and
the `token`, and a reference to the `finalizer`.
An entry only holds on weakly to the value, detach key, and finalizer.
(Similar to how `WeakReference` only holds on weakly to target).

A `Finalizer` contains all entries, a list of entries of which the value
is collected, and a reference to the isolate.

When a the value of an entry is GCed, the enry is added over to the
collected list.
If any entry is moved to the collected list, a message is sent that
invokes the finalizer to call the callback on all entries in that list.

When a finalizer is detached by the user, the entry token is set to the
entry itself and is removed from the all entries set.
This ensures that if the entry was already moved to the collected list,
the finalizer is not executed.

To speed up detaching, we use a weak map from detach keys to list of
entries. This ensures entries can be GCed.

Both the scavenger and marker tasks process finalizer entries in
parallel.
Parallel tasks use an atomic exchange on the head of the collected
entries list, ensuring no entries get lost.
The mutator thread is guaranteed to be stopped when processing entries.
This ensures that we do not need barriers for moving entries into the
finalizers collected list.
Dart reads and replaces the collected entries list also with an atomic
exchange, ensuring the GC doesn't run in between a load/store.

When a finalizer gets posted a message to process finalized objects, it
is being kept alive by the message.
An alternative design would be to pre-allocate a `WeakReference` in the
finalizer pointing to the finalizer, and send that itself.
This would be at the cost of an extra object.

Send and exit is not supported in this CL, support will be added in a
follow up CL. Trying to send will throw.

Bug: https://github.com/dart-lang/sdk/issues/47777

TEST=runtime/tests/vm/dart/finalizer/*
TEST=runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
TEST=runtime/vm/object_test.cc

Change-Id: Ibdfeadc16d5d69ade50aae5b9f794284c4c4dbab
Cq-Include-Trybots: luci.dart.try:vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-reload-linux-debug-x64-try,vm-ffi-android-debug-arm64c-try,dart-sdk-mac-arm64-try,vm-kernel-mac-release-arm64-try,pkg-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-win-debug-x64c-try,vm-kernel-win-debug-x64-try,vm-kernel-precomp-win-debug-x64c-try,vm-kernel-nnbd-win-release-ia32-try,vm-ffi-android-debug-arm-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-mac-debug-x64-try,vm-kernel-nnbd-mac-debug-x64-try,vm-kernel-nnbd-linux-debug-ia32-try,benchmark-linux-try,flutter-analyze-try,flutter-frontend-try,pkg-linux-debug-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-gcc-linux-try,vm-kernel-optcounter-threshold-linux-release-x64-try,vm-kernel-precomp-linux-debug-simarm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64c-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/238086
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
2022-03-25 10:29:30 +00:00

255 lines
6.9 KiB
C++

// Copyright (c) 2011, 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/message.h"
#include <utility>
#include "vm/dart_api_state.h"
#include "vm/dart_entry.h"
#include "vm/json_stream.h"
#include "vm/object.h"
#include "vm/port.h"
namespace dart {
const Dart_Port Message::kIllegalPort = 0;
Message::Message(Dart_Port dest_port,
uint8_t* snapshot,
intptr_t snapshot_length,
MessageFinalizableData* finalizable_data,
Priority priority)
: dest_port_(dest_port),
payload_(snapshot),
snapshot_length_(snapshot_length),
finalizable_data_(finalizable_data),
priority_(priority) {
ASSERT(IsSnapshot());
}
Message::Message(Dart_Port dest_port, ObjectPtr raw_obj, Priority priority)
: dest_port_(dest_port), payload_(raw_obj), priority_(priority) {
ASSERT(!raw_obj->IsHeapObject() || raw_obj->untag()->InVMIsolateHeap());
ASSERT(IsRaw());
}
Message::Message(Dart_Port dest_port,
PersistentHandle* handle,
Priority priority)
: dest_port_(dest_port),
payload_(handle),
snapshot_length_(kPersistentHandleSnapshotLen),
priority_(priority) {
ASSERT(IsPersistentHandle());
}
Message::Message(PersistentHandle* handle, Priority priority)
: dest_port_(ILLEGAL_PORT),
payload_(handle),
snapshot_length_(kFinalizerSnapshotLen),
priority_(priority) {
ASSERT(IsFinalizerInvocationRequest());
}
Message::~Message() {
if (IsSnapshot()) {
free(payload_.snapshot_);
}
delete finalizable_data_;
if (IsPersistentHandle() || IsFinalizerInvocationRequest()) {
auto isolate_group = IsolateGroup::Current();
isolate_group->api_state()->FreePersistentHandle(
payload_.persistent_handle_);
}
}
intptr_t Message::Id() const {
// Messages are allocated on the C heap. Use the raw address as the id.
return reinterpret_cast<intptr_t>(this);
}
const char* Message::PriorityAsString(Priority priority) {
switch (priority) {
case kNormalPriority:
return "Normal";
break;
case kOOBPriority:
return "OOB";
break;
default:
UNIMPLEMENTED();
return NULL;
}
}
MessageQueue::MessageQueue() {
head_ = NULL;
tail_ = NULL;
}
MessageQueue::~MessageQueue() {
// Ensure that all pending messages have been released.
Clear();
ASSERT(head_ == NULL);
}
void MessageQueue::Enqueue(std::unique_ptr<Message> msg0, bool before_events) {
// TODO(mdempsky): Use unique_ptr internally?
Message* msg = msg0.release();
// Make sure messages are not reused.
ASSERT(msg->next_ == NULL);
if (head_ == NULL) {
// Only element in the queue.
ASSERT(tail_ == NULL);
head_ = msg;
tail_ = msg;
} else {
ASSERT(tail_ != NULL);
if (!before_events) {
// Append at the tail.
tail_->next_ = msg;
tail_ = msg;
} else {
ASSERT(msg->dest_port() == Message::kIllegalPort);
if (head_->dest_port() != Message::kIllegalPort) {
msg->next_ = head_;
head_ = msg;
} else {
Message* cur = head_;
while (cur->next_ != NULL) {
if (cur->next_->dest_port() != Message::kIllegalPort) {
// Splice in the new message at the break.
msg->next_ = cur->next_;
cur->next_ = msg;
return;
}
cur = cur->next_;
}
// All pending messages are isolate library control messages. Append at
// the tail.
ASSERT(tail_ == cur);
ASSERT(tail_->dest_port() == Message::kIllegalPort);
tail_->next_ = msg;
tail_ = msg;
}
}
}
}
std::unique_ptr<Message> MessageQueue::Dequeue() {
Message* result = head_;
if (result != nullptr) {
head_ = result->next_;
// The following update to tail_ is not strictly needed.
if (head_ == nullptr) {
tail_ = nullptr;
}
#if defined(DEBUG)
result->next_ = result; // Make sure to trigger ASSERT in Enqueue.
#endif // DEBUG
return std::unique_ptr<Message>(result);
}
return nullptr;
}
void MessageQueue::Clear() {
std::unique_ptr<Message> cur(head_);
head_ = nullptr;
tail_ = nullptr;
while (cur != nullptr) {
std::unique_ptr<Message> next(cur->next_);
cur = std::move(next);
}
}
MessageQueue::Iterator::Iterator(const MessageQueue* queue) : next_(NULL) {
Reset(queue);
}
MessageQueue::Iterator::~Iterator() {}
void MessageQueue::Iterator::Reset(const MessageQueue* queue) {
ASSERT(queue != NULL);
next_ = queue->head_;
}
// returns false when there are no more messages left.
bool MessageQueue::Iterator::HasNext() {
return next_ != NULL;
}
// Returns the current message and moves forward.
Message* MessageQueue::Iterator::Next() {
Message* current = next_;
next_ = next_->next_;
return current;
}
intptr_t MessageQueue::Length() const {
MessageQueue::Iterator it(this);
intptr_t length = 0;
while (it.HasNext()) {
it.Next();
length++;
}
return length;
}
Message* MessageQueue::FindMessageById(intptr_t id) {
MessageQueue::Iterator it(this);
while (it.HasNext()) {
Message* current = it.Next();
ASSERT(current != NULL);
if (current->Id() == id) {
return current;
}
}
return NULL;
}
void MessageQueue::PrintJSON(JSONStream* stream) {
#ifndef PRODUCT
JSONArray messages(stream);
Object& msg_handler = Object::Handle();
MessageQueue::Iterator it(this);
intptr_t depth = 0;
while (it.HasNext()) {
Message* current = it.Next();
JSONObject message(&messages);
message.AddProperty("type", "Message");
message.AddPropertyF("name", "Isolate Message (%" Px ")", current->Id());
message.AddPropertyF("messageObjectId", "messages/%" Px "", current->Id());
message.AddProperty("size", current->Size());
message.AddProperty("index", depth++);
message.AddPropertyF("_destinationPort", "%" Pd64 "",
static_cast<int64_t>(current->dest_port()));
message.AddProperty("_priority",
Message::PriorityAsString(current->priority()));
// TODO(johnmccutchan): Move port -> handler map out of Dart and into the
// VM, that way we can lookup the handler without invoking Dart code.
msg_handler = DartLibraryCalls::LookupHandler(current->dest_port());
if (msg_handler.IsClosure()) {
// Grab function from closure.
msg_handler = Closure::Cast(msg_handler).function();
}
if (msg_handler.IsFunction()) {
const Function& function = Function::Cast(msg_handler);
message.AddProperty("handler", function);
const Script& script = Script::Handle(function.script());
if (!script.IsNull()) {
message.AddLocation(script, function.token_pos(),
function.end_token_pos());
}
}
}
#endif // !PRODUCT
}
} // namespace dart