blob: 547835cf5cd81b28573aeffb7cf5dbd62bd0d593 [file] [log] [blame]
// Copyright (c) 2013, 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.
/// Note: the VM concatenates all patch files into a single patch file. This
/// file is the first patch in "dart:_internal" which contains all the imports
/// used by patches of that library. We plan to change this when we have a
/// shared front end and simply use parts.
import "dart:async" show Timer;
import "dart:core" hide Symbol;
import "dart:ffi" show Pointer, Struct, Union;
import "dart:isolate" show SendPort;
import "dart:typed_data" show Int32List, Uint8List;
/// These are the additional parts of this patch library:
// part "class_id_fasta.dart";
// part "print_patch.dart";
// part "symbol_patch.dart";
// On the VM, we don't make the entire legacy weak mode check
// const to avoid having a constant in the platform libraries
// which evaluates differently in weak vs strong mode.
@patch
bool typeAcceptsNull<T>() => (const <Null>[]) is List<int> || null is T;
@patch
@pragma("vm:external-name", "Internal_makeListFixedLength")
@pragma("vm:exact-result-type", "dart:core#_List")
external List<T> makeListFixedLength<T>(List<T> growableList);
@patch
@pragma("vm:external-name", "Internal_makeFixedListUnmodifiable")
@pragma("vm:exact-result-type", "dart:core#_ImmutableList")
external List<T> makeFixedListUnmodifiable<T>(List<T> fixedLengthList);
@patch
@pragma("vm:external-name", "Internal_extractTypeArguments")
external Object? extractTypeArguments<T>(T instance, Function extract);
/// The returned string is a [_OneByteString] with uninitialized content.
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:external-name", "Internal_allocateOneByteString")
@pragma("vm:exact-result-type", "dart:core#_OneByteString")
external String allocateOneByteString(int length);
/// The [string] must be a [_OneByteString]. The [index] must be valid.
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:external-name", "Internal_writeIntoOneByteString")
external void writeIntoOneByteString(String string, int index, int codePoint);
/// It is assumed that [from] is a native [Uint8List] class and [to] is a
/// [_OneByteString]. The [fromStart] and [toStart] indices together with the
/// [length] must specify ranges within the bounds of the list / string.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
void copyRangeFromUint8ListToOneByteString(
Uint8List from, String to, int fromStart, int toStart, int length) {
for (int i = 0; i < length; i++) {
writeIntoOneByteString(to, toStart + i, from[fromStart + i]);
}
}
/// The returned string is a [_TwoByteString] with uninitialized content.
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:external-name", "Internal_allocateTwoByteString")
@pragma("vm:exact-result-type", "dart:core#_TwoByteString")
external String allocateTwoByteString(int length);
/// The [string] must be a [_TwoByteString]. The [index] must be valid.
@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:external-name", "Internal_writeIntoTwoByteString")
external void writeIntoTwoByteString(String string, int index, int codePoint);
class VMLibraryHooks {
// Example: "dart:isolate _Timer._factory"
static Timer Function(int, void Function(Timer), bool)? timerFactory;
// Example: "dart:io _EventHandler._sendData"
static late void Function(Object?, SendPort, int) eventHandlerSendData;
// A nullary closure that answers the current clock value in milliseconds.
// Example: "dart:io _EventHandler._timerMillisecondClock"
static late int Function() timerMillisecondClock;
// Implementation of package root/map provision.
static String? packageRootString;
static String? packageConfigString;
static Future<Uri?> Function()? packageConfigUriFuture;
static Future<Uri?> Function(Uri)? resolvePackageUriFuture;
static Uri Function()? _computeScriptUri;
static Uri? _cachedScript;
static set platformScript(Object? f) {
_computeScriptUri = f as Uri Function()?;
_cachedScript = null;
}
static Uri? get platformScript {
return _cachedScript ??= _computeScriptUri?.call();
}
}
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external bool get has63BitSmis;
@pragma("vm:recognized", "other")
@pragma("vm:entry-point", "call")
@pragma("vm:exact-result-type", bool)
@pragma("vm:prefer-inline")
bool _classRangeCheck(int cid, int lowerLimit, int upperLimit) {
return cid >= lowerLimit && cid <= upperLimit;
}
// Utility class now only used by the VM.
class Lists {
@pragma("vm:prefer-inline")
static void copy(List src, int srcStart, List dst, int dstStart, int count) {
if (srcStart < dstStart) {
for (int i = srcStart + count - 1, j = dstStart + count - 1;
i >= srcStart;
i--, j--) {
dst[j] = src[i];
}
} else {
for (int i = srcStart, j = dstStart; i < srcStart + count; i++, j++) {
dst[j] = src[i];
}
}
}
}
// Prepend the parent type arguments (maybe null) of length 'parentLen' to the
// function type arguments (may be null). The result is null if both input
// vectors are null or is a newly allocated and canonicalized vector of length
// 'totalLen'.
@pragma("vm:entry-point", "call")
@pragma("vm:external-name", "Internal_prependTypeArguments")
external _prependTypeArguments(
functionTypeArguments, parentTypeArguments, parentLen, totalLen);
// Check that a set of type arguments satisfy the type parameter bounds on a
// closure.
@pragma("vm:entry-point", "call")
@pragma("vm:external-name", "Internal_boundsCheckForPartialInstantiation")
external _boundsCheckForPartialInstantiation(closure, typeArgs);
// Called by IRRegExpMacroAssembler::GrowStack.
Int32List _growRegExpStack(Int32List stack) {
final newStack = new Int32List(stack.length * 2);
for (int i = 0; i < stack.length; i++) {
newStack[i] = stack[i];
}
return newStack;
}
@patch
@pragma("vm:external-name", "Internal_unsafeCast")
external T unsafeCast<T>(dynamic v);
// This function can be used to keep an object alive till that point.
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external void reachabilityFence(Object? object);
// This function can be used to encode native side effects.
//
// The function call and it's argument are removed in flow graph construction.
@pragma("vm:recognized", "other")
@pragma("vm:external-name", "Internal_nativeEffect")
external void _nativeEffect(Object object);
// Collection of functions which should only be used for testing purposes.
abstract class VMInternalsForTesting {
// This function can be used by tests to enforce garbage collection.
@pragma("vm:external-name", "Internal_collectAllGarbage")
external static void collectAllGarbage();
@pragma("vm:external-name", "Internal_deoptimizeFunctionsOnStack")
external static void deoptimizeFunctionsOnStack();
}
@patch
T createSentinel<T>() => throw UnsupportedError('createSentinel');
@patch
bool isSentinel(dynamic value) => throw UnsupportedError('isSentinel');
@patch
class LateError {
@pragma("vm:entry-point")
static _throwFieldAlreadyInitialized(String fieldName) {
throw new LateError.fieldAI(fieldName);
}
@pragma("vm:entry-point")
static _throwLocalNotInitialized(String localName) {
throw new LateError.localNI(localName);
}
@pragma("vm:entry-point")
static _throwLocalAlreadyInitialized(String localName) {
throw new LateError.localAI(localName);
}
@pragma("vm:entry-point")
static _throwLocalAssignedDuringInitialization(String localName) {
throw new LateError.localADI(localName);
}
}
void checkValidWeakTarget(object, name) {
if ((object == null) ||
(object is bool) ||
(object is num) ||
(object is String) ||
(object is Pointer) ||
(object is Struct) ||
(object is Union)) {
throw new ArgumentError.value(object, name,
"Cannot be a string, number, boolean, null, Pointer, Struct or Union");
}
}
@pragma("vm:entry-point")
class FinalizerBase {
/// The list of finalizers of this isolate.
///
/// Reuses [WeakReference] so that we don't have to implement yet another
/// mechanism to hold on weakly to things.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external static List<WeakReference<FinalizerBase>>? get _isolateFinalizers;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external static set _isolateFinalizers(
List<WeakReference<FinalizerBase>>? value);
static int _isolateFinalizersPurgeCollectedAt = 1;
/// Amortizes the cost for purging nulled out entries.
///
/// Similar to how Expandos purge their nulled out entries on a rehash when
/// resizing.
static void _isolateFinalizersEnsureCapacity() {
_isolateFinalizers ??= <WeakReference<FinalizerBase>>[];
if (_isolateFinalizers!.length < _isolateFinalizersPurgeCollectedAt) {
return;
}
// retainWhere does a single traversal.
_isolateFinalizers!.retainWhere((weak) => weak.target != null);
// We might have dropped most finalizers, trigger next resize at 2x.
_isolateFinalizersPurgeCollectedAt = _isolateFinalizers!.length * 2;
}
/// Registers this [FinalizerBase] to the isolate.
///
/// This is used to prevent sending messages from the GC to the isolate after
/// isolate shutdown.
void _isolateRegisterFinalizer() {
_isolateFinalizersEnsureCapacity();
_isolateFinalizers!.add(WeakReference(this));
}
/// The isolate this [FinalizerBase] belongs to.
///
/// This is used to send finalizer messages to `_handleFinalizerMessage`
/// without a Dart_Port.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external _setIsolate();
/// All active attachments.
///
/// This keeps the [FinalizerEntry]s belonging to this finalizer alive. If an
/// entry gets collected, the finalizer is not run when the
/// [FinalizerEntry.value] is collected.
///
/// TODO(http://dartbug.com/47777): For native finalizers, what data structure
/// can we use that we can modify in the VM. So that we don't have to send a
/// message to Dart to clean up entries for which the GC has run.
///
/// Requirements for data structure:
/// 1. Keeps entries reachable. Entries that are collected will never run
/// the GC.
/// 2. Atomic insert in Dart on `attach`. GC should not run in between.
/// 3. Atomic remove in Dart on `detach`. multiple GC tasks run in parallel.
/// 4. Atomic remove in C++ on value being collected. Multiple GC tasks run in
/// parallel.
///
/// For Dart finalizers we execute the remove in Dart, much simpler.
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external Set<FinalizerEntry> get _allEntries;
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external set _allEntries(Set<FinalizerEntry> entries);
/// Entries of which the value has been collected.
///
/// This is a linked list, with [FinalizerEntry.next].
///
/// Atomic exchange: The GC cannot run between reading the value and storing
/// `null`. Atomicity guaranteed by force optimizing the function.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external FinalizerEntry? _exchangeEntriesCollectedWithNull();
/// A weak map from `detach` keys to [FinalizerEntry]s.
///
/// Using the [FinalizerEntry.detach] keys as keys in an [Expando] ensures
/// they can be GCed.
///
/// [FinalizerEntry]s do not get GCed themselves when their
/// [FinalizerEntry.detach] is unreachable, in contrast to `WeakProperty`s
/// which are GCed themselves when their `key` is no longer reachable.
/// To prevent [FinalizerEntry]s staying around in [_detachments] forever,
/// we reuse `WeakProperty`s.
/// To avoid code duplication, we do not inline the code but use an [Expando]
/// here instead.
///
/// We cannot eagerly purge entries from the map (in the Expando) when GCed.
/// The map is indexed on detach, and doesn't enable finding the entries
/// based on their identity.
/// Instead we rely on the WeakProperty being nulled out (assuming the
/// `detach` key gets GCed) and then reused.
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external Expando<Set<FinalizerEntry>>? get _detachments;
@pragma("vm:recognized", "other")
@pragma('vm:prefer-inline')
external set _detachments(Expando<Set<FinalizerEntry>>? value);
void detach(Object detach) {
final entries = detachments[detach];
if (entries != null) {
for (final entry in entries) {
entry.token = entry;
_allEntries.remove(entry);
}
detachments[detach] = null;
}
}
}
// Extension so that the members can be accessed from other libs.
extension FinalizerBaseMembers on FinalizerBase {
/// See documentation on [_allEntries].
@pragma('vm:prefer-inline')
Set<FinalizerEntry> get allEntries => _allEntries;
@pragma('vm:prefer-inline')
set allEntries(Set<FinalizerEntry> value) => _allEntries = value;
/// See documentation on [_exchangeEntriesCollectedWithNull].
FinalizerEntry? exchangeEntriesCollectedWithNull() =>
_exchangeEntriesCollectedWithNull();
/// See documentation on [_detachments].
@pragma('vm:prefer-inline')
Expando<Set<FinalizerEntry>> get detachments {
_detachments ??= Expando<Set<FinalizerEntry>>();
return unsafeCast<Expando<Set<FinalizerEntry>>>(_detachments);
}
/// See documentation on [_isolateRegisterFinalizer].
isolateRegisterFinalizer() => _isolateRegisterFinalizer();
/// See documentation on [_setIsolate].
setIsolate() => _setIsolate();
}
/// Contains the informatation of an active [Finalizer.attach].
///
/// It holds on to the [value], optional [detach], and [token]. In addition, it
/// also keeps a reference the [finalizer] it belings to and a [next] field for
/// when being used in a linked list.
///
/// This is being kept alive by [FinalizerBase._allEntries] until either (1)
/// [Finalizer.detach] detaches it, or (2) [value] is collected and the
/// `callback` has been invoked.
///
/// Note that the GC itself uses an extra hidden field `next_seen_by_gc` to keep a
/// linked list of pending entries while running the GC.
@pragma("vm:entry-point")
class FinalizerEntry {
/// The [value] the [FinalizerBase] is attached to.
///
/// Set to `null` by GC when unreachable.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external Object? get value;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set value(Object? value);
/// The [detach] object can be passed to [FinalizerBase] to detach
/// the finalizer.
///
/// Set to `null` by GC when unreachable.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external Object? get detach;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set detach(Object? value);
/// The [token] is passed to [FinalizerBase] when the finalizer is run.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external Object? get token;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set token(Object? value);
/// The [finalizer] this [FinalizerEntry] belongs to.
///
/// Set to `null` by GC when unreachable.
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set finalizer(FinalizerBase? finalizer);
/// The [next] entry in a linked list.
///
/// Used in for the linked list starting from
/// [FinalizerBase._exchangeEntriesCollectedWithNull].
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external FinalizerEntry? get next;
@pragma("vm:recognized", "other")
@pragma("vm:prefer-inline")
external set next(FinalizerEntry? value);
}