[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:
committed by
Commit Queue
parent
903d77cc82
commit
ef4e39a6ef
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -163,7 +163,6 @@ namespace dart {
|
||||
V(InitLateInstanceField) \
|
||||
V(InitLateFinalInstanceField) \
|
||||
V(InitSharedLateStaticField) \
|
||||
V(InitSharedLateFinalStaticField) \
|
||||
V(Throw) \
|
||||
V(ReThrow) \
|
||||
V(InstanceOf) \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user