Files
sdk/pkg/vm/lib/target/vm.dart
T
Daco Harkes 532c116cd2 [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>
2022-03-26 09:41:21 +00:00

516 lines
18 KiB
Dart

// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart';
import 'package:kernel/clone.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/reference_from_index.dart';
import 'package:kernel/target/changed_structure_notifier.dart';
import 'package:kernel/target/targets.dart';
import 'package:kernel/type_environment.dart';
import '../transformations/call_site_annotator.dart' as callSiteAnnotator;
import '../transformations/continuation.dart' as transformAsync
show transformLibraries, transformProcedure;
import '../transformations/lowering.dart' as lowering
show transformLibraries, transformProcedure;
import '../transformations/mixin_full_resolution.dart' as transformMixins
show transformLibraries;
import '../transformations/ffi/common.dart' as ffiHelper
show calculateTransitiveImportsOfDartFfiIfUsed;
import '../transformations/ffi/definitions.dart' as transformFfiDefinitions
show transformLibraries;
import '../transformations/ffi/native.dart' as transformFfiNative
show transformLibraries;
import '../transformations/ffi/use_sites.dart' as transformFfiUseSites
show transformLibraries;
/// Specializes the kernel IR to the Dart VM.
class VmTarget extends Target {
final TargetFlags flags;
Class? _growableList;
Class? _immutableList;
Class? _internalImmutableLinkedHashMap;
Class? _internalImmutableLinkedHashSet;
Class? _internalLinkedHashMap;
Class? _internalLinkedHashSet;
Class? _oneByteString;
Class? _twoByteString;
Class? _smi;
Class? _double; // _Double, not double.
VmTarget(this.flags);
@override
bool get enableNoSuchMethodForwarders => true;
@override
bool get supportsSetLiterals => false;
@override
int get enabledLateLowerings => LateLowering.none;
@override
bool get supportsLateLoweringSentinel => false;
@override
bool get useStaticFieldLowering => false;
@override
bool get supportsExplicitGetterCalls => true;
@override
int get enabledConstructorTearOffLowerings =>
ConstructorTearOffLowering.typedefs;
@override
String get name => 'vm';
// This is the order that bootstrap libraries are loaded according to
// `runtime/vm/object_store.h`.
@override
List<String> get extraRequiredLibraries => const <String>[
'dart:async',
'dart:collection',
'dart:convert',
'dart:developer',
'dart:ffi',
'dart:_internal',
'dart:isolate',
'dart:math',
// The library dart:mirrors may be ignored by the VM, e.g. when built in
// PRODUCT mode.
'dart:mirrors',
'dart:typed_data',
'dart:vmservice_io',
'dart:_vmservice',
'dart:_builtin',
'dart:nativewrappers',
'dart:io',
'dart:cli',
];
@override
List<String> get extraRequiredLibrariesPlatform => const <String>[];
void _patchVmConstants(CoreTypes coreTypes) {
// Fix Endian.host to be a const field equal to Endian.little instead of
// a final field. VM does not support big-endian architectures at the
// moment.
// Can't use normal patching process for this because CFE does not
// support patching fields.
// See http://dartbug.com/32836 for the background.
final Field host =
coreTypes.index.getField('dart:typed_data', 'Endian', 'host');
final Field little =
coreTypes.index.getField('dart:typed_data', 'Endian', 'little');
host.isConst = true;
host.initializer = new CloneVisitorNotMembers().clone(little.initializer!)
..parent = host;
}
@override
void performPreConstantEvaluationTransformations(
Component component,
CoreTypes coreTypes,
List<Library> libraries,
DiagnosticReporter diagnosticReporter,
{void Function(String msg)? logger,
ChangedStructureNotifier? changedStructureNotifier}) {
super.performPreConstantEvaluationTransformations(
component, coreTypes, libraries, diagnosticReporter,
logger: logger, changedStructureNotifier: changedStructureNotifier);
_patchVmConstants(coreTypes);
}
@override
List<String> get extraIndexedLibraries => const <String>[
// TODO(askesc): When the VM supports set literals, we no longer
// need to index dart:collection, as it is only needed for desugaring of
// const sets. We can remove it from this list at that time.
"dart:collection",
// TODO(askesc): This is for the VM host endian optimization, which
// could possibly be done more cleanly after the VM no longer supports
// doing constant evaluation on its own. See http://dartbug.com/32836
"dart:typed_data",
];
@override
void performModularTransformationsOnLibraries(
Component component,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
List<Library> libraries,
Map<String, String>? environmentDefines,
DiagnosticReporter diagnosticReporter,
ReferenceFromIndex? referenceFromIndex,
{void Function(String msg)? logger,
ChangedStructureNotifier? changedStructureNotifier}) {
transformMixins.transformLibraries(
this, coreTypes, hierarchy, libraries, referenceFromIndex);
logger?.call("Transformed mixin applications");
List<Library>? transitiveImportingDartFfi = ffiHelper
.calculateTransitiveImportsOfDartFfiIfUsed(component, libraries);
if (transitiveImportingDartFfi == null) {
logger?.call("Skipped ffi transformation");
} else {
// Transform @FfiNative(..) functions into FFI native call functions.
// Pass instance method receivers as implicit first argument to the static
// native function.
// Transform arguments that extend NativeFieldWrapperClass1 to Pointer if
// the native function expects Pointer (to avoid Handle overhead).
transformFfiNative.transformLibraries(component, coreTypes, hierarchy,
transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
logger?.call("Transformed ffi natives");
transformFfiDefinitions.transformLibraries(
component,
coreTypes,
hierarchy,
transitiveImportingDartFfi,
diagnosticReporter,
referenceFromIndex,
changedStructureNotifier);
transformFfiUseSites.transformLibraries(component, coreTypes, hierarchy,
transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
logger?.call("Transformed ffi annotations");
}
// TODO(kmillikin): Make this run on a per-method basis.
bool productMode = environmentDefines!["dart.vm.product"] == "true";
transformAsync.transformLibraries(
new TypeEnvironment(coreTypes, hierarchy), libraries,
productMode: productMode);
logger?.call("Transformed async methods");
lowering.transformLibraries(
libraries, coreTypes, hierarchy, flags.enableNullSafety);
logger?.call("Lowering transformations performed");
callSiteAnnotator.transformLibraries(
component, libraries, coreTypes, hierarchy);
logger?.call("Annotated call sites");
}
@override
void performTransformationsOnProcedure(
CoreTypes coreTypes,
ClassHierarchy hierarchy,
Procedure procedure,
Map<String, String>? environmentDefines,
{void Function(String msg)? logger}) {
bool productMode = environmentDefines!["dart.vm.product"] == "true";
transformAsync.transformProcedure(
new TypeEnvironment(coreTypes, hierarchy), procedure,
productMode: productMode);
logger?.call("Transformed async functions");
lowering.transformProcedure(
procedure, coreTypes, hierarchy, flags.enableNullSafety);
logger?.call("Lowering transformations performed");
}
Expression _instantiateInvocationMirrorWithType(
CoreTypes coreTypes,
Expression receiver,
String name,
Arguments arguments,
int offset,
int type) {
return new ConstructorInvocation(
coreTypes.invocationMirrorWithTypeConstructor,
new Arguments(<Expression>[
new SymbolLiteral(name)..fileOffset = offset,
new IntLiteral(type)..fileOffset = offset,
_fixedLengthList(
coreTypes,
coreTypes.typeLegacyRawType,
arguments.types.map((t) => new TypeLiteral(t)).toList(),
arguments.fileOffset),
_fixedLengthList(coreTypes, const DynamicType(), arguments.positional,
arguments.fileOffset),
new StaticInvocation(
coreTypes.mapUnmodifiable,
new Arguments([
new MapLiteral(new List<MapLiteralEntry>.from(
arguments.named.map((NamedExpression arg) {
return new MapLiteralEntry(
new SymbolLiteral(arg.name)..fileOffset = arg.fileOffset,
arg.value)
..fileOffset = arg.fileOffset;
})), keyType: coreTypes.symbolLegacyRawType)
..isConst = (arguments.named.length == 0)
..fileOffset = arguments.fileOffset
], types: [
coreTypes.symbolLegacyRawType,
new DynamicType()
]))
..fileOffset = offset
]));
}
@override
Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver,
String name, Arguments arguments, int offset, bool isSuper) {
bool isGetter = false, isSetter = false, isMethod = false;
if (name.startsWith("set:")) {
isSetter = true;
name = name.substring(4);
} else if (name.startsWith("get:")) {
isGetter = true;
name = name.substring(4);
} else {
isMethod = true;
}
int type = _invocationType(
isGetter: isGetter,
isSetter: isSetter,
isMethod: isMethod,
isSuper: isSuper);
return _instantiateInvocationMirrorWithType(
coreTypes, receiver, name, arguments, offset, type);
}
@override
Expression instantiateNoSuchMethodError(CoreTypes coreTypes,
Expression receiver, String name, Arguments arguments, int offset,
{bool isMethod: false,
bool isGetter: false,
bool isSetter: false,
bool isField: false,
bool isLocalVariable: false,
bool isDynamic: false,
bool isSuper: false,
bool isStatic: false,
bool isConstructor: false,
bool isTopLevel: false}) {
int type = _invocationType(
isMethod: isMethod,
isGetter: isGetter,
isSetter: isSetter,
isField: isField,
isLocalVariable: isLocalVariable,
isDynamic: isDynamic,
isSuper: isSuper,
isStatic: isStatic,
isConstructor: isConstructor,
isTopLevel: isTopLevel);
return new ConstructorInvocation(
coreTypes.noSuchMethodErrorDefaultConstructor,
new Arguments(<Expression>[
receiver,
_instantiateInvocationMirrorWithType(
coreTypes, receiver, name, arguments, offset, type)
]));
}
int _invocationType(
{bool isMethod: false,
bool isGetter: false,
bool isSetter: false,
bool isField: false,
bool isLocalVariable: false,
bool isDynamic: false,
bool isSuper: false,
bool isStatic: false,
bool isConstructor: false,
bool isTopLevel: false}) {
// This is copied from [_InvocationMirror](
// ../../../../../../runtime/lib/invocation_mirror_patch.dart).
// Constants describing the invocation type.
// _FIELD cannot be generated by regular invocation mirrors.
const int _METHOD = 0;
const int _GETTER = 1;
const int _SETTER = 2;
const int _FIELD = 3;
const int _LOCAL_VAR = 4;
// ignore: UNUSED_LOCAL_VARIABLE
const int _KIND_SHIFT = 0;
const int _KIND_BITS = 3;
// ignore: UNUSED_LOCAL_VARIABLE
const int _KIND_MASK = (1 << _KIND_BITS) - 1;
// These values, except _DYNAMIC and _SUPER, are only used when throwing
// NoSuchMethodError for compile-time resolution failures.
const int _DYNAMIC = 0;
const int _SUPER = 1;
const int _STATIC = 2;
const int _CONSTRUCTOR = 3;
const int _TOP_LEVEL = 4;
const int _LEVEL_SHIFT = _KIND_BITS;
const int _LEVEL_BITS = 3;
// ignore: UNUSED_LOCAL_VARIABLE
const int _LEVEL_MASK = (1 << _LEVEL_BITS) - 1;
int type = -1;
// For convenience, [isGetter] and [isSetter] takes precedence over
// [isMethod].
if (isGetter) {
type = _GETTER;
} else if (isSetter) {
type = _SETTER;
} else if (isMethod) {
type = _METHOD;
} else if (isField) {
type = _FIELD;
} else if (isLocalVariable) {
type = _LOCAL_VAR;
}
if (isDynamic) {
type |= (_DYNAMIC << _LEVEL_SHIFT);
} else if (isSuper) {
type |= (_SUPER << _LEVEL_SHIFT);
} else if (isStatic) {
type |= (_STATIC << _LEVEL_SHIFT);
} else if (isConstructor) {
type |= (_CONSTRUCTOR << _LEVEL_SHIFT);
} else if (isTopLevel) {
type |= (_TOP_LEVEL << _LEVEL_SHIFT);
}
return type;
}
Expression _fixedLengthList(CoreTypes coreTypes, DartType typeArgument,
List<Expression> elements, int offset) {
// TODO(ahe): It's possible that it would be better to create a fixed-length
// list first, and then populate it. That would create fewer objects. But as
// this is currently only used in (statically resolved) no-such-method
// handling, the current approach seems sufficient.
// The 0-element list must be exactly 'const[]'.
if (elements.length == 0) {
return new ListLiteral([], typeArgument: typeArgument)..isConst = true;
}
return new StaticInvocation(
coreTypes.listUnmodifiableConstructor,
new Arguments([
new ListLiteral(elements, typeArgument: typeArgument)
..fileOffset = offset
], types: [
typeArgument,
]));
}
// In addition to the default implementation, we allow VM tests to import
// private platform libraries - such as `dart:_internal` - for testing
// purposes.
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('tests/ffi');
// TODO(sigmund,ahe): limit this to `dart-ext` libraries only (see
// https://github.com/dart-lang/sdk/issues/29763).
@override
bool enableNative(Uri uri) => true;
@override
bool get nativeExtensionExpectsString => true;
@override
Component configureComponent(Component component) {
callSiteAnnotator.addRepositoryTo(component);
return super.configureComponent(component);
}
@override
Class concreteListLiteralClass(CoreTypes coreTypes) {
return _growableList ??=
coreTypes.index.getClass('dart:core', '_GrowableList');
}
@override
Class concreteConstListLiteralClass(CoreTypes coreTypes) {
return _immutableList ??=
coreTypes.index.getClass('dart:core', '_ImmutableList');
}
@override
Class concreteMapLiteralClass(CoreTypes coreTypes) {
return _internalLinkedHashMap ??=
coreTypes.index.getClass('dart:collection', '_InternalLinkedHashMap');
}
@override
Class concreteConstMapLiteralClass(CoreTypes coreTypes) {
return _internalImmutableLinkedHashMap ??= coreTypes.index
.getClass('dart:collection', '_InternalImmutableLinkedHashMap');
}
@override
Class concreteSetLiteralClass(CoreTypes coreTypes) {
return _internalLinkedHashSet ??=
coreTypes.index.getClass('dart:collection', '_CompactLinkedHashSet');
}
@override
Class concreteConstSetLiteralClass(CoreTypes coreTypes) {
return _internalImmutableLinkedHashSet ??= coreTypes.index
.getClass('dart:collection', '_CompactImmutableLinkedHashSet');
}
@override
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) {
const int bitsPerInt32 = 32;
const int smiBits32 = bitsPerInt32 - 2;
const int smiMin32 = -(1 << smiBits32);
const int smiMax32 = (1 << smiBits32) - 1;
if ((smiMin32 <= value) && (value <= smiMax32)) {
// Value fits into Smi on all platforms.
return _smi ??= coreTypes.index.getClass('dart:core', '_Smi');
}
// Otherwise, class could be either _Smi or _Mint depending on a platform.
return null;
}
@override
Class concreteDoubleLiteralClass(CoreTypes coreTypes, double value) {
return _double ??= coreTypes.index.getClass('dart:core', '_Double');
}
@override
Class concreteStringLiteralClass(CoreTypes coreTypes, String value) {
const int maxLatin1 = 0xff;
for (int i = 0; i < value.length; ++i) {
if (value.codeUnitAt(i) > maxLatin1) {
return _twoByteString ??=
coreTypes.index.getClass('dart:core', '_TwoByteString');
}
}
return _oneByteString ??=
coreTypes.index.getClass('dart:core', '_OneByteString');
}
@override
ConstantsBackend get constantsBackend => const ConstantsBackend();
@override
Map<String, String> updateEnvironmentDefines(Map<String, String> map) {
// TODO(alexmarkov): Call this from the front-end in order to have
// the same defines when compiling platform.
map['dart.isVM'] = 'true';
return map;
}
@override
DartLibrarySupport get dartLibrarySupport => flags.supportMirrors
? const DefaultDartLibrarySupport()
: const CustomizedDartLibrarySupport(unsupported: {'mirrors'});
@override
bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("vm:");
}