[sdk/vm] Use FinalThreadLocal for caching double toString values.
This allows use of double.toString in isolategroup-bound callbacks.
BUG=https://github.com/dart-lang/sdk/issues/61541
TEST=run_isolate_group_run_test
Change-Id: I14443221ffda6f2e639cdbaeea4a2e460a6f42a1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/460960
Commit-Queue: Alexander Aprelev <aam@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
diff --git a/pkg/vm/lib/modular/transformations/deeply_immutable.dart b/pkg/vm/lib/modular/transformations/deeply_immutable.dart
index 5d7c048..0b6bf0b 100644
--- a/pkg/vm/lib/modular/transformations/deeply_immutable.dart
+++ b/pkg/vm/lib/modular/transformations/deeply_immutable.dart
@@ -139,7 +139,7 @@
}
}
- if (node.name == 'ScopedThreadLocal') {
+ if ((node.name == 'ScopedThreadLocal' || node.name == 'FinalThreadLocal')) {
final uri = node.enclosingLibrary.importUri;
if (uri.isScheme('dart') && uri.path == '_vm') {
// ScopedThreadLocal has non-deeply-immutable initializer,
diff --git a/sdk/lib/_internal/vm/lib/core_patch.dart b/sdk/lib/_internal/vm/lib/core_patch.dart
index c9c2365..7e266ac 100644
--- a/sdk/lib/_internal/vm/lib/core_patch.dart
+++ b/sdk/lib/_internal/vm/lib/core_patch.dart
@@ -59,6 +59,8 @@
import "dart:typed_data" show Uint8List, Uint16List, Int32List;
+import 'dart:_vm' show FinalThreadLocal;
+
/// These are the additional parts of this patch library:
part "array.dart";
part "double.dart";
diff --git a/sdk/lib/_internal/vm/lib/double.dart b/sdk/lib/_internal/vm/lib/double.dart
index 9a8ca17..7633e21 100644
--- a/sdk/lib/_internal/vm/lib/double.dart
+++ b/sdk/lib/_internal/vm/lib/double.dart
@@ -245,34 +245,26 @@
return this;
}
- static const int CACHE_SIZE_LOG2 = 3;
- static const int CACHE_LENGTH = 1 << (CACHE_SIZE_LOG2 + 1);
- static const int CACHE_MASK = CACHE_LENGTH - 1;
// Each key (double) followed by its toString result.
- static final List _cache = List.filled(CACHE_LENGTH, null);
- static int _cacheEvictIndex = 0;
+ @pragma("vm:shared")
+ static final _cacheThreadLocal = FinalThreadLocal<_DoubleToStringCache>(
+ () => _DoubleToStringCache(),
+ );
@pragma("vm:external-name", "Double_toString")
external String _toString();
String toString() {
- // TODO(koda): Consider starting at most recently inserted.
- for (int i = 0; i < CACHE_LENGTH; i += 2) {
- // Need 'identical' to handle negative zero, etc.
- if (identical(_cache[i], this)) {
- return _cache[i + 1];
- }
+ final cache = _cacheThreadLocal.value;
+ final cachedValue = cache.lookup(this);
+ if (cachedValue != null) {
+ return cachedValue;
}
// TODO(koda): Consider optimizing all small integral values.
if (identical(0.0, this)) {
return "0.0";
}
- String result = _toString();
- // Replace the least recently inserted entry.
- _cache[_cacheEvictIndex] = this;
- _cache[_cacheEvictIndex + 1] = result;
- _cacheEvictIndex = (_cacheEvictIndex + 2) & CACHE_MASK;
- return result;
+ return cache.store(this, _toString());
}
String toStringAsFixed(int fractionDigits) {
@@ -407,3 +399,32 @@
}
}
}
+
+class _DoubleToStringCache {
+ static const int _CACHE_SIZE_LOG2 = 3;
+ static const int _CACHE_LENGTH = 1 << (_CACHE_SIZE_LOG2 + 1);
+ static const int _CACHE_MASK = _CACHE_LENGTH - 1;
+
+ // Each key (double) followed by its toString result.
+ final List list = List.filled(_CACHE_LENGTH, null);
+ int evictIndex = 0;
+
+ String? lookup(double v) {
+ // TODO(koda): Consider starting at most recently inserted.
+ for (int i = 0; i < _CACHE_LENGTH; i += 2) {
+ // Need 'identical' to handle negative zero, etc.
+ if (identical(list[i], v)) {
+ return list[i + 1];
+ }
+ }
+ return null;
+ }
+
+ String store(double v, String s) {
+ // Replace the least recently inserted entry.
+ list[evictIndex] = v;
+ list[evictIndex + 1] = s;
+ evictIndex = (evictIndex + 2) & _CACHE_MASK;
+ return s;
+ }
+}
diff --git a/sdk/lib/_vm/_vm.dart b/sdk/lib/_vm/_vm.dart
index 5358c6c..5ba32baa 100644
--- a/sdk/lib/_vm/_vm.dart
+++ b/sdk/lib/_vm/_vm.dart
@@ -8,68 +8,34 @@
@pragma("vm:deeply-immutable")
@pragma('vm:entry-point')
-final class ScopedThreadLocal<T> {
- /// Creates scoped thread local value with the given [initializer] function.
- ///
- /// [initializer] must be trivially shareable.
- ScopedThreadLocal([this._initializer]) : _id = _allocateId();
+final class ThreadLocal<T> {
+ /// Creates dart thread local variable.
+ ThreadLocal() : _id = _allocateId();
- /// Execute [f] binding this [ScopedThreadLocal] to the given
- /// [value] for the duration of the execution.
- R runWith<R>(T value, R Function(T) f) {
- bool hadValue = _hasValue(_id);
- Object? previous_value = hadValue ? _getValue(_id) : null;
- _setValue(_id, value);
- R result = f(value);
- if (hadValue) {
- _setValue(_id, previous_value!);
- } else {
- _clearValue(_id);
- }
- return result;
- }
-
- /// Execute [f] initializing this [ScopedThreadLocal] using default initializer if needed.
- /// Throws [StateError] if this [ScopedThreadLocal] does not have an initializer.
- R runInitialized<R>(R Function(T) f) {
- bool hadValue = _hasValue(_id);
- Object? previous_value = hadValue ? _getValue(_id) : null;
- late T v;
- if (!isBound) {
- if (_initializer == null) {
- throw StateError(
- "No initializer was provided for this ScopedThreadLocal.",
- );
- }
- v = _initializer!();
- _setValue(_id, v);
- } else {
- v = unsafeCast<T>(_getValue(_id));
- }
- R result = f(v);
- if (hadValue) {
- _setValue(_id, previous_value!);
- } else {
- _clearValue(_id);
- }
- return result;
- }
-
- /// Returns the value specified by the closest enclosing invocation of
- /// [runWith] or [runInititalized] or throws [StateError] if this
- /// [ScopedThreadLocal] is not bound to a value.
+ /// Returns the value of this thread-local variable or throws [StateError]
+ /// if it has no value.
T get value {
if (!_hasValue(_id)) {
throw StateError(
- "Attempt to access value that was not bound. "
- "Use runInititalized or runWith.",
+ "Attempt to access variable that was not assigned a value.",
);
}
return unsafeCast<T>(_getValue(_id));
}
- /// Returns `true` if this [ScopedThreadLocal] is bound to a value.
- bool get isBound => _hasValue(_id);
+ /// Sets the value of this variable. Overwrites old value if it was previously
+ /// set.
+ set value(T newValue) {
+ _setValue(_id, newValue);
+ }
+
+ /// Returns `true` if some value was assigned to this variable.
+ bool get hasValue => _hasValue(_id);
+
+ // Clears this variable of its assigned value.
+ void clearValue() {
+ _clearValue(_id);
+ }
@pragma("vm:external-name", "ScopedThreadLocal_allocateId")
external static int _allocateId();
@@ -87,5 +53,89 @@
external static void _clearValue(int id);
final int _id;
+}
+
+@pragma("vm:deeply-immutable")
+@pragma('vm:entry-point')
+final class ScopedThreadLocal<T> {
+ /// Creates scoped thread-local variable with given [initializer] function.
+ ///
+ /// [initializer] must be trivially shareable.
+ ScopedThreadLocal([this._initializer]);
+
+ /// Execute [f] binding this [ScopedThreadLocal] to the given
+ /// [value] for the duration of the execution.
+ R runWith<R>(T new_value, R Function(T) f) {
+ bool had_value = variable.hasValue;
+ T? previous_value = had_value ? variable.value : null;
+ variable.value = new_value;
+ R result = f(new_value);
+ if (had_value) {
+ variable.value = previous_value as T;
+ } else {
+ variable.clearValue();
+ }
+ return result;
+ }
+
+ /// Execute [f] initializing this [ScopedThreadLocal] using default initializer if needed.
+ /// Throws [StateError] if this [ScopedThreadLocal] does not have an initializer.
+ R runInitialized<R>(R Function(T) f) {
+ bool had_value = variable.hasValue;
+ T? previous_value = had_value ? variable.value : null;
+ if (!variable.hasValue) {
+ if (_initializer == null) {
+ throw StateError(
+ "No initializer was provided for this ScopedThreadLocal.",
+ );
+ }
+ variable.value = _initializer!();
+ }
+ R result = f(variable.value);
+ if (had_value) {
+ variable.value = previous_value as T;
+ } else {
+ variable.clearValue();
+ }
+ return result;
+ }
+
+ /// Returns the value specified by the closest enclosing invocation of
+ /// [runWith] or [runInititalized] or throws [StateError] if this
+ /// [ScopedThreadLocal] is not bound to a value.
+ T get value => variable.value;
+
+ /// Returns `true` if this [ScopedThreadLocal] is bound to a value.
+ bool get isBound => variable.hasValue;
final T Function()? _initializer;
+
+ final variable = ThreadLocal<T>();
+}
+
+@pragma("vm:deeply-immutable")
+@pragma('vm:entry-point')
+final class FinalThreadLocal<T> {
+ /// Creates thread local value with the given [initializer] function.
+ ///
+ /// The value can be assigned only once, remains assigned for the duration
+ /// of this dart thread lifetime.
+ ///
+ /// [initializer] must be trivially shareable.
+ FinalThreadLocal(this._initializer);
+
+ /// Returns the value bound to [FinalThreadLocal].
+ T get value {
+ if (!variable.hasValue) {
+ variable.value = _initializer();
+ }
+ return variable.value;
+ }
+
+ set value(_) {
+ throw StateError("Final value can not be updated");
+ }
+
+ final T Function() _initializer;
+
+ final variable = ThreadLocal<T>();
}
diff --git a/tests/ffi/run_isolate_group_run_test.dart b/tests/ffi/run_isolate_group_run_test.dart
index 1e2e7f7..d82e3d3 100644
--- a/tests/ffi/run_isolate_group_run_test.dart
+++ b/tests/ffi/run_isolate_group_run_test.dart
@@ -82,6 +82,9 @@
@pragma('vm:shared')
String default_tag = "";
+@pragma('vm:shared')
+double pi = 3.14159;
+
main(List<String> args) {
IsolateGroup.runSync(() {
final l = <int>[];
@@ -247,5 +250,14 @@
});
Expect.notEquals("", default_tag);
+ final result = IsolateGroup.runSync(() {
+ return pi.toString();
+ });
+ Expect.equals("3.14159", result);
+ final resultIdentical = IsolateGroup.runSync(() {
+ return identical(pi.toString(), pi.toString());
+ });
+ Expect.isTrue(resultIdentical);
+
print("All tests completed :)");
}