dart-sdk/runtime/vm/message.h
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

213 lines
5.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.
#ifndef RUNTIME_VM_MESSAGE_H_
#define RUNTIME_VM_MESSAGE_H_
#include <memory>
#include <utility>
#include "platform/assert.h"
#include "vm/allocation.h"
#include "vm/finalizable_data.h"
#include "vm/globals.h"
#include "vm/tagged_pointer.h"
// Duplicated from dart_api.h to avoid including the whole header.
typedef int64_t Dart_Port;
namespace dart {
class JSONStream;
class PersistentHandle;
class OldPage;
class WeakTable;
class FreeList;
class Message {
public:
typedef enum {
kNormalPriority = 0, // Deliver message when idle.
kOOBPriority = 1, // Deliver message asap.
// Iteration.
kFirstPriority = 0,
kNumPriorities = 2,
} Priority;
// Values defining the type of OOB messages. OOB messages can only be
// fixed length arrays where the first element is a Smi with one of the
// valid values below.
typedef enum {
kIllegalOOB = 0,
kServiceOOBMsg = 1,
kIsolateLibOOBMsg = 2,
kDelayedIsolateLibOOBMsg = 3,
} OOBMsgTag;
// A port number which is never used.
static const Dart_Port kIllegalPort;
// A new message to be sent between two isolates. The data handed to this
// message will be disposed by calling free() once the message object is
// being destructed (after delivery or when the receiving port is closed).
Message(Dart_Port dest_port,
uint8_t* snapshot,
intptr_t snapshot_length,
MessageFinalizableData* finalizable_data,
Priority priority);
// Message objects can also carry RawObject pointers for Smis and objects in
// the VM heap. This is indicated by setting the len_ field to 0.
Message(Dart_Port dest_port, ObjectPtr raw_obj, Priority priority);
// A message sent from SendPort.send or SendPort.sendAndExit where sender and
// receiver are in the same isolate group.
Message(Dart_Port dest_port, PersistentHandle* handle, Priority priority);
// A message sent from GC to run a finalizer.
Message(PersistentHandle* handle, Priority priority);
~Message();
template <typename... Args>
static std::unique_ptr<Message> New(Args&&... args) {
return std::unique_ptr<Message>(new Message(std::forward<Args>(args)...));
}
Dart_Port dest_port() const { return dest_port_; }
uint8_t* snapshot() const {
ASSERT(IsSnapshot());
return payload_.snapshot_;
}
intptr_t snapshot_length() const { return snapshot_length_; }
MessageFinalizableData* finalizable_data() { return finalizable_data_; }
intptr_t Size() const {
intptr_t size = snapshot_length_;
if (finalizable_data_ != NULL) {
size += finalizable_data_->external_size();
}
return size;
}
ObjectPtr raw_obj() const {
ASSERT(IsRaw());
return payload_.raw_obj_;
}
PersistentHandle* persistent_handle() const {
ASSERT(IsPersistentHandle() || IsFinalizerInvocationRequest());
return payload_.persistent_handle_;
}
Priority priority() const { return priority_; }
// A message processed at any interrupt point (stack overflow check) instead
// of at the top of the message loop. Control messages from dart:isolate or
// vm-service requests.
bool IsOOB() const { return priority_ == Message::kOOBPriority; }
bool IsSnapshot() const {
return !IsRaw() && !IsPersistentHandle() && !IsFinalizerInvocationRequest();
}
// A message whose object is an immortal object from the vm-isolate's heap.
bool IsRaw() const { return snapshot_length_ == 0; }
// A message sent from SendPort.send or SendPort.sendAndExit where sender and
// receiver are in the same isolate group.
bool IsPersistentHandle() const {
return snapshot_length_ == kPersistentHandleSnapshotLen;
}
// A message sent from GC to run a finalizer.
bool IsFinalizerInvocationRequest() const {
return snapshot_length_ == kFinalizerSnapshotLen;
}
void DropFinalizers() {
if (finalizable_data_ != nullptr) {
finalizable_data_->DropFinalizers();
}
}
intptr_t Id() const;
static const char* PriorityAsString(Priority priority);
private:
static intptr_t const kPersistentHandleSnapshotLen = -1;
static intptr_t const kFinalizerSnapshotLen = -2;
friend class MessageQueue;
Message* next_ = nullptr;
Dart_Port dest_port_;
union Payload {
Payload(uint8_t* snapshot) : snapshot_(snapshot) {}
Payload(ObjectPtr raw_obj) : raw_obj_(raw_obj) {}
Payload(PersistentHandle* persistent_handle)
: persistent_handle_(persistent_handle) {}
uint8_t* snapshot_;
ObjectPtr raw_obj_;
PersistentHandle* persistent_handle_;
} payload_;
intptr_t snapshot_length_ = 0;
MessageFinalizableData* finalizable_data_ = nullptr;
Priority priority_;
DISALLOW_COPY_AND_ASSIGN(Message);
};
// There is a message queue per isolate.
class MessageQueue {
public:
MessageQueue();
~MessageQueue();
void Enqueue(std::unique_ptr<Message> msg, bool before_events);
// Gets the next message from the message queue or NULL if no
// message is available. This function will not block.
std::unique_ptr<Message> Dequeue();
bool IsEmpty() { return head_ == NULL; }
// Clear all messages from the message queue.
void Clear();
// Iterator class.
class Iterator : public ValueObject {
public:
explicit Iterator(const MessageQueue* queue);
virtual ~Iterator();
void Reset(const MessageQueue* queue);
// Returns false when there are no more messages left.
bool HasNext();
// Returns the current message and moves forward.
Message* Next();
private:
Message* next_;
};
intptr_t Length() const;
// Returns the message with id or NULL.
Message* FindMessageById(intptr_t id);
void PrintJSON(JSONStream* stream);
private:
Message* head_;
Message* tail_;
DISALLOW_COPY_AND_ASSIGN(MessageQueue);
};
} // namespace dart
#endif // RUNTIME_VM_MESSAGE_H_