[vm/shared] Ensure exclusive execution of shared field initialization.

Add a mutex to guard execution of vm:shared field initialization.

TEST=isolate_group_shared_init_test
BUG=dartbug.com/60699

Change-Id: If544351fc26bfcc7fb9703954efe785989d488bc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/431742
Commit-Queue: Alexander Aprelev <aam@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Aprelev
2025-06-05 14:12:33 -07:00
committed by Commit Queue
parent 903d77cc82
commit ef4e39a6ef
16 changed files with 1362 additions and 1198 deletions
@@ -2035,20 +2035,20 @@ void AsmIntrinsifier::Timeline_getNextTaskId(Assembler* assembler,
__ LoadImmediate(A0, target::ToRawSmi(0));
__ ret();
#elif XLEN == 64
__ ld(A0, Address(THR, target::Thread::next_task_id_offset()));
__ LoadFromOffset(A0, THR, target::Thread::next_task_id_offset());
__ addi(A1, A0, 1);
__ sd(A1, Address(THR, target::Thread::next_task_id_offset()));
__ StoreToOffset(A1, THR, target::Thread::next_task_id_offset());
__ SmiTag(A0); // Ignore loss of precision.
__ ret();
#else
__ lw(T0, Address(THR, target::Thread::next_task_id_offset()));
__ lw(T1, Address(THR, target::Thread::next_task_id_offset() + 4));
__ LoadFromOffset(T0, THR, target::Thread::next_task_id_offset());
__ LoadFromOffset(T1, THR, target::Thread::next_task_id_offset() + 4);
__ SmiTag(A0, T0); // Ignore loss of precision.
__ addi(T2, T0, 1);
__ sltu(T3, T2, T0); // Carry.
__ add(T1, T1, T3);
__ sw(T2, Address(THR, target::Thread::next_task_id_offset()));
__ sw(T1, Address(THR, target::Thread::next_task_id_offset() + 4));
__ StoreToOffset(T2, THR, target::Thread::next_task_id_offset());
__ StoreToOffset(T1, THR, target::Thread::next_task_id_offset() + 4);
__ ret();
#endif
}
+1 -4
View File
@@ -4574,10 +4574,7 @@ void LoadStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
original_field.EnsureInitializerFunction();
}
stub = field().is_shared()
? (field().is_final()
? object_store
->init_shared_late_final_static_field_stub()
: object_store->init_shared_late_static_field_stub())
? object_store->init_shared_late_static_field_stub()
: (field().is_final()
? object_store->init_late_final_static_field_stub()
: object_store->init_late_static_field_stub());
File diff suppressed because it is too large Load Diff
+29 -22
View File
@@ -91,14 +91,26 @@ void StubCodeCompiler::GenerateInitLateStaticFieldStub(bool is_final,
__ EnterStubFrame();
if (FLAG_experimental_shared_data && is_shared) {
// Since initialization of shared fields has to be guarded by
// a mutex, do the initialization in the runtime.
__ PushObject(NullObject()); // Make room for the result
__ PushRegister(kFieldReg);
__ CallRuntime(kInitializeSharedFieldRuntimeEntry, /*argument_count=*/1);
__ PopRegister(kFieldReg);
__ PopRegister(kResultReg);
__ LeaveStubFrame();
__ Ret();
return;
}
Label throw_since_no_isolate_is_present;
if (FLAG_experimental_shared_data) {
if (!is_shared) {
// This stub is also called from mutator thread running without an
// isolate and attempts to load value from isolate static field.
__ LoadIsolate(kScratchReg);
__ BranchIfZero(kScratchReg, &throw_since_no_isolate_is_present);
}
ASSERT(!is_shared);
// This stub is also called from mutator thread running without an
// isolate and attempts to load value from isolate static field.
__ LoadIsolate(kScratchReg);
__ BranchIfZero(kScratchReg, &throw_since_no_isolate_is_present);
}
__ Comment("Calling initializer function");
@@ -143,20 +155,19 @@ void StubCodeCompiler::GenerateInitLateStaticFieldStub(bool is_final,
}
if (FLAG_experimental_shared_data) {
if (!is_shared) {
ASSERT(!is_shared);
#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
// We are jumping over LeaveStubFrame so restore LR state to match one
// at the jump point.
__ set_lr_state(compiler::LRState::OnEntry().EnterFrame());
// We are jumping over LeaveStubFrame so restore LR state to match one
// at the jump point.
__ set_lr_state(compiler::LRState::OnEntry().EnterFrame());
#endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
// Throw FieldAccessError
__ Bind(&throw_since_no_isolate_is_present);
__ PushObject(NullObject()); // Make room for (unused) result.
__ PushRegister(kFieldReg);
__ CallRuntime(kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry,
/*argument_count=*/1);
__ Breakpoint();
}
// Throw FieldAccessError
__ Bind(&throw_since_no_isolate_is_present);
__ PushObject(NullObject()); // Make room for (unused) result.
__ PushRegister(kFieldReg);
__ CallRuntime(kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry,
/*argument_count=*/1);
__ Breakpoint();
}
}
@@ -172,10 +183,6 @@ void StubCodeCompiler::GenerateInitSharedLateStaticFieldStub() {
GenerateInitLateStaticFieldStub(/*is_final=*/false, /*is_shared=*/true);
}
void StubCodeCompiler::GenerateInitSharedLateFinalStaticFieldStub() {
GenerateInitLateStaticFieldStub(/*is_final=*/true, /*is_shared=*/true);
}
void StubCodeCompiler::GenerateInitInstanceFieldStub() {
__ EnterStubFrame();
__ PushObject(NullObject()); // Make room for result.
-1
View File
@@ -190,7 +190,6 @@ class StubCodeCompiler {
// Common function for generating InitLateStaticField,
// InitLateFinalStaticField, InitSharedLateStaticField,
// InitSharedLateFinalStaticField,
void GenerateInitLateStaticFieldStub(bool is_final, bool is_shared);
// Common function for generating InitLateInstanceField and
+2
View File
@@ -354,6 +354,7 @@ IsolateGroup::IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
kernel_data_lib_cache_mutex_(),
kernel_data_class_cache_mutex_(),
kernel_constants_mutex_(),
shared_field_initializer_rwlock_(),
field_list_mutex_(),
boxed_field_list_(GrowableObjectArray::null()),
program_lock_(new SafepointRwLock(SafepointLevel::kGCAndDeopt)),
@@ -890,6 +891,7 @@ void IsolateGroup::FreeStaticField(const Field& field) {
const intptr_t field_id = field.field_id();
if (field.is_shared()) {
shared_field_table()->Free(field_id);
shared_initial_field_table()->Free(field_id);
} else {
initial_field_table()->Free(field_id);
sentinel_field_table()->Free(field_id);
+7
View File
@@ -534,6 +534,10 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
Mutex* initializer_functions_mutex() { return &initializer_functions_mutex_; }
#endif // !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES)
SafepointRwLock* shared_field_initializer_rwlock() {
return &shared_field_initializer_rwlock_;
}
SafepointRwLock* program_lock() { return program_lock_.get(); }
static inline IsolateGroup* Current() {
@@ -936,6 +940,9 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
Mutex initializer_functions_mutex_;
#endif // !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES)
// Ensure exclusive execution of shared field initializers.
SafepointRwLock shared_field_initializer_rwlock_;
// Protect access to boxed_field_list_.
Mutex field_list_mutex_;
// List of fields that became boxed and that trigger deoptimization.
+5 -2
View File
@@ -13430,7 +13430,6 @@ TypePtr Class::GetInstantiationOf(Zone* zone, const Type& type) const {
}
void Field::SetStaticValue(const Object& value) const {
ASSERT(!is_shared());
auto thread = Thread::Current();
ASSERT(thread->IsDartMutatorThread());
ASSERT(value.IsNull() || value.IsSentinel() || value.IsInstance());
@@ -13440,7 +13439,11 @@ void Field::SetStaticValue(const Object& value) const {
ASSERT(id >= 0);
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
thread->isolate()->field_table()->SetAt(id, value.ptr());
if (is_shared()) {
thread->isolate_group()->shared_field_table()->SetAt(id, value.ptr());
} else {
thread->isolate()->field_table()->SetAt(id, value.ptr());
}
}
static StaticTypeExactnessState TrivialTypeExactnessFor(const Class& cls) {
+3 -1
View File
@@ -13399,7 +13399,9 @@ void Field::SetOffset(intptr_t host_offset_in_bytes,
ObjectPtr Field::StaticValue() const {
ASSERT(is_static()); // Valid only for static dart fields.
return Isolate::Current()->field_table()->At(field_id());
return is_shared()
? IsolateGroup::Current()->shared_field_table()->At(field_id())
: Isolate::Current()->field_table()->At(field_id());
}
inline intptr_t Field::field_id() const {
-2
View File
@@ -268,7 +268,6 @@ class ObjectPointerVisitor;
RW(Code, init_late_instance_field_stub) \
RW(Code, init_late_final_instance_field_stub) \
RW(Code, init_shared_late_static_field_stub) \
RW(Code, init_shared_late_final_static_field_stub) \
RW(Code, call_closure_no_such_method_stub) \
RW(Code, default_tts_stub) \
RW(Code, default_nullable_tts_stub) \
@@ -383,7 +382,6 @@ class ObjectPointerVisitor;
DO(init_late_instance_field_stub, InitLateInstanceField) \
DO(init_late_final_instance_field_stub, InitLateFinalInstanceField) \
DO(init_shared_late_static_field_stub, InitSharedLateStaticField) \
DO(init_shared_late_final_static_field_stub, InitSharedLateFinalStaticField) \
DO(await_stub, Await) \
DO(await_with_type_check_stub, AwaitWithTypeCheck) \
DO(clone_suspend_state_stub, CloneSuspendState) \
+15
View File
@@ -4724,6 +4724,21 @@ DEFINE_LEAF_RUNTIME_ENTRY(PropagateError,
/*argument_count=*/1,
DLRT_PropagateError);
DEFINE_RUNTIME_ENTRY(InitializeSharedField, 1) {
SafepointWriteRwLocker locker(
thread, thread->isolate_group()->shared_field_initializer_rwlock());
const Field& field = Field::CheckedHandle(zone, arguments.ArgAt(0));
Object& result = Object::Handle(zone, field.StaticValue());
if (result.ptr() == Object::sentinel().ptr()) {
// Haven't lost a race to set the initial value.
result = field.InitializeStatic();
ThrowIfError(result);
result = field.StaticValue();
ASSERT(result.ptr() != Object::sentinel().ptr());
}
arguments.SetReturn(result);
}
#if !defined(USING_MEMORY_SANITIZER)
extern "C" void __msan_unpoison(const volatile void*, size_t) {
UNREACHABLE();
+2 -1
View File
@@ -81,7 +81,8 @@ namespace dart {
V(ResolveCallFunction) \
V(InterpretedInstanceCallMissHandler) \
V(InvokeNoSuchMethod) \
V(ResumeInterpreter)
V(ResumeInterpreter) \
V(InitializeSharedField)
// Note: Leaf runtime function have C linkage, so they cannot pass C++ struct
// values like ObjectPtr.
-1
View File
@@ -163,7 +163,6 @@ namespace dart {
V(InitLateInstanceField) \
V(InitLateFinalInstanceField) \
V(InitSharedLateStaticField) \
V(InitSharedLateFinalStaticField) \
V(Throw) \
V(ReThrow) \
V(InstanceOf) \
+7 -14
View File
@@ -1926,22 +1926,15 @@ class Native<T> {
_get_ffi_native_resolver<T extends NativeFunction>();
// Resolver for FFI Native C function pointers.
//
// TODO(dartbug.com/60699): Once the referenced issue is fixed, this can be
// restored back to use of a field with initializer.
@pragma('vm:entry-point')
@pragma('vm:shared')
static int Function(Object, Object, int)? _ffi_resolver = null;
static final _ffi_resolver =
_get_ffi_native_resolver<
NativeFunction<IntPtr Function(Handle, Handle, IntPtr)>
>()
.asFunction<int Function(Object, Object, int)>();
@pragma('vm:entry-point')
static int _ffi_resolver_function(Object a, Object s, int n) {
if (_ffi_resolver == null) {
_ffi_resolver =
_get_ffi_native_resolver<
NativeFunction<IntPtr Function(Handle, Handle, IntPtr)>
>()
.asFunction<int Function(Object, Object, int)>();
}
return _ffi_resolver!(a, s, n);
}
static int _ffi_resolver_function(Object a, Object s, int n) =>
_ffi_resolver(a, s, n);
}
+1
View File
@@ -57,6 +57,7 @@ function_callbacks_structs_by_value_generated_test/*: Skip # Test harness doesn'
function_callbacks_structs_by_value_native_callable_generated_test/*: Skip # Test harness doesn't support multitest with Fuchsia
function_callbacks_subtype_test/*: Skip # Test harness doesn't support multitest with Fuchsia
isolate_group_shared_callback_test: Skip # gen_snapshot requires experimental-shared-data flag
isolate_group_shared_init_test: Skip # gen_snapshot requires experimental-shared-data flag
isolate_group_shared_send_test: Skip # gen_snapshot requires experimental-shared-data flag
native_assets/*: Skip # Source not available in the emulator
run_isolate_group_run_test: Skip # gen_snapshot requires experimental-shared-data flag
@@ -0,0 +1,140 @@
// Copyright (c) 2025, 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.
//
// Tests IsolateGroup.runSync - what works, what doesn't.
//
// VMOptions=--experimental-shared-data
// VMOptions=--experimental-shared-data --use-slow-path
// VMOptions=--experimental-shared-data --use-slow-path --stacktrace-every=100
// VMOptions=--experimental-shared-data --dwarf_stack_traces --no-retain_function_objects --no-retain_code_objects
// VMOptions=--experimental-shared-data --test_il_serialization
// VMOptions=--experimental-shared-data --profiler --profile_vm=true
// VMOptions=--experimental-shared-data --profiler --profile_vm=false
import 'dart:async';
import 'dart:concurrent';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:dart_internal/isolate_group.dart' show IsolateGroup;
import "package:expect/async_helper.dart";
import "package:expect/expect.dart";
import 'package:ffi/ffi.dart';
@pragma('vm:shared')
late final Mutex mutex;
@pragma('vm:shared')
late final String shared_late_final_string = () {
return "${int.parse('123') + 42}";
}();
@pragma('vm:shared')
late final String shared_late_final_throw = () {
throw "${int.parse('123') + 42}";
}();
@pragma('vm:shared')
late final String foo = () {
return "${int.parse('123') + 42}";
}();
testInitStrings() async {
const int nWorkers = 20;
mutex = Mutex();
final rp = ReceivePort();
final rpExitAndErrors = ReceivePort()
..listen((e) {
print('e: $e');
});
final completer = Completer();
int counter = 0;
rp.listen((data) {
counter++;
print('got $data, counter: $counter');
Expect.equals("165", data);
if (counter == nWorkers) {
completer.complete(data);
}
});
for (int i = 0; i < nWorkers; i++) {
Isolate.spawn(
(sendPort) {
IsolateGroup.runSync(() {
sendPort.send(shared_late_final_string);
});
},
rp.sendPort,
onExit: rpExitAndErrors.sendPort,
onError: rpExitAndErrors.sendPort,
);
print("spawned isolate #$i");
}
Expect.equals("165", await completer.future);
rpExitAndErrors.close();
rp.close();
}
testInitThrows() async {
const int nWorkers = 20;
final rp = ReceivePort();
int exitCounter = 0;
final completer = Completer();
ReceivePort rpExits = ReceivePort()
..listen((e) {
exitCounter++;
print('exitCounter: $exitCounter, exit: $e');
if (exitCounter == nWorkers) {
completer.complete(true);
}
});
int errorCounter = 0;
ReceivePort rpErrors = ReceivePort()
..listen((e) {
errorCounter++;
Expect.equals("165", e[0]);
print('errorCounter: $errorCounter, error: $e');
});
for (int i = 0; i < nWorkers; i++) {
Isolate.spawn(
(sendPort) {
IsolateGroup.runSync(() {
try {
sendPort.send(shared_late_final_throw);
} catch (e) {
Expect.equals("165", e);
rethrow;
}
});
},
rp.sendPort,
onExit: rpExits.sendPort,
onError: rpErrors.sendPort,
);
print("spawned isolate #$i");
}
await completer.future;
Expect.equals(nWorkers, errorCounter);
rpErrors.close();
rpExits.close();
rp.close();
}
@pragma('vm:shared')
late final String foo_bar = () {
return "${int.parse('123') + 42} $foo";
}();
testNestedInitCall() {
Expect.equals("165 165", foo_bar);
}
main() async {
asyncStart();
await testInitStrings();
await testInitThrows();
testNestedInitCall();
asyncEnd();
}