[vm] Implement NativeFinalizer

This CL implements `NativeFinalizer` in the GC.

`FinalizerEntry`s are extended to track `external_size` and in which
`Heap::Space` the finalizable value is.

On attaching a native finalizer, the external size is added to the
relevant heap. When the finalizable value is promoted from new to old
space, the external size is promoted as well. And when a native
finalizer is run or is detached, the external size is removed from the
relevant heap again.

In contrast to Dart `Finalizer`s, `NativeFinalizer`s are run on isolate
shutdown.

When the `NativeFinalizer`s themselves are collected, the finalizers are
not run. Users should stick the native finalizer in a global variable to
ensure finalization. We will revisit this design when we add send and
exit support, because there is a design space to explore what to do in
that case. This current solution promises the least to users.

In this implementation native finalizers have a Dart entry to clean up
the entries from the `all_entries` field of the finalizer. We should
consider using another data structure that avoids the need for this Dart
entry. See the TODO left in the code.

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

TEST=runtime/tests/vm/dart(_2)/isolates/fast_object_copy_test.dart
TEST=runtime/vm/object_test.cc
TEST=tests/ffi(_2)/vmspecific_native_finalizer_*

Change-Id: I8f594c80c3c344ad83e1f2de10de028eb8456121
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-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/+/236320
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Daco Harkes 2022-03-26 09:41:21 +00:00 committed by Commit Bot
parent e5f118d194
commit 532c116cd2
81 changed files with 2449 additions and 391 deletions

View file

@ -4083,6 +4083,36 @@ const MessageCode messageFfiAbiSpecificIntegerMappingInvalid = const MessageCode
problemMessage:
r"""Classes extending 'AbiSpecificInteger' must have exactly one 'AbiSpecificIntegerMapping' annotation specifying the mapping from ABI to a NativeType integer with a fixed size.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String string, String name)>
templateFfiCompoundImplementsFinalizable =
const Template<Message Function(String string, String name)>(
problemMessageTemplate:
r"""#string '#name' can't implement Finalizable.""",
correctionMessageTemplate:
r"""Try removing the implements clause from '#name'.""",
withArguments: _withArgumentsFfiCompoundImplementsFinalizable);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(String string, String name)>
codeFfiCompoundImplementsFinalizable =
const Code<Message Function(String string, String name)>(
"FfiCompoundImplementsFinalizable",
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsFfiCompoundImplementsFinalizable(
String string, String name) {
if (string.isEmpty) throw 'No string provided';
if (name.isEmpty) throw 'No name provided';
name = demangleMixinApplicationName(name);
return new Message(codeFfiCompoundImplementsFinalizable,
problemMessage: """${string} '${name}' can't implement Finalizable.""",
correctionMessage:
"""Try removing the implements clause from '${name}'.""",
arguments: {'string': string, 'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(

View file

@ -1087,6 +1087,8 @@ FfiCode.ANNOTATION_ON_POINTER_FIELD:
status: needsEvaluation
FfiCode.ARGUMENT_MUST_BE_A_CONSTANT:
status: needsEvaluation
FfiCode.COMPOUND_IMPLEMENTS_FINALIZABLE:
status: needsEvaluation
FfiCode.CREATION_OF_STRUCT_OR_UNION:
status: needsEvaluation
since: ~2.15

View file

@ -507,6 +507,7 @@ const List<ErrorCode> errorCodeValues = [
FfiCode.ABI_SPECIFIC_INTEGER_MAPPING_UNSUPPORTED,
FfiCode.ANNOTATION_ON_POINTER_FIELD,
FfiCode.ARGUMENT_MUST_BE_A_CONSTANT,
FfiCode.COMPOUND_IMPLEMENTS_FINALIZABLE,
FfiCode.CREATION_OF_STRUCT_OR_UNION,
FfiCode.EMPTY_STRUCT,
FfiCode.EXTRA_ANNOTATION_ON_STRUCT_FIELD,

View file

@ -167,6 +167,48 @@ class FfiCode extends AnalyzerErrorCode {
hasPublishedDocs: true,
);
/**
* Parameters:
* 0: the name of the struct or union class
*/
// #### Description
//
// The analyzer produces this diagnostic when a subclass of either `Struct`
// or `Union` implements `Finalizable`.
//
// For more information about FFI, see [C interop using dart:ffi][].
//
// #### Example
//
// The following code produces this diagnostic because the class `S`
// implements `Finalizable`:
//
// ```dart
// import 'dart:ffi';
//
// class [!S!] extends Struct implements Finalizable {
// external Pointer notEmpty;
// }
// ```
//
// #### Common fixes
//
// Try removing the implements clause from the class:
//
// ```dart
// import 'dart:ffi';
//
// class S extends Struct {
// external Pointer notEmpty;
// }
// ```
static const FfiCode COMPOUND_IMPLEMENTS_FINALIZABLE = FfiCode(
'COMPOUND_IMPLEMENTS_FINALIZABLE',
"The class '{0}' can't implement Finalizable.",
correctionMessage: "Try removing the implements clause from '{0}'.",
hasPublishedDocs: true,
);
/**
* No parameters.
*/

View file

@ -146,9 +146,25 @@ class FfiVerifier extends RecursiveAstVisitor<void> {
}
}
if (inCompound && node.declaredElement!.typeParameters.isNotEmpty) {
_errorReporter.reportErrorForNode(
FfiCode.GENERIC_STRUCT_SUBCLASS, node.name, [node.name.name]);
if (inCompound) {
if (node.declaredElement!.typeParameters.isNotEmpty) {
_errorReporter.reportErrorForNode(
FfiCode.GENERIC_STRUCT_SUBCLASS, node.name, [node.name.name]);
}
final implementsClause = node.implementsClause;
if (implementsClause != null) {
final compoundType = node.declaredElement!.thisType;
final structType = compoundType.superclass!;
final ffiLibrary = structType.element.library;
final finalizableElement = ffiLibrary.getType(_finalizableClassName)!;
final finalizableType = finalizableElement.thisType;
if (typeSystem.isSubtypeOf(compoundType, finalizableType)) {
_errorReporter.reportErrorForNode(
FfiCode.COMPOUND_IMPLEMENTS_FINALIZABLE,
node.name,
[node.name.name]);
}
}
}
super.visitClassDeclaration(node);
}

View file

@ -15689,6 +15689,45 @@ FfiCode:
return p.asFunction(isLeaf: true);
}
```
COMPOUND_IMPLEMENTS_FINALIZABLE:
problemMessage: "The class '{0}' can't implement Finalizable."
correctionMessage: "Try removing the implements clause from '{0}'."
comment: |-
Parameters:
0: the name of the struct or union class
hasPublishedDocs: true
documentation: |-
#### Description
The analyzer produces this diagnostic when a subclass of either `Struct`
or `Union` implements `Finalizable`.
For more information about FFI, see [C interop using dart:ffi][].
#### Example
The following code produces this diagnostic because the class `S`
implements `Finalizable`:
```dart
import 'dart:ffi';
class [!S!] extends Struct implements Finalizable {
external Pointer notEmpty;
}
```
#### Common fixes
Try removing the implements clause from the class:
```dart
import 'dart:ffi';
class S extends Struct {
external Pointer notEmpty;
}
```
CREATION_OF_STRUCT_OR_UNION:
problemMessage: "Subclasses of 'Struct' and 'Union' are backed by native memory, and can't be instantiated by a generative constructor."
correctionMessage: "Try allocating it via allocation, or load from a 'Pointer'."

View file

@ -1992,6 +1992,42 @@ suitable value:
var l = const [0];
{% endprettify %}
### compound_implements_finalizable
_The class '{0}' can't implement Finalizable._
#### Description
The analyzer produces this diagnostic when a subclass of either `Struct`
or `Union` implements `Finalizable`.
For more information about FFI, see [C interop using dart:ffi][].
#### Example
The following code produces this diagnostic because the class `S`
implements `Finalizable`:
{% prettify dart tag=pre+code %}
import 'dart:ffi';
class [!S!] extends Struct implements Finalizable {
external Pointer notEmpty;
}
{% endprettify %}
#### Common fixes
Try removing the implements clause from the class:
{% prettify dart tag=pre+code %}
import 'dart:ffi';
class S extends Struct {
external Pointer notEmpty;
}
{% endprettify %}
### concrete_class_has_enum_superinterface
_Concrete classes can't have 'Enum' as a superinterface._

View file

@ -70,6 +70,7 @@ export '../fasta/fasta_codes.dart'
noLength,
templateCantHaveNamedParameters,
templateCantHaveOptionalParameters,
templateFfiCompoundImplementsFinalizable,
templateFfiDartTypeMismatch,
templateFfiEmptyStruct,
templateFfiExpectedConstantArg,

View file

@ -361,6 +361,7 @@ FastaUsageShort/analyzerCode: Fail
FastaUsageShort/example: Fail
FfiAbiSpecificIntegerInvalid/analyzerCode: Fail
FfiAbiSpecificIntegerMappingInvalid/analyzerCode: Fail
FfiCompoundImplementsFinalizable/analyzerCode: Fail
FfiDartTypeMismatch/analyzerCode: Fail
FfiEmptyStruct/analyzerCode: Fail
FfiExceptionalReturnNull/analyzerCode: Fail

View file

@ -4690,6 +4690,12 @@ FfiStructGeneric:
problemMessage: "#string '#name' should not be generic."
external: test/ffi_test.dart
FfiCompoundImplementsFinalizable:
# Used by dart:ffi
problemMessage: "#string '#name' can't implement Finalizable."
correctionMessage: "Try removing the implements clause from '#name'."
external: test/ffi_test.dart
FfiDartTypeMismatch:
# Used by dart:ffi
problemMessage: "Expected '#type' to be a subtype of '#type2'."

View file

@ -1203,6 +1203,7 @@ filter
filtered
filtering
final
finalizable
finalization
finalize
finalized

View file

@ -39,6 +39,7 @@ additionalExports = (ffi::nullptr,
ffi::unsized,
ffi::sizeOf,
ffi::Dart_NativeMessageHandler,
ffi::NativeFinalizerFunction,
ffi::Abi,
ffi::AbiSpecificInteger,
ffi::AbiSpecificIntegerArray,
@ -81,6 +82,7 @@ additionalExports = (ffi::nullptr,
ffi::Long,
ffi::LongLong,
ffi::NativeApi,
ffi::NativeFinalizer,
ffi::NativeFunction,
ffi::NativeFunctionPointer,
ffi::NativePort,
@ -129,6 +131,7 @@ additionalExports = (ffi::nullptr,
ffi::unsized,
ffi::sizeOf,
ffi::Dart_NativeMessageHandler,
ffi::NativeFinalizerFunction,
ffi::Abi,
ffi::AbiSpecificInteger,
ffi::AbiSpecificIntegerArray,
@ -171,6 +174,7 @@ additionalExports = (ffi::nullptr,
ffi::Long,
ffi::LongLong,
ffi::NativeApi,
ffi::NativeFinalizer,
ffi::NativeFunction,
ffi::NativeFunctionPointer,
ffi::NativePort,

View file

@ -39,6 +39,7 @@ additionalExports = (ffi::nullptr,
ffi::unsized,
ffi::sizeOf,
ffi::Dart_NativeMessageHandler,
ffi::NativeFinalizerFunction,
ffi::Abi,
ffi::AbiSpecificInteger,
ffi::AbiSpecificIntegerArray,
@ -81,6 +82,7 @@ additionalExports = (ffi::nullptr,
ffi::Long,
ffi::LongLong,
ffi::NativeApi,
ffi::NativeFinalizer,
ffi::NativeFunction,
ffi::NativeFunctionPointer,
ffi::NativePort,
@ -129,6 +131,7 @@ additionalExports = (ffi::nullptr,
ffi::unsized,
ffi::sizeOf,
ffi::Dart_NativeMessageHandler,
ffi::NativeFinalizerFunction,
ffi::Abi,
ffi::AbiSpecificInteger,
ffi::AbiSpecificIntegerArray,
@ -171,6 +174,7 @@ additionalExports = (ffi::nullptr,
ffi::Long,
ffi::LongLong,
ffi::NativeApi,
ffi::NativeFinalizer,
ffi::NativeFunction,
ffi::NativeFunctionPointer,
ffi::NativePort,

View file

@ -409,7 +409,8 @@ class VmTarget extends Target {
bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) =>
super.allowPlatformPrivateLibraryAccess(importer, imported) ||
importer.path.contains('runtime/tests/vm/dart') ||
importer.path.contains('test-lib');
importer.path.contains('test-lib') ||
importer.path.contains('tests/ffi');
// TODO(sigmund,ahe): limit this to `dart-ext` libraries only (see
// https://github.com/dart-lang/sdk/issues/29763).

View file

@ -8,6 +8,7 @@ import 'package:front_end/src/api_unstable/vm.dart'
messageFfiAbiSpecificIntegerMappingInvalid,
messageFfiPackedAnnotationAlignment,
messageNonPositiveArrayDimensions,
templateFfiCompoundImplementsFinalizable,
templateFfiEmptyStruct,
templateFfiFieldAnnotation,
templateFfiFieldNull,
@ -321,6 +322,19 @@ class _FfiDefinitionTransformer extends FfiTransformer {
return null;
}
final finalizableType = FutureOrType(
InterfaceType(finalizableClass, Nullability.nullable),
Nullability.nullable);
if (env.isSubtypeOf(InterfaceType(node, Nullability.nonNullable),
finalizableType, SubtypeCheckMode.ignoringNullabilities)) {
diagnosticReporter.report(
templateFfiCompoundImplementsFinalizable.withArguments(
node.superclass!.name, node.name),
node.fileOffset,
1,
node.location!.file);
}
if (node.superclass == structClass) {
final packingAnnotations = _getPackedAnnotations(node);
if (packingAnnotations.length > 1) {

View file

@ -14,6 +14,9 @@ import 'package:kernel/type_environment.dart';
/// This transformation is not AST-node preserving. [Expression]s and
/// [Statement]s can be replaced by other [Expression]s and [Statement]s
/// respectively. This means one cannot do `visitX() { super.visitX() as X }`.
///
/// This transform must be run on the standard libaries as well. For example
/// `NativeFinalizer`s `attach` implementation depends on it.
mixin FinalizableTransformer on Transformer {
TypeEnvironment get env;
Procedure get reachabilityFenceFunction;

View file

@ -1108,6 +1108,13 @@ DART_EXPORT void ReleaseClosureCallback() {
Dart_DeletePersistentHandle_DL(closure_to_callback_);
}
////////////////////////////////////////////////////////////////////////////////
// NativeFinalizer tests
DART_EXPORT void SetArgumentTo42(intptr_t* token) {
*token = 42;
}
////////////////////////////////////////////////////////////////////////////////
// Functions for testing @FfiNative.

View file

@ -210,15 +210,17 @@ StoreInstanceField(container, value, NoBarrier)
The GC is aware of two types of objects for the purposes of running finalizers.
1) `FinalizerEntry`
2) `Finalizer` (`FinalizerBase`, `_FinalizerImpl`)
2) `Finalizer` (`FinalizerBase`, `_FinalizerImpl`, `_NativeFinalizer`)
A `FinalizerEntry` contains the `value`, the optional `detach` key, and the `token`, and a reference to the `finalizer`.
A `FinalizerEntry` contains the `value`, the optional `detach` key, and the `token`, a reference to the `finalizer`, and an `external_size`.
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.
For native finalizers, the native callback is immediately invoked in the GC.
However, we still send a message to the native finalizer to clean up the entries from all entries and the detachments.
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.
@ -236,3 +238,5 @@ An alternative design would be to pre-allocate a `WeakReference` in the finalize
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.
On Isolate shutdown, native finalizers are run, but regular finalizers are not.

View file

@ -15,6 +15,7 @@
#include "vm/dart_api_impl.h"
#include "vm/exceptions.h"
#include "vm/flags.h"
#include "vm/heap/gc_shared.h"
#include "vm/log.h"
#include "vm/native_arguments.h"
#include "vm/native_entry.h"
@ -243,4 +244,40 @@ DEFINE_NATIVE_ENTRY(Ffi_GetFfiNativeResolver, 1, 0) {
return Pointer::New(type_arg, reinterpret_cast<intptr_t>(FfiResolve));
}
DEFINE_FFI_NATIVE_ENTRY(FinalizerEntry_SetExternalSize,
void,
(Dart_Handle entry_handle, intptr_t external_size)) {
Thread* const thread = Thread::Current();
TransitionNativeToVM transition(thread);
Zone* const zone = thread->zone();
const auto& entry_object =
Object::Handle(zone, Api::UnwrapHandle(entry_handle));
const auto& entry = FinalizerEntry::Cast(entry_object);
Heap::Space space;
intptr_t external_size_diff;
{
NoSafepointScope no_safepoint;
space = SpaceForExternal(entry.ptr());
const intptr_t external_size_old = entry.external_size();
if (FLAG_trace_finalizers) {
THR_Print("Setting external size from %" Pd " to %" Pd
" bytes in %s space\n",
external_size_old, external_size, space == 0 ? "new" : "old");
}
external_size_diff = external_size - external_size_old;
if (external_size_diff == 0) {
return;
}
entry.set_external_size(external_size);
}
// The next call cannot be in safepoint.
if (external_size_diff > 0) {
IsolateGroup::Current()->heap()->AllocatedExternal(external_size_diff,
space);
} else {
IsolateGroup::Current()->heap()->FreedExternal(-external_size_diff, space);
}
};
} // namespace dart

View file

@ -184,6 +184,7 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
bool error_found = false;
Function& erroneous_closure_function = Function::Handle(zone);
Class& erroneous_nativewrapper_class = Class::Handle(zone);
Class& erroneous_finalizable_class = Class::Handle(zone);
const char* error_message = nullptr;
{
@ -244,6 +245,7 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
MESSAGE_SNAPSHOT_ILLEGAL(DynamicLibrary);
// TODO(http://dartbug.com/47777): Send and exit support: remove this.
MESSAGE_SNAPSHOT_ILLEGAL(Finalizer);
MESSAGE_SNAPSHOT_ILLEGAL(NativeFinalizer);
MESSAGE_SNAPSHOT_ILLEGAL(MirrorReference);
MESSAGE_SNAPSHOT_ILLEGAL(Pointer);
MESSAGE_SNAPSHOT_ILLEGAL(ReceivePort);
@ -257,6 +259,11 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
error_found = true;
break;
}
if (klass.implements_finalizable()) {
erroneous_finalizable_class = klass.ptr();
error_found = true;
break;
}
}
}
raw->untag()->VisitPointers(&visitor);
@ -271,13 +278,18 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
"Illegal argument in isolate message"
" : (object is a closure - %s)",
erroneous_closure_function.ToCString());
} else {
ASSERT(!erroneous_nativewrapper_class.IsNull());
} else if (!erroneous_nativewrapper_class.IsNull()) {
exception_message =
OS::SCreate(zone,
"Illegal argument in isolate message"
" : (object extends NativeWrapper - %s)",
erroneous_nativewrapper_class.ToCString());
} else {
ASSERT(!erroneous_finalizable_class.IsNull());
exception_message = OS::SCreate(zone,
"Illegal argument in isolate message"
" : (object implements Finalizable - %s)",
erroneous_finalizable_class.ToCString());
}
return Exceptions::CreateUnhandledException(
zone, Exceptions::kArgumentValue, exception_message);

View file

@ -18,6 +18,7 @@
// inserting an object that cannot be allocated in new space.
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:nativewrappers';
@ -246,6 +247,9 @@ class SendReceiveTest extends SendReceiveTestBase {
await testWeakProperty();
await testWeakReference();
await testFinalizer();
await testNativeFinalizer();
await testFinalizable();
await testPointer();
await testForbiddenClosures();
}
@ -765,6 +769,27 @@ class SendReceiveTest extends SendReceiveTestBase {
Expect.throwsArgumentError(() => sendPort.send(finalizer));
}
Future testNativeFinalizer() async {
print('testNativeFinalizer');
final finalizer = NativeFinalizer(nullptr);
Expect.throwsArgumentError(() => sendPort.send(finalizer));
}
Future testFinalizable() async {
print('testFinalizable');
final finalizable = MyFinalizable();
Expect.throwsArgumentError(() => sendPort.send(finalizable));
}
Future testPointer() async {
print('testPointer');
final pointer = Pointer<Int8>.fromAddress(0xdeadbeef);
Expect.throwsArgumentError(() => sendPort.send(pointer));
}
Future testForbiddenClosures() async {
print('testForbiddenClosures');
for (final closure in nonCopyableClosures) {
@ -788,6 +813,8 @@ class Nonce {
String toString() => 'Nonce($value)';
}
class MyFinalizable implements Finalizable {}
main() async {
await SendReceiveTest().run();
}

View file

@ -20,6 +20,7 @@
// inserting an object that cannot be allocated in new space.
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:nativewrappers';
@ -248,6 +249,9 @@ class SendReceiveTest extends SendReceiveTestBase {
await testWeakProperty();
await testWeakReference();
await testFinalizer();
await testNativeFinalizer();
await testFinalizable();
await testPointer();
await testForbiddenClosures();
}
@ -767,6 +771,27 @@ class SendReceiveTest extends SendReceiveTestBase {
Expect.throwsArgumentError(() => sendPort.send(finalizer));
}
Future testNativeFinalizer() async {
print('testNativeFinalizer');
final finalizer = NativeFinalizer(nullptr);
Expect.throwsArgumentError(() => sendPort.send(finalizer));
}
Future testFinalizable() async {
print('testFinalizable');
final finalizable = MyFinalizable();
Expect.throwsArgumentError(() => sendPort.send(finalizable));
}
Future testPointer() async {
print('testPointer');
final pointer = Pointer<Int8>.fromAddress(0xdeadbeef);
Expect.throwsArgumentError(() => sendPort.send(pointer));
}
Future testForbiddenClosures() async {
print('testForbiddenClosures');
for (final closure in nonCopyableClosures) {
@ -790,6 +815,8 @@ class Nonce {
String toString() => 'Nonce($value)';
}
class MyFinalizable implements Finalizable {}
main() async {
await SendReceiveTest().run();
}

View file

@ -31,6 +31,15 @@ static const struct NativeEntries {
#endif // !DART_PRECOMPILED_RUNTIME
};
#define REGISTER_FFI_NATIVE_ENTRY(name, return_type, argument_types) \
{"" #name, reinterpret_cast<void*>(BootstrapNatives::FN_##name)},
static const struct FfiNativeEntries {
const char* name_;
void* function_;
} BootStrapFfiEntries[] = {
BOOTSTRAP_FFI_NATIVE_LIST(REGISTER_FFI_NATIVE_ENTRY)};
Dart_NativeFunction BootstrapNatives::Lookup(Dart_Handle name,
int argument_count,
bool* auto_setup_scope) {
@ -55,6 +64,19 @@ Dart_NativeFunction BootstrapNatives::Lookup(Dart_Handle name,
return NULL;
}
void* BootstrapNatives::LookupFfiNative(const char* name,
uintptr_t argument_count) {
int num_entries =
sizeof(BootStrapFfiEntries) / sizeof(struct FfiNativeEntries);
for (int i = 0; i < num_entries; i++) {
const struct FfiNativeEntries* entry = &(BootStrapFfiEntries[i]);
if (strcmp(name, entry->name_) == 0) {
return entry->function_;
}
}
return nullptr;
}
const uint8_t* BootstrapNatives::Symbol(Dart_NativeFunction nf) {
int num_entries = sizeof(BootStrapEntries) / sizeof(struct NativeEntries);
for (int i = 0; i < num_entries; i++) {
@ -71,6 +93,9 @@ void Bootstrap::SetupNativeResolver() {
Dart_NativeEntryResolver resolver = BootstrapNatives::Lookup;
Dart_FfiNativeResolver ffi_native_resolver =
BootstrapNatives::LookupFfiNative;
Dart_NativeEntrySymbol symbol_resolver = BootstrapNatives::Symbol;
library = Library::AsyncLibrary();
@ -107,6 +132,7 @@ void Bootstrap::SetupNativeResolver() {
ASSERT(!library.IsNull());
library.set_native_entry_resolver(resolver);
library.set_native_entry_symbol_resolver(symbol_resolver);
library.set_ffi_native_resolver(ffi_native_resolver);
library = Library::IsolateLibrary();
ASSERT(!library.IsNull());

View file

@ -452,12 +452,18 @@ namespace dart {
V(ParameterMirror_type, 3) \
V(VariableMirror_type, 2)
#define BOOTSTRAP_FFI_NATIVE_LIST(V) \
V(FinalizerEntry_SetExternalSize, void, (Dart_Handle, intptr_t))
class BootstrapNatives : public AllStatic {
public:
static Dart_NativeFunction Lookup(Dart_Handle name,
int argument_count,
bool* auto_setup_scope);
// For use with @FfiNative.
static void* LookupFfiNative(const char* name, uintptr_t argument_count);
static const uint8_t* Symbol(Dart_NativeFunction nf);
#define DECLARE_BOOTSTRAP_NATIVE(name, ignored) \
@ -468,8 +474,12 @@ class BootstrapNatives : public AllStatic {
#if !defined(DART_PRECOMPILED_RUNTIME)
MIRRORS_BOOTSTRAP_NATIVE_LIST(DECLARE_BOOTSTRAP_NATIVE)
#endif
#undef DECLARE_BOOTSTRAP_NATIVE
#define DECLARE_BOOTSTRAP_FFI_NATIVE(name, return_type, argument_types) \
static return_type FN_##name argument_types;
BOOTSTRAP_FFI_NATIVE_LIST(DECLARE_BOOTSTRAP_FFI_NATIVE)
#undef DECLARE_BOOTSTRAP_FFI_NATIVE
};
} // namespace dart

View file

@ -1034,6 +1034,10 @@ void ClassFinalizer::FinalizeTypesInClass(const Class& cls) {
if (FLAG_trace_class_finalization) {
THR_Print("Finalize types in %s\n", cls.ToCString());
}
bool implements_finalizable =
cls.Name() == Symbols::Finalizable().ptr() &&
Library::UrlOf(cls.library()) == Symbols::DartFfi().ptr();
// Finalize super class.
Class& super_class = Class::Handle(zone, cls.SuperClass());
if (!super_class.IsNull()) {
@ -1049,15 +1053,24 @@ void ClassFinalizer::FinalizeTypesInClass(const Class& cls) {
if (!super_type.IsNull()) {
super_type = FinalizeType(super_type);
cls.set_super_type(super_type);
implements_finalizable |=
Class::ImplementsFinalizable(super_type.type_class());
}
// Finalize interface types (but not necessarily interface classes).
Array& interface_types = Array::Handle(zone, cls.interfaces());
AbstractType& interface_type = AbstractType::Handle(zone);
const auto& interface_types = Array::Handle(zone, cls.interfaces());
auto& interface_type = AbstractType::Handle(zone);
auto& interface_class = Class::Handle(zone);
for (intptr_t i = 0; i < interface_types.Length(); i++) {
interface_type ^= interface_types.At(i);
interface_type = FinalizeType(interface_type);
interface_class = interface_type.type_class();
ASSERT(!interface_class.IsNull());
FinalizeTypesInClass(interface_class);
interface_types.SetAt(i, interface_type);
implements_finalizable |=
Class::ImplementsFinalizable(interface_type.type_class());
}
cls.set_implements_finalizable(implements_finalizable);
cls.set_is_type_finalized();
RegisterClassInHierarchy(thread->zone(), cls);

View file

@ -70,6 +70,7 @@ typedef uint16_t ClassIdTagType;
V(Type) \
V(FinalizerBase) \
V(Finalizer) \
V(NativeFinalizer) \
V(FinalizerEntry) \
V(FunctionType) \
V(TypeRef) \

View file

@ -2807,6 +2807,7 @@ void LoadFieldInstr::InferRange(RangeAnalysis* analysis, Range* range) {
case Slot::Kind::kFinalizerEntry_next:
case Slot::Kind::kFinalizerEntry_token:
case Slot::Kind::kFinalizerEntry_value:
case Slot::Kind::kNativeFinalizer_callback:
case Slot::Kind::kFunction_data:
case Slot::Kind::kFunction_signature:
case Slot::Kind::kFunctionType_named_parameter_names:

View file

@ -237,6 +237,7 @@ bool Slot::IsImmutableLengthSlot() const {
case Slot::Kind::kFinalizerEntry_next:
case Slot::Kind::kFinalizerEntry_token:
case Slot::Kind::kFinalizerEntry_value:
case Slot::Kind::kNativeFinalizer_callback:
case Slot::Kind::kFunction_data:
case Slot::Kind::kFunction_signature:
case Slot::Kind::kFunctionType_named_parameter_names:

View file

@ -101,6 +101,7 @@ class ParsedFunction;
V(Closure, UntaggedClosure, context, Context, FINAL) \
V(Closure, UntaggedClosure, hash, Context, VAR) \
V(Finalizer, UntaggedFinalizer, callback, Closure, FINAL) \
V(NativeFinalizer, UntaggedFinalizer, callback, Pointer, FINAL) \
V(Function, UntaggedFunction, data, Dynamic, FINAL) \
V(FunctionType, UntaggedFunctionType, named_parameter_names, Array, FINAL) \
V(FunctionType, UntaggedFunctionType, parameter_types, Array, FINAL) \
@ -170,6 +171,7 @@ NONNULLABLE_BOXED_NATIVE_SLOTS_LIST(FOR_EACH_NATIVE_SLOT)
V(ClosureData, UntaggedClosureData, default_type_arguments_kind, Uint8, \
FINAL) \
V(FinalizerBase, UntaggedFinalizerBase, isolate, IntPtr, VAR) \
V(FinalizerEntry, UntaggedFinalizerEntry, external_size, 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

@ -825,6 +825,7 @@ Fragment FlowGraphBuilder::NativeFunctionBody(const Function& function,
V(FinalizerEntry_getNext, FinalizerEntry_next) \
V(FinalizerEntry_getToken, FinalizerEntry_token) \
V(FinalizerEntry_getValue, FinalizerEntry_value) \
V(NativeFinalizer_getCallback, NativeFinalizer_callback) \
V(GrowableArrayLength, GrowableObjectArray_length) \
V(ImmutableLinkedHashBase_getData, ImmutableLinkedHashBase_data) \
V(ImmutableLinkedHashBase_getIndex, ImmutableLinkedHashBase_index) \
@ -845,11 +846,8 @@ Fragment FlowGraphBuilder::NativeFunctionBody(const Function& function,
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(NativeFinalizer_setCallback, NativeFinalizer_callback) \
V(LinkedHashBase_setData, LinkedHashBase_data) \
V(LinkedHashBase_setIndex, LinkedHashBase_index) \
V(WeakProperty_setKey, WeakProperty_key) \
@ -938,6 +936,8 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
case MethodRecognizer::kFinalizerBase_getIsolateFinalizers:
case MethodRecognizer::kFinalizerBase_setIsolate:
case MethodRecognizer::kFinalizerBase_setIsolateFinalizers:
case MethodRecognizer::kFinalizerEntry_allocate:
case MethodRecognizer::kFinalizerEntry_getExternalSize:
case MethodRecognizer::kObjectEquals:
case MethodRecognizer::kStringBaseLength:
case MethodRecognizer::kStringBaseIsEmpty:
@ -1621,6 +1621,42 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
body += NullConstant();
body += StoreNativeField(Slot::FinalizerBase_entries_collected());
break;
case MethodRecognizer::kFinalizerEntry_allocate: {
// Object value, Object token, Object detach, FinalizerBase finalizer
ASSERT_EQUAL(function.NumParameters(), 4);
const auto class_table = thread_->isolate_group()->class_table();
ASSERT(class_table->HasValidClassAt(kFinalizerEntryCid));
const auto& finalizer_entry_class =
Class::ZoneHandle(H.zone(), class_table->At(kFinalizerEntryCid));
body +=
AllocateObject(TokenPosition::kNoSource, finalizer_entry_class, 0);
LocalVariable* const entry = MakeTemporary("entry");
// No GC from here to the end.
body += LoadLocal(entry);
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += StoreNativeField(Slot::FinalizerEntry_value());
body += LoadLocal(entry);
body += LoadLocal(parsed_function_->RawParameterVariable(1));
body += StoreNativeField(Slot::FinalizerEntry_token());
body += LoadLocal(entry);
body += LoadLocal(parsed_function_->RawParameterVariable(2));
body += StoreNativeField(Slot::FinalizerEntry_detach());
body += LoadLocal(entry);
body += LoadLocal(parsed_function_->RawParameterVariable(3));
body += StoreNativeField(Slot::FinalizerEntry_finalizer());
body += LoadLocal(entry);
body += UnboxedIntConstant(0, kUnboxedIntPtr);
body += StoreNativeField(Slot::FinalizerEntry_external_size());
break;
}
case MethodRecognizer::kFinalizerEntry_getExternalSize:
ASSERT_EQUAL(function.NumParameters(), 1);
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += LoadNativeField(Slot::FinalizerEntry_external_size());
body += Box(kUnboxedInt64);
break;
#define IL_BODY(method, slot) \
case MethodRecognizer::k##method: \
ASSERT_EQUAL(function.NumParameters(), 1); \

View file

@ -123,15 +123,16 @@ namespace dart {
0xb3e66928) \
V(_FinalizerImpl, get:_callback, Finalizer_getCallback, 0x6f3d56bc) \
V(_FinalizerImpl, set:_callback, Finalizer_setCallback, 0xc6aa96f9) \
V(_NativeFinalizer, get:_callback, NativeFinalizer_getCallback, 0x5cb374f5) \
V(_NativeFinalizer, set:_callback, NativeFinalizer_setCallback, 0xb12268f2) \
V(FinalizerEntry, allocate, FinalizerEntry_allocate, 0xe0bad878) \
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(FinalizerEntry, get:externalSize, FinalizerEntry_getExternalSize, \
0x47df4d22) \
V(Float32x4, _Float32x4FromDoubles, Float32x4FromDoubles, 0x1845792b) \
V(Float32x4, Float32x4.zero, Float32x4Zero, 0xd3b64002) \
V(Float32x4, _Float32x4Splat, Float32x4Splat, 0x13a552c3) \

View file

@ -447,6 +447,8 @@ static uword GetInstanceSizeImpl(const dart::Class& handle) {
return Finalizer::InstanceSize();
case kFinalizerEntryCid:
return FinalizerEntry::InstanceSize();
case kNativeFinalizerCid:
return NativeFinalizer::InstanceSize();
case kByteBufferCid:
case kByteDataViewCid:
case kPointerCid:

View file

@ -1026,7 +1026,14 @@ class FinalizerBase : public AllStatic {
class Finalizer : public AllStatic {
public:
static word callback_offset();
static word type_arguments_offset();
static word InstanceSize();
FINAL_CLASS();
};
class NativeFinalizer : public AllStatic {
public:
static word callback_offset();
static word InstanceSize();
FINAL_CLASS();
@ -1034,11 +1041,12 @@ class Finalizer : public AllStatic {
class FinalizerEntry : public AllStatic {
public:
static word value_offset();
static word detach_offset();
static word token_offset();
static word next_offset();
static word external_size_offset();
static word finalizer_offset();
static word next_offset();
static word token_offset();
static word value_offset();
static word InstanceSize();
FINAL_CLASS();
};

File diff suppressed because it is too large Load diff

View file

@ -305,11 +305,13 @@
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, external_size_offset) \
FIELD(FinalizerEntry, finalizer_offset) \
FIELD(FinalizerEntry, next_offset) \
FIELD(FinalizerEntry, token_offset) \
FIELD(FinalizerEntry, value_offset) \
FIELD(NativeFinalizer, callback_offset) \
FIELD(FunctionType, hash_offset) \
FIELD(FunctionType, named_parameter_names_offset) \
FIELD(FunctionType, nullability_offset) \
@ -374,6 +376,7 @@
SIZEOF(Field, InstanceSize, UntaggedField) \
SIZEOF(Finalizer, InstanceSize, UntaggedFinalizer) \
SIZEOF(FinalizerEntry, InstanceSize, UntaggedFinalizerEntry) \
SIZEOF(NativeFinalizer, InstanceSize, UntaggedNativeFinalizer) \
SIZEOF(Float32x4, InstanceSize, UntaggedFloat32x4) \
SIZEOF(Float64x2, InstanceSize, UntaggedFloat64x2) \
SIZEOF(Function, InstanceSize, UntaggedFunction) \

View file

@ -744,8 +744,13 @@ ObjectPtr DartLibraryCalls::HandleFinalizerMessage(
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());
auto& function = Function::Handle(zone);
if (finalizer.IsFinalizer()) {
function ^= object_store->handle_finalizer_message_function();
} else {
ASSERT(finalizer.IsNativeFinalizer());
function ^= object_store->handle_native_finalizer_message_function();
}
ASSERT(!function.IsNull());
Array& args =
Array::Handle(zone, isolate->isolate_object_store()->dart_args_1());

View file

@ -36,4 +36,9 @@ void GCLinkedLists::FlushInto(GCLinkedLists* to) {
#undef FOREACH
}
Heap::Space SpaceForExternal(FinalizerEntryPtr raw_entry) {
ASSERT(!raw_entry->untag()->value().IsSmi());
return raw_entry->untag()->value()->IsOldObject() ? Heap::kOld : Heap::kNew;
}
} // namespace dart

View file

@ -37,6 +37,7 @@ template <typename Type, typename PtrType>
class GCLinkedList {
public:
void Enqueue(PtrType ptr) {
ASSERT(ptr->untag()->next_seen_by_gc().IsRawNull());
ptr->untag()->next_seen_by_gc_ = head_;
if (head_ == Type::null()) {
tail_ = ptr;
@ -92,6 +93,55 @@ struct GCLinkedLists {
#define TRACE_FINALIZER(format, ...)
#endif
// The space in which `raw_entry`'s `value` is.
Heap::Space SpaceForExternal(FinalizerEntryPtr raw_entry);
// Runs the finalizer if not detached, detaches the value and set external size
// to 0.
// TODO(http://dartbug.com/47777): Can this be merged with
// NativeFinalizer::RunCallback?
template <typename GCVisitorType>
void RunNativeFinalizerCallback(NativeFinalizerPtr raw_finalizer,
FinalizerEntryPtr raw_entry,
Heap::Space before_gc_space,
GCVisitorType* visitor) {
PointerPtr callback_pointer = raw_finalizer->untag()->callback();
const auto callback = reinterpret_cast<NativeFinalizer::Callback>(
callback_pointer->untag()->data());
ObjectPtr token_object = raw_entry->untag()->token();
const bool is_detached = token_object == raw_entry;
const intptr_t external_size = raw_entry->untag()->external_size();
if (is_detached) {
// Detached from Dart code.
ASSERT(token_object == raw_entry);
ASSERT(external_size == 0);
if (FLAG_trace_finalizers) {
TRACE_FINALIZER("Not running native finalizer %p callback %p, detached",
raw_finalizer->untag(), callback);
}
} else {
// TODO(http://dartbug.com/48615): Unbox pointer address in entry.
ASSERT(token_object.IsPointer());
PointerPtr token = static_cast<PointerPtr>(token_object);
void* peer = reinterpret_cast<void*>(token->untag()->data());
if (FLAG_trace_finalizers) {
TRACE_FINALIZER("Running native finalizer %p callback %p with token %p",
raw_finalizer->untag(), callback, peer);
}
raw_entry.untag()->set_token(raw_entry);
callback(peer);
if (external_size > 0) {
if (FLAG_trace_finalizers) {
TRACE_FINALIZER("Clearing external size %" Pd " bytes in %s space",
external_size, before_gc_space == 0 ? "new" : "old");
}
visitor->isolate_group()->heap()->FreedExternal(external_size,
before_gc_space);
raw_entry->untag()->set_external_size(0);
}
}
}
// This function processes all finalizer entries discovered by a scavenger or
// marker. If an entry is referencing an object that is going to die, such entry
// is cleared and enqueued in the respective finalizer.
@ -102,9 +152,9 @@ struct GCLinkedLists {
// 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.
// It is expected to provide |ForwardOrSetNullIfCollected| method for clearing
// fields referring to dead objects and |kName| field which contains visitor
// name for tracing output.
template <typename GCVisitorType>
void MournFinalized(GCVisitorType* visitor) {
FinalizerEntryPtr current_entry =
@ -117,12 +167,24 @@ void MournFinalized(GCVisitorType* visitor) {
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_);
const Heap::Space before_gc_space = SpaceForExternal(current_entry);
const bool value_collected_this_gc =
GCVisitorType::ForwardOrSetNullIfCollected(
heap_base, &current_entry->untag()->value_);
if (!value_collected_this_gc && before_gc_space == Heap::kNew) {
const Heap::Space after_gc_space = SpaceForExternal(current_entry);
if (after_gc_space == Heap::kOld) {
const intptr_t external_size = current_entry->untag()->external_size_;
TRACE_FINALIZER("Promoting external size %" Pd
" bytes from new to old space",
external_size);
visitor->isolate_group()->heap()->PromotedExternal(external_size);
}
}
GCVisitorType::ForwardOrSetNullIfCollected(
heap_base, &current_entry->untag()->detach_);
GCVisitorType::ForwardOrSetNullIfCollected(
heap_base, &current_entry->untag()->finalizer_);
ObjectPtr token_object = current_entry->untag()->token();
// See sdk/lib/_internal/vm/lib/internal_patch.dart FinalizerBase.detach.
@ -136,7 +198,7 @@ void MournFinalized(GCVisitorType* visitor) {
current_entry->untag());
// Do nothing, the finalizer has been GCed.
} else if (finalizer.IsFinalizer()) {
} else {
TRACE_FINALIZER("Value collected entry %p finalizer %p",
current_entry->untag(), finalizer->untag());
@ -155,6 +217,18 @@ void MournFinalized(GCVisitorType* visitor) {
ASSERT(Thread::Current()->IsAtSafepoint() ||
Thread::Current()->BypassSafepoints());
if (finalizer.IsNativeFinalizer()) {
NativeFinalizerPtr native_finalizer =
static_cast<NativeFinalizerPtr>(finalizer);
// Immediately call native callback.
RunNativeFinalizerCallback(native_finalizer, current_entry,
before_gc_space, visitor);
// Fall-through sending a message to clear the entries and remove
// from detachments.
}
FinalizerEntryPtr previous_head =
finalizer_dart->untag()->exchange_entries_collected(current_entry);
current_entry->untag()->set_next(previous_head);
@ -179,9 +253,6 @@ void MournFinalized(GCVisitorType* visitor) {
/*before_events*/ false);
}
}
} else {
// TODO(http://dartbug.com/47777): Implement NativeFinalizer.
UNREACHABLE();
}
}

View file

@ -296,15 +296,16 @@ class MarkingVisitorBase : public ObjectPointerVisitor {
// If we did not mark the target through a weak property in a later round,
// then the target is dead and we should clear it.
SetNullIfCollected(cur_weak->heap_base(), &cur_weak->untag()->target_);
ForwardOrSetNullIfCollected(cur_weak->heap_base(),
&cur_weak->untag()->target_);
cur_weak = next_weak;
}
}
// Returns whether the object referred to in `ptr_address` was GCed this GC.
static bool SetNullIfCollected(uword heap_base,
CompressedObjectPtr* ptr_address) {
static bool ForwardOrSetNullIfCollected(uword heap_base,
CompressedObjectPtr* ptr_address) {
ObjectPtr raw = ptr_address->Decompress(heap_base);
if (raw.IsRawNull()) {
// Object already null before this GC.

View file

@ -7,9 +7,11 @@
#include "platform/assert.h"
#include "platform/leak_sanitizer.h"
#include "vm/class_id.h"
#include "vm/compiler/runtime_api.h"
#include "vm/dart.h"
#include "vm/dart_api_state.h"
#include "vm/flag_list.h"
#include "vm/flags.h"
#include "vm/heap/become.h"
#include "vm/heap/gc_shared.h"
#include "vm/heap/pages.h"
@ -19,12 +21,14 @@
#include "vm/heap/weak_table.h"
#include "vm/isolate.h"
#include "vm/lockers.h"
#include "vm/log.h"
#include "vm/longjump.h"
#include "vm/object.h"
#include "vm/object_id_ring.h"
#include "vm/object_set.h"
#include "vm/port.h"
#include "vm/stack_frame.h"
#include "vm/tagged_pointer.h"
#include "vm/thread_barrier.h"
#include "vm/thread_registry.h"
#include "vm/timeline.h"
@ -318,8 +322,8 @@ class ScavengerVisitorBase : public ObjectPointerVisitor {
NewPage* head() const { return head_; }
NewPage* tail() const { return tail_; }
static bool SetNullIfCollected(uword heap_base,
CompressedObjectPtr* ptr_address);
static bool ForwardOrSetNullIfCollected(uword heap_base,
CompressedObjectPtr* ptr_address);
private:
void UpdateStoreBuffer(ObjectPtr obj) {
@ -1197,9 +1201,44 @@ void Scavenger::IterateStoreBuffers(ScavengerVisitorBase<parallel>* visitor) {
ASSERT(raw_object->untag()->IsRemembered());
raw_object->untag()->ClearRememberedBit();
visitor->VisitingOldObject(raw_object);
// Note that this treats old-space WeakProperties as strong. A dead key
// won't be reclaimed until after the key is promoted.
raw_object->untag()->VisitPointersNonvirtual(visitor);
intptr_t class_id = raw_object->GetClassId();
// This treats old-space weak references in WeakProperty, WeakReference,
// and FinalizerEntry as strong references. This prevents us from having
// to enqueue them in `visitor->delayed_`. Enqueuing them in the delayed
// would require having two `next_seen_by_gc` fields. One for used during
// marking and one for the objects seen in the store buffers + new space.
// Treating the weak references as strong here means we can have a single
// `next_seen_by_gc` field.
if (UNLIKELY(class_id == kFinalizerEntryCid)) {
FinalizerEntryPtr raw_entry =
static_cast<FinalizerEntryPtr>(raw_object);
// Detect `FinalizerEntry::value` promotion to update external space.
//
// This treats old-space FinalizerEntry fields as strong. Values, deatch
// keys, and finalizers in new space won't be reclaimed until after they
// are promoted.
// This will only visit the strong references, end enqueue the entry.
// This enables us to update external space in MournFinalized.
const Heap::Space before_gc_space = SpaceForExternal(raw_entry);
UntaggedFinalizerEntry::VisitFinalizerEntryPointers(raw_entry, visitor);
if (before_gc_space == Heap::kNew) {
const Heap::Space after_gc_space = SpaceForExternal(raw_entry);
if (after_gc_space == Heap::kOld) {
const intptr_t external_size = raw_entry->untag()->external_size_;
if (FLAG_trace_finalizers) {
THR_Print(
"Scavenger %p Store buffer, promoting external size %" Pd
" bytes from new to old space\n",
visitor, external_size);
}
visitor->isolate_group()->heap()->PromotedExternal(external_size);
}
}
} else {
// This treats old-space WeakProperties and WeakReferences as strong. A
// dead key or target won't be reclaimed until after it is promoted.
raw_object->untag()->VisitPointersNonvirtual(visitor);
}
}
pending->Reset();
// Return the emptied block for recycling (no need to check threshold).
@ -1542,7 +1581,8 @@ void ScavengerVisitorBase<parallel>::MournOrUpdateWeakReferences() {
// If we did not mark the target through a weak property in a later round,
// then the target is dead and we should clear it.
SetNullIfCollected(cur_weak->heap_base(), &cur_weak->untag()->target_);
ForwardOrSetNullIfCollected(cur_weak->heap_base(),
&cur_weak->untag()->target_);
// Advance to next weak reference in the queue.
cur_weak = next_weak;
@ -1551,7 +1591,7 @@ void ScavengerVisitorBase<parallel>::MournOrUpdateWeakReferences() {
// Returns whether the object referred to in `ptr_address` was GCed this GC.
template <bool parallel>
bool ScavengerVisitorBase<parallel>::SetNullIfCollected(
bool ScavengerVisitorBase<parallel>::ForwardOrSetNullIfCollected(
uword heap_base,
CompressedObjectPtr* ptr_address) {
ObjectPtr raw = ptr_address->Decompress(heap_base);

View file

@ -449,6 +449,7 @@ class Scavenger {
template <bool>
friend class ScavengerVisitorBase;
friend class ScavengerWeakVisitor;
friend class ScavengerFinalizerVisitor;
DISALLOW_COPY_AND_ASSIGN(Scavenger);
};

View file

@ -1955,6 +1955,10 @@ void Isolate::set_origin_id(Dart_Port id) {
origin_id_ = id;
}
void Isolate::set_finalizers(const GrowableObjectArray& value) {
finalizers_ = value.ptr();
}
bool Isolate::IsPaused() const {
#if defined(PRODUCT)
return false;
@ -2476,14 +2480,14 @@ 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());
auto& current_entry = FinalizerEntry::Handle(stack_zone.GetZone());
auto& all_entries = LinkedHashSet::Handle(stack_zone.GetZone());
for (int i = 0; i < num_finalizers; i++) {
weak_reference ^= finalizers.At(i);
finalizer ^= weak_reference.target();
@ -2499,6 +2503,17 @@ void Isolate::LowLevelShutdown() {
// TODO(http://dartbug.com/47777): Send and exit support.
UNREACHABLE();
}
if (finalizer.IsNativeFinalizer()) {
// Immediately call native callback.
const auto& native_finalizer = NativeFinalizer::Cast(finalizer);
all_entries = finalizer.all_entries();
LinkedHashSet::Iterator iterator(all_entries);
while (iterator.MoveNext()) {
current_entry ^= iterator.CurrentKey();
native_finalizer.RunCallback(current_entry, "Isolate shutdown");
}
}
}
}
}

View file

@ -1065,6 +1065,7 @@ 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_; }
void set_finalizers(const GrowableObjectArray& value);
static intptr_t finalizers_offset() {
return OFFSET_OF(Isolate, finalizers_);
}

View file

@ -3944,7 +3944,7 @@ ObjectPtr ReadMessage(Thread* thread, Message* message) {
} else if (message->IsFinalizerInvocationRequest()) {
PersistentHandle* handle = message->persistent_handle();
Object& msg_obj = Object::Handle(thread->zone(), handle->ptr());
ASSERT(msg_obj.IsFinalizer());
ASSERT(msg_obj.IsFinalizer() || msg_obj.IsNativeFinalizer());
return msg_obj.ptr();
} else if (message->IsPersistentHandle()) {
return ReadObjectGraphCopyMessage(thread, message->persistent_handle());

View file

@ -15,7 +15,6 @@
#include "vm/native_function.h"
#include "vm/runtime_entry.h"
namespace dart {
// Forward declarations.
@ -51,6 +50,9 @@ typedef ObjectPtr (*BootstrapNativeFunction)(Thread* thread,
static ObjectPtr DN_Helper##name(Isolate* isolate, Thread* thread, \
Zone* zone, NativeArguments* arguments)
#define DEFINE_FFI_NATIVE_ENTRY(name, return_type, argument_types) \
return_type BootstrapNatives::FN_##name argument_types
// Helpers that throw an argument exception.
void DartNativeThrowTypeArgumentCountException(int num_type_args,
int num_type_args_expected);

View file

@ -45,6 +45,7 @@
#include "vm/kernel_binary.h"
#include "vm/kernel_isolate.h"
#include "vm/kernel_loader.h"
#include "vm/log.h"
#include "vm/native_symbol.h"
#include "vm/object_graph.h"
#include "vm/object_store.h"
@ -2344,6 +2345,10 @@ ErrorPtr Object::Init(IsolateGroup* isolate_group,
pending_classes.Add(cls);
RegisterClass(cls, Symbols::FfiDynamicLibrary(), lib);
cls = Class::New<NativeFinalizer, RTN::NativeFinalizer>(isolate_group);
object_store->set_native_finalizer_class(cls);
RegisterPrivateClass(cls, Symbols::_NativeFinalizer(), lib);
cls = Class::New<Finalizer, RTN::Finalizer>(isolate_group);
cls.set_type_arguments_field_offset(
Finalizer::type_arguments_offset(),
@ -2539,6 +2544,8 @@ ErrorPtr Object::Init(IsolateGroup* 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<NativeFinalizer, RTN::NativeFinalizer>(isolate_group);
object_store->set_native_finalizer_class(cls);
cls = Class::New<FinalizerEntry, RTN::FinalizerEntry>(isolate_group);
object_store->set_finalizer_entry_class(cls);
@ -2992,6 +2999,11 @@ void Class::set_has_pragma(bool value) const {
set_state_bits(HasPragmaBit::update(value, state_bits()));
}
void Class::set_implements_finalizable(bool value) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_state_bits(ImplementsFinalizableBit::update(value, state_bits()));
}
// Initialize class fields of type Array with empty array.
void Class::InitEmptyFields() {
if (Object::empty_array().ptr() == Array::null()) {
@ -8078,6 +8090,38 @@ void Function::SetIsOptimizable(bool value) const {
}
}
bool Function::ForceOptimize() const {
return IsFfiFromAddress() || IsFfiGetAddress() || IsFfiLoad() ||
IsFfiStore() || IsFfiTrampoline() || IsFfiAsExternalTypedData() ||
IsTypedDataViewFactory() || IsUtf8Scan() || IsGetNativeField() ||
IsFinalizerForceOptimized();
}
bool Function::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:
case MethodRecognizer::kFinalizerEntry_getExternalSize:
// 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;
case MethodRecognizer::kFinalizerEntry_allocate:
// Both of the above reasons.
return true;
default:
return false;
}
}
#if !defined(DART_PRECOMPILED_RUNTIME)
bool Function::CanBeInlined() const {
// Our force-optimized functions cannot deoptimize to an unoptimized frame.
@ -26044,6 +26088,10 @@ const char* FinalizerBase::ToCString() const {
FinalizerPtr Finalizer::New(Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->finalizer_class() !=
Class::null());
ASSERT(
Class::Handle(IsolateGroup::Current()->object_store()->finalizer_class())
.EnsureIsAllocateFinalized(Thread::Current()) == Error::null());
ObjectPtr raw =
Object::Allocate(Finalizer::kClassId, Finalizer::InstanceSize(), space,
Finalizer::ContainsCompressedPointers());
@ -26057,13 +26105,84 @@ const char* Finalizer::ToCString() const {
type_args_name.ToCString());
}
FinalizerEntryPtr FinalizerEntry::New(Heap::Space space) {
NativeFinalizerPtr NativeFinalizer::New(Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->native_finalizer_class() !=
Class::null());
ASSERT(Class::Handle(
IsolateGroup::Current()->object_store()->native_finalizer_class())
.EnsureIsAllocateFinalized(Thread::Current()) == Error::null());
ObjectPtr raw = Object::Allocate(
NativeFinalizer::kClassId, NativeFinalizer::InstanceSize(), space,
NativeFinalizer::ContainsCompressedPointers());
return static_cast<NativeFinalizerPtr>(raw);
}
// Runs the finalizer if not detached, detaches the value and set external size
// to 0.
// TODO(http://dartbug.com/47777): Can this be merged with
// RunNativeFinalizerCallback?
void NativeFinalizer::RunCallback(const FinalizerEntry& entry,
const char* trace_context) const {
Thread* const thread = Thread::Current();
Zone* const zone = thread->zone();
IsolateGroup* const group = thread->isolate_group();
const intptr_t external_size = entry.external_size();
const auto& token_object = Object::Handle(zone, entry.token());
const auto& callback_pointer = Pointer::Handle(zone, this->callback());
const auto callback = reinterpret_cast<NativeFinalizer::Callback>(
callback_pointer.NativeAddress());
if (token_object.IsFinalizerEntry()) {
// Detached from Dart code.
ASSERT(token_object.ptr() == entry.ptr());
ASSERT(external_size == 0);
if (FLAG_trace_finalizers) {
THR_Print(
"%s: Not running native finalizer %p callback %p, "
"detached\n",
trace_context, ptr()->untag(), callback);
}
} else {
const auto& token = Pointer::Cast(token_object);
void* peer = reinterpret_cast<void*>(token.NativeAddress());
if (FLAG_trace_finalizers) {
THR_Print(
"%s: Running native finalizer %p callback %p "
"with token %p\n",
trace_context, ptr()->untag(), callback, peer);
}
entry.set_token(entry);
callback(peer);
if (external_size > 0) {
ASSERT(!entry.value()->IsSmi());
Heap::Space space =
entry.value()->IsOldObject() ? Heap::kOld : Heap::kNew;
if (FLAG_trace_finalizers) {
THR_Print("%s: Clearing external size %" Pd " bytes in %s space\n",
trace_context, external_size, space == 0 ? "new" : "old");
}
group->heap()->FreedExternal(external_size, space);
entry.set_external_size(0);
}
}
}
const char* NativeFinalizer::ToCString() const {
const auto& pointer = Pointer::Handle(callback());
return OS::SCreate(Thread::Current()->zone(), "_NativeFinalizer %s",
pointer.ToCString());
}
FinalizerEntryPtr FinalizerEntry::New(const FinalizerBase& finalizer,
Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->finalizer_entry_class() !=
Class::null());
ObjectPtr raw =
auto& entry = FinalizerEntry::Handle();
entry ^=
Object::Allocate(FinalizerEntry::kClassId, FinalizerEntry::InstanceSize(),
space, FinalizerEntry::ContainsCompressedPointers());
return static_cast<FinalizerEntryPtr>(raw);
entry.set_external_size(0);
entry.set_finalizer(finalizer);
return entry.ptr();
}
void FinalizerEntry::set_finalizer(const FinalizerBase& value) const {

View file

@ -1608,6 +1608,10 @@ class Class : public Object {
static uint16_t NumNativeFieldsOf(ClassPtr clazz) {
return clazz->untag()->num_native_fields_;
}
static bool ImplementsFinalizable(ClassPtr clazz) {
ASSERT(Class::Handle(clazz).is_type_finalized());
return ImplementsFinalizableBit::decode(clazz->untag()->state_bits_);
}
#if !defined(DART_PRECOMPILED_RUNTIME)
CodePtr allocation_stub() const { return untag()->allocation_stub(); }
@ -1831,6 +1835,7 @@ class Class : public Object {
kIsAllocatedBit,
kIsLoadedBit,
kHasPragmaBit,
kImplementsFinalizableBit,
};
class ConstBit : public BitField<uint32_t, bool, kConstBit, 1> {};
class ImplementedBit : public BitField<uint32_t, bool, kImplementedBit, 1> {};
@ -1853,6 +1858,8 @@ class Class : public Object {
class IsAllocatedBit : public BitField<uint32_t, bool, kIsAllocatedBit, 1> {};
class IsLoadedBit : public BitField<uint32_t, bool, kIsLoadedBit, 1> {};
class HasPragmaBit : public BitField<uint32_t, bool, kHasPragmaBit, 1> {};
class ImplementsFinalizableBit
: public BitField<uint32_t, bool, kImplementsFinalizableBit, 1> {};
void set_name(const String& value) const;
void set_user_name(const String& value) const;
@ -1891,6 +1898,12 @@ class Class : public Object {
bool has_pragma() const { return HasPragmaBit::decode(state_bits()); }
void set_has_pragma(bool has_pragma) const;
bool implements_finalizable() const {
ASSERT(is_type_finalized());
return ImplementsFinalizable(ptr());
}
void set_implements_finalizable(bool value) const;
private:
void set_functions(const Array& value) const;
void set_fields(const Array& value) const;
@ -3185,33 +3198,9 @@ class Function : public Object {
// deoptimize, since we won't generate deoptimization info or register
// dependencies. It will be compiled into optimized code immediately when it's
// run.
bool ForceOptimize() const {
return IsFfiFromAddress() || IsFfiGetAddress() || IsFfiLoad() ||
IsFfiStore() || IsFfiTrampoline() || IsFfiAsExternalTypedData() ||
IsTypedDataViewFactory() || IsUtf8Scan() || IsGetNativeField() ||
IsFinalizerForceOptimized();
}
bool ForceOptimize() const;
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 IsFinalizerForceOptimized() const;
bool CanBeInlined() const;
@ -4743,6 +4732,7 @@ class Library : public Object {
void SetName(const String& name) const;
StringPtr url() const { return untag()->url(); }
static StringPtr UrlOf(LibraryPtr lib) { return lib->untag()->url(); }
StringPtr private_key() const { return untag()->private_key(); }
bool LoadNotStarted() const {
return untag()->load_state_ == UntaggedLibrary::kAllocated;
@ -5768,7 +5758,7 @@ class PcDescriptors : public Object {
// pc descriptors table to visit objects if any in the table.
// Note: never return a reference to a UntaggedPcDescriptors::PcDescriptorRec
// as the object can move.
class Iterator : ValueObject {
class Iterator : public ValueObject {
public:
Iterator(const PcDescriptors& descriptors, intptr_t kind_mask)
: descriptors_(descriptors),
@ -9754,7 +9744,7 @@ class String : public Instance {
};
// Synchronize with implementation in compiler (intrinsifier).
class StringHasher : ValueObject {
class StringHasher : public ValueObject {
public:
StringHasher() : hash_(0) {}
void Add(uint16_t code_unit) { hash_ = CombineHashes(hash_, code_unit); }
@ -11326,7 +11316,7 @@ class LinkedHashMap : public LinkedHashBase {
// - There are no checks for concurrent modifications.
// - Accessing a key or value before the first call to MoveNext and after
// MoveNext returns false will result in crashes.
class Iterator : ValueObject {
class Iterator : public ValueObject {
public:
explicit Iterator(const LinkedHashMap& map)
: data_(Array::Handle(map.data())),
@ -11422,7 +11412,7 @@ class LinkedHashSet : public LinkedHashBase {
// - There are no checks for concurrent modifications.
// - Accessing a key or value before the first call to MoveNext and after
// MoveNext returns false will result in crashes.
class Iterator : ValueObject {
class Iterator : public ValueObject {
public:
explicit Iterator(const LinkedHashSet& set)
: data_(Array::Handle(set.data())),
@ -12048,6 +12038,7 @@ class WeakReference : public Instance {
friend class Class;
};
class FinalizerBase;
class FinalizerEntry : public Instance {
public:
ObjectPtr value() const { return untag()->value(); }
@ -12084,11 +12075,31 @@ class FinalizerEntry : public Instance {
return OFFSET_OF(UntaggedFinalizerEntry, next_);
}
intptr_t external_size() const { return untag()->external_size(); }
void set_external_size(intptr_t value) const {
untag()->set_external_size(value);
}
static intptr_t external_size_offset() {
return OFFSET_OF(UntaggedFinalizerEntry, external_size_);
}
static intptr_t InstanceSize() {
return RoundedAllocationSize(sizeof(UntaggedFinalizerEntry));
}
static FinalizerEntryPtr New(Heap::Space space = Heap::kNew);
// Allocates a new FinalizerEntry, initializing the external size (to 0) and
// finalizer.
//
// Should only be used for object tests.
//
// Does not initialize `value`, `token`, and `detach` to allow for flexible
// testing code setting those manually.
//
// Does _not_ add the entry to the finalizer. We could add the entry to
// finalizer.all_entries.data, but we have no way of initializing the hashset
// index.
static FinalizerEntryPtr New(const FinalizerBase& finalizer,
Heap::Space space = Heap::kNew);
private:
FINAL_HEAP_OBJECT_IMPLEMENTATION(FinalizerEntry, Instance);
@ -12108,6 +12119,9 @@ class FinalizerBase : public Instance {
}
LinkedHashSetPtr all_entries() const { return untag()->all_entries(); }
void set_all_entries(const LinkedHashSet& value) const {
untag()->set_all_entries(value.ptr());
}
static intptr_t all_entries_offset() {
return OFFSET_OF(UntaggedFinalizerBase, all_entries_);
}
@ -12149,6 +12163,32 @@ class Finalizer : public FinalizerBase {
friend class Class;
};
class NativeFinalizer : public FinalizerBase {
public:
typedef void (*Callback)(void*);
PointerPtr callback() const { return untag()->callback(); }
void set_callback(const Pointer& value) const {
untag()->set_callback(value.ptr());
}
static intptr_t callback_offset() {
return OFFSET_OF(UntaggedNativeFinalizer, callback_);
}
static intptr_t InstanceSize() {
return RoundedAllocationSize(sizeof(UntaggedNativeFinalizer));
}
static NativeFinalizerPtr New(Heap::Space space = Heap::kNew);
void RunCallback(const FinalizerEntry& entry,
const char* trace_context) const;
private:
FINAL_HEAP_OBJECT_IMPLEMENTATION(NativeFinalizer, FinalizerBase);
friend class Class;
};
class MirrorReference : public Instance {
public:
ObjectPtr referent() const { return untag()->referent(); }

View file

@ -38,6 +38,7 @@
V(Finalizer) \
V(FinalizerBase) \
V(FinalizerEntry) \
V(NativeFinalizer) \
V(Function) \
V(FunctionType) \
V(FutureOr) \
@ -588,13 +589,23 @@ class ObjectCopyBase {
Class::Handle(class_table_->At(cid)).ToCString());
return false;
}
const bool implements_finalizable =
Class::ImplementsFinalizable(class_table_->At(cid));
if (implements_finalizable) {
exception_msg_ = OS::SCreate(
zone_,
"Illegal argument in isolate message: (object implements "
"Finalizable - %s)",
Class::Handle(class_table_->At(cid)).ToCString());
return false;
}
return true;
}
#define HANDLE_ILLEGAL_CASE(Type) \
case k##Type##Cid: { \
exception_msg_ = \
"Illegal argument in isolate message: " \
"(object is a" #Type ")"; \
"(object is a " #Type ")"; \
return false; \
}
@ -604,6 +615,7 @@ class ObjectCopyBase {
// here that cannot happen in reality)
HANDLE_ILLEGAL_CASE(DynamicLibrary)
HANDLE_ILLEGAL_CASE(Finalizer)
HANDLE_ILLEGAL_CASE(NativeFinalizer)
HANDLE_ILLEGAL_CASE(MirrorReference)
HANDLE_ILLEGAL_CASE(Pointer)
HANDLE_ILLEGAL_CASE(ReceivePort)

View file

@ -1719,6 +1719,21 @@ void Finalizer::PrintJSONImpl(JSONStream* stream, bool ref) const {
// Not exposing entries.
}
void NativeFinalizer::PrintJSONImpl(JSONStream* stream, bool ref) const {
JSONObject jsobj(stream);
PrintSharedInstanceJSON(&jsobj, ref);
jsobj.AddProperty("kind", "NativeFinalizer");
jsobj.AddServiceId(*this);
if (ref) {
return;
}
const Object& finalizer_callback = Object::Handle(callback());
jsobj.AddProperty("callback_address", finalizer_callback);
// Not exposing entries.
}
void FinalizerEntry::PrintJSONImpl(JSONStream* stream, bool ref) const {
UNREACHABLE();
}

View file

@ -486,6 +486,15 @@ void ObjectStore::LazyInitFfiMembers() {
cls.LookupFunctionAllowPrivate(Symbols::_handleFinalizerMessage());
ASSERT(!function.IsNull());
handle_finalizer_message_function_.store(function.ptr());
cls = native_finalizer_class();
ASSERT(!cls.IsNull());
error = cls.EnsureIsFinalized(thread);
ASSERT(error.IsNull());
function = cls.LookupFunctionAllowPrivate(
Symbols::_handleNativeFinalizerMessage());
ASSERT(!function.IsNull());
handle_native_finalizer_message_function_.store(function.ptr());
}
}

View file

@ -56,6 +56,7 @@ class ObjectPointerVisitor;
LAZY_INTERNAL(Class, symbol_class) \
LAZY_INTERNAL(Field, symbol_name_field) \
LAZY_FFI(Function, handle_finalizer_message_function) \
LAZY_FFI(Function, handle_native_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) \
@ -129,7 +130,7 @@ class ObjectPointerVisitor;
RW(Class, weak_reference_class) \
RW(Class, finalizer_class) \
RW(Class, finalizer_entry_class) \
RW(Class, finalizer_native_class) \
RW(Class, native_finalizer_class) \
ARW_AR(Array, symbol_table) \
RW(Array, canonical_types) \
RW(Array, canonical_function_types) \

View file

@ -31,6 +31,7 @@
#include "vm/resolver.h"
#include "vm/simulator.h"
#include "vm/symbols.h"
#include "vm/tagged_pointer.h"
#include "vm/unit_test.h"
namespace dart {
@ -4031,8 +4032,8 @@ static void Finalizer_PreserveOne(Thread* thread,
const auto& finalizer = Finalizer::Handle(Finalizer::New(space));
finalizer.set_isolate(thread->isolate());
const auto& entry = FinalizerEntry::Handle(FinalizerEntry::New(space));
entry.set_finalizer(finalizer);
const auto& entry =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, space));
const auto& value = String::Handle(OneByteString::New("value", space));
entry.set_value(value);
auto& detach = Object::Handle();
@ -4097,8 +4098,8 @@ static void Finalizer_ClearDetachOne(Thread* thread, Heap::Space space) {
const auto& finalizer = Finalizer::Handle(Finalizer::New(space));
finalizer.set_isolate(thread->isolate());
const auto& entry = FinalizerEntry::Handle(FinalizerEntry::New(space));
entry.set_finalizer(finalizer);
const auto& entry =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, space));
const auto& value = String::Handle(OneByteString::New("value", space));
entry.set_value(value);
const auto& token = String::Handle(OneByteString::New("token", space));
@ -4156,8 +4157,8 @@ static void Finalizer_ClearValueOne(Thread* thread,
const auto& finalizer = Finalizer::Handle(Finalizer::New(space));
finalizer.set_isolate(thread->isolate());
const auto& entry = FinalizerEntry::Handle(FinalizerEntry::New(space));
entry.set_finalizer(finalizer);
const auto& entry =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, space));
const auto& detach = String::Handle(OneByteString::New("detach", space));
auto& token = Object::Handle();
if (null_token) {
@ -4228,8 +4229,8 @@ static void Finalizer_DetachOne(Thread* thread,
const auto& finalizer = Finalizer::Handle(Finalizer::New(space));
finalizer.set_isolate(thread->isolate());
const auto& entry = FinalizerEntry::Handle(FinalizerEntry::New(space));
entry.set_finalizer(finalizer);
const auto& entry =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, space));
const auto& detach = String::Handle(OneByteString::New("detach", space));
entry.set_detach(detach);
@ -4303,8 +4304,8 @@ static void Finalizer_GcFinalizer(Thread* thread, Heap::Space space) {
HANDLESCOPE(thread);
const auto& finalizer = Finalizer::Handle(Finalizer::New(space));
finalizer.set_isolate(thread->isolate());
const auto& entry = FinalizerEntry::Handle(FinalizerEntry::New(space));
entry.set_finalizer(finalizer);
const auto& entry =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, space));
entry.set_detach(detach);
entry.set_token(token);
const auto& value = String::Handle(OneByteString::New("value", space));
@ -4358,10 +4359,10 @@ static void Finalizer_TwoEntriesCrossGen(
const auto& finalizer = Finalizer::Handle(Finalizer::New(spaces[0]));
finalizer.set_isolate(thread->isolate());
const auto& entry1 = FinalizerEntry::Handle(FinalizerEntry::New(spaces[1]));
entry1.set_finalizer(finalizer);
const auto& entry2 = FinalizerEntry::Handle(FinalizerEntry::New(spaces[2]));
entry2.set_finalizer(finalizer);
const auto& entry1 =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, spaces[1]));
const auto& entry2 =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, spaces[2]));
auto& value1 = String::Handle();
auto& detach1 = String::Handle();
@ -5040,6 +5041,241 @@ static void Finalizer_TwoEntriesCrossGen(Thread* thread, intptr_t test_i) {
REPEAT_512(FINALIZER_CROSS_GEN_TEST_CASE)
#undef FINALIZER_CROSS_GEN_TEST_CASE
void NativeFinalizer_TwoEntriesCrossGen_Finalizer(intptr_t* token) {
(*token)++;
}
static void NativeFinalizer_TwoEntriesCrossGen(
Thread* thread,
Heap::Space* spaces,
bool collect_new_space,
bool evacuate_new_space_and_collect_old_space,
bool clear_value_1,
bool clear_value_2,
bool clear_detach_1,
bool clear_detach_2) {
#ifdef DEBUG
SetFlagScope<bool> sfs(&FLAG_trace_finalizers, true);
#endif
intptr_t token1_memory = 0;
intptr_t token2_memory = 0;
MessageHandler* handler = thread->isolate()->message_handler();
// We're reusing the isolate in a loop, so there are messages from previous
// runs of this test.
intptr_t queue_length_start = 0;
{
MessageHandler::AcquiredQueues aq(handler);
queue_length_start = aq.queue()->Length();
}
ObjectStore* object_store = thread->isolate_group()->object_store();
const auto& void_type = Type::Handle(object_store->never_type());
const auto& callback = Pointer::Handle(Pointer::New(
void_type,
reinterpret_cast<uword>(&NativeFinalizer_TwoEntriesCrossGen_Finalizer),
spaces[3]));
const auto& finalizer =
NativeFinalizer::Handle(NativeFinalizer::New(spaces[0]));
finalizer.set_callback(callback);
finalizer.set_isolate(thread->isolate());
const auto& isolate_finalizers =
GrowableObjectArray::Handle(GrowableObjectArray::New());
const auto& weak1 = WeakReference::Handle(WeakReference::New());
weak1.set_target(finalizer);
isolate_finalizers.Add(weak1);
thread->isolate()->set_finalizers(isolate_finalizers);
const auto& all_entries = LinkedHashSet::Handle(LinkedHashSet::NewDefault());
finalizer.set_all_entries(all_entries);
const auto& all_entries_data = Array::Handle(all_entries.data());
THR_Print("entry1 space: %s\n", spaces[1] == Heap::kNew ? "new" : "old");
const auto& entry1 =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, spaces[1]));
all_entries_data.SetAt(0, entry1);
THR_Print("entry2 space: %s\n", spaces[2] == Heap::kNew ? "new" : "old");
const auto& entry2 =
FinalizerEntry::Handle(FinalizerEntry::New(finalizer, spaces[2]));
all_entries_data.SetAt(1, entry2);
all_entries.set_used_data(2); // Don't bother setting the index.
const intptr_t external_size1 = 1024;
const intptr_t external_size2 = 2048;
entry1.set_external_size(external_size1);
entry2.set_external_size(external_size2);
IsolateGroup::Current()->heap()->AllocatedExternal(external_size1, spaces[5]);
IsolateGroup::Current()->heap()->AllocatedExternal(external_size2, spaces[7]);
auto& value1 = String::Handle();
auto& detach1 = String::Handle();
const auto& token1 = Pointer::Handle(Pointer::New(
void_type, reinterpret_cast<uword>(&token1_memory), spaces[3]));
entry1.set_token(token1);
auto& value2 = String::Handle();
auto& detach2 = String::Handle();
const auto& token2 = Pointer::Handle(Pointer::New(
void_type, reinterpret_cast<uword>(&token2_memory), spaces[4]));
entry2.set_token(token2);
entry2.set_detach(detach2);
{
HANDLESCOPE(thread);
auto& object = String::Handle();
THR_Print("value1 space: %s\n", spaces[5] == Heap::kNew ? "new" : "old");
object ^= OneByteString::New("value1", spaces[5]);
entry1.set_value(object);
if (!clear_value_1) {
value1 = object.ptr();
}
object ^= OneByteString::New("detach", spaces[6]);
entry1.set_detach(object);
if (!clear_detach_1) {
detach1 = object.ptr();
}
THR_Print("value2 space: %s\n", spaces[7] == Heap::kNew ? "new" : "old");
object ^= OneByteString::New("value2", spaces[7]);
entry2.set_value(object);
if (!clear_value_2) {
value2 = object.ptr();
}
object ^= OneByteString::New("detach", spaces[8]);
entry2.set_detach(object);
if (!clear_detach_2) {
detach2 = object.ptr();
}
}
THR_Print("CollectOldSpace\n");
GCTestHelper::CollectOldSpace();
if (collect_new_space) {
THR_Print("CollectNewSpace\n");
GCTestHelper::CollectNewSpace();
}
if (evacuate_new_space_and_collect_old_space) {
THR_Print("CollectAllGarbage\n");
GCTestHelper::CollectAllGarbage();
}
EXPECT((entry1.value() == Object::null()) ^ !clear_value_1);
EXPECT((entry2.value() == Object::null()) ^ !clear_value_2);
EXPECT((entry1.detach() == Object::null()) ^ !clear_detach_1);
EXPECT((entry2.detach() == Object::null()) ^ !clear_detach_2);
EXPECT_NE(Object::null(), entry1.token());
EXPECT_NE(Object::null(), entry2.token());
const intptr_t expect_num_cleared =
(clear_value_1 ? 1 : 0) + (clear_value_2 ? 1 : 0);
EXPECT_EQ(expect_num_cleared,
NumEntries(FinalizerEntry::Handle(finalizer.entries_collected())));
EXPECT_EQ(clear_value_1 ? 1 : 0, token1_memory);
EXPECT_EQ(clear_value_2 ? 1 : 0, token2_memory);
const intptr_t expect_num_messages = expect_num_cleared == 0 ? 0 : 1;
{
// Acquire ownership of message handler queues.
MessageHandler::AcquiredQueues aq(handler);
EXPECT_EQ(expect_num_messages + queue_length_start, aq.queue()->Length());
}
// Simulate detachments.
entry1.set_token(entry1);
entry2.set_token(entry2);
all_entries_data.SetAt(0, Object::Handle(Object::null()));
all_entries_data.SetAt(1, Object::Handle(Object::null()));
all_entries.set_used_data(0);
}
static void NativeFinalizer_TwoEntriesCrossGen(Thread* thread,
intptr_t test_i) {
ASSERT(test_i < (1 << kFinalizerTwoEntriesNumObjects));
Heap::Space spaces[kFinalizerTwoEntriesNumObjects];
for (intptr_t i = 0; i < kFinalizerTwoEntriesNumObjects; i++) {
spaces[i] = ((test_i >> i) & 0x1) == 0x1 ? Heap::kOld : Heap::kNew;
}
// Either collect or evacuate new space.
for (const bool collect_new_space : {true, false}) {
// Always run old space collection after new space.
const bool evacuate_new_space_and_collect_old_space = true;
const bool clear_value_1 = true;
const bool clear_value_2 = true;
const bool clear_detach_1 = false;
const bool clear_detach_2 = false;
THR_Print(
"collect_new_space: %s evacuate_new_space_and_collect_old_space: %s\n",
collect_new_space ? "true" : "false",
evacuate_new_space_and_collect_old_space ? "true" : "false");
NativeFinalizer_TwoEntriesCrossGen(thread, spaces, collect_new_space,
evacuate_new_space_and_collect_old_space,
clear_value_1, clear_value_2,
clear_detach_1, clear_detach_2);
}
}
#define FINALIZER_NATIVE_CROSS_GEN_TEST_CASE(n) \
ISOLATE_UNIT_TEST_CASE(NativeFinalizer_CrossGen_##n) { \
NativeFinalizer_TwoEntriesCrossGen(thread, n); \
}
REPEAT_512(FINALIZER_NATIVE_CROSS_GEN_TEST_CASE)
#undef FINALIZER_NATIVE_CROSS_GEN_TEST_CASE
#undef REPEAT_512
static ClassPtr GetClass(const Library& lib, const char* name) {
const Class& cls = Class::Handle(
lib.LookupClass(String::Handle(Symbols::New(Thread::Current(), name))));
EXPECT(!cls.IsNull()); // No ambiguity error expected.
return cls.ptr();
}
TEST_CASE(ImplementsFinalizable) {
Zone* const zone = Thread::Current()->zone();
const char* kScript = R"(
import 'dart:ffi';
class AImpl implements A {}
class ASub extends A {}
// Wonky class order and non-alhpabetic naming on purpose.
class C extends Z {}
class E extends D {}
class A implements Finalizable {}
class Z implements A {}
class D implements C {}
class X extends E {}
)";
Dart_Handle h_lib = TestCase::LoadTestScript(kScript, nullptr);
EXPECT_VALID(h_lib);
TransitionNativeToVM transition(thread);
const Library& lib = Library::CheckedHandle(zone, Api::UnwrapHandle(h_lib));
EXPECT(!lib.IsNull());
const auto& class_x = Class::Handle(zone, GetClass(lib, "X"));
ClassFinalizer::FinalizeTypesInClass(class_x);
EXPECT(class_x.implements_finalizable());
const auto& class_a_impl = Class::Handle(zone, GetClass(lib, "AImpl"));
ClassFinalizer::FinalizeTypesInClass(class_a_impl);
EXPECT(class_a_impl.implements_finalizable());
const auto& class_a_sub = Class::Handle(zone, GetClass(lib, "ASub"));
ClassFinalizer::FinalizeTypesInClass(class_a_sub);
EXPECT(class_a_sub.implements_finalizable());
}
ISOLATE_UNIT_TEST_CASE(MirrorReference) {
const MirrorReference& reference =
MirrorReference::Handle(MirrorReference::New(Object::Handle()));
@ -5098,13 +5334,6 @@ static FieldPtr GetField(const Class& cls, const char* name) {
return field.ptr();
}
static ClassPtr GetClass(const Library& lib, const char* name) {
const Class& cls = Class::Handle(
lib.LookupClass(String::Handle(Symbols::New(Thread::Current(), name))));
EXPECT(!cls.IsNull()); // No ambiguity error expected.
return cls.ptr();
}
ISOLATE_UNIT_TEST_CASE(FindClosureIndex) {
// Allocate the class first.
const String& class_name = String::Handle(Symbols::New(thread, "MyClass"));

View file

@ -554,6 +554,7 @@ COMPRESSED_VISITOR(WeakProperty)
COMPRESSED_VISITOR(WeakReference)
COMPRESSED_VISITOR(Finalizer)
COMPRESSED_VISITOR(FinalizerEntry)
COMPRESSED_VISITOR(NativeFinalizer)
COMPRESSED_VISITOR(MirrorReference)
COMPRESSED_VISITOR(UserTag)
REGULAR_VISITOR(SubtypeTestCache)

View file

@ -2846,6 +2846,9 @@ class UntaggedTwoByteString : public UntaggedString {
// TypedData extends this with a length field, while Pointer extends this with
// TypeArguments field.
class UntaggedPointerBase : public UntaggedInstance {
public:
uint8_t* data() { return data_; }
protected:
// The contents of [data_] depends on what concrete subclass is used:
//
@ -3391,7 +3394,26 @@ class UntaggedFinalizer : public UntaggedFinalizerBase {
friend class ScavengerVisitorBase;
};
class UntaggedNativeFinalizer : public UntaggedFinalizerBase {
RAW_HEAP_OBJECT_IMPLEMENTATION(NativeFinalizer);
COMPRESSED_POINTER_FIELD(PointerPtr, callback)
VISIT_TO(callback)
friend class GCMarker;
template <bool>
friend class MarkingVisitorBase;
friend class Scavenger;
template <bool>
friend class ScavengerVisitorBase;
};
class UntaggedFinalizerEntry : public UntaggedInstance {
public:
intptr_t external_size() { return external_size_; }
void set_external_size(intptr_t value) { external_size_ = value; }
private:
RAW_HEAP_OBJECT_IMPLEMENTATION(FinalizerEntry);
COMPRESSED_POINTER_FIELD(ObjectPtr, value) // Weak reference.
@ -3409,6 +3431,8 @@ class UntaggedFinalizerEntry : public UntaggedInstance {
// Only populated during the GC, otherwise null.
COMPRESSED_POINTER_FIELD(FinalizerEntryPtr, next_seen_by_gc)
intptr_t external_size_;
template <typename Type, typename PtrType>
friend class GCLinkedList;
template <typename GCVisitorType>
@ -3419,6 +3443,7 @@ class UntaggedFinalizerEntry : public UntaggedInstance {
friend class Scavenger;
template <bool>
friend class ScavengerVisitorBase;
friend class ScavengerFinalizerVisitor;
};
// MirrorReferences are used by mirrors to hold reflectees that are VM

View file

@ -137,6 +137,7 @@ class ObjectPointerVisitor;
V(FfiVoid, "Void") \
V(FfiHandle, "Handle") \
V(Field, "Field") \
V(Finalizable, "Finalizable") \
V(FinalizerBase, "FinalizerBase") \
V(FinalizerEntry, "FinalizerEntry") \
V(FinallyRetVal, ":finally_ret_val") \
@ -311,6 +312,7 @@ class ObjectPointerVisitor;
V(_ExternalUint8Array, "_ExternalUint8Array") \
V(_ExternalUint8ClampedArray, "_ExternalUint8ClampedArray") \
V(_FinalizerImpl, "_FinalizerImpl") \
V(_NativeFinalizer, "_NativeFinalizer") \
V(_Float32ArrayFactory, "Float32List.") \
V(_Float32ArrayView, "_Float32ArrayView") \
V(_Float32List, "_Float32List") \
@ -411,6 +413,7 @@ class ObjectPointerVisitor;
V(_future, "_future") \
V(_handleMessage, "_handleMessage") \
V(_handleFinalizerMessage, "_handleFinalizerMessage") \
V(_handleNativeFinalizerMessage, "_handleNativeFinalizerMessage") \
V(_instanceOf, "_instanceOf") \
V(_listGetAt, "_listGetAt") \
V(_listLength, "_listLength") \

View file

@ -416,6 +416,7 @@ DEFINE_TAGGED_POINTER(WeakReference, Instance)
DEFINE_TAGGED_POINTER(FinalizerBase, Instance)
DEFINE_TAGGED_POINTER(Finalizer, Instance)
DEFINE_TAGGED_POINTER(FinalizerEntry, Instance)
DEFINE_TAGGED_POINTER(NativeFinalizer, Instance)
DEFINE_TAGGED_POINTER(MirrorReference, Instance)
DEFINE_TAGGED_POINTER(UserTag, Instance)
DEFINE_TAGGED_POINTER(FutureOr, Instance)

View file

@ -2303,7 +2303,7 @@ ISOLATE_UNIT_TEST_CASE(TTS_Partial_Reload) {
// during the checks that the instantiation of Y is int.
ISOLATE_UNIT_TEST_CASE(TTS_Regress_CidRangeChecks) {
// Bump this appropriately if the EXPECT_EQ below fails.
const intptr_t kNumUnrelated = 1183;
const intptr_t kNumUnrelated = 1185;
TextBuffer buffer(1024);
buffer.AddString(R"(
abstract class B<X> {}

View file

@ -4,9 +4,9 @@
// All imports must be in all FFI patch files to not depend on the order
// the patches are applied.
import "dart:_internal" show patch;
import 'dart:typed_data';
import 'dart:_internal';
import 'dart:isolate';
import 'dart:typed_data';
extension AllocatorAlloc on Allocator {
// TODO(http://dartbug.com/39964): Add `alignmentOf<T>()` call.

View file

@ -4,9 +4,9 @@
// All imports must be in all FFI patch files to not depend on the order
// the patches are applied.
import "dart:_internal" show patch;
import 'dart:typed_data';
import 'dart:_internal';
import 'dart:isolate';
import 'dart:typed_data';
@pragma("vm:external-name", "Ffi_dl_open")
external DynamicLibrary _open(String path);

View file

@ -8,5 +8,96 @@ import 'dart:_internal';
import 'dart:isolate';
import 'dart:typed_data';
// This is a placeholder file which will shortly contain a NativeFinalizer
// implementation.
@patch
@pragma("vm:entry-point")
abstract class Finalizable {}
@patch
@pragma("vm:entry-point")
abstract class NativeFinalizer {
@patch
factory NativeFinalizer(Pointer<NativeFinalizerFunction> callback) =
_NativeFinalizer;
}
@pragma("vm:entry-point")
class _NativeFinalizer extends FinalizerBase implements NativeFinalizer {
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external Pointer<NativeFinalizerFunction> get _callback;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set _callback(Pointer<NativeFinalizerFunction> value);
_NativeFinalizer(Pointer<NativeFinalizerFunction> callback) {
allEntries = <FinalizerEntry>{};
_callback = callback;
setIsolate();
isolateRegisterFinalizer();
}
void attach(
Finalizable value,
Pointer<Void> token, {
Object? detach,
int? externalSize,
}) {
externalSize ??= 0;
RangeError.checkNotNegative(externalSize, 'externalSize');
if (value == null) {
throw ArgumentError.value(value, 'value', "Cannot be a null");
}
if (detach != null) {
checkValidWeakTarget(detach, 'detach');
}
final entry = FinalizerEntry.allocate(value, token, detach, this);
allEntries.add(entry);
if (externalSize > 0) {
entry.setExternalSize(externalSize);
}
if (detach != null) {
(detachments[detach] ??= <FinalizerEntry>{}).add(entry);
}
// The `value` stays reachable till here because the static type is
// `Finalizable`.
}
@override
void detach(Object detach) {
final entries = detachments[detach];
if (entries != null) {
for (final entry in entries) {
entry.token = entry;
final externalSize = entry.externalSize;
if (externalSize > 0) {
entry.setExternalSize(0);
assert(entry.externalSize == 0);
}
allEntries.remove(entry);
}
detachments[detach] = null;
}
}
void _removeEntries() {
FinalizerEntry? entry = exchangeEntriesCollectedWithNull();
while (entry != null) {
assert(entry.externalSize == 0);
allEntries.remove(entry);
final detach = entry.detach;
if (detach != null) {
detachments[detach]?.remove(entry);
}
entry = entry.next;
}
}
@pragma("vm:entry-point", "call")
static _handleNativeFinalizerMessage(_NativeFinalizer finalizer) {
finalizer._removeEntries();
}
}

View file

@ -4,9 +4,9 @@
// All imports must be in all FFI patch files to not depend on the order
// the patches are applied.
import "dart:_internal" show patch;
import 'dart:typed_data';
import 'dart:_internal';
import 'dart:isolate';
import 'dart:typed_data';
// NativeType is not private, because it is used in type arguments.
// NativeType is abstract because it not used with const constructors in

View file

@ -4,9 +4,9 @@
// All imports must be in all FFI patch files to not depend on the order
// the patches are applied.
import "dart:_internal" show patch, has63BitSmis;
import 'dart:typed_data';
import 'dart:_internal';
import 'dart:isolate';
import 'dart:typed_data';
const Map<Type, int> _knownSizes = {
Int8: 1,

View file

@ -4,9 +4,9 @@
// All imports must be in all FFI patch files to not depend on the order
// the patches are applied.
import "dart:_internal" show patch;
import 'dart:typed_data';
import 'dart:_internal';
import 'dart:isolate';
import 'dart:typed_data';
@pragma("vm:entry-point")
abstract class _Compound extends NativeType {}

View file

@ -43,15 +43,7 @@ class _FinalizerImpl<T> extends FinalizerBase implements Finalizer<T> {
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;
final entry = FinalizerEntry.allocate(value, token, detach, this);
allEntries.add(entry);
// Ensure value stays reachable until after having initialized the entry.
// This ensures the token and finalizer are set.

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:ffi" show Pointer, Struct, Union, IntPtr, Handle, Void, FfiNative;
import "dart:isolate" show SendPort;
import "dart:typed_data" show Int32List, Uint8List;
@ -384,15 +384,18 @@ extension FinalizerBaseMembers on FinalizerBase {
/// linked list of pending entries while running the GC.
@pragma("vm:entry-point")
class FinalizerEntry {
@pragma('vm:never-inline')
@pragma("vm:recognized", "other")
@pragma("vm:external-name", "FinalizerEntry_allocate")
external static FinalizerEntry allocate(
Object value, Object? token, Object? detach, FinalizerBase finalizer);
/// 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.
@ -401,9 +404,6 @@ class FinalizerEntry {
@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")
@ -413,13 +413,6 @@ class FinalizerEntry {
@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
@ -427,7 +420,12 @@ class FinalizerEntry {
@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);
external int get externalSize;
/// Update the external size.
@FfiNative<Void Function(Handle, IntPtr)>('FinalizerEntry_SetExternalSize')
external void setExternalSize(int externalSize);
}

View file

@ -221,3 +221,77 @@ part of dart.ffi;
abstract class Finalizable {
factory Finalizable._() => throw UnsupportedError("");
}
/// The native function type for [NativeFinalizer]s.
///
/// A [NativeFinalizer]'s `callback` should have the C
/// `void nativeFinalizer(void* token)` type.
typedef NativeFinalizerFunction
= NativeFunction<Void Function(Pointer<Void> token)>;
/// A native finalizer which can be attached to Dart objects.
///
/// When [attach]ed to a Dart object, this finalizer's native callback is called
/// after the Dart object is garbage collected or becomes inaccessible for other
/// reasons.
///
/// Callbacks will happen as early as possible, when the object becomes
/// inaccessible to the program, and may happen at any moment during execution
/// of the program. At the latest, when an isolate group shuts down,
/// this callback is guaranteed to be called for each object in that isolate
/// group that the finalizer is still attached to.
///
/// Compared to the [Finalizer] from `dart:core`, which makes no promises to
/// ever call an attached callback, this native finalizer promises that all
/// attached finalizers are definitely called at least once before the program
/// ends, and the callbacks are called as soon as possible after an object
/// is recognized as inaccessible.
abstract class NativeFinalizer {
/// Creates a finalizer with the given finalization callback.
///
/// The [callback] must be a native function which can be executed outside of
/// a Dart isolate. This means that passing an FFI trampoline (a function
/// pointer obtained via [Pointer.fromFunction]) is not supported.
///
/// The [callback] might be invoked on an arbitrary thread and not necessary
/// on the same thread that created [NativeFinalizer].
// TODO(https://dartbug.com/47778): Implement isolate independent code and
// update the above comment.
external factory NativeFinalizer(Pointer<NativeFinalizerFunction> callback);
/// Attaches this finalizer to [value].
///
/// When [value] is no longer accessible to the program,
/// the finalizer will call its callback function with [token]
/// as argument.
///
/// If a non-`null` [detach] value is provided, that object can be
/// passed to [Finalizer.detach] to remove the attachment again.
///
/// The [value] and [detach] arguments do not count towards those
/// objects being accessible to the program. Both must be objects supported
/// as an [Expando] key. They may be the *same* object.
///
/// Multiple objects may be using the same finalization token,
/// and the finalizer can be attached multiple times to the same object
/// with different, or the same, finalization token.
///
/// The callback will be called exactly once per attachment, except for
/// registrations which have been detached since they were attached.
///
/// The [externalSize] should represent the amount of native (non-Dart) memory
/// owned by the given [value]. This information is used for garbage
/// collection scheduling heuristics.
void attach(Finalizable value, Pointer<Void> token,
{Object? detach, int? externalSize});
/// Detaches this finalizer from values attached with [detach].
///
/// If this finalizer was attached multiple times to the same object with
/// different detachment keys, only those attachments which used [detach]
/// are removed.
///
/// After detaching, an attachment won't cause any callbacks to happen if the
/// object become inaccessible.
void detach(Object detach);
}

View file

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

View file

@ -20,6 +20,7 @@ regress_47594_test: Skip # Profiler is not available in Product.
[ $system == android ]
*: Pass, Slow # https://github.com/dart-lang/sdk/issues/38489
regress_47594_test: Skip # DartDev is not available on Android.
vmspecific_native_finalizer_isolate_groups_test: Skip # SpawnUri not available on Android tester.
[ $system == windows ]
regress_47594_test: Skip # DynamicLibrary.process() is not available on Windows.

View file

@ -4,6 +4,8 @@
//
// Helpers for tests which trigger GC in delicate places.
// ignore: import_internal_library, unused_import
import 'dart:_internal';
import 'dart:ffi';
import 'dylib_utils.dart';
@ -17,12 +19,45 @@ typedef UnaryOpVoid = void Function(int);
final DynamicLibrary ffiTestFunctions =
dlopenPlatformSpecific("ffi_test_functions");
final triggerGc = ffiTestFunctions
.lookupFunction<NativeNullaryOp, NullaryOpVoid>("TriggerGC");
final collectOnNthAllocation = ffiTestFunctions
.lookupFunction<NativeUnaryOp, UnaryOpVoid>("CollectOnNthAllocation");
extension PointerOffsetBy<T extends NativeType> on Pointer<T> {
Pointer<T> offsetBy(int bytes) => Pointer.fromAddress(address + bytes);
}
/// 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');
}
void createAndLoseFinalizable(Pointer<IntPtr> token) {
final myFinalizable = MyFinalizable();
setTokenFinalizer.attach(myFinalizable, token.cast());
}
final setTokenTo42Ptr =
ffiTestFunctions.lookup<NativeFinalizerFunction>("SetArgumentTo42");
final setTokenFinalizer = NativeFinalizer(setTokenTo42Ptr);
class MyFinalizable implements Finalizable {}

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.
//
// SharedObjects=ffi_test_functions
//
// VMOptions=--trace-finalizers
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'dylib_utils.dart';
import 'ffi_test_helpers.dart';
void main() {
testFinalizerRuns();
testFinalizerDetach();
testDoubleDetach();
testDetachNonDetach();
testWrongArguments();
}
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
void testFinalizerRuns() {
using((Arena allocator) {
final token = allocator<IntPtr>();
createAndLoseFinalizable(token);
doGC();
Expect.equals(42, token.value);
});
}
void createAndLoseFinalizable(Pointer<IntPtr> token) {
final myFinalizable = MyFinalizable();
setTokenFinalizer.attach(myFinalizable, token.cast());
Expect.equals(0, token.value);
}
void testFinalizerDetach() {
using((Arena allocator) {
final token = allocator<IntPtr>();
attachAndDetach(token);
doGC();
Expect.equals(0, token.value);
});
}
class Detach {
String identifier;
Detach(this.identifier);
}
void attachAndDetach(Pointer<IntPtr> token) {
final myFinalizable = MyFinalizable();
final detach = Detach('detach 123');
setTokenFinalizer.attach(myFinalizable, token.cast(), detach: detach);
setTokenFinalizer.detach(detach);
Expect.equals(0, token.value);
}
void testDoubleDetach() {
using((Arena allocator) {
final token = allocator<IntPtr>();
final myFinalizable = MyFinalizable();
final detach = Detach('detach 321');
setTokenFinalizer.attach(myFinalizable, token.cast(), detach: detach);
setTokenFinalizer.detach(detach);
setTokenFinalizer.detach(detach);
Expect.equals(0, token.value);
});
}
void testDetachNonDetach() {
final detach = Detach('detach 456');
setTokenFinalizer.detach(detach);
setTokenFinalizer.detach(detach);
}
void testWrongArguments() {
using((Arena allocator) {
final token = allocator<IntPtr>().cast<Void>();
Expect.throws(() {
final myFinalizable = MyFinalizable();
setTokenFinalizer.attach(myFinalizable, token, externalSize: -1024);
});
Expect.throws(() {
final myFinalizable = MyFinalizable();
setTokenFinalizer.attach(myFinalizable, token, detach: 123);
});
});
}

View file

@ -0,0 +1,51 @@
// 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.
//
// SharedObjects=ffi_test_functions
//
// VMOptions=--trace-finalizers
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'ffi_test_helpers.dart';
void main(List<String> args, int? address) async {
if (address != null) {
await mainHelper(args, address);
} else {
await testFinalizerRunsOnIsolateGroupShutdown();
}
}
Future mainHelper(List<String> args, int address) async {
final token = Pointer<IntPtr>.fromAddress(address);
createAndLoseFinalizable(token);
print('Isolate done.');
}
Future<void> testFinalizerRunsOnIsolateGroupShutdown() async {
await using((Arena allocator) async {
final token = allocator<IntPtr>();
Expect.equals(0, token.value);
final portExitMessage = ReceivePort();
await Isolate.spawnUri(
Platform.script,
[],
token.address,
onExit: portExitMessage.sendPort,
);
await portExitMessage.first;
print('Helper isolate has exited.');
Expect.equals(42, token.value);
print('End of test, shutting down.');
});
}

View file

@ -0,0 +1,83 @@
// 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.
//
// SharedObjects=ffi_test_functions
//
// VMOptions=--trace-finalizers
import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'dylib_utils.dart';
import 'ffi_test_helpers.dart';
void main() async {
await testSendAndExitFinalizable();
await testSendAndExitFinalizer();
await testFinalizerRunsOnIsolateShutdown();
print('End of test, shutting down.');
}
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
void runIsolateAttachFinalizer(int address) {
final token = Pointer<IntPtr>.fromAddress(address);
createAndLoseFinalizable(token);
print('Isolate done.');
}
Future<void> testFinalizerRunsOnIsolateShutdown() async {
await using((Arena allocator) async {
final token = allocator<IntPtr>();
Expect.equals(0, token.value);
final portExitMessage = ReceivePort();
await Isolate.spawn(
runIsolateAttachFinalizer,
token.address,
onExit: portExitMessage.sendPort,
);
await portExitMessage.first;
doGC();
Expect.equals(42, token.value);
});
}
Future<void> testSendAndExitFinalizable() async {
final receivePort = ReceivePort();
await Isolate.spawn(
(SendPort sendPort) {
try {
Isolate.exit(sendPort, MyFinalizable());
} catch (e) {
print('Expected exception: $e.');
Isolate.exit(sendPort, e);
}
},
receivePort.sendPort,
);
final result = await receivePort.first;
Expect.type<ArgumentError>(result);
}
Future<void> testSendAndExitFinalizer() async {
final receivePort = ReceivePort();
await Isolate.spawn(
(SendPort sendPort) {
try {
Isolate.exit(sendPort, MyFinalizable());
} catch (e) {
print('Expected exception: $e.');
Isolate.exit(sendPort, e);
}
},
receivePort.sendPort,
);
final result = await receivePort.first;
Expect.type<ArgumentError>(result);
}

View file

@ -0,0 +1,84 @@
// 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.
//
// SharedObjects=ffi_test_functions
import 'dart:ffi';
import 'dart:io';
import 'ffi_test_helpers.dart';
void main() {
testMallocFree();
print('end of test, shutting down');
}
void testMallocFree() {
if (Platform.isWindows) {
// malloc and free not supported.
return;
}
print('freePtr $freePtr');
{
final resource = MyNativeResource();
resource.close();
doGC();
}
{
MyNativeResource();
doGC();
}
// Run finalizer on shutdown (or on a GC that runs before shutdown).
MyNativeResource();
}
class MyNativeResource implements Finalizable {
final Pointer<Void> pointer;
bool _closed = false;
MyNativeResource._(this.pointer, {int? externalSize}) {
print('pointer $pointer');
freeFinalizer.attach(this, pointer,
externalSize: externalSize, detach: this);
}
factory MyNativeResource() {
const num = 1;
const size = 16;
final pointer = calloc(num, size);
return MyNativeResource._(pointer, externalSize: size);
}
/// Eagerly stop using the native resource. Cancelling the finalizer.
void close() {
_closed = true;
freeFinalizer.detach(this);
free(pointer);
}
void useResource() {
if (_closed) {
throw UnsupportedError('The native resource has already been released');
}
print(pointer.address);
}
}
final DynamicLibrary stdlib = DynamicLibrary.process();
typedef PosixCallocNative = Pointer<Void> Function(IntPtr num, IntPtr size);
typedef PosixCalloc = Pointer<Void> Function(int num, int size);
final PosixCalloc calloc =
stdlib.lookupFunction<PosixCallocNative, PosixCalloc>('calloc');
typedef PosixFreeNative = Void Function(Pointer<Void>);
final freePtr = stdlib.lookup<NativeFunction<PosixFreeNative>>('free');
final free = freePtr.asFunction<void Function(Pointer<Void>)>();
final freeFinalizer = NativeFinalizer(freePtr);

View file

@ -880,3 +880,9 @@ class AbiSpecificInteger4
{
const AbiSpecificInteger4();
}
class MyFinalizableStruct extends Struct
implements Finalizable //# 2000: compile-time error
{
external Pointer<Void> field;
}

View file

@ -20,6 +20,7 @@ regress_47594_test: Skip # Profiler is not available in Product.
[ $system == android ]
*: Pass, Slow # https://github.com/dart-lang/sdk/issues/38489
regress_47594_test: Skip # DartDev is not available on Android.
vmspecific_native_finalizer_isolate_groups_test: Skip # SpawnUri not available on Android tester.
[ $system == windows ]
regress_47594_test: Skip # DynamicLibrary.process() is not available on Windows.

View file

@ -6,6 +6,8 @@
// @dart = 2.9
// ignore: import_internal_library, unused_import
import 'dart:_internal';
import 'dart:ffi';
import 'dylib_utils.dart';
@ -19,12 +21,45 @@ typedef UnaryOpVoid = void Function(int);
final DynamicLibrary ffiTestFunctions =
dlopenPlatformSpecific("ffi_test_functions");
final triggerGc = ffiTestFunctions
.lookupFunction<NativeNullaryOp, NullaryOpVoid>("TriggerGC");
final collectOnNthAllocation = ffiTestFunctions
.lookupFunction<NativeUnaryOp, UnaryOpVoid>("CollectOnNthAllocation");
extension PointerOffsetBy<T extends NativeType> on Pointer<T> {
Pointer<T> offsetBy(int bytes) => Pointer.fromAddress(address + bytes);
}
/// 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');
}
void createAndLoseFinalizable(Pointer<IntPtr> token) {
final myFinalizable = MyFinalizable();
setTokenFinalizer.attach(myFinalizable, token.cast());
}
final setTokenTo42Ptr =
ffiTestFunctions.lookup<NativeFinalizerFunction>("SetArgumentTo42");
final setTokenFinalizer = NativeFinalizer(setTokenTo42Ptr);
class MyFinalizable implements Finalizable {}

View file

@ -0,0 +1,97 @@
// 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.
//
// SharedObjects=ffi_test_functions
//
// VMOptions=--trace-finalizers
// @dart = 2.9
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'dylib_utils.dart';
import 'ffi_test_helpers.dart';
void main() {
testFinalizerRuns();
testFinalizerDetach();
testDoubleDetach();
testDetachNonDetach();
testWrongArguments();
}
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
void testFinalizerRuns() {
using((Arena allocator) {
final token = allocator<IntPtr>();
createAndLoseFinalizable(token);
doGC();
Expect.equals(42, token.value);
});
}
void createAndLoseFinalizable(Pointer<IntPtr> token) {
final myFinalizable = MyFinalizable();
setTokenFinalizer.attach(myFinalizable, token.cast());
Expect.equals(0, token.value);
}
void testFinalizerDetach() {
using((Arena allocator) {
final token = allocator<IntPtr>();
attachAndDetach(token);
doGC();
Expect.equals(0, token.value);
});
}
class Detach {
String identifier;
Detach(this.identifier);
}
void attachAndDetach(Pointer<IntPtr> token) {
final myFinalizable = MyFinalizable();
final detach = Detach('detach 123');
setTokenFinalizer.attach(myFinalizable, token.cast(), detach: detach);
setTokenFinalizer.detach(detach);
Expect.equals(0, token.value);
}
void testDoubleDetach() {
using((Arena allocator) {
final token = allocator<IntPtr>();
final myFinalizable = MyFinalizable();
final detach = Detach('detach 321');
setTokenFinalizer.attach(myFinalizable, token.cast(), detach: detach);
setTokenFinalizer.detach(detach);
setTokenFinalizer.detach(detach);
Expect.equals(0, token.value);
});
}
void testDetachNonDetach() {
final detach = Detach('detach 456');
setTokenFinalizer.detach(detach);
setTokenFinalizer.detach(detach);
}
void testWrongArguments() {
using((Arena allocator) {
final token = allocator<IntPtr>().cast<Void>();
Expect.throws(() {
final myFinalizable = MyFinalizable();
setTokenFinalizer.attach(myFinalizable, token, externalSize: -1024);
});
Expect.throws(() {
final myFinalizable = MyFinalizable();
setTokenFinalizer.attach(myFinalizable, token, detach: 123);
});
});
}

View file

@ -0,0 +1,53 @@
// 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.
//
// SharedObjects=ffi_test_functions
//
// VMOptions=--trace-finalizers
// @dart = 2.9
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'ffi_test_helpers.dart';
void main(List<String> args, int address) async {
if (address != null) {
await mainHelper(args, address);
} else {
await testFinalizerRunsOnIsolateGroupShutdown();
}
}
Future mainHelper(List<String> args, int address) async {
final token = Pointer<IntPtr>.fromAddress(address);
createAndLoseFinalizable(token);
print('Isolate done.');
}
Future<void> testFinalizerRunsOnIsolateGroupShutdown() async {
await using((Arena allocator) async {
final token = allocator<IntPtr>();
Expect.equals(0, token.value);
final portExitMessage = ReceivePort();
await Isolate.spawnUri(
Platform.script,
[],
token.address,
onExit: portExitMessage.sendPort,
);
await portExitMessage.first;
print('Helper isolate has exited.');
Expect.equals(42, token.value);
print('End of test, shutting down.');
});
}

View file

@ -0,0 +1,85 @@
// 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.
//
// SharedObjects=ffi_test_functions
//
// VMOptions=--trace-finalizers
// @dart = 2.9
import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'dylib_utils.dart';
import 'ffi_test_helpers.dart';
void main() async {
await testSendAndExitFinalizable();
await testSendAndExitFinalizer();
await testFinalizerRunsOnIsolateShutdown();
print('End of test, shutting down.');
}
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
void runIsolateAttachFinalizer(int address) {
final token = Pointer<IntPtr>.fromAddress(address);
createAndLoseFinalizable(token);
print('Isolate done.');
}
Future<void> testFinalizerRunsOnIsolateShutdown() async {
await using((Arena allocator) async {
final token = allocator<IntPtr>();
Expect.equals(0, token.value);
final portExitMessage = ReceivePort();
await Isolate.spawn(
runIsolateAttachFinalizer,
token.address,
onExit: portExitMessage.sendPort,
);
await portExitMessage.first;
doGC();
Expect.equals(42, token.value);
});
}
Future<void> testSendAndExitFinalizable() async {
final receivePort = ReceivePort();
await Isolate.spawn(
(SendPort sendPort) {
try {
Isolate.exit(sendPort, MyFinalizable());
} catch (e) {
print('Expected exception: $e.');
Isolate.exit(sendPort, e);
}
},
receivePort.sendPort,
);
final result = await receivePort.first;
Expect.type<ArgumentError>(result);
}
Future<void> testSendAndExitFinalizer() async {
final receivePort = ReceivePort();
await Isolate.spawn(
(SendPort sendPort) {
try {
Isolate.exit(sendPort, MyFinalizable());
} catch (e) {
print('Expected exception: $e.');
Isolate.exit(sendPort, e);
}
},
receivePort.sendPort,
);
final result = await receivePort.first;
Expect.type<ArgumentError>(result);
}

View file

@ -0,0 +1,89 @@
// 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.
//
// SharedObjects=ffi_test_functions
// @dart = 2.9
import 'dart:ffi';
import 'dart:io';
import 'dylib_utils.dart';
import 'ffi_test_helpers.dart';
void main() {
testMallocFree();
print('end of test, shutting down');
}
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
void testMallocFree() {
if (Platform.isWindows) {
// malloc and free not supported.
return;
}
print('freePtr $freePtr');
{
final resource = MyNativeResource();
resource.close();
doGC();
}
{
MyNativeResource();
doGC();
}
// Run finalizer on shutdown (or on a GC that runs before shutdown).
MyNativeResource();
}
class MyNativeResource implements Finalizable {
final Pointer<Void> pointer;
bool _closed = false;
MyNativeResource._(this.pointer, {int externalSize}) {
print('pointer $pointer');
freeFinalizer.attach(this, pointer,
externalSize: externalSize, detach: this);
}
factory MyNativeResource() {
const num = 1;
const size = 16;
final pointer = calloc(num, size);
return MyNativeResource._(pointer, externalSize: size);
}
/// Eagerly stop using the native resource. Cancelling the finalizer.
void close() {
_closed = true;
freeFinalizer.detach(this);
free(pointer);
}
void useResource() {
if (_closed) {
throw UnsupportedError('The native resource has already been released');
}
print(pointer.address);
}
}
final DynamicLibrary stdlib = DynamicLibrary.process();
typedef PosixCallocNative = Pointer<Void> Function(IntPtr num, IntPtr size);
typedef PosixCalloc = Pointer<Void> Function(int num, int size);
final PosixCalloc calloc =
stdlib.lookupFunction<PosixCallocNative, PosixCalloc>('calloc');
typedef PosixFreeNative = Void Function(Pointer<Void>);
final freePtr = stdlib.lookup<NativeFunction<PosixFreeNative>>('free');
final free = freePtr.asFunction<void Function(Pointer<Void>)>();
final freeFinalizer = NativeFinalizer(freePtr);

View file

@ -879,3 +879,10 @@ class AbiSpecificInteger4
{
const AbiSpecificInteger4();
}
class MyFinalizableStruct extends Struct
implements Finalizable //# 2000: compile-time error
{
Pointer<Void> field;
}