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>
This commit is contained in:
Daco Harkes 2022-03-25 10:29:30 +00:00 committed by Commit Bot
parent e2fa5d8d09
commit e151a81108
64 changed files with 4077 additions and 274 deletions

View file

@ -13,7 +13,7 @@ A tag of 1 has no penalty on heap object access because removing the tag can be
Heap objects are always allocated in double-word increments. Objects in old-space are kept at double-word alignment (address % double-word == 0), and objects in new-space are kept offset from double-word alignment (address % double-word == word). This allows checking an object's age without comparing to a boundary address, avoiding restrictions on heap placement and avoiding loading the boundary from thread-local storage. Additionally, the scavenger can quickly skip over both immediates and old objects with a single branch.
| Pointer | Referent |
| --- | --- |
| ---------- | --------------------------------------- |
| 0x00000002 | Small integer 1 |
| 0xFFFFFFFE | Small integer -1 |
| 0x00A00001 | Heap object at 0x00A00000, in old-space |
@ -75,7 +75,7 @@ The Dart VM includes a sliding compactor. The forwarding table is compactly repr
## Concurrent Marking
To reduce the time the mutator is paused for old-space GCs, we allow the mutator to continue running during most of the marking work.
To reduce the time the mutator is paused for old-space GCs, we allow the mutator to continue running during most of the marking work.
### Barrier
@ -204,3 +204,35 @@ container <- AllocateObject
<instructions that cannot directly call Dart functions>
StoreInstanceField(container, value, NoBarrier)
```
## Finalizers
The GC is 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 the value of an entry is GCed, the entry 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 can process finalizer entries in parallel.
Parallel tasks use an atomic exchange on the head of the collected entries list, ensuring no entries get lost.
Mutator threads are 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.
If the finalizer object itself is GCed, the callback is not run for any of the attachments.

View file

@ -242,6 +242,8 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
break;
MESSAGE_SNAPSHOT_ILLEGAL(DynamicLibrary);
// TODO(http://dartbug.com/47777): Send and exit support: remove this.
MESSAGE_SNAPSHOT_ILLEGAL(Finalizer);
MESSAGE_SNAPSHOT_ILLEGAL(MirrorReference);
MESSAGE_SNAPSHOT_ILLEGAL(Pointer);
MESSAGE_SNAPSHOT_ILLEGAL(ReceivePort);
@ -284,6 +286,7 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
return obj.ptr();
}
// TODO(http://dartbug.com/47777): Add support for Finalizers.
DEFINE_NATIVE_ENTRY(Isolate_exit_, 0, 2) {
GET_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
if (!port.IsNull()) {
@ -638,9 +641,10 @@ class SpawnIsolateTask : public ThreadPool::Task {
// Make a copy of the state's isolate flags and hand it to the callback.
Dart_IsolateFlags api_flags = *(state_->isolate_flags());
api_flags.is_system_isolate = false;
Dart_Isolate isolate = (create_group_callback)(
state_->script_url(), name, nullptr, state_->package_config(),
&api_flags, parent_isolate_->init_callback_data(), &error);
Dart_Isolate isolate =
(create_group_callback)(state_->script_url(), name, nullptr,
state_->package_config(), &api_flags,
parent_isolate_->init_callback_data(), &error);
parent_isolate_->DecrementSpawnCount();
parent_isolate_ = nullptr;

View file

@ -0,0 +1,36 @@
// 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.
import 'dart:isolate';
import 'helpers.dart';
int callbackCount = 0;
void callback(Nonce token) {
callbackCount++;
print('$name: Running finalizer: token: $token');
}
final finalizer = Finalizer<Nonce>(callback);
late String name;
void main(List<String> arguments, SendPort port) async {
name = arguments[0];
final token = Nonce(42);
makeObjectWithFinalizer(finalizer, token);
final awaitBeforeShuttingDown = ReceivePort();
port.send(awaitBeforeShuttingDown.sendPort);
final message = await awaitBeforeShuttingDown.first;
print('$name: $message');
await Future.delayed(Duration(milliseconds: 1));
print('$name: Awaited to see if there were any callbacks.');
print('$name: Helper isolate exiting. num callbacks: $callbackCount.');
port.send(callbackCount);
}

View file

@ -0,0 +1,93 @@
// 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
import 'dart:async';
import 'dart:isolate';
import 'package:async/async.dart';
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() async {
await testFinalizerInOtherIsolateGroupGCBeforeExit();
await testFinalizerInOtherIsolateGroupGCAfterExit();
await testFinalizerInOtherIsolateGroupNoGC();
print('$name: End of test, shutting down.');
}
const name = 'main';
late bool hotReloadBot;
Future<void> testFinalizerInOtherIsolateGroupGCBeforeExit() async {
final receivePort = ReceivePort();
final messagesQueue = StreamQueue(receivePort);
await Isolate.spawnUri(
Uri.parse('finalizer_isolate_groups_run_gc_helper.dart'),
['helper 1'],
receivePort.sendPort,
);
final signalHelperIsolate = await messagesQueue.next as SendPort;
doGC(name: name);
await yieldToMessageLoop(name: name);
signalHelperIsolate.send('Done GCing.');
final helperCallbacks = await messagesQueue.next as int;
messagesQueue.cancel();
print('$name: Helper exited.');
// Different isolate group, so we don't expect a GC in this isolate to cause
// collected objects in the helper.
// Except for in --hot-reload-test-mode, then the GC is triggered.
hotReloadBot = helperCallbacks == 1;
}
Future<void> testFinalizerInOtherIsolateGroupGCAfterExit() async {
final receivePort = ReceivePort();
final messagesQueue = StreamQueue(receivePort);
await Isolate.spawnUri(
Uri.parse('finalizer_isolate_groups_run_gc_helper.dart'),
['helper 2'],
receivePort.sendPort,
);
final signalHelperIsolate = await messagesQueue.next as SendPort;
signalHelperIsolate.send('Before GCing.');
final helperCallbacks = await messagesQueue.next as int;
messagesQueue.cancel();
print('$name: Helper exited.');
Expect.equals(hotReloadBot ? 1 : 0, helperCallbacks);
doGC(name: name);
await yieldToMessageLoop(name: name);
}
Future<void> testFinalizerInOtherIsolateGroupNoGC() async {
final receivePort = ReceivePort();
final messagesQueue = StreamQueue(receivePort);
await Isolate.spawnUri(
Uri.parse('finalizer_isolate_groups_run_gc_helper.dart'),
['helper 3'],
receivePort.sendPort,
);
final signalHelperIsolate = await messagesQueue.next as SendPort;
signalHelperIsolate.send('Before quitting main isolate.');
final helperCallbacks = await messagesQueue.next as int;
messagesQueue.cancel();
print('$name: Helper exited.');
Expect.equals(hotReloadBot ? 1 : 0, helperCallbacks);
}

View file

@ -0,0 +1,103 @@
// 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
import 'dart:async';
import 'dart:isolate';
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() async {
await testNormalExit();
await testSendAndExit();
await testSendAndExitFinalizer();
print('End of test, shutting down.');
}
final finalizerTokens = <Nonce>{};
void callback(Nonce token) {
print('Running finalizer: token: $token');
finalizerTokens.add(token);
}
void runIsolateAttachFinalizer(Object? message) {
final finalizer = Finalizer<Nonce>(callback);
final value = Nonce(1001);
final token = Nonce(1002);
finalizer.attach(value, token);
final token9 = Nonce(9002);
makeObjectWithFinalizer(finalizer, token9);
if (message == null) {
print('Isolate done.');
return;
}
final list = message as List;
assert(list.length == 2);
final sendPort = list[0] as SendPort;
final tryToSendFinalizer = list[1] as bool;
if (tryToSendFinalizer) {
Expect.throws(() {
// TODO(http://dartbug.com/47777): Send and exit support.
print('Trying to send and exit finalizer.');
Isolate.exit(sendPort, [value, finalizer]);
});
}
print('Isolate sending and exit.');
Isolate.exit(sendPort, [value]);
}
Future testNormalExit() async {
final portExitMessage = ReceivePort();
await Isolate.spawn(
runIsolateAttachFinalizer,
null,
onExit: portExitMessage.sendPort,
);
await portExitMessage.first;
doGC();
await yieldToMessageLoop();
Expect.equals(0, finalizerTokens.length);
}
@pragma('vm:never-inline')
Future<Finalizer?> testSendAndExitHelper(
{bool trySendFinalizer = false}) async {
final port = ReceivePort();
await Isolate.spawn(
runIsolateAttachFinalizer,
[port.sendPort, trySendFinalizer],
);
final message = await port.first as List;
print('Received message ($message).');
final value = message[0] as Nonce;
print('Received value ($value), but now forgetting about it.');
Expect.equals(1, message.length);
// TODO(http://dartbug.com/47777): Send and exit support.
return null;
}
Future testSendAndExit() async {
await testSendAndExitHelper(trySendFinalizer: false);
doGC();
await yieldToMessageLoop();
Expect.equals(0, finalizerTokens.length);
}
Future testSendAndExitFinalizer() async {
final finalizer = await testSendAndExitHelper(trySendFinalizer: true);
// TODO(http://dartbug.com/47777): Send and exit support.
Expect.isNull(finalizer);
}

View file

@ -0,0 +1,43 @@
// 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.
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() {
testFinalizer();
}
void testFinalizer() async {
final finalizerTokens = <Nonce?>{};
void callback(Nonce? token) {
print('Running finalizer: token: $token');
finalizerTokens.add(token);
}
final finalizer = Finalizer<Nonce?>(callback);
{
final detach = Nonce(2022);
final token = null;
makeObjectWithFinalizer(finalizer, token, detach: detach);
doGC();
// We haven't stopped running synchronous dart code yet.
Expect.isFalse(finalizerTokens.contains(token));
await Future.delayed(Duration(milliseconds: 1));
// Now we have.
Expect.isTrue(finalizerTokens.contains(token));
// Try detaching after finalizer ran.
finalizer.detach(detach);
}
print('End of test, shutting down.');
}

View file

@ -0,0 +1,90 @@
// 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() {
testWrongArguments();
testFinalizer();
}
void testWrongArguments() {
void callback(Object token) {
throw 'This should never happen!';
}
final finalizer = Finalizer<Nonce>(callback);
final myFinalizable = Nonce(1000);
final token = Nonce(2000);
final detach = Nonce(3000);
Expect.throws(() {
finalizer.attach(myFinalizable, token, detach: 123);
});
Expect.throws(() {
finalizer.attach(123, token, detach: detach);
});
}
void testFinalizer() async {
final finalizerTokens = <Object>{};
void callback(Object token) {
print('Running finalizer: token: $token');
finalizerTokens.add(token);
}
final finalizer = Finalizer<Nonce>(callback);
{
final detach = Nonce(2022);
final token = Nonce(42);
makeObjectWithFinalizer(finalizer, token, detach: detach);
doGC();
// We haven't stopped running synchronous dart code yet.
Expect.isFalse(finalizerTokens.contains(token));
await Future.delayed(Duration(milliseconds: 1));
// Now we have.
Expect.isTrue(finalizerTokens.contains(token));
// Try detaching after finalizer ran.
finalizer.detach(detach);
}
{
final token = Nonce(1337);
final token2 = Nonce(1338);
final detachkey = Nonce(1984);
{
final value = Nonce(2);
final value2 = Nonce(2000000);
finalizer.attach(value, token, detach: detachkey);
finalizer.attach(value2, token2, detach: detachkey);
// Should detach 2 finalizers.
finalizer.detach(detachkey);
// Try detaching again, should do nothing.
finalizer.detach(detachkey);
}
doGC();
await yieldToMessageLoop();
Expect.isFalse(finalizerTokens.contains(token));
Expect.isFalse(finalizerTokens.contains(token2));
}
// Not running finalizer on shutdown.
final value = Nonce(3);
final token = Nonce(1337);
finalizer.attach(value, token);
print('End of test, shutting down.');
}

View file

@ -0,0 +1,73 @@
// 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
import 'dart:async';
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() async {
await testFinalizerZone();
await testFinalizerException();
}
Future<void> testFinalizerZone() async {
Zone? expectedZone;
Zone? actualZone;
final finalizer = runZoned(() {
expectedZone = Zone.current;
void callback(Object token) {
actualZone = Zone.current;
}
return Finalizer<Nonce>(callback);
});
final detach = Nonce(2022);
final token = Nonce(42);
makeObjectWithFinalizer(finalizer, token, detach: detach);
doGC();
// We haven't stopped running synchronous dart code yet.
Expect.isNull(actualZone);
await yieldToMessageLoop();
// Now we have.
Expect.equals(expectedZone, actualZone);
}
Future<void> testFinalizerException() async {
Object? caughtError;
final finalizer = runZonedGuarded(() {
void callback(Object token) {
throw 'uncaught!';
}
return Finalizer<Nonce>(callback);
}, (Object error, StackTrace stack) {
caughtError = error;
})!;
final detach = Nonce(2022);
final token = Nonce(42);
makeObjectWithFinalizer(finalizer, token, detach: detach);
doGC();
Expect.isNull(caughtError);
await yieldToMessageLoop();
Expect.isNotNull(caughtError);
}

View file

@ -0,0 +1,55 @@
// 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.
// ignore: import_internal_library, unused_import
import 'dart:_internal';
import 'dart:async';
/// A user-defined class of which objects can be identified with a field value.
class Nonce {
final int value;
Nonce(this.value);
String toString() => 'Nonce($value)';
}
/// Never inline to ensure `object` becomes unreachable.
@pragma('vm:never-inline')
void makeObjectWithFinalizer<T>(Finalizer<T> finalizer, T token,
{Object? detach}) {
final value = Nonce(1);
finalizer.attach(value, token, detach: detach);
}
/// Triggers garbage collection.
// Defined in `dart:_internal`.
// ignore: undefined_identifier
void triggerGc() => VMInternalsForTesting.collectAllGarbage();
void Function(String) _namedPrint(String? name) {
if (name != null) {
return (String value) => print('$name: $value');
}
return (String value) => print(value);
}
/// Does a GC and if [doAwait] awaits a future to enable running finalizers.
///
/// Also prints for debug purposes.
///
/// If provided, [name] prefixes the debug prints.
void doGC({String? name}) {
final _print = _namedPrint(name);
_print('Do GC.');
triggerGc();
_print('GC done');
}
Future<void> yieldToMessageLoop({String? name}) async {
await Future.delayed(Duration(milliseconds: 1));
_namedPrint(name)('Await done.');
return null;
}

View file

@ -2,25 +2,16 @@
// 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.
// ignore: import_internal_library, unused_import
import 'dart:_internal';
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() {
testWeakReferenceNonExpandoKey();
testWeakReferenceTypeArgument();
testWeakReference();
}
class Nonce {
final int value;
Nonce(this.value);
String toString() => 'Nonce($value)';
}
void testWeakReferenceNonExpandoKey() {
Expect.throwsArgumentError(() {
WeakReference<String>("Hello world!");
@ -55,7 +46,3 @@ void testWeakReference() {
print('End of test, shutting down.');
}
// Defined in `dart:_internal`.
// ignore: undefined_identifier
void triggerGc() => VMInternalsForTesting.collectAllGarbage();

View file

@ -245,6 +245,7 @@ class SendReceiveTest extends SendReceiveTestBase {
await testWeakProperty();
await testWeakReference();
await testFinalizer();
await testForbiddenClosures();
}
@ -756,6 +757,14 @@ class SendReceiveTest extends SendReceiveTestBase {
}
}
Future testFinalizer() async {
print('testFinalizer');
void callback(Object token) {}
final finalizer = Finalizer<Object>(callback);
Expect.throwsArgumentError(() => sendPort.send(finalizer));
}
Future testForbiddenClosures() async {
print('testForbiddenClosures');
for (final closure in nonCopyableClosures) {

View file

@ -0,0 +1,38 @@
// 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.
// @dart = 2.9
import 'dart:isolate';
import 'helpers.dart';
int callbackCount = 0;
void callback(Nonce token) {
callbackCount++;
print('$name: Running finalizer: token: $token');
}
final finalizer = Finalizer<Nonce>(callback);
String name;
void main(List<String> arguments, SendPort port) async {
name = arguments[0];
final token = Nonce(42);
makeObjectWithFinalizer(finalizer, token);
final awaitBeforeShuttingDown = ReceivePort();
port.send(awaitBeforeShuttingDown.sendPort);
final message = await awaitBeforeShuttingDown.first;
print('$name: $message');
await Future.delayed(Duration(milliseconds: 1));
print('$name: Awaited to see if there were any callbacks.');
print('$name: Helper isolate exiting. num callbacks: $callbackCount.');
port.send(callbackCount);
}

View file

@ -0,0 +1,95 @@
// 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
// @dart = 2.9
import 'dart:async';
import 'dart:isolate';
import 'package:async/async.dart';
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() async {
await testFinalizerInOtherIsolateGroupGCBeforeExit();
await testFinalizerInOtherIsolateGroupGCAfterExit();
await testFinalizerInOtherIsolateGroupNoGC();
print('$name: End of test, shutting down.');
}
const name = 'main';
bool hotReloadBot;
Future<void> testFinalizerInOtherIsolateGroupGCBeforeExit() async {
final receivePort = ReceivePort();
final messagesQueue = StreamQueue(receivePort);
await Isolate.spawnUri(
Uri.parse('finalizer_isolate_groups_run_gc_helper.dart'),
['helper 1'],
receivePort.sendPort,
);
final signalHelperIsolate = await messagesQueue.next as SendPort;
doGC(name: name);
await yieldToMessageLoop(name: name);
signalHelperIsolate.send('Done GCing.');
final helperCallbacks = await messagesQueue.next as int;
messagesQueue.cancel();
print('$name: Helper exited.');
// Different isolate group, so we don't expect a GC in this isolate to cause
// collected objects in the helper.
// Except for in --hot-reload-test-mode, then the GC is triggered.
hotReloadBot = helperCallbacks == 1;
}
Future<void> testFinalizerInOtherIsolateGroupGCAfterExit() async {
final receivePort = ReceivePort();
final messagesQueue = StreamQueue(receivePort);
await Isolate.spawnUri(
Uri.parse('finalizer_isolate_groups_run_gc_helper.dart'),
['helper 2'],
receivePort.sendPort,
);
final signalHelperIsolate = await messagesQueue.next as SendPort;
signalHelperIsolate.send('Before GCing.');
final helperCallbacks = await messagesQueue.next as int;
messagesQueue.cancel();
print('$name: Helper exited.');
Expect.equals(hotReloadBot ? 1 : 0, helperCallbacks);
doGC(name: name);
await yieldToMessageLoop(name: name);
}
Future<void> testFinalizerInOtherIsolateGroupNoGC() async {
final receivePort = ReceivePort();
final messagesQueue = StreamQueue(receivePort);
await Isolate.spawnUri(
Uri.parse('finalizer_isolate_groups_run_gc_helper.dart'),
['helper 3'],
receivePort.sendPort,
);
final signalHelperIsolate = await messagesQueue.next as SendPort;
signalHelperIsolate.send('Before quitting main isolate.');
final helperCallbacks = await messagesQueue.next as int;
messagesQueue.cancel();
print('$name: Helper exited.');
Expect.equals(hotReloadBot ? 1 : 0, helperCallbacks);
}

View file

@ -0,0 +1,104 @@
// 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
// @dart = 2.9
import 'dart:async';
import 'dart:isolate';
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() async {
await testNormalExit();
await testSendAndExit();
await testSendAndExitFinalizer();
print('End of test, shutting down.');
}
final finalizerTokens = <Nonce>{};
void callback(Nonce token) {
print('Running finalizer: token: $token');
finalizerTokens.add(token);
}
void runIsolateAttachFinalizer(Object message) {
final finalizer = Finalizer<Nonce>(callback);
final value = Nonce(1001);
final token = Nonce(1002);
finalizer.attach(value, token);
final token9 = Nonce(9002);
makeObjectWithFinalizer(finalizer, token9);
if (message == null) {
print('Isolate done.');
return;
}
final list = message as List;
assert(list.length == 2);
final sendPort = list[0] as SendPort;
final tryToSendFinalizer = list[1] as bool;
if (tryToSendFinalizer) {
Expect.throws(() {
// TODO(http://dartbug.com/47777): Send and exit support.
print('Trying to send and exit finalizer.');
Isolate.exit(sendPort, [value, finalizer]);
});
}
print('Isolate sending and exit.');
Isolate.exit(sendPort, [value]);
}
Future testNormalExit() async {
final portExitMessage = ReceivePort();
await Isolate.spawn(
runIsolateAttachFinalizer,
null,
onExit: portExitMessage.sendPort,
);
await portExitMessage.first;
doGC();
await yieldToMessageLoop();
Expect.equals(0, finalizerTokens.length);
}
@pragma('vm:never-inline')
Future<Finalizer> testSendAndExitHelper({bool trySendFinalizer = false}) async {
final port = ReceivePort();
await Isolate.spawn(
runIsolateAttachFinalizer,
[port.sendPort, trySendFinalizer],
);
final message = await port.first as List;
print('Received message ($message).');
final value = message[0] as Nonce;
print('Received value ($value), but now forgetting about it.');
Expect.equals(1, message.length);
// TODO(http://dartbug.com/47777): Send and exit support.
return null;
}
Future testSendAndExit() async {
await testSendAndExitHelper(trySendFinalizer: false);
doGC();
await yieldToMessageLoop();
Expect.equals(0, finalizerTokens.length);
}
Future testSendAndExitFinalizer() async {
final finalizer = await testSendAndExitHelper(trySendFinalizer: true);
// TODO(http://dartbug.com/47777): Send and exit support.
Expect.isNull(finalizer);
}

View file

@ -0,0 +1,45 @@
// 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.
// @dart = 2.9
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() {
testFinalizer();
}
void testFinalizer() async {
final finalizerTokens = <Nonce>{};
void callback(Nonce token) {
print('Running finalizer: token: $token');
finalizerTokens.add(token);
}
final finalizer = Finalizer<Nonce>(callback);
{
final detach = Nonce(2022);
final token = null;
makeObjectWithFinalizer(finalizer, token, detach: detach);
doGC();
// We haven't stopped running synchronous dart code yet.
Expect.isFalse(finalizerTokens.contains(token));
await Future.delayed(Duration(milliseconds: 1));
// Now we have.
Expect.isTrue(finalizerTokens.contains(token));
// Try detaching after finalizer ran.
finalizer.detach(detach);
}
print('End of test, shutting down.');
}

View file

@ -0,0 +1,92 @@
// 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
// @dart = 2.9
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() {
testWrongArguments();
testFinalizer();
}
void testWrongArguments() {
void callback(Object token) {
throw 'This should never happen!';
}
final finalizer = Finalizer<Nonce>(callback);
final myFinalizable = Nonce(1000);
final token = Nonce(2000);
final detach = Nonce(3000);
Expect.throws(() {
finalizer.attach(myFinalizable, token, detach: 123);
});
Expect.throws(() {
finalizer.attach(123, token, detach: detach);
});
}
void testFinalizer() async {
final finalizerTokens = <Object>{};
void callback(Object token) {
print('Running finalizer: token: $token');
finalizerTokens.add(token);
}
final finalizer = Finalizer<Nonce>(callback);
{
final detach = Nonce(2022);
final token = Nonce(42);
makeObjectWithFinalizer(finalizer, token, detach: detach);
doGC();
// We haven't stopped running synchronous dart code yet.
Expect.isFalse(finalizerTokens.contains(token));
await Future.delayed(Duration(milliseconds: 1));
// Now we have.
Expect.isTrue(finalizerTokens.contains(token));
// Try detaching after finalizer ran.
finalizer.detach(detach);
}
{
final token = Nonce(1337);
final token2 = Nonce(1338);
final detachkey = Nonce(1984);
{
final value = Nonce(2);
final value2 = Nonce(2000000);
finalizer.attach(value, token, detach: detachkey);
finalizer.attach(value2, token2, detach: detachkey);
// Should detach 2 finalizers.
finalizer.detach(detachkey);
// Try detaching again, should do nothing.
finalizer.detach(detachkey);
}
doGC();
await yieldToMessageLoop();
Expect.isFalse(finalizerTokens.contains(token));
Expect.isFalse(finalizerTokens.contains(token2));
}
// Not running finalizer on shutdown.
final value = Nonce(3);
final token = Nonce(1337);
finalizer.attach(value, token);
print('End of test, shutting down.');
}

View file

@ -0,0 +1,75 @@
// 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.
// VMOptions=
// VMOptions=--use_compactor
// VMOptions=--use_compactor --force_evacuation
// @dart = 2.9
import 'dart:async';
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() async {
await testFinalizerZone();
await testFinalizerException();
}
Future<void> testFinalizerZone() async {
Zone expectedZone;
Zone actualZone;
final finalizer = runZoned(() {
expectedZone = Zone.current;
void callback(Object token) {
actualZone = Zone.current;
}
return Finalizer<Nonce>(callback);
});
final detach = Nonce(2022);
final token = Nonce(42);
makeObjectWithFinalizer(finalizer, token, detach: detach);
doGC();
// We haven't stopped running synchronous dart code yet.
Expect.isNull(actualZone);
await yieldToMessageLoop();
// Now we have.
Expect.equals(expectedZone, actualZone);
}
Future<void> testFinalizerException() async {
Object caughtError;
final finalizer = runZonedGuarded(() {
void callback(Object token) {
throw 'uncaught!';
}
return Finalizer<Nonce>(callback);
}, (Object error, StackTrace stack) {
caughtError = error;
});
final detach = Nonce(2022);
final token = Nonce(42);
makeObjectWithFinalizer(finalizer, token, detach: detach);
doGC();
Expect.isNull(caughtError);
await yieldToMessageLoop();
Expect.isNotNull(caughtError);
}

View file

@ -0,0 +1,57 @@
// 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.
// @dart = 2.9
// ignore: import_internal_library, unused_import
import 'dart:_internal';
import 'dart:async';
/// A user-defined class of which objects can be identified with a field value.
class Nonce {
final int value;
Nonce(this.value);
String toString() => 'Nonce($value)';
}
/// Never inline to ensure `value` becomes unreachable.
@pragma('vm:never-inline')
void makeObjectWithFinalizer<T>(Finalizer<T> finalizer, T token,
{Object detach}) {
final value = Nonce(1);
finalizer.attach(value, token, detach: detach);
}
/// Triggers garbage collection.
// Defined in `dart:_internal`.
// ignore: undefined_identifier
void triggerGc() => VMInternalsForTesting.collectAllGarbage();
void Function(String) _namedPrint(String name) {
if (name != null) {
return (String value) => print('$name: $value');
}
return (String value) => print(value);
}
/// Does a GC and if [doAwait] awaits a future to enable running finalizers.
///
/// Also prints for debug purposes.
///
/// If provided, [name] prefixes the debug prints.
void doGC({String name}) {
final _print = _namedPrint(name);
_print('Do GC.');
triggerGc();
_print('GC done');
}
Future<void> yieldToMessageLoop({String name}) async {
await Future.delayed(Duration(milliseconds: 1));
_namedPrint(name)('Await done.');
return null;
}

View file

@ -4,25 +4,16 @@
// @dart = 2.9
// ignore: import_internal_library, unused_import
import 'dart:_internal';
import 'package:expect/expect.dart';
import 'helpers.dart';
void main() {
testWeakReferenceNonExpandoKey();
testWeakReferenceTypeArgument();
testWeakReference();
}
class Nonce {
final int value;
Nonce(this.value);
String toString() => 'Nonce($value)';
}
void testWeakReferenceNonExpandoKey() {
Expect.throwsArgumentError(() {
WeakReference<String>("Hello world!");
@ -57,7 +48,3 @@ void testWeakReference() {
print('End of test, shutting down.');
}
// Defined in `dart:_internal`.
// ignore: undefined_identifier
void triggerGc() => VMInternalsForTesting.collectAllGarbage();

View file

@ -247,6 +247,7 @@ class SendReceiveTest extends SendReceiveTestBase {
await testWeakProperty();
await testWeakReference();
await testFinalizer();
await testForbiddenClosures();
}
@ -758,6 +759,14 @@ class SendReceiveTest extends SendReceiveTestBase {
}
}
Future testFinalizer() async {
print('testFinalizer');
void callback(Object token) {}
final finalizer = Finalizer<Object>(callback);
Expect.throwsArgumentError(() => sendPort.send(finalizer));
}
Future testForbiddenClosures() async {
print('testForbiddenClosures');
for (final closure in nonCopyableClosures) {

View file

@ -108,6 +108,7 @@ dart_2/isolates/reload_*: SkipByDesign # These tests only run on normal JIT.
[ $compiler == dartkp ]
dart/causal_stacks/async_throws_stack_no_causal_non_symbolic_test: SkipByDesign # --no-lazy... does nothing on precompiler.
dart/causal_stacks/async_throws_stack_no_causal_test: SkipByDesign # --no-lazy... does nothing on precompiler.
dart/finalizer/finalizer_isolate_groups_run_gc_test: SkipByDesign # Isolate.spawnUri is not supported in AOT.
dart/redirection_type_shuffling_test: SkipByDesign # Uses dart:mirrors.
dart/scavenger_abort_test: SkipSlow
dart/v8_snapshot_profile_writer_test: Pass, Slow # Can be slow due to re-invoking the precompiler.
@ -442,9 +443,11 @@ dart_2/*: SkipByDesign # Legacy tests are not supposed to run on NNBD bots.
# These Isolate tests that use spawnURI are hence skipped on purpose.
[ $runtime == dart_precompiled || $runtime == vm && ($arch == simarm || $arch == simarm64 || $arch == simarm64c || $arch == simriscv32 || $arch == simriscv64) ]
dart/data_uri_spawn_test: SkipByDesign # Isolate.spawnUri
dart/finalizer/finalizer_isolate_groups_run_gc_test: SkipByDesign # uses spawnUri.
dart/isolates/send_object_to_spawn_uri_isolate_test: SkipByDesign # uses spawnUri
dart/issue32950_test: SkipByDesign # uses spawnUri.
dart_2/data_uri_spawn_test: SkipByDesign # Isolate.spawnUri
dart_2/finalizer/finalizer_isolate_groups_run_gc_test: SkipByDesign # uses spawnUri.
dart_2/isolates/send_object_to_spawn_uri_isolate_test: SkipByDesign # uses spawnUri
dart_2/issue32950_test: SkipByDesign # uses spawnUri.

View file

@ -267,6 +267,10 @@ void ClassFinalizer::VerifyBootstrapClasses() {
ASSERT_EQUAL(WeakProperty::InstanceSize(), cls.host_instance_size());
cls = object_store->weak_reference_class();
ASSERT_EQUAL(WeakReference::InstanceSize(), cls.host_instance_size());
cls = object_store->finalizer_class();
ASSERT_EQUAL(Finalizer::InstanceSize(), cls.host_instance_size());
cls = object_store->finalizer_entry_class();
ASSERT_EQUAL(FinalizerEntry::InstanceSize(), cls.host_instance_size());
cls = object_store->linked_hash_map_class();
ASSERT_EQUAL(LinkedHashMap::InstanceSize(), cls.host_instance_size());
cls = object_store->immutable_linked_hash_map_class();

View file

@ -68,6 +68,9 @@ typedef uint16_t ClassIdTagType;
V(TypeArguments) \
V(AbstractType) \
V(Type) \
V(FinalizerBase) \
V(Finalizer) \
V(FinalizerEntry) \
V(FunctionType) \
V(TypeRef) \
V(TypeParameter) \

View file

@ -2797,6 +2797,16 @@ void LoadFieldInstr::InferRange(RangeAnalysis* analysis, Range* range) {
case Slot::Kind::kClosure_function:
case Slot::Kind::kClosure_function_type_arguments:
case Slot::Kind::kClosure_instantiator_type_arguments:
case Slot::Kind::kFinalizer_callback:
case Slot::Kind::kFinalizer_type_arguments:
case Slot::Kind::kFinalizerBase_all_entries:
case Slot::Kind::kFinalizerBase_detachments:
case Slot::Kind::kFinalizerBase_entries_collected:
case Slot::Kind::kFinalizerEntry_detach:
case Slot::Kind::kFinalizerEntry_finalizer:
case Slot::Kind::kFinalizerEntry_next:
case Slot::Kind::kFinalizerEntry_token:
case Slot::Kind::kFinalizerEntry_value:
case Slot::Kind::kFunction_data:
case Slot::Kind::kFunction_signature:
case Slot::Kind::kFunctionType_named_parameter_names:

View file

@ -227,6 +227,16 @@ bool Slot::IsImmutableLengthSlot() const {
case Slot::Kind::kClosure_hash:
case Slot::Kind::kCapturedVariable:
case Slot::Kind::kDartField:
case Slot::Kind::kFinalizer_callback:
case Slot::Kind::kFinalizer_type_arguments:
case Slot::Kind::kFinalizerBase_all_entries:
case Slot::Kind::kFinalizerBase_detachments:
case Slot::Kind::kFinalizerBase_entries_collected:
case Slot::Kind::kFinalizerEntry_detach:
case Slot::Kind::kFinalizerEntry_finalizer:
case Slot::Kind::kFinalizerEntry_next:
case Slot::Kind::kFinalizerEntry_token:
case Slot::Kind::kFinalizerEntry_value:
case Slot::Kind::kFunction_data:
case Slot::Kind::kFunction_signature:
case Slot::Kind::kFunctionType_named_parameter_names:

View file

@ -53,6 +53,15 @@ class ParsedFunction;
// that) or like a non-final field.
#define NULLABLE_BOXED_NATIVE_SLOTS_LIST(V) \
V(Array, UntaggedArray, type_arguments, TypeArguments, FINAL) \
V(Finalizer, UntaggedFinalizer, type_arguments, TypeArguments, FINAL) \
V(FinalizerBase, UntaggedFinalizerBase, all_entries, LinkedHashSet, VAR) \
V(FinalizerBase, UntaggedFinalizerBase, detachments, Dynamic, VAR) \
V(FinalizerBase, UntaggedFinalizer, entries_collected, FinalizerEntry, VAR) \
V(FinalizerEntry, UntaggedFinalizerEntry, value, Dynamic, VAR) \
V(FinalizerEntry, UntaggedFinalizerEntry, detach, Dynamic, VAR) \
V(FinalizerEntry, UntaggedFinalizerEntry, token, Dynamic, VAR) \
V(FinalizerEntry, UntaggedFinalizerEntry, finalizer, FinalizerBase, VAR) \
V(FinalizerEntry, UntaggedFinalizerEntry, next, FinalizerEntry, VAR) \
V(Function, UntaggedFunction, signature, FunctionType, FINAL) \
V(Context, UntaggedContext, parent, Context, FINAL) \
V(Closure, UntaggedClosure, instantiator_type_arguments, TypeArguments, \
@ -91,6 +100,7 @@ class ParsedFunction;
V(Closure, UntaggedClosure, function, Function, FINAL) \
V(Closure, UntaggedClosure, context, Context, FINAL) \
V(Closure, UntaggedClosure, hash, Context, VAR) \
V(Finalizer, UntaggedFinalizer, callback, Closure, FINAL) \
V(Function, UntaggedFunction, data, Dynamic, FINAL) \
V(FunctionType, UntaggedFunctionType, named_parameter_names, Array, FINAL) \
V(FunctionType, UntaggedFunctionType, parameter_types, Array, FINAL) \
@ -159,6 +169,7 @@ NONNULLABLE_BOXED_NATIVE_SLOTS_LIST(FOR_EACH_NATIVE_SLOT)
AOT_ONLY_UNBOXED_NATIVE_SLOTS_LIST(V) \
V(ClosureData, UntaggedClosureData, default_type_arguments_kind, Uint8, \
FINAL) \
V(FinalizerBase, UntaggedFinalizerBase, isolate, IntPtr, VAR) \
V(Function, UntaggedFunction, entry_point, Uword, FINAL) \
V(Function, UntaggedFunction, kind_tag, Uint32, FINAL) \
V(Function, UntaggedFunction, packed_fields, Uint32, FINAL) \

View file

@ -818,6 +818,13 @@ Fragment FlowGraphBuilder::NativeFunctionBody(const Function& function,
V(ByteDataViewLength, TypedDataBase_length) \
V(ByteDataViewOffsetInBytes, TypedDataView_offset_in_bytes) \
V(ByteDataViewTypedData, TypedDataView_typed_data) \
V(Finalizer_getCallback, Finalizer_callback) \
V(FinalizerBase_getAllEntries, FinalizerBase_all_entries) \
V(FinalizerBase_getDetachments, FinalizerBase_detachments) \
V(FinalizerEntry_getDetach, FinalizerEntry_detach) \
V(FinalizerEntry_getNext, FinalizerEntry_next) \
V(FinalizerEntry_getToken, FinalizerEntry_token) \
V(FinalizerEntry_getValue, FinalizerEntry_value) \
V(GrowableArrayLength, GrowableObjectArray_length) \
V(ImmutableLinkedHashBase_getData, ImmutableLinkedHashBase_data) \
V(ImmutableLinkedHashBase_getIndex, ImmutableLinkedHashBase_index) \
@ -835,6 +842,14 @@ Fragment FlowGraphBuilder::NativeFunctionBody(const Function& function,
V(WeakReference_getTarget, WeakReference_target)
#define STORE_NATIVE_FIELD(V) \
V(Finalizer_setCallback, Finalizer_callback) \
V(FinalizerBase_setAllEntries, FinalizerBase_all_entries) \
V(FinalizerBase_setDetachments, FinalizerBase_detachments) \
V(FinalizerEntry_setDetach, FinalizerEntry_detach) \
V(FinalizerEntry_setFinalizer, FinalizerEntry_finalizer) \
V(FinalizerEntry_setNext, FinalizerEntry_next) \
V(FinalizerEntry_setToken, FinalizerEntry_token) \
V(FinalizerEntry_setValue, FinalizerEntry_value) \
V(LinkedHashBase_setData, LinkedHashBase_data) \
V(LinkedHashBase_setIndex, LinkedHashBase_index) \
V(WeakProperty_setKey, WeakProperty_key) \
@ -919,6 +934,10 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
case MethodRecognizer::kFfiAsExternalTypedDataFloat:
case MethodRecognizer::kFfiAsExternalTypedDataDouble:
case MethodRecognizer::kGetNativeField:
case MethodRecognizer::kFinalizerBase_exchangeEntriesCollectedWithNull:
case MethodRecognizer::kFinalizerBase_getIsolateFinalizers:
case MethodRecognizer::kFinalizerBase_setIsolate:
case MethodRecognizer::kFinalizerBase_setIsolateFinalizers:
case MethodRecognizer::kObjectEquals:
case MethodRecognizer::kStringBaseLength:
case MethodRecognizer::kStringBaseIsEmpty:
@ -1567,6 +1586,41 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += MathUnary(MathUnaryInstr::kSqrt);
} break;
case MethodRecognizer::kFinalizerBase_setIsolate:
ASSERT_EQUAL(function.NumParameters(), 1);
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += LoadIsolate();
body += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
body += StoreNativeField(Slot::FinalizerBase_isolate());
body += NullConstant();
break;
case MethodRecognizer::kFinalizerBase_getIsolateFinalizers:
ASSERT_EQUAL(function.NumParameters(), 0);
body += LoadIsolate();
body += RawLoadField(compiler::target::Isolate::finalizers_offset());
break;
case MethodRecognizer::kFinalizerBase_setIsolateFinalizers:
ASSERT_EQUAL(function.NumParameters(), 1);
body += LoadIsolate();
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += RawStoreField(compiler::target::Isolate::finalizers_offset());
body += NullConstant();
break;
case MethodRecognizer::kFinalizerBase_exchangeEntriesCollectedWithNull:
ASSERT_EQUAL(function.NumParameters(), 1);
ASSERT(this->optimizing_);
// This relies on being force-optimized to do an 'atomic' exchange w.r.t.
// the GC.
// As an alternative design we could introduce an ExchangeNativeFieldInstr
// that uses the same machine code as std::atomic::exchange. Or we could
// use an FfiNative to do that in C.
body += LoadLocal(parsed_function_->RawParameterVariable(0));
// No GC from here til StoreNativeField.
body += LoadNativeField(Slot::FinalizerBase_entries_collected());
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += NullConstant();
body += StoreNativeField(Slot::FinalizerBase_entries_collected());
break;
#define IL_BODY(method, slot) \
case MethodRecognizer::k##method: \
ASSERT_EQUAL(function.NumParameters(), 1); \
@ -3974,6 +4028,19 @@ Fragment FlowGraphBuilder::UnboxTruncate(Representation to) {
return Fragment(unbox);
}
Fragment FlowGraphBuilder::LoadThread() {
LoadThreadInstr* instr = new (Z) LoadThreadInstr();
Push(instr);
return Fragment(instr);
}
Fragment FlowGraphBuilder::LoadIsolate() {
Fragment body;
body += LoadThread();
body += LoadUntagged(compiler::target::Thread::isolate_offset());
return body;
}
// TODO(http://dartbug.com/47487): Support unboxed output value.
Fragment FlowGraphBuilder::BoolToInt() {
// TODO(http://dartbug.com/36855) Build IfThenElseInstr, instead of letting

View file

@ -276,6 +276,12 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
// target representation.
Fragment UnboxTruncate(Representation to);
// Loads the (untagged) thread address.
Fragment LoadThread();
// Loads the (untagged) isolate address.
Fragment LoadIsolate();
// Converts a true to 1 and false to 0.
Fragment BoolToInt();

View file

@ -110,6 +110,28 @@ namespace dart {
V(::, _sqrt, MathSqrt, 0x03183390) \
V(::, _exp, MathExp, 0x00f4ffd0) \
V(::, _log, MathLog, 0x09ae8462) \
V(FinalizerBase, get:_allEntries, FinalizerBase_getAllEntries, 0xf03ff26b) \
V(FinalizerBase, set:_allEntries, FinalizerBase_setAllEntries, 0x8f0920e8) \
V(FinalizerBase, get:_detachments, FinalizerBase_getDetachments, 0x2f650f36) \
V(FinalizerBase, set:_detachments, FinalizerBase_setDetachments, 0x788f1df3) \
V(FinalizerBase, _exchangeEntriesCollectedWithNull, \
FinalizerBase_exchangeEntriesCollectedWithNull, 0x6c9124fb) \
V(FinalizerBase, _setIsolate, FinalizerBase_setIsolate, 0xbcf7db91) \
V(FinalizerBase, get:_isolateFinalizers, FinalizerBase_getIsolateFinalizers, \
0x70f53b2b) \
V(FinalizerBase, set:_isolateFinalizers, FinalizerBase_setIsolateFinalizers, \
0xb3e66928) \
V(_FinalizerImpl, get:_callback, Finalizer_getCallback, 0x6f3d56bc) \
V(_FinalizerImpl, set:_callback, Finalizer_setCallback, 0xc6aa96f9) \
V(FinalizerEntry, get:value, FinalizerEntry_getValue, 0xf5c9b9d7) \
V(FinalizerEntry, set:value, FinalizerEntry_setValue, 0x5501cc54) \
V(FinalizerEntry, get:detach, FinalizerEntry_getDetach, 0x171cd968) \
V(FinalizerEntry, set:detach, FinalizerEntry_setDetach, 0x7654ebe5) \
V(FinalizerEntry, set:finalizer, FinalizerEntry_setFinalizer, 0x15cfefe9) \
V(FinalizerEntry, get:token, FinalizerEntry_getToken, 0x04915a72) \
V(FinalizerEntry, set:token, FinalizerEntry_setToken, 0x63c96cef) \
V(FinalizerEntry, get:next, FinalizerEntry_getNext, 0x7102d7a4) \
V(FinalizerEntry, set:next, FinalizerEntry_setNext, 0xd0b2ee61) \
V(Float32x4, _Float32x4FromDoubles, Float32x4FromDoubles, 0x1845792b) \
V(Float32x4, Float32x4.zero, Float32x4Zero, 0xd3b64002) \
V(Float32x4, _Float32x4Splat, Float32x4Splat, 0x13a552c3) \

View file

@ -443,6 +443,10 @@ static uword GetInstanceSizeImpl(const dart::Class& handle) {
return WeakProperty::InstanceSize();
case kWeakReferenceCid:
return WeakReference::InstanceSize();
case kFinalizerCid:
return Finalizer::InstanceSize();
case kFinalizerEntryCid:
return FinalizerEntry::InstanceSize();
case kByteBufferCid:
case kByteDataViewCid:
case kPointerCid:

View file

@ -1015,6 +1015,34 @@ class WeakReference : public AllStatic {
FINAL_CLASS();
};
class FinalizerBase : public AllStatic {
public:
static word all_entries_offset();
static word detachments_offset();
static word entries_collected_offset();
static word isolate_offset();
FINAL_CLASS();
};
class Finalizer : public AllStatic {
public:
static word type_arguments_offset();
static word callback_offset();
static word InstanceSize();
FINAL_CLASS();
};
class FinalizerEntry : public AllStatic {
public:
static word value_offset();
static word detach_offset();
static word token_offset();
static word next_offset();
static word finalizer_offset();
static word InstanceSize();
FINAL_CLASS();
};
class MirrorReference : public AllStatic {
public:
static word InstanceSize();
@ -1212,6 +1240,7 @@ class Isolate : public AllStatic {
static word current_tag_offset();
static word user_tag_offset();
static word ic_miss_code_offset();
static word finalizers_offset();
#if !defined(PRODUCT)
static word single_step_offset();
#endif // !defined(PRODUCT)

File diff suppressed because it is too large Load diff

View file

@ -149,6 +149,7 @@
FIELD(Int32x4, value_offset) \
FIELD(Isolate, current_tag_offset) \
FIELD(Isolate, default_tag_offset) \
FIELD(Isolate, finalizers_offset) \
FIELD(Isolate, ic_miss_code_offset) \
FIELD(IsolateGroup, object_store_offset) \
FIELD(IsolateGroup, shared_class_table_offset) \
@ -298,6 +299,17 @@
FIELD(Type, type_class_id_offset) \
FIELD(Type, type_state_offset) \
FIELD(Type, nullability_offset) \
FIELD(Finalizer, type_arguments_offset) \
FIELD(Finalizer, callback_offset) \
FIELD(FinalizerBase, all_entries_offset) \
FIELD(FinalizerBase, detachments_offset) \
FIELD(FinalizerBase, entries_collected_offset) \
FIELD(FinalizerBase, isolate_offset) \
FIELD(FinalizerEntry, value_offset) \
FIELD(FinalizerEntry, detach_offset) \
FIELD(FinalizerEntry, token_offset) \
FIELD(FinalizerEntry, finalizer_offset) \
FIELD(FinalizerEntry, next_offset) \
FIELD(FunctionType, hash_offset) \
FIELD(FunctionType, named_parameter_names_offset) \
FIELD(FunctionType, nullability_offset) \
@ -360,6 +372,8 @@
SIZEOF(ExternalTypedData, InstanceSize, UntaggedExternalTypedData) \
SIZEOF(FfiTrampolineData, InstanceSize, UntaggedFfiTrampolineData) \
SIZEOF(Field, InstanceSize, UntaggedField) \
SIZEOF(Finalizer, InstanceSize, UntaggedFinalizer) \
SIZEOF(FinalizerEntry, InstanceSize, UntaggedFinalizerEntry) \
SIZEOF(Float32x4, InstanceSize, UntaggedFloat32x4) \
SIZEOF(Float64x2, InstanceSize, UntaggedFloat64x2) \
SIZEOF(Function, InstanceSize, UntaggedFunction) \

View file

@ -370,6 +370,7 @@ char* Dart::DartInit(const Dart_InitializeParams* params) {
Object::InitNullAndBool(vm_isolate_->group());
vm_isolate_->isolate_group_->set_object_store(new ObjectStore());
vm_isolate_->isolate_object_store()->Init();
vm_isolate_->finalizers_ = GrowableObjectArray::null();
Object::Init(vm_isolate_->group());
OffsetsTable::Init();
ArgumentsDescriptor::Init();

View file

@ -677,18 +677,14 @@ ObjectPtr DartLibraryCalls::Equals(const Instance& left,
}
ObjectPtr DartLibraryCalls::LookupHandler(Dart_Port port_id) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Thread* const thread = Thread::Current();
Zone* const zone = thread->zone();
const auto& function = Function::Handle(
zone, thread->isolate_group()->object_store()->lookup_port_handler());
const int kNumArguments = 1;
ASSERT(!function.IsNull());
Array& args = Array::Handle(
zone, thread->isolate()->isolate_object_store()->dart_args_1());
if (args.IsNull()) {
args = Array::New(kNumArguments);
thread->isolate()->isolate_object_store()->set_dart_args_1(args);
}
ASSERT(!args.IsNull());
args.SetAt(0, Integer::Handle(zone, Integer::New(port_id)));
const Object& result =
Object::Handle(zone, DartEntry::InvokeFunction(function, args));
@ -706,24 +702,7 @@ ObjectPtr DartLibraryCalls::LookupOpenPorts() {
return result.ptr();
}
ObjectPtr DartLibraryCalls::HandleMessage(Dart_Port port_id,
const Instance& message) {
auto thread = Thread::Current();
auto zone = thread->zone();
auto isolate = thread->isolate();
auto object_store = thread->isolate_group()->object_store();
const auto& function =
Function::Handle(zone, object_store->handle_message_function());
const int kNumArguments = 2;
ASSERT(!function.IsNull());
Array& args =
Array::Handle(zone, isolate->isolate_object_store()->dart_args_2());
if (args.IsNull()) {
args = Array::New(kNumArguments);
isolate->isolate_object_store()->set_dart_args_2(args);
}
args.SetAt(0, Integer::Handle(zone, Integer::New(port_id)));
args.SetAt(1, message);
static void DebuggerSetResumeIfStepping(Isolate* isolate) {
#if !defined(PRODUCT)
if (isolate->debugger()->IsStepping()) {
// If the isolate is being debugged and the debugger was stepping
@ -732,6 +711,47 @@ ObjectPtr DartLibraryCalls::HandleMessage(Dart_Port port_id,
isolate->debugger()->SetResumeAction(Debugger::kStepInto);
}
#endif
}
ObjectPtr DartLibraryCalls::HandleMessage(Dart_Port port_id,
const Instance& message) {
auto* const thread = Thread::Current();
auto* const zone = thread->zone();
auto* const isolate = thread->isolate();
auto* const object_store = thread->isolate_group()->object_store();
const auto& function =
Function::Handle(zone, object_store->handle_message_function());
ASSERT(!function.IsNull());
Array& args =
Array::Handle(zone, isolate->isolate_object_store()->dart_args_2());
ASSERT(!args.IsNull());
args.SetAt(0, Integer::Handle(zone, Integer::New(port_id)));
args.SetAt(1, message);
DebuggerSetResumeIfStepping(isolate);
const Object& handler =
Object::Handle(zone, DartEntry::InvokeFunction(function, args));
return handler.ptr();
}
ObjectPtr DartLibraryCalls::HandleFinalizerMessage(
const FinalizerBase& finalizer) {
if (FLAG_trace_finalizers) {
THR_Print("Running finalizer %p callback on isolate %p\n",
finalizer.ptr()->untag(), finalizer.isolate());
}
auto* const thread = Thread::Current();
auto* const zone = thread->zone();
auto* const isolate = thread->isolate();
auto* const object_store = thread->isolate_group()->object_store();
const auto& function =
Function::Handle(zone, object_store->handle_finalizer_message_function());
ASSERT(!function.IsNull());
Array& args =
Array::Handle(zone, isolate->isolate_object_store()->dart_args_1());
ASSERT(!args.IsNull());
args.SetAt(0, finalizer);
DebuggerSetResumeIfStepping(isolate);
const Object& handler =
Object::Handle(zone, DartEntry::InvokeFunction(function, args));
return handler.ptr();

View file

@ -36,8 +36,8 @@ class ArgumentsDescriptor : public ValueObject {
intptr_t TypeArgsLen() const; // 0 if no type argument vector is passed.
intptr_t FirstArgIndex() const { return TypeArgsLen() > 0 ? 1 : 0; }
intptr_t CountWithTypeArgs() const { return FirstArgIndex() + Count(); }
intptr_t Count() const; // Excluding type arguments vector.
intptr_t Size() const; // Excluding type arguments vector.
intptr_t Count() const; // Excluding type arguments vector.
intptr_t Size() const; // Excluding type arguments vector.
intptr_t SizeWithTypeArgs() const { return FirstArgIndex() + Size(); }
intptr_t PositionalCount() const; // Excluding type arguments vector.
intptr_t NamedCount() const { return Count() - PositionalCount(); }
@ -292,10 +292,12 @@ class DartLibraryCalls : public AllStatic {
// handler for this port id.
static ObjectPtr HandleMessage(Dart_Port port_id, const Instance& message);
// Invokes the finalizer to run its callbacks.
static ObjectPtr HandleFinalizerMessage(const FinalizerBase& finalizer);
// Returns a list of open ReceivePorts.
static ObjectPtr LookupOpenPorts();
// Returns null on success, an ErrorPtr on failure.
static ObjectPtr DrainMicrotaskQueue();

View file

@ -202,6 +202,7 @@ constexpr bool FLAG_support_il_printer = false;
"Generate code for a generic CPU, unknown at compile time") \
D(trace_cha, bool, false, "Trace CHA operations") \
R(trace_field_guards, false, bool, false, "Trace changes in field's cids.") \
D(trace_finalizers, bool, false, "Traces finalizers.") \
D(trace_ic, bool, false, "Trace IC handling") \
D(trace_ic_miss_in_optimized, bool, false, \
"Trace IC miss in optimized code") \

View file

@ -30,7 +30,8 @@ namespace dart {
// - variable name
#define GC_LINKED_LIST(V) \
V(WeakProperty, weak_properties) \
V(WeakReference, weak_references)
V(WeakReference, weak_references) \
V(FinalizerEntry, finalizer_entries)
template <typename Type, typename PtrType>
class GCLinkedList {
@ -81,6 +82,115 @@ struct GCLinkedLists {
#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
// 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 |SetNullIfCollected| 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 bool value_collected_this_gc = GCVisitorType::SetNullIfCollected(
heap_base, &current_entry->untag()->value_);
GCVisitorType::SetNullIfCollected(heap_base,
&current_entry->untag()->detach_);
GCVisitorType::SetNullIfCollected(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 if (finalizer.IsFinalizer()) {
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());
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);
}
}
} else {
// TODO(http://dartbug.com/47777): Implement NativeFinalizer.
UNREACHABLE();
}
}
current_entry = next_entry;
}
}
#undef TRACE_FINALIZER
} // namespace dart
#endif // RUNTIME_VM_HEAP_GC_SHARED_H_

View file

@ -47,6 +47,10 @@ class MarkingVisitorBase : public ObjectPointerVisitor {
int64_t marked_micros() const { return marked_micros_; }
void AddMicros(int64_t micros) { marked_micros_ += micros; }
#ifdef DEBUG
constexpr static const char* const kName = "Marker";
#endif
static bool IsMarked(ObjectPtr raw) {
ASSERT(raw->IsHeapObject());
ASSERT(raw->IsOldObject());
@ -126,6 +130,9 @@ class MarkingVisitorBase : public ObjectPointerVisitor {
} else if (class_id == kWeakReferenceCid) {
WeakReferencePtr raw_weak = static_cast<WeakReferencePtr>(raw_obj);
size = ProcessWeakReference(raw_weak);
} else if (class_id == kFinalizerEntryCid) {
FinalizerEntryPtr raw_weak = static_cast<FinalizerEntryPtr>(raw_obj);
size = ProcessFinalizerEntry(raw_weak);
} else {
size = raw_obj->untag()->VisitPointersNonvirtual(this);
}
@ -220,6 +227,17 @@ class MarkingVisitorBase : public ObjectPointerVisitor {
return raw_weak->untag()->HeapSize();
}
intptr_t ProcessFinalizerEntry(FinalizerEntryPtr raw_entry) {
ASSERT(IsMarked(raw_entry));
delayed_.finalizer_entries.Enqueue(raw_entry);
// Only visit token and next.
MarkObject(LoadCompressedPointerIgnoreRace(&raw_entry->untag()->token_)
.Decompress(raw_entry->heap_base()));
MarkObject(LoadCompressedPointerIgnoreRace(&raw_entry->untag()->next_)
.Decompress(raw_entry->heap_base()));
return raw_entry->untag()->HeapSize();
}
void ProcessDeferredMarking() {
ObjectPtr raw_obj;
while ((raw_obj = deferred_work_list_.Pop()) != nullptr) {
@ -254,6 +272,7 @@ class MarkingVisitorBase : public ObjectPointerVisitor {
void FinalizeMarking() {
work_list_.Finalize();
deferred_work_list_.Finalize();
MournFinalized(this);
}
void MournWeakProperties() {
@ -394,6 +413,9 @@ class MarkingVisitorBase : public ObjectPointerVisitor {
uintptr_t marked_bytes_;
int64_t marked_micros_;
template <typename GCVisitorType>
friend void MournFinalized(GCVisitorType* visitor);
DISALLOW_IMPLICIT_CONSTRUCTORS(MarkingVisitorBase);
};
@ -703,6 +725,8 @@ class ParallelMarkTask : public ThreadPool::Task {
// Phase 3: Weak processing and statistics.
visitor_->MournWeakProperties();
visitor_->MournWeakReferences();
// Don't MournFinalized here, do it on main thread, so that we don't have
// to coordinate workers.
marker_->IterateWeakRoots(thread);
int64_t stop = OS::GetCurrentMonotonicMicros();
@ -942,6 +966,7 @@ void GCMarker::MarkObjects(PageSpace* page_space) {
visitor.FinalizeMarking();
visitor.MournWeakProperties();
visitor.MournWeakReferences();
MournFinalized(&visitor);
IterateWeakRoots(thread);
// All marking done; detach code, etc.
int64_t stop = OS::GetCurrentMonotonicMicros();

View file

@ -138,6 +138,10 @@ class ScavengerVisitorBase : public ObjectPointerVisitor {
promoted_list_(promotion_stack) {}
~ScavengerVisitorBase() { ASSERT(delayed_.IsEmpty()); }
#ifdef DEBUG
constexpr static const char* const kName = "Scavenger";
#endif
virtual void VisitTypedDataViewPointers(TypedDataViewPtr view,
CompressedObjectPtr* first,
CompressedObjectPtr* last) {
@ -298,6 +302,7 @@ class ScavengerVisitorBase : public ObjectPointerVisitor {
MournWeakProperties();
MournOrUpdateWeakReferences();
MournFinalized(this);
}
page_space_->ReleaseLock(freelist_);
thread_ = nullptr;
@ -536,6 +541,9 @@ class ScavengerVisitorBase : public ObjectPointerVisitor {
NewPage* tail_ = nullptr; // Allocating from here.
NewPage* scan_ = nullptr; // Resolving from here.
template <typename GCVisitorType>
friend void MournFinalized(GCVisitorType* visitor);
DISALLOW_COPY_AND_ASSIGN(ScavengerVisitorBase);
};
@ -1418,6 +1426,21 @@ intptr_t ScavengerVisitorBase<parallel>::ProcessCopied(ObjectPtr raw_obj) {
return raw_weak->untag()->HeapSize();
}
}
} else if (UNLIKELY(class_id == kFinalizerEntryCid)) {
FinalizerEntryPtr raw_entry = static_cast<FinalizerEntryPtr>(raw_obj);
ASSERT(IsNotForwarding(raw_entry));
delayed_.finalizer_entries.Enqueue(raw_entry);
// Only visit token and next.
#if !defined(DART_COMPRESSED_POINTERS)
ScavengePointer(&raw_entry->untag()->token_);
ScavengePointer(&raw_entry->untag()->next_);
#else
ScavengeCompressedPointer(raw_entry->heap_base(),
&raw_entry->untag()->token_);
ScavengeCompressedPointer(raw_entry->heap_base(),
&raw_entry->untag()->next_);
#endif
return raw_entry->untag()->HeapSize();
}
return raw_obj->untag()->VisitPointersNonvirtual(this);
}

View file

@ -1388,6 +1388,15 @@ MessageHandler::MessageStatus IsolateMessageHandler::HandleMessage(
}
}
}
} else if (message->IsFinalizerInvocationRequest()) {
const Object& msg_handler = Object::Handle(
zone,
DartLibraryCalls::HandleFinalizerMessage(FinalizerBase::Cast(msg)));
if (msg_handler.IsError()) {
status = ProcessUnhandledException(Error::Cast(msg_handler));
} else {
// The handler closure which was used to successfully handle the message.
}
} else if (message->dest_port() == Message::kIllegalPort) {
// Check whether this is a delayed OOB message which needed handling as
// part of the regular message dispatch. All other messages are dropped on
@ -1686,6 +1695,7 @@ Isolate::Isolate(IsolateGroup* isolate_group,
default_tag_(UserTag::null()),
ic_miss_code_(Code::null()),
field_table_(new FieldTable(/*isolate=*/this)),
finalizers_(GrowableObjectArray::null()),
isolate_group_(isolate_group),
isolate_object_store_(new IsolateObjectStore()),
#if !defined(DART_PRECOMPILED_RUNTIME)
@ -2465,6 +2475,34 @@ void Isolate::LowLevelShutdown() {
}
}
// Set live finalizers isolate to null, before deleting the message handler.
// TODO(http://dartbug.com/47777): How to detect if the isolate field was ever
// initialized beyond RAW_NULL?
const auto& finalizers =
GrowableObjectArray::Handle(stack_zone.GetZone(), finalizers_);
if (!finalizers.IsNull()) {
const intptr_t num_finalizers = finalizers.Length();
auto& weak_reference = WeakReference::Handle(stack_zone.GetZone());
auto& finalizer = FinalizerBase::Handle(stack_zone.GetZone());
for (int i = 0; i < num_finalizers; i++) {
weak_reference ^= finalizers.At(i);
finalizer ^= weak_reference.target();
if (!finalizer.IsNull()) {
if (finalizer.isolate() == this) {
if (FLAG_trace_finalizers) {
THR_Print("Isolate %p Setting finalizer %p isolate to null\n", this,
finalizer.ptr()->untag());
}
// Finalizer was not sent to another isolate with send and exit.
finalizer.set_isolate(nullptr);
} else {
// TODO(http://dartbug.com/47777): Send and exit support.
UNREACHABLE();
}
}
}
}
// Close all the ports owned by this isolate.
PortMap::ClosePorts(message_handler());
@ -2571,7 +2609,6 @@ void Isolate::Shutdown() {
"--check-reloaded is enabled.\n");
}
}
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
// Then, proceed with low-level teardown.
@ -2721,6 +2758,7 @@ void Isolate::VisitObjectPointers(ObjectPointerVisitor* visitor,
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&ic_miss_code_));
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&tag_table_));
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&sticky_error_));
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&finalizers_));
#if !defined(PRODUCT)
visitor->VisitPointer(
reinterpret_cast<ObjectPtr*>(&pending_service_extension_calls_));

View file

@ -1065,6 +1065,10 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
void set_init_callback_data(void* value) { init_callback_data_ = value; }
void* init_callback_data() const { return init_callback_data_; }
static intptr_t finalizers_offset() {
return OFFSET_OF(Isolate, finalizers_);
}
#if !defined(DART_PRECOMPILED_RUNTIME)
NativeCallbackTrampolines* native_callback_trampolines() {
return &native_callback_trampolines_;
@ -1540,6 +1544,9 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
UserTagPtr default_tag_;
CodePtr ic_miss_code_;
FieldTable* field_table_ = nullptr;
// Used to clear out `UntaggedFinalizerBase::isolate_` pointers on isolate
// shutdown to prevent usage of dangling pointers.
GrowableObjectArrayPtr finalizers_;
bool single_step_ = false;
bool is_system_isolate_ = false;
// End accessed from generated code.
@ -1651,7 +1658,7 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
Dart_EnvironmentCallback environment_callback_ = nullptr;
Random random_;
Simulator* simulator_ = nullptr;
Mutex mutex_; // Protects compiler stats.
Mutex mutex_; // Protects compiler stats.
MessageHandler* message_handler_ = nullptr;
intptr_t defer_finalization_count_ = 0;
DeoptContext* deopt_context_ = nullptr;
@ -1727,7 +1734,7 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
friend class ServiceIsolate;
friend class Thread;
friend class Timeline;
friend class IsolateGroup; // reload_context_
friend class IsolateGroup; // reload_context_
DISALLOW_COPY_AND_ASSIGN(Isolate);
};

View file

@ -1300,7 +1300,8 @@ void KernelLoader::LoadLibraryImportsAndExports(Library* library,
}
if (!Api::IsFfiEnabled() &&
target_library.url() == Symbols::DartFfi().ptr() &&
library->url() != Symbols::DartCore().ptr()) {
library->url() != Symbols::DartCore().ptr() &&
library->url() != Symbols::DartInternal().ptr()) {
H.ReportError(
"import of dart:ffi is not supported in the current Dart runtime");
}

View file

@ -45,12 +45,20 @@ Message::Message(Dart_Port dest_port,
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()) {
if (IsPersistentHandle() || IsFinalizerInvocationRequest()) {
auto isolate_group = IsolateGroup::Current();
isolate_group->api_state()->FreePersistentHandle(
payload_.persistent_handle_);

View file

@ -62,8 +62,13 @@ class Message {
// 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>
@ -94,7 +99,7 @@ class Message {
return payload_.raw_obj_;
}
PersistentHandle* persistent_handle() const {
ASSERT(IsPersistentHandle());
ASSERT(IsPersistentHandle() || IsFinalizerInvocationRequest());
return payload_.persistent_handle_;
}
Priority priority() const { return priority_; }
@ -103,7 +108,9 @@ class Message {
// 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(); }
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
@ -111,6 +118,10 @@ class Message {
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) {
@ -124,6 +135,7 @@ class Message {
private:
static intptr_t const kPersistentHandleSnapshotLen = -1;
static intptr_t const kFinalizerSnapshotLen = -2;
friend class MessageQueue;

View file

@ -211,6 +211,8 @@ class MessageHandler {
Thread* thread() const { return Thread::Current(); }
private:
template <typename GCVisitorType>
friend void MournFinalized(GCVisitorType* visitor);
friend class PortMap;
friend class MessageHandlerTestPeer;
friend class MessageHandlerTask;

View file

@ -26,7 +26,6 @@
namespace dart {
static Dart_CObject cobj_sentinel = {Dart_CObject_kUnsupported, {false}};
static Dart_CObject cobj_transition_sentinel = {Dart_CObject_kUnsupported,
{false}};
@ -46,8 +45,12 @@ class PredefinedCObjects {
}
static Dart_CObject* cobj_null() { return &getInstance().cobj_null_; }
static Dart_CObject* cobj_empty_array() { return &getInstance().cobj_empty_array_; }
static Dart_CObject* cobj_zero_array() { return &getInstance().cobj_zero_array_; }
static Dart_CObject* cobj_empty_array() {
return &getInstance().cobj_empty_array_;
}
static Dart_CObject* cobj_zero_array() {
return &getInstance().cobj_zero_array_;
}
private:
PredefinedCObjects() {
@ -3938,6 +3941,11 @@ ObjectPtr ReadObjectGraphCopyMessage(Thread* thread, PersistentHandle* handle) {
ObjectPtr ReadMessage(Thread* thread, Message* message) {
if (message->IsRaw()) {
return message->raw_obj();
} else if (message->IsFinalizerInvocationRequest()) {
PersistentHandle* handle = message->persistent_handle();
Object& msg_obj = Object::Handle(thread->zone(), handle->ptr());
ASSERT(msg_obj.IsFinalizer());
return msg_obj.ptr();
} else if (message->IsPersistentHandle()) {
return ReadObjectGraphCopyMessage(thread, message->persistent_handle());
} else {

View file

@ -118,7 +118,7 @@ ArrayPtr SubtypeTestCache::cached_array_;
cpp_vtable Object::builtin_vtables_[kNumPredefinedCids] = {};
// These are initialized to a value that will force a illegal memory access if
// These are initialized to a value that will force an illegal memory access if
// they are being used.
#if defined(RAW_NULL)
#error RAW_NULL should not be defined.
@ -2344,6 +2344,15 @@ ErrorPtr Object::Init(IsolateGroup* isolate_group,
pending_classes.Add(cls);
RegisterClass(cls, Symbols::FfiDynamicLibrary(), lib);
cls = Class::New<Finalizer, RTN::Finalizer>(isolate_group);
cls.set_type_arguments_field_offset(
Finalizer::type_arguments_offset(),
RTN::Finalizer::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
object_store->set_finalizer_class(cls);
pending_classes.Add(cls);
RegisterPrivateClass(cls, Symbols::_FinalizerImpl(), core_lib);
// Pre-register the internal library so we can place the vm class
// FinalizerEntry there rather than the core library.
lib = Library::LookupLibrary(thread, Symbols::DartInternal());
@ -2356,6 +2365,11 @@ ErrorPtr Object::Init(IsolateGroup* isolate_group,
ASSERT(!lib.IsNull());
ASSERT(lib.ptr() == Library::InternalLibrary());
cls = Class::New<FinalizerEntry, RTN::FinalizerEntry>(isolate_group);
object_store->set_finalizer_entry_class(cls);
pending_classes.Add(cls);
RegisterClass(cls, Symbols::FinalizerEntry(), lib);
// Finish the initialization by compiling the bootstrap scripts containing
// the base interfaces and the implementation of the internal classes.
const Error& error = Error::Handle(
@ -2523,6 +2537,10 @@ ErrorPtr Object::Init(IsolateGroup* isolate_group,
object_store->set_weak_property_class(cls);
cls = Class::New<WeakReference, RTN::WeakReference>(isolate_group);
object_store->set_weak_reference_class(cls);
cls = Class::New<Finalizer, RTN::Finalizer>(isolate_group);
object_store->set_finalizer_class(cls);
cls = Class::New<FinalizerEntry, RTN::FinalizerEntry>(isolate_group);
object_store->set_finalizer_entry_class(cls);
cls = Class::New<MirrorReference, RTN::MirrorReference>(isolate_group);
cls = Class::New<UserTag, RTN::UserTag>(isolate_group);
@ -11250,8 +11268,7 @@ class FieldGuardUpdater {
FieldGuardUpdater(const Field* field, const Object& value);
bool IsUpdateNeeded() {
return does_guarded_cid_need_update_ ||
does_is_nullable_need_update_ ||
return does_guarded_cid_need_update_ || does_is_nullable_need_update_ ||
does_list_length_and_offset_need_update_ ||
does_static_type_exactness_state_need_update_;
}
@ -11274,7 +11291,8 @@ class FieldGuardUpdater {
}
intptr_t guarded_list_length() { return list_length_; }
void set_guarded_list_length_and_offset(intptr_t list_length,
void set_guarded_list_length_and_offset(
intptr_t list_length,
intptr_t list_length_in_object_offset) {
list_length_ = list_length;
list_length_in_object_offset_ = list_length_in_object_offset;
@ -11678,15 +11696,15 @@ void FieldGuardUpdater::ReviewExactnessState() {
return;
}
FieldGuardUpdater::FieldGuardUpdater(const Field* field, const Object& value):
field_(field),
value_(value),
guarded_cid_(field->guarded_cid()),
is_nullable_(field->is_nullable()),
list_length_(field->guarded_list_length()),
list_length_in_object_offset_(
field->guarded_list_length_in_object_offset()),
static_type_exactness_state_(field->static_type_exactness_state()) {
FieldGuardUpdater::FieldGuardUpdater(const Field* field, const Object& value)
: field_(field),
value_(value),
guarded_cid_(field->guarded_cid()),
is_nullable_(field->is_nullable()),
list_length_(field->guarded_list_length()),
list_length_in_object_offset_(
field->guarded_list_length_in_object_offset()),
static_type_exactness_state_(field->static_type_exactness_state()) {
ReviewGuards();
ReviewExactnessState();
}
@ -26012,14 +26030,50 @@ WeakReferencePtr WeakReference::New(Heap::Space space) {
space, WeakReference::ContainsCompressedPointers());
return static_cast<WeakReferencePtr>(raw);
}
const char* WeakReference::ToCString() const {
TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments());
String& type_args_name = String::Handle(type_args.UserVisibleName());
return OS::SCreate(Thread::Current()->zone(), "WeakReference%s",
return OS::SCreate(Thread::Current()->zone(), "_WeakReference%s",
type_args_name.ToCString());
}
const char* FinalizerBase::ToCString() const {
return "FinalizerBase";
}
FinalizerPtr Finalizer::New(Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->finalizer_class() !=
Class::null());
ObjectPtr raw =
Object::Allocate(Finalizer::kClassId, Finalizer::InstanceSize(), space,
Finalizer::ContainsCompressedPointers());
return static_cast<FinalizerPtr>(raw);
}
const char* Finalizer::ToCString() const {
TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments());
String& type_args_name = String::Handle(type_args.UserVisibleName());
return OS::SCreate(Thread::Current()->zone(), "_FinalizerImpl%s",
type_args_name.ToCString());
}
FinalizerEntryPtr FinalizerEntry::New(Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->finalizer_entry_class() !=
Class::null());
ObjectPtr raw =
Object::Allocate(FinalizerEntry::kClassId, FinalizerEntry::InstanceSize(),
space, FinalizerEntry::ContainsCompressedPointers());
return static_cast<FinalizerEntryPtr>(raw);
}
void FinalizerEntry::set_finalizer(const FinalizerBase& value) const {
untag()->set_finalizer(value.ptr());
}
const char* FinalizerEntry::ToCString() const {
return "FinalizerEntry";
}
AbstractTypePtr MirrorReference::GetAbstractTypeReferent() const {
ASSERT(Object::Handle(referent()).IsAbstractType());
return AbstractType::Cast(Object::Handle(referent())).ptr();

View file

@ -3188,7 +3188,29 @@ class Function : public Object {
bool ForceOptimize() const {
return IsFfiFromAddress() || IsFfiGetAddress() || IsFfiLoad() ||
IsFfiStore() || IsFfiTrampoline() || IsFfiAsExternalTypedData() ||
IsTypedDataViewFactory() || IsUtf8Scan() || IsGetNativeField();
IsTypedDataViewFactory() || IsUtf8Scan() || IsGetNativeField() ||
IsFinalizerForceOptimized();
}
bool IsFinalizerForceOptimized() const {
// Either because of unboxed/untagged data, or because we don't want the GC
// to trigger in between.
switch (recognized_kind()) {
case MethodRecognizer::kFinalizerBase_getIsolateFinalizers:
case MethodRecognizer::kFinalizerBase_setIsolate:
case MethodRecognizer::kFinalizerBase_setIsolateFinalizers:
// Unboxed/untagged representation not supported in unoptimized.
return true;
case MethodRecognizer::kFinalizerBase_exchangeEntriesCollectedWithNull:
// Prevent the GC from running so that the operation is atomic from
// a GC point of view. Always double check implementation in
// kernel_to_il.cc that no GC can happen in between the relevant IL
// instructions.
// TODO(https://dartbug.com/48527): Support inlining.
return true;
default:
return false;
}
}
bool CanBeInlined() const;
@ -12021,15 +12043,109 @@ class WeakReference : public Instance {
return RoundedAllocationSize(sizeof(UntaggedWeakReference));
}
static void Clear(WeakReferencePtr raw_weak) {
ASSERT(raw_weak->untag()->next_seen_by_gc_ ==
CompressedWeakReferencePtr(WeakReference::null()));
// This action is performed by the GC. No barrier.
raw_weak->untag()->target_ = Object::null();
private:
FINAL_HEAP_OBJECT_IMPLEMENTATION(WeakReference, Instance);
friend class Class;
};
class FinalizerEntry : public Instance {
public:
ObjectPtr value() const { return untag()->value(); }
void set_value(const Object& value) const { untag()->set_value(value.ptr()); }
static intptr_t value_offset() {
return OFFSET_OF(UntaggedFinalizerEntry, value_);
}
ObjectPtr detach() const { return untag()->detach(); }
void set_detach(const Object& value) const {
untag()->set_detach(value.ptr());
}
static intptr_t detach_offset() {
return OFFSET_OF(UntaggedFinalizerEntry, detach_);
}
ObjectPtr token() const { return untag()->token(); }
void set_token(const Object& value) const { untag()->set_token(value.ptr()); }
static intptr_t token_offset() {
return OFFSET_OF(UntaggedFinalizerEntry, token_);
}
FinalizerBasePtr finalizer() const { return untag()->finalizer(); }
void set_finalizer(const FinalizerBase& value) const;
static intptr_t finalizer_offset() {
return OFFSET_OF(UntaggedFinalizerEntry, finalizer_);
}
FinalizerEntryPtr next() const { return untag()->next(); }
void set_next(const FinalizerEntry& value) const {
untag()->set_next(value.ptr());
}
static intptr_t next_offset() {
return OFFSET_OF(UntaggedFinalizerEntry, next_);
}
static intptr_t InstanceSize() {
return RoundedAllocationSize(sizeof(UntaggedFinalizerEntry));
}
static FinalizerEntryPtr New(Heap::Space space = Heap::kNew);
private:
FINAL_HEAP_OBJECT_IMPLEMENTATION(FinalizerEntry, Instance);
friend class Class;
};
class FinalizerBase : public Instance {
public:
static intptr_t isolate_offset() {
return OFFSET_OF(UntaggedFinalizerBase, isolate_);
}
Isolate* isolate() const { return untag()->isolate_; }
void set_isolate(Isolate* value) const { untag()->isolate_ = value; }
static intptr_t detachments_offset() {
return OFFSET_OF(UntaggedFinalizerBase, detachments_);
}
LinkedHashSetPtr all_entries() const { return untag()->all_entries(); }
static intptr_t all_entries_offset() {
return OFFSET_OF(UntaggedFinalizerBase, all_entries_);
}
FinalizerEntryPtr entries_collected() const {
return untag()->entries_collected();
}
void set_entries_collected(const FinalizerEntry& value) const {
untag()->set_entries_collected(value.ptr());
}
static intptr_t entries_collected_offset() {
return OFFSET_OF(UntaggedFinalizer, entries_collected_);
}
private:
FINAL_HEAP_OBJECT_IMPLEMENTATION(WeakReference, Instance);
HEAP_OBJECT_IMPLEMENTATION(FinalizerBase, Instance);
friend class Class;
};
class Finalizer : public FinalizerBase {
public:
static intptr_t type_arguments_offset() {
return OFFSET_OF(UntaggedFinalizer, type_arguments_);
}
ObjectPtr callback() const { return untag()->callback(); }
static intptr_t callback_offset() {
return OFFSET_OF(UntaggedFinalizer, callback_);
}
static intptr_t InstanceSize() {
return RoundedAllocationSize(sizeof(UntaggedFinalizer));
}
static FinalizerPtr New(Heap::Space space = Heap::kNew);
private:
FINAL_HEAP_OBJECT_IMPLEMENTATION(Finalizer, FinalizerBase);
friend class Class;
};

View file

@ -35,6 +35,9 @@
V(ExceptionHandlers) \
V(FfiTrampolineData) \
V(Field) \
V(Finalizer) \
V(FinalizerBase) \
V(FinalizerEntry) \
V(Function) \
V(FunctionType) \
V(FutureOr) \
@ -600,6 +603,7 @@ class ObjectCopyBase {
// those are the only non-abstract classes (so we avoid checking more cids
// here that cannot happen in reality)
HANDLE_ILLEGAL_CASE(DynamicLibrary)
HANDLE_ILLEGAL_CASE(Finalizer)
HANDLE_ILLEGAL_CASE(MirrorReference)
HANDLE_ILLEGAL_CASE(Pointer)
HANDLE_ILLEGAL_CASE(ReceivePort)
@ -1815,7 +1819,7 @@ class ObjectGraphCopier {
// We force the GC to compact, which is more likely to discover
// untracked pointers (and other issues, like incorrect class table).
thread_->heap()->CollectAllGarbage(GCReason::kDebugging,
/*compact=*/ true);
/*compact=*/true);
}
// Fast copy failed due to

View file

@ -1700,6 +1700,29 @@ void WeakReference::PrintJSONImpl(JSONStream* stream, bool ref) const {
jsobj.AddProperty("target", target_handle);
}
void FinalizerBase::PrintJSONImpl(JSONStream* stream, bool ref) const {
UNREACHABLE();
}
void Finalizer::PrintJSONImpl(JSONStream* stream, bool ref) const {
JSONObject jsobj(stream);
PrintSharedInstanceJSON(&jsobj, ref);
jsobj.AddProperty("kind", "Finalizer");
jsobj.AddServiceId(*this);
if (ref) {
return;
}
const Object& finalizer_callback = Object::Handle(callback());
jsobj.AddProperty("callback", finalizer_callback);
// Not exposing entries.
}
void FinalizerEntry::PrintJSONImpl(JSONStream* stream, bool ref) const {
UNREACHABLE();
}
void MirrorReference::PrintJSONImpl(JSONStream* stream, bool ref) const {
JSONObject jsobj(stream);
PrintSharedInstanceJSON(&jsobj, ref);

View file

@ -78,6 +78,8 @@ ErrorPtr IsolateObjectStore::PreallocateObjects(const Object& out_of_memory) {
resume_capabilities_ = GrowableObjectArray::New();
exit_listeners_ = GrowableObjectArray::New();
error_listeners_ = GrowableObjectArray::New();
dart_args_1_ = Array::New(1);
dart_args_2_ = Array::New(2);
// Allocate pre-allocated unhandled exception object initialized with the
// pre-allocated OutOfMemoryError.
@ -467,7 +469,24 @@ void ObjectStore::LazyInitFfiMembers() {
auto* const thread = Thread::Current();
SafepointWriteRwLocker locker(thread,
thread->isolate_group()->program_lock());
// TODO(http://dartbug.com/47777): Implement finalizers.
if (handle_finalizer_message_function_.load() == Function::null()) {
auto* const zone = thread->zone();
auto& cls = Class::Handle(zone);
auto& function = Function::Handle(zone);
auto& error = Error::Handle(zone);
const auto& ffi_lib = Library::Handle(zone, Library::FfiLibrary());
ASSERT(!ffi_lib.IsNull());
cls = finalizer_class();
ASSERT(!cls.IsNull());
error = cls.EnsureIsFinalized(thread);
ASSERT(error.IsNull());
function =
cls.LookupFunctionAllowPrivate(Symbols::_handleFinalizerMessage());
ASSERT(!function.IsNull());
handle_finalizer_message_function_.store(function.ptr());
}
}
void ObjectStore::LazyInitIsolateMembers() {
@ -512,13 +531,14 @@ void ObjectStore::LazyInitInternalMembers() {
auto* const zone = thread->zone();
auto& cls = Class::Handle(zone);
auto& field = Field::Handle(zone);
auto& error = Error::Handle(zone);
const auto& internal_lib =
Library::Handle(zone, Library::InternalLibrary());
cls = internal_lib.LookupClass(Symbols::Symbol());
ASSERT(!cls.IsNull());
const auto& error = cls.EnsureIsFinalized(thread);
ASSERT(error == Error::null());
error = cls.EnsureIsFinalized(thread);
ASSERT(error.IsNull());
symbol_class_.store(cls.ptr());
field = cls.LookupInstanceFieldAllowPrivate(Symbols::_name());

View file

@ -55,6 +55,7 @@ class ObjectPointerVisitor;
LAZY_CORE(Function, _object_to_string_function) \
LAZY_INTERNAL(Class, symbol_class) \
LAZY_INTERNAL(Field, symbol_name_field) \
LAZY_FFI(Function, handle_finalizer_message_function) \
LAZY_ASYNC(Type, non_nullable_future_rare_type) \
LAZY_ASYNC(Type, non_nullable_future_never_type) \
LAZY_ASYNC(Type, nullable_future_null_type) \
@ -126,6 +127,9 @@ class ObjectPointerVisitor;
RW(Class, expando_class) \
RW(Class, weak_property_class) \
RW(Class, weak_reference_class) \
RW(Class, finalizer_class) \
RW(Class, finalizer_entry_class) \
RW(Class, finalizer_native_class) \
ARW_AR(Array, symbol_table) \
RW(Array, canonical_types) \
RW(Array, canonical_function_types) \
@ -315,8 +319,8 @@ class ObjectPointerVisitor;
RW(UnhandledException, preallocated_unhandled_exception) \
RW(StackTrace, preallocated_stack_trace) \
RW(UnwindError, preallocated_unwind_error) \
RW(Array, dart_args_1) \
RW(Array, dart_args_2) \
R_(Array, dart_args_1) \
R_(Array, dart_args_2) \
R_(GrowableObjectArray, resume_capabilities) \
R_(GrowableObjectArray, exit_listeners) \
R_(GrowableObjectArray, error_listeners)

File diff suppressed because it is too large Load diff

View file

@ -552,6 +552,8 @@ COMPRESSED_VISITOR(StackTrace)
COMPRESSED_VISITOR(RegExp)
COMPRESSED_VISITOR(WeakProperty)
COMPRESSED_VISITOR(WeakReference)
COMPRESSED_VISITOR(Finalizer)
COMPRESSED_VISITOR(FinalizerEntry)
COMPRESSED_VISITOR(MirrorReference)
COMPRESSED_VISITOR(UserTag)
REGULAR_VISITOR(SubtypeTestCache)
@ -594,6 +596,7 @@ UNREACHABLE_VISITOR(AbstractType)
UNREACHABLE_VISITOR(CallSiteData)
UNREACHABLE_VISITOR(TypedDataBase)
UNREACHABLE_VISITOR(Error)
UNREACHABLE_VISITOR(FinalizerBase)
UNREACHABLE_VISITOR(Number)
UNREACHABLE_VISITOR(Integer)
UNREACHABLE_VISITOR(String)

View file

@ -623,6 +623,20 @@ class UntaggedObject {
}
}
template <typename type,
typename compressed_type,
std::memory_order order = std::memory_order_relaxed>
type ExchangeCompressedPointer(compressed_type const* addr, type value) {
compressed_type previous_value =
reinterpret_cast<std::atomic<compressed_type>*>(
const_cast<compressed_type*>(addr))
->exchange(static_cast<compressed_type>(value), order);
if (value.IsHeapObject()) {
CheckHeapPointerStore(value, Thread::Current());
}
return static_cast<type>(previous_value.Decompress(heap_base()));
}
template <std::memory_order order = std::memory_order_relaxed>
SmiPtr LoadSmi(SmiPtr const* addr) const {
return reinterpret_cast<std::atomic<SmiPtr>*>(const_cast<SmiPtr*>(addr))
@ -3321,6 +3335,92 @@ class UntaggedWeakReference : public UntaggedInstance {
friend class SlowObjectCopy; // For OFFSET_OF
};
class UntaggedFinalizerBase : public UntaggedInstance {
RAW_HEAP_OBJECT_IMPLEMENTATION(FinalizerBase);
// The isolate this finalizer belongs to. Updated on sent and exit and set
// to null on isolate shutdown. See Isolate::finalizers_.
Isolate* isolate_;
COMPRESSED_POINTER_FIELD(ObjectPtr, detachments)
VISIT_FROM(detachments)
COMPRESSED_POINTER_FIELD(LinkedHashSetPtr, all_entries)
COMPRESSED_POINTER_FIELD(FinalizerEntryPtr, entries_collected)
// With compressed pointers, the first field in a subclass is at offset 28.
// If the fields would be public, the first field in a subclass is at offset 32.
// On Windows, it is always at offset 32, no matter public/private.
// This makes it 32 for all OSes.
// We can't use ALIGN8 on the first fields of the subclasses because they use
// the COMPRESSED_POINTER_FIELD macro to define it.
#ifdef DART_COMPRESSED_POINTERS
uint32_t align_next_field;
#endif
template <typename GCVisitorType>
friend void MournFinalized(GCVisitorType* visitor);
friend class GCMarker;
template <bool>
friend class MarkingVisitorBase;
friend class Scavenger;
template <bool>
friend class ScavengerVisitorBase;
};
class UntaggedFinalizer : public UntaggedFinalizerBase {
RAW_HEAP_OBJECT_IMPLEMENTATION(Finalizer);
COMPRESSED_POINTER_FIELD(ClosurePtr, callback)
COMPRESSED_POINTER_FIELD(TypeArgumentsPtr, type_arguments)
VISIT_TO(type_arguments)
template <std::memory_order order = std::memory_order_relaxed>
FinalizerEntryPtr exchange_entries_collected(FinalizerEntryPtr value) {
return ExchangeCompressedPointer<FinalizerEntryPtr,
CompressedFinalizerEntryPtr, order>(
&entries_collected_, value);
}
template <typename GCVisitorType>
friend void MournFinalized(GCVisitorType* visitor);
friend class GCMarker;
template <bool>
friend class MarkingVisitorBase;
friend class Scavenger;
template <bool>
friend class ScavengerVisitorBase;
};
class UntaggedFinalizerEntry : public UntaggedInstance {
RAW_HEAP_OBJECT_IMPLEMENTATION(FinalizerEntry);
COMPRESSED_POINTER_FIELD(ObjectPtr, value) // Weak reference.
VISIT_FROM(value)
COMPRESSED_POINTER_FIELD(ObjectPtr, detach) // Weak reference.
COMPRESSED_POINTER_FIELD(ObjectPtr, token)
COMPRESSED_POINTER_FIELD(FinalizerBasePtr, finalizer) // Weak reference.
// Used for the linked list in Finalizer::entries_collected_. That cannot be
// an ordinary list because we need to add elements during a GC so we cannot
// modify the heap.
COMPRESSED_POINTER_FIELD(FinalizerEntryPtr, next)
VISIT_TO(next)
// Linked list is chaining all pending. Not visited by pointer visitors.
// Only populated during the GC, otherwise null.
COMPRESSED_POINTER_FIELD(FinalizerEntryPtr, next_seen_by_gc)
template <typename Type, typename PtrType>
friend class GCLinkedList;
template <typename GCVisitorType>
friend void MournFinalized(GCVisitorType* visitor);
friend class GCMarker;
template <bool>
friend class MarkingVisitorBase;
friend class Scavenger;
template <bool>
friend class ScavengerVisitorBase;
};
// MirrorReferences are used by mirrors to hold reflectees that are VM
// internal objects, such as libraries, classes, functions or types.
class UntaggedMirrorReference : public UntaggedInstance {

View file

@ -137,6 +137,8 @@ class ObjectPointerVisitor;
V(FfiVoid, "Void") \
V(FfiHandle, "Handle") \
V(Field, "Field") \
V(FinalizerBase, "FinalizerBase") \
V(FinalizerEntry, "FinalizerEntry") \
V(FinallyRetVal, ":finally_ret_val") \
V(FirstArg, "x") \
V(Float32List, "Float32List") \
@ -308,6 +310,7 @@ class ObjectPointerVisitor;
V(_ExternalUint64Array, "_ExternalUint64Array") \
V(_ExternalUint8Array, "_ExternalUint8Array") \
V(_ExternalUint8ClampedArray, "_ExternalUint8ClampedArray") \
V(_FinalizerImpl, "_FinalizerImpl") \
V(_Float32ArrayFactory, "Float32List.") \
V(_Float32ArrayView, "_Float32ArrayView") \
V(_Float32List, "_Float32List") \
@ -407,6 +410,7 @@ class ObjectPointerVisitor;
V(_ensureScheduleImmediate, "_ensureScheduleImmediate") \
V(_future, "_future") \
V(_handleMessage, "_handleMessage") \
V(_handleFinalizerMessage, "_handleFinalizerMessage") \
V(_instanceOf, "_instanceOf") \
V(_listGetAt, "_listGetAt") \
V(_listLength, "_listLength") \

View file

@ -413,6 +413,9 @@ DEFINE_TAGGED_POINTER(StackTrace, Instance)
DEFINE_TAGGED_POINTER(RegExp, Instance)
DEFINE_TAGGED_POINTER(WeakProperty, Instance)
DEFINE_TAGGED_POINTER(WeakReference, Instance)
DEFINE_TAGGED_POINTER(FinalizerBase, Instance)
DEFINE_TAGGED_POINTER(Finalizer, Instance)
DEFINE_TAGGED_POINTER(FinalizerEntry, Instance)
DEFINE_TAGGED_POINTER(MirrorReference, Instance)
DEFINE_TAGGED_POINTER(UserTag, Instance)
DEFINE_TAGGED_POINTER(FutureOr, Instance)

View file

@ -13,10 +13,14 @@ import "dart:_internal"
show
allocateOneByteString,
allocateTwoByteString,
checkValidWeakTarget,
ClassID,
CodeUnits,
copyRangeFromUint8ListToOneByteString,
EfficientLengthIterable,
FinalizerBase,
FinalizerBaseMembers,
FinalizerEntry,
FixedLengthListBase,
IterableElementError,
ListIterator,
@ -28,11 +32,12 @@ import "dart:_internal"
makeFixedListUnmodifiable,
makeListFixedLength,
patch,
reachabilityFence,
unsafeCast,
writeIntoOneByteString,
writeIntoTwoByteString;
import "dart:async" show Completer, DeferredLoadException, Future, Timer;
import "dart:async" show Completer, DeferredLoadException, Future, Timer, Zone;
import "dart:collection"
show
@ -49,9 +54,9 @@ import "dart:collection"
import "dart:convert" show ascii, Encoding, json, latin1, utf8;
import "dart:ffi" show Pointer, Struct, Union;
import "dart:ffi" show Pointer, Struct, Union, NativePort;
import "dart:isolate" show Isolate;
import "dart:isolate" show Isolate, RawReceivePort;
import "dart:typed_data"
show Endian, Uint8List, Int64List, Uint16List, Uint32List;

View file

@ -28,7 +28,8 @@ class Expando<T> {
@patch
T? operator [](Object object) {
_checkType(object);
// TODO(http://dartbug.com/48634): Rename to `key`.
checkValidWeakTarget(object, 'object');
var mask = _size - 1;
var idx = object._identityHashCode & mask;
@ -50,7 +51,8 @@ class Expando<T> {
@patch
void operator []=(Object object, T? value) {
_checkType(object);
// TODO(http://dartbug.com/48634): Rename to `key`.
checkValidWeakTarget(object, 'object');
var mask = _size - 1;
var idx = object._identityHashCode & mask;
@ -147,19 +149,6 @@ class Expando<T> {
}
}
static _checkType(object) {
if ((object == null) ||
(object is bool) ||
(object is num) ||
(object is String) ||
(object is Pointer) ||
(object is Struct) ||
(object is Union)) {
throw new ArgumentError.value(object,
"Expandos are not allowed on strings, numbers, booleans, null, Pointers, Structs or Unions.");
}
}
int get _size => _data.length;
int get _limit => (3 * (_size ~/ 4));
@ -170,14 +159,14 @@ class Expando<T> {
@patch
class WeakReference<T extends Object> {
@patch
factory WeakReference(T object) = _WeakReferenceImpl<T>;
factory WeakReference(T target) = _WeakReferenceImpl<T>;
}
@pragma("vm:entry-point")
class _WeakReferenceImpl<T extends Object> implements WeakReference<T> {
_WeakReferenceImpl(T object) {
Expando._checkType(object);
_target = object;
_WeakReferenceImpl(T target) {
checkValidWeakTarget(target, 'target');
_target = target;
}
@pragma("vm:recognized", "other")
@ -190,11 +179,3 @@ class _WeakReferenceImpl<T extends Object> implements WeakReference<T> {
@pragma("vm:external-name", "WeakReference_setTarget")
external set _target(T? value);
}
@patch
class Finalizer<T> {
@patch
factory Finalizer(void Function(T) object) {
throw UnimplementedError("Finalizer");
}
}

View file

@ -4,5 +4,83 @@
// part of "core_patch.dart";
// This is a placeholder file which will shortly contain a Finalizer
// implementation.
@patch
@pragma("vm:entry-point")
abstract class Finalizer<T> {
@patch
factory Finalizer(void Function(T) callback) = _FinalizerImpl<T>;
}
@pragma("vm:entry-point")
class _FinalizerImpl<T> extends FinalizerBase implements Finalizer<T> {
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external void Function(T) get _callback;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set _callback(void Function(T) value);
/// Constructs a finalizer.
///
/// This is fine as a non-atomic operation, because the GC only looks at
/// finalizer instances when it process their entries. By preventing inlining
/// we ensure the the finalizer to have been fully initialized by the time
/// any [attach] on it is called.
///
/// Alternatively, we could make it a recognized method and add a reachability
/// fence on the relevant members.
@pragma('vm:never-inline')
_FinalizerImpl(void Function(T) callback) {
allEntries = <FinalizerEntry>{};
_callback = Zone.current.bindUnaryCallbackGuarded(callback);
setIsolate();
isolateRegisterFinalizer();
}
void attach(Object value, T token, {Object? detach}) {
checkValidWeakTarget(value, 'value');
if (detach != null) {
checkValidWeakTarget(detach, 'detach');
}
// Initializing the entry in a non-atomic way should be fine.
// The only interesting step in the GC is when value is collected.
// If the entry gets processed before initializing value, it will be null,
// and this is fine. We will not consider it as being collected that GC.
final entry = FinalizerEntry()
..value = value
..token = token
..detach = detach
..finalizer = this;
allEntries.add(entry);
// Ensure value stays reachable until after having initialized the entry.
// This ensures the token and finalizer are set.
reachabilityFence(value);
if (detach != null) {
(detachments[detach] ??= <FinalizerEntry>{}).add(entry);
}
}
void _runFinalizers() {
FinalizerEntry? entry = exchangeEntriesCollectedWithNull();
while (entry != null) {
final token = entry.token;
// Check token for identical, detach might have been called.
if (!identical(token, entry)) {
_callback(unsafeCast<T>(token));
}
allEntries.remove(entry);
final detach = entry.detach;
if (detach != null) {
detachments[detach]?.remove(entry);
}
entry = entry.next;
}
}
@pragma("vm:entry-point", "call")
static _handleFinalizerMessage(_FinalizerImpl finalizer) {
finalizer._runFinalizers();
}
}

View file

@ -9,7 +9,7 @@
import "dart:async" show Timer;
import "dart:core" hide Symbol;
import "dart:ffi" show Pointer, Struct, Union;
import "dart:isolate" show SendPort;
import "dart:typed_data" show Int32List, Uint8List;
@ -209,3 +209,225 @@ class LateError {
throw new LateError.localADI(localName);
}
}
void checkValidWeakTarget(object, name) {
if ((object == null) ||
(object is bool) ||
(object is num) ||
(object is String) ||
(object is Pointer) ||
(object is Struct) ||
(object is Union)) {
throw new ArgumentError.value(object, name,
"Cannot be a string, number, boolean, null, Pointer, Struct or Union");
}
}
@pragma("vm:entry-point")
class FinalizerBase {
/// The list of finalizers of this isolate.
///
/// Reuses [WeakReference] so that we don't have to implement yet another
/// mechanism to hold on weakly to things.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external static List<WeakReference<FinalizerBase>>? get _isolateFinalizers;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external static set _isolateFinalizers(
List<WeakReference<FinalizerBase>>? value);
static int _isolateFinalizersPurgeCollectedAt = 1;
/// Amortizes the cost for purging nulled out entries.
///
/// Similar to how Expandos purge their nulled out entries on a rehash when
/// resizing.
static void _isolateFinalizersEnsureCapacity() {
_isolateFinalizers ??= <WeakReference<FinalizerBase>>[];
if (_isolateFinalizers!.length < _isolateFinalizersPurgeCollectedAt) {
return;
}
// retainWhere does a single traversal.
_isolateFinalizers!.retainWhere((weak) => weak.target != null);
// We might have dropped most finalizers, trigger next resize at 2x.
_isolateFinalizersPurgeCollectedAt = _isolateFinalizers!.length * 2;
}
/// Registers this [FinalizerBase] to the isolate.
///
/// This is used to prevent sending messages from the GC to the isolate after
/// isolate shutdown.
void _isolateRegisterFinalizer() {
_isolateFinalizersEnsureCapacity();
_isolateFinalizers!.add(WeakReference(this));
}
/// The isolate this [FinalizerBase] belongs to.
///
/// This is used to send finalizer messages to `_handleFinalizerMessage`
/// without a Dart_Port.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external _setIsolate();
/// All active attachments.
///
/// This keeps the [FinalizerEntry]s belonging to this finalizer alive. If an
/// entry gets collected, the finalizer is not run when the
/// [FinalizerEntry.value] is collected.
///
/// TODO(http://dartbug.com/47777): For native finalizers, what data structure
/// can we use that we can modify in the VM. So that we don't have to send a
/// message to Dart to clean up entries for which the GC has run.
///
/// Requirements for data structure:
/// 1. Keeps entries reachable. Entries that are collected will never run
/// the GC.
/// 2. Atomic insert in Dart on `attach`. GC should not run in between.
/// 3. Atomic remove in Dart on `detach`. multiple GC tasks run in parallel.
/// 4. Atomic remove in C++ on value being collected. Multiple GC tasks run in
/// parallel.
///
/// For Dart finalizers we execute the remove in Dart, much simpler.
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external Set<FinalizerEntry> get _allEntries;
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external set _allEntries(Set<FinalizerEntry> entries);
/// Entries of which the value has been collected.
///
/// This is a linked list, with [FinalizerEntry.next].
///
/// Atomic exchange: The GC cannot run between reading the value and storing
/// `null`. Atomicity guaranteed by force optimizing the function.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external FinalizerEntry? _exchangeEntriesCollectedWithNull();
/// A weak map from `detach` keys to [FinalizerEntry]s.
///
/// Using the [FinalizerEntry.detach] keys as keys in an [Expando] ensures
/// they can be GCed.
///
/// [FinalizerEntry]s do not get GCed themselves when their
/// [FinalizerEntry.detach] is unreachable, in contrast to `WeakProperty`s
/// which are GCed themselves when their `key` is no longer reachable.
/// To prevent [FinalizerEntry]s staying around in [_detachments] forever,
/// we reuse `WeakProperty`s.
/// To avoid code duplication, we do not inline the code but use an [Expando]
/// here instead.
///
/// We cannot eagerly purge entries from the map (in the Expando) when GCed.
/// The map is indexed on detach, and doesn't enable finding the entries
/// based on their identity.
/// Instead we rely on the WeakProperty being nulled out (assuming the
/// `detach` key gets GCed) and then reused.
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external Expando<Set<FinalizerEntry>>? get _detachments;
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external set _detachments(Expando<Set<FinalizerEntry>>? value);
void detach(Object detach) {
final entries = detachments[detach];
if (entries != null) {
for (final entry in entries) {
entry.token = entry;
_allEntries.remove(entry);
}
detachments[detach] = null;
}
}
}
// Extension so that the members can be accessed from other libs.
extension FinalizerBaseMembers on FinalizerBase {
/// See documentation on [_allEntries].
@pragma('vm:prefer-inline')
Set<FinalizerEntry> get allEntries => _allEntries;
@pragma('vm:prefer-inline')
set allEntries(Set<FinalizerEntry> value) => _allEntries = value;
/// See documentation on [_exchangeEntriesCollectedWithNull].
FinalizerEntry? exchangeEntriesCollectedWithNull() =>
_exchangeEntriesCollectedWithNull();
/// See documentation on [_detachments].
@pragma('vm:prefer-inline')
Expando<Set<FinalizerEntry>> get detachments {
_detachments ??= Expando<Set<FinalizerEntry>>();
return unsafeCast<Expando<Set<FinalizerEntry>>>(_detachments);
}
/// See documentation on [_isolateRegisterFinalizer].
isolateRegisterFinalizer() => _isolateRegisterFinalizer();
/// See documentation on [_setIsolate].
setIsolate() => _setIsolate();
}
/// Contains the informatation of an active [Finalizer.attach].
///
/// It holds on to the [value], optional [detach], and [token]. In addition, it
/// also keeps a reference the [finalizer] it belings to and a [next] field for
/// when being used in a linked list.
///
/// This is being kept alive by [FinalizerBase._allEntries] until either (1)
/// [Finalizer.detach] detaches it, or (2) [value] is collected and the
/// `callback` has been invoked.
///
/// Note that the GC itself uses an extra hidden field `next_seen_by_gc` to keep a
/// linked list of pending entries while running the GC.
@pragma("vm:entry-point")
class FinalizerEntry {
/// The [value] the [FinalizerBase] is attached to.
///
/// Set to `null` by GC when unreachable.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external Object? get value;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set value(Object? value);
/// The [detach] object can be passed to [FinalizerBase] to detach
/// the finalizer.
///
/// Set to `null` by GC when unreachable.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external Object? get detach;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set detach(Object? value);
/// The [token] is passed to [FinalizerBase] when the finalizer is run.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external Object? get token;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set token(Object? value);
/// The [finalizer] this [FinalizerEntry] belongs to.
///
/// Set to `null` by GC when unreachable.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set finalizer(FinalizerBase? finalizer);
/// The [next] entry in a linked list.
///
/// Used in for the linked list starting from
/// [FinalizerBase._exchangeEntriesCollectedWithNull].
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external FinalizerEntry? get next;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set next(FinalizerEntry? value);
}

View file

@ -207,15 +207,15 @@ abstract class Finalizer<T> {
/// with different, or the same, finalization token.
void attach(Object value, T finalizationToken, {Object? detach});
/// Detaches the finalizer from values attached with [detachToken].
/// Detaches this finalizer from values attached with [detach].
///
/// Each attachment between this finalizer and a value,
/// which was created by calling [attach] with the [detachToken] object as
/// which was created by calling [attach] with the [detach] object as
/// `detach` argument, is removed.
///
/// If the finalizer was attached multiple times to the same value
/// with different detachment keys,
/// only those attachments which used [detachToken] are removed.
/// only those attachments which used [detach] are removed.
///
/// After detaching, an attachment won't cause any callbacks to happen
/// if the object become inaccessible.
@ -242,5 +242,5 @@ abstract class Finalizer<T> {
/// }
/// }
/// ```
void detach(Object detachToken);
void detach(Object detach);
}

View file

@ -723,6 +723,7 @@ abstract class SendPort implements Capability {
/// therefore not be sent.
/// - [ReceivePort]
/// - [DynamicLibrary]
/// - [Finalizer]
/// - [Pointer]
/// - [UserTag]
/// - `MirrorReference`