[vm/shared] Introduce 'vm:shared' pragma.

Decorating a field with 'vm:shared' pragma makes values in this field accessible to all isolates in an isolate group.

Introduce `channel` to the `Version` class so that the pragma can only be enabled on main and dev channels.

TEST=shared_test, shared_fail_without_flag_test
BUG=https://github.com/dart-lang/sdk/issues/55991

Change-Id: I843c9f0d2ffc9f2ced7ddc4006bb6f9ca4e2ddf4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370064
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Alexander Aprelev
2024-06-14 23:24:19 +00:00
committed by Commit Queue
parent 9ead08f053
commit 4138277ee6
42 changed files with 4017 additions and 3331 deletions
+1
View File
@@ -32,6 +32,7 @@ understands potential repercussions.
| --- | --- |
| `vm:unsafe:no-interrupts` | Removes all `CheckStackOverflow` instructions from the optimized version of the marked function, which disables stack overflow checking and interruption within that function. This pragma exists mainly for performance evaluation and should not be used in a general-purpose code, because VM relies on these checks for OOB message delivery and GC scheduling. |
| `vm:unsafe:no-bounds-checks` | Removes all array bounds checks from the optimized version of the marked function in AOT mode. This pragma exists for optimizing throughput of extremely tight loops. |
| `vm:shared` | Makes content of the static field visible to all isolates in one isolate group. Unsafe and experimental at this point as it requires developer to take care of access synchronization to ensure race-free read/write access. |
## Pragmas for internal use
+7
View File
@@ -0,0 +1,7 @@
# Shared memory
https://github.com/dart-lang/language/blob/main/working/333%20-%20shared%20memory%20multithreading/proposal.md provided a framework for adding shared memory support to applications running on dart vm.
First, support for `pragma("vm:shared")` was added, which could be put on static fields. Static fields
decorated with such pragma become shared by all isolates in isolate group.
@@ -0,0 +1,94 @@
// Copyright (c) 2024, 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.
//
// This checks that use of 'vm:shared' pragma crashes VM if no flag is passed.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:expect/config.dart';
import 'package:expect/expect.dart';
import 'package:path/path.dart' as path;
import 'package:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
void main(List<String> args) async {
if (isVmAotConfiguration) return; // Skip testing on AOT
asyncStart();
final dartExecutable = Platform.executable;
final Directory tempDir = Directory.systemTemp.createTempSync();
try {
final sharedUseTest = path.join(tempDir.path, 'shared_use_test.dart');
File(sharedUseTest).writeAsStringSync(r'''
@pragma('vm:shared') int foo = 1;
void main() {}
''');
{
final process = await Process.start(dartExecutable,
<String>[...Platform.executableArguments, sharedUseTest]);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
stdout.writeln('stdout:>$line');
stdout.writeln(line);
});
final sb = StringBuffer();
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
stderr.writeln('stderr:>$line');
sb.writeln(line);
});
Expect.notEquals(0, await process.exitCode);
Expect.contains(
"Encountered vm:shared when functionality is disabled. "
"Pass --experimental-shared-data",
sb.toString());
}
{
final process = await Process.start(dartExecutable, <String>[
...Platform.executableArguments,
'--experimental_shared_data',
sharedUseTest
]);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
stdout.writeln('stdout:>$line');
stdout.writeln(line);
});
final sb = StringBuffer();
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
stderr.writeln('stderr:>$line');
sb.writeln(line);
});
final exitCode = await process.exitCode;
if (Platform.version.contains('(main)') ||
Platform.version.contains('(dev)')) {
Expect.equals(0, exitCode);
} else {
Expect.notEquals(0, exitCode);
Expect.contains(
"Shared memory multithreading in only available for "
"experimentation in dev or main",
sb.toString());
}
}
} finally {
tempDir.deleteSync(recursive: true);
}
asyncEnd();
}
@@ -0,0 +1,87 @@
// Copyright (c) 2024, 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.
//
// OtherResources=shared_test_body.dart
//
// This launches shared_test test if the test runs on the appropriate channel.
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:async_helper/async_helper.dart';
import 'package:expect/config.dart';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'package:path/path.dart' as path;
import '../use_flag_test_helper.dart';
void main(List<String> args) async {
if (Platform.isAndroid) {
return; // No vm_platform_strong.dill easily available.
}
asyncStart();
final testerScriptPath = Platform.script.toFilePath();
final testeeScriptPath =
Platform.script.resolve('shared_test_body.dart').toFilePath();
final Directory tempDir = Directory.systemTemp.createTempSync();
try {
if (isVmAotConfiguration) {
final scriptDill =
path.join(tempDir.path, 'shared_test_content.dart.dill');
await run(
path.joinAll([
'pkg',
'vm',
'tool',
'gen_kernel${Platform.isWindows ? ".bat" : ""}'
]),
<String>[
'--aot',
'--platform=$platformDill',
'-o',
scriptDill,
testeeScriptPath
]);
final elfFile =
path.join(tempDir.path, 'shared_test_content.dart.dill.elf');
final stderr = (await runError(genSnapshot, <String>[
'--snapshot-kind=app-aot-elf',
'--elf=$elfFile',
scriptDill,
]))
.join('\n');
print('stderr: $stderr');
Expect.contains(
'Encountered vm:shared when functionality is disabled. '
'Pass --experimental-shared-data',
stderr);
} else {
final result = await Process.run(Platform.executable, <String>[
...Platform.executableArguments,
'--experimental_shared_data',
testeeScriptPath
]);
if (Platform.version.contains('(main)') ||
Platform.version.contains('(dev)')) {
Expect.equals(0, result.exitCode);
} else {
Expect.notEquals(0, result.exitCode);
Expect.contains(
'Shared memory multithreading in only available for '
'experimentation in dev or main',
result.stderr);
}
}
} finally {
tempDir.deleteSync(recursive: true);
}
asyncEnd();
}
@@ -0,0 +1,257 @@
// Copyright (c) 2024, 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.
//
// This exercises 'vm:shared' pragma.
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
sealed class Mutex {
Mutex._();
factory Mutex() => Platform.isWindows ? WindowsMutex() : PosixMutex();
factory Mutex.fromAddress(int address) => Platform.isWindows
? WindowsMutex.fromAddress(address)
: PosixMutex.fromAddress(address);
int get rawAddress;
void lock();
void unlock();
R holdingLock<R>(R Function() action) {
lock();
try {
return action();
} finally {
unlock();
}
}
}
//
// POSIX threading primitives
//
/// Represents `pthread_mutex_t`
final class PthreadMutex extends Opaque {}
/// Represents `pthread_cond_t`
final class PthreadCond extends Opaque {}
@Native<Int Function(Pointer<PthreadMutex>, Pointer<Void>)>()
external int pthread_mutex_init(
Pointer<PthreadMutex> mutex, Pointer<Void> attrs);
@Native<Int Function(Pointer<PthreadMutex>)>()
external int pthread_mutex_lock(Pointer<PthreadMutex> mutex);
@Native<Int Function(Pointer<PthreadMutex>)>()
external int pthread_mutex_unlock(Pointer<PthreadMutex> mutex);
@Native<Int Function(Pointer<PthreadMutex>)>()
external int pthread_mutex_destroy(Pointer<PthreadMutex> cond);
@Native<Int Function(Pointer<PthreadCond>, Pointer<Void>)>()
external int pthread_cond_init(Pointer<PthreadCond> cond, Pointer<Void> attrs);
@Native<Int Function(Pointer<PthreadCond>, Pointer<PthreadMutex>)>()
external int pthread_cond_wait(
Pointer<PthreadCond> cond, Pointer<PthreadMutex> mutex);
@Native<Int Function(Pointer<PthreadCond>)>()
external int pthread_cond_destroy(Pointer<PthreadCond> cond);
@Native<Int Function(Pointer<PthreadCond>)>()
external int pthread_cond_signal(Pointer<PthreadCond> cond);
class PosixMutex extends Mutex {
static const _sizeInBytes = 64;
final Pointer<PthreadMutex> _impl;
// TODO(@mraleph) this should be a native finalizer, also we probably want to
// do reference counting on the mutex so that the last owner destroys it.
static final _finalizer = Finalizer<Pointer<PthreadMutex>>((ptr) {
pthread_mutex_destroy(ptr);
calloc.free(ptr);
});
PosixMutex()
: _impl = calloc.allocate(PosixMutex._sizeInBytes),
super._() {
if (pthread_mutex_init(_impl, nullptr) != 0) {
calloc.free(_impl);
throw StateError('failed to initialize mutex');
}
_finalizer.attach(this, _impl);
}
PosixMutex.fromAddress(int address)
: _impl = Pointer.fromAddress(address),
super._();
@override
void lock() {
if (pthread_mutex_lock(_impl) != 0) {
throw StateError('failed to lock mutex');
}
}
@override
void unlock() {
if (pthread_mutex_unlock(_impl) != 0) {
throw StateError('failed to unlock mutex');
}
}
@override
int get rawAddress => _impl.address;
}
//
// WinAPI implementation of the synchronization primitives
//
final class SRWLOCK extends Opaque {}
@Native<Void Function(Pointer<SRWLOCK>)>()
external void InitializeSRWLock(Pointer<SRWLOCK> lock);
@Native<Void Function(Pointer<SRWLOCK>)>()
external void AcquireSRWLockExclusive(Pointer<SRWLOCK> lock);
@Native<Void Function(Pointer<SRWLOCK>)>()
external void ReleaseSRWLockExclusive(Pointer<SRWLOCK> mutex);
class WindowsMutex extends Mutex {
static const _sizeInBytes = 8;
final Pointer<SRWLOCK> _impl;
// TODO(@mraleph) this should be a native finalizer, also we probably want to
// do reference counting on the mutex so that the last owner destroys it.
static final _finalizer = Finalizer<Pointer<SRWLOCK>>((ptr) {
calloc.free(ptr);
});
WindowsMutex()
: _impl = calloc.allocate(WindowsMutex._sizeInBytes),
super._() {
InitializeSRWLock(_impl);
_finalizer.attach(this, _impl);
}
WindowsMutex.fromAddress(int address)
: _impl = Pointer.fromAddress(address),
super._();
@override
void lock() => AcquireSRWLockExclusive(_impl);
@override
void unlock() => ReleaseSRWLockExclusive(_impl);
@override
int get rawAddress => _impl.address;
}
class WorkItem {
int i;
int result = 0;
WorkItem(this.i);
doWork(SendPort results) {
// Calculate fibonacci number i.
if (i < 3) {
result = 1;
} else {
int pp = 1;
int p = 1;
int j = 3;
while (j <= i) {
result = pp + p;
pp = p;
p = result;
j++;
}
}
results.send(<int>[i, result]);
}
}
class SharedState {
@pragma('vm:shared')
static late int totalProcessed;
}
int totalWorkItems = 10000;
int numberOfWorkers = 8;
@pragma('vm:shared')
late List<WorkItem> workItems;
@pragma('vm:shared')
late int lastProcessed;
@pragma('vm:shared')
late Mutex mutex;
late var rpResults;
late var results = <int, int>{};
@pragma('vm:never-inline')
void init() {
SharedState.totalProcessed = 0;
lastProcessed = 0;
workItems = List<WorkItem>.generate(totalWorkItems, (i) => WorkItem(i + 1));
mutex = Mutex();
}
void main(List<String> args) async {
asyncStart();
if (args.length > 0) {
totalWorkItems = int.parse(args[0]);
if (args.length > 1) {
numberOfWorkers = int.parse(args[1]);
}
}
print('workItems: $totalWorkItems workers: $numberOfWorkers');
init();
rpResults = RawReceivePort((message) {
Expect.isFalse(results.containsKey(message[0]));
results[message[0]] = message[1];
});
var sendPort = rpResults.sendPort;
var list = List.generate(
numberOfWorkers,
(index) => Isolate.run(() async {
int countProcessed = 0;
while (true) {
var mine = mutex.holdingLock(() => lastProcessed++);
if (mine >= workItems.length) {
break;
}
workItems[mine].doWork(sendPort);
countProcessed++;
mutex.holdingLock(() => SharedState.totalProcessed++);
await Future.delayed(Duration(seconds: 0));
}
print('worker $index processed $countProcessed items');
}, debugName: 'worker $index'));
await Future.wait(list);
rpResults.close();
Expect.equals(results.keys.length, totalWorkItems);
Expect.equals(SharedState.totalProcessed, totalWorkItems);
print('all ${SharedState.totalProcessed} done');
asyncEnd();
}
+1
View File
@@ -191,6 +191,7 @@ cc/Service_Profile: SkipByDesign
dart/gc/splay_c_finalizer_test: SkipByDesign # No FFI on simulators
dart/isolates/dart_api_create_lightweight_isolate_test: SkipByDesign # https://dartbug.com/37299 Test uses dart:ffi which is not supported on simulators.
dart/isolates/regress_54528_test: SkipByDesign # Invokes gen_kernel/gen_snapshot
dart/isolates/shared_test: SkipByDesign # https://dartbug.com/37299 Test uses dart:ffi which is not supported on simulators.
dart/isolates/thread_pool_test: SkipByDesign # https://dartbug.com/37299 Test uses dart:ffi which is not supported on simulators.
dart/reachability_test: SkipByDesign # Test takes too long on the simulator
dart/regress_41971_test: SkipByDesign # https://dartbug.com/37299 dart:ffi is not supported on simulator
+32 -6
View File
@@ -7112,6 +7112,12 @@ class ProgramSerializationRoots : public SerializationRoots {
s->Push(initial_field_table->At(i));
}
FieldTable* shared_field_table =
s->thread()->isolate_group()->shared_field_table();
for (intptr_t i = 0, n = shared_field_table->NumFieldIds(); i < n; i++) {
s->Push(shared_field_table->At(i));
}
dispatch_table_entries_ = object_store_->dispatch_table_code_entries();
// We should only have a dispatch table in precompiled mode.
ASSERT(dispatch_table_entries_.IsNull() || s->kind() == Snapshot::kFullAOT);
@@ -7144,6 +7150,14 @@ class ProgramSerializationRoots : public SerializationRoots {
s->WriteRootRef(initial_field_table->At(i), "some-static-field");
}
FieldTable* shared_field_table =
s->thread()->isolate_group()->shared_field_table();
intptr_t n_shared = shared_field_table->NumFieldIds();
s->WriteUnsigned(n_shared);
for (intptr_t i = 0; i < n_shared; i++) {
s->WriteRootRef(shared_field_table->At(i), "some-shared-static-field");
}
// The dispatch table is serialized only for precompiled snapshots.
s->WriteDispatchTable(dispatch_table_entries_);
}
@@ -7187,12 +7201,24 @@ class ProgramDeserializationRoots : public DeserializationRoots {
*p = d->ReadRef();
}
FieldTable* initial_field_table =
d->thread()->isolate_group()->initial_field_table();
intptr_t n = d->ReadUnsigned();
initial_field_table->AllocateIndex(n - 1);
for (intptr_t i = 0; i < n; i++) {
initial_field_table->SetAt(i, d->ReadRef());
{
FieldTable* initial_field_table =
d->thread()->isolate_group()->initial_field_table();
intptr_t n = d->ReadUnsigned();
initial_field_table->AllocateIndex(n - 1);
for (intptr_t i = 0; i < n; i++) {
initial_field_table->SetAt(i, d->ReadRef());
}
}
{
FieldTable* shared_field_table =
d->thread()->isolate_group()->shared_field_table();
intptr_t n_shared = d->ReadUnsigned();
shared_field_table->AllocateIndex(n_shared);
for (intptr_t i = 0; i < n_shared; i++) {
shared_field_table->SetAt(i, d->ReadRef());
}
}
// Deserialize dispatch table (when applicable)
+3 -2
View File
@@ -1329,8 +1329,9 @@ void Precompiler::AddField(const Field& field) {
fields_to_retain_.Insert(&Field::ZoneHandle(Z, field.ptr()));
if (field.is_static()) {
const Object& value =
Object::Handle(Z, IG->initial_field_table()->At(field.field_id()));
auto field_table = field.is_shared() ? IG->shared_field_table()
: IG->initial_field_table();
const Object& value = Object::Handle(Z, field_table->At(field.field_id()));
// Should not be in the middle of initialization while precompiling.
ASSERT(value.ptr() != Object::transition_sentinel().ptr());
@@ -3825,11 +3825,13 @@ void Assembler::LoadElementAddressForRegIndex(Register address,
void Assembler::LoadStaticFieldAddress(Register address,
Register field,
Register scratch) {
Register scratch,
bool is_shared) {
LoadFieldFromOffset(scratch, field,
target::Field::host_offset_or_field_id_offset());
const intptr_t field_table_offset =
compiler::target::Thread::field_table_values_offset();
is_shared ? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset();
LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
add(address, address,
Operand(scratch, LSL, target::kWordSizeLog2 - kSmiTagShift));
@@ -1504,7 +1504,8 @@ class Assembler : public AssemblerBase {
void LoadStaticFieldAddress(Register address,
Register field,
Register scratch);
Register scratch,
bool is_shared);
void LoadFieldAddressForRegOffset(Register address,
Register instance,
@@ -2173,11 +2173,13 @@ void Assembler::ComputeElementAddressForRegIndex(Register address,
void Assembler::LoadStaticFieldAddress(Register address,
Register field,
Register scratch) {
Register scratch,
bool is_shared) {
LoadCompressedSmiFieldFromOffset(
scratch, field, target::Field::host_offset_or_field_id_offset());
const intptr_t field_table_offset =
compiler::target::Thread::field_table_values_offset();
is_shared ? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset();
LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
add(address, address,
Operand(scratch, LSL, target::kWordSizeLog2 - kSmiTagShift));
@@ -2247,7 +2247,8 @@ class Assembler : public AssemblerBase {
void LoadStaticFieldAddress(Register address,
Register field,
Register scratch);
Register scratch,
bool is_shared);
#if defined(DART_COMPRESSED_POINTERS)
void LoadCompressedFieldAddressForRegOffset(
@@ -952,11 +952,13 @@ class Assembler : public AssemblerBase {
void LoadStaticFieldAddress(Register address,
Register field,
Register scratch) {
Register scratch,
bool is_shared) {
LoadFieldFromOffset(scratch, field,
target::Field::host_offset_or_field_id_offset());
const intptr_t field_table_offset =
compiler::target::Thread::field_table_values_offset();
is_shared ? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset();
LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
static_assert(kSmiTagShift == 1, "adjust scale factor");
leal(address, Address(address, scratch, TIMES_HALF_WORD_SIZE, 0));
@@ -4634,11 +4634,13 @@ void Assembler::ComputeElementAddressForRegIndex(Register address,
void Assembler::LoadStaticFieldAddress(Register address,
Register field,
Register scratch) {
Register scratch,
bool is_shared) {
LoadCompressedSmiFieldFromOffset(
scratch, field, target::Field::host_offset_or_field_id_offset());
const intptr_t field_table_offset =
compiler::target::Thread::field_table_values_offset();
is_shared ? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset();
LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
slli(scratch, scratch, target::kWordSizeLog2 - kSmiTagShift);
add(address, address, scratch);
@@ -1455,7 +1455,8 @@ class Assembler : public MicroAssembler {
void LoadStaticFieldAddress(Register address,
Register field,
Register scratch);
Register scratch,
bool is_shared);
void LoadFieldAddressForRegOffset(Register address,
Register instance,
@@ -1310,12 +1310,14 @@ class Assembler : public AssemblerBase {
void LoadStaticFieldAddress(Register address,
Register field,
Register scratch) {
Register scratch,
bool is_shared) {
LoadCompressedSmi(
scratch, compiler::FieldAddress(
field, target::Field::host_offset_or_field_id_offset()));
const intptr_t field_table_offset =
compiler::target::Thread::field_table_values_offset();
is_shared ? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset();
LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
static_assert(kSmiTagShift == 1, "adjust scale factor");
leaq(address, Address(address, scratch, TIMES_HALF_WORD_SIZE, 0));
+11 -4
View File
@@ -4547,7 +4547,9 @@ void LoadStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// Note: static fields ids won't be changed by hot-reload.
const intptr_t field_table_offset =
compiler::target::Thread::field_table_values_offset();
field().is_shared()
? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset();
const intptr_t field_offset = compiler::target::FieldTable::OffsetOf(field());
__ LoadMemoryValue(result, THR, static_cast<int32_t>(field_table_offset));
@@ -4583,9 +4585,14 @@ void LoadStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// The stubs below call the initializer function directly, so make sure
// one is created.
original_field.EnsureInitializerFunction();
stub = field().is_final()
? object_store->init_late_final_static_field_stub()
: object_store->init_late_static_field_stub();
stub =
field().is_shared()
? (field().is_final()
? object_store->init_shared_late_final_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());
} else {
// We call to runtime for non-late fields because the stub would need to
// catch any exception generated by the initialization function to change
+6 -2
View File
@@ -2988,8 +2988,12 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
compiler->used_static_fields().Add(&field());
__ LoadFromOffset(temp, THR,
compiler::target::Thread::field_table_values_offset());
__ LoadFromOffset(
temp, THR,
field().is_shared()
? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset());
// Note: static fields ids won't be changed by hot-reload.
__ StoreToOffset(value, temp,
compiler::target::FieldTable::OffsetOf(field()));
+5 -2
View File
@@ -2600,8 +2600,11 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
compiler->used_static_fields().Add(&field());
__ LoadFromOffset(temp, THR,
compiler::target::Thread::field_table_values_offset());
__ LoadFromOffset(
temp, THR,
field().is_shared()
? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset());
// Note: static fields ids won't be changed by hot-reload.
__ StoreToOffset(value, temp,
compiler::target::FieldTable::OffsetOf(field()));
+4 -1
View File
@@ -2183,7 +2183,10 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ movl(temp,
compiler::Address(
THR, compiler::target::Thread::field_table_values_offset()));
THR,
field().is_shared()
? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset()));
// Note: static fields ids won't be changed by hot-reload.
__ movl(
compiler::Address(temp, compiler::target::FieldTable::OffsetOf(field())),
+5 -2
View File
@@ -2786,8 +2786,11 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
compiler->used_static_fields().Add(&field());
__ LoadFromOffset(TMP, THR,
compiler::target::Thread::field_table_values_offset());
__ LoadFromOffset(
TMP, THR,
field().is_shared()
? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset());
// Note: static fields ids won't be changed by hot-reload.
__ StoreToOffset(value, TMP, compiler::target::FieldTable::OffsetOf(field()));
}
+4 -1
View File
@@ -2566,7 +2566,10 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ movq(temp,
compiler::Address(
THR, compiler::target::Thread::field_table_values_offset()));
THR,
field().is_shared()
? compiler::target::Thread::shared_field_table_values_offset()
: compiler::target::Thread::field_table_values_offset()));
// Note: static fields ids won't be changed by hot-reload.
__ movq(
compiler::Address(temp, compiler::target::FieldTable::OffsetOf(field())),
+1
View File
@@ -1168,6 +1168,7 @@ class Thread : public AllStatic {
static word isolate_offset();
static word isolate_group_offset();
static word field_table_values_offset();
static word shared_field_table_values_offset();
static word store_buffer_block_offset();
static word call_to_runtime_entry_point_offset();
static word write_barrier_mask_offset();
File diff suppressed because it is too large Load Diff
@@ -315,6 +315,7 @@
FIELD(Thread, resume_pc_offset) \
FIELD(Thread, saved_shadow_call_stack_offset) \
FIELD(Thread, safepoint_state_offset) \
FIELD(Thread, shared_field_table_values_offset) \
FIELD(Thread, slow_type_test_stub_offset) \
FIELD(Thread, slow_type_test_entry_point_offset) \
FIELD(Thread, stack_limit_offset) \
+13 -4
View File
@@ -50,7 +50,8 @@ void StubCodeCompiler::GenerateInitStaticFieldStub() {
__ Ret();
}
void StubCodeCompiler::GenerateInitLateStaticFieldStub(bool is_final) {
void StubCodeCompiler::GenerateInitLateStaticFieldStub(bool is_final,
bool is_shared) {
const Register kResultReg = InitStaticFieldABI::kResultReg;
const Register kFieldReg = InitStaticFieldABI::kFieldReg;
const Register kAddressReg = InitLateStaticFieldInternalRegs::kAddressReg;
@@ -71,7 +72,7 @@ void StubCodeCompiler::GenerateInitLateStaticFieldStub(bool is_final) {
__ Call(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset()));
__ MoveRegister(kResultReg, CallingConventions::kReturnReg);
__ PopRegister(kFieldReg);
__ LoadStaticFieldAddress(kAddressReg, kFieldReg, kScratchReg);
__ LoadStaticFieldAddress(kAddressReg, kFieldReg, kScratchReg, is_shared);
Label throw_exception;
if (is_final) {
@@ -101,11 +102,19 @@ void StubCodeCompiler::GenerateInitLateStaticFieldStub(bool is_final) {
}
void StubCodeCompiler::GenerateInitLateStaticFieldStub() {
GenerateInitLateStaticFieldStub(/*is_final=*/false);
GenerateInitLateStaticFieldStub(/*is_final=*/false, /*is_shared=*/false);
}
void StubCodeCompiler::GenerateInitLateFinalStaticFieldStub() {
GenerateInitLateStaticFieldStub(/*is_final=*/true);
GenerateInitLateStaticFieldStub(/*is_final=*/true, /*shared=*/false);
}
void StubCodeCompiler::GenerateInitSharedLateStaticFieldStub() {
GenerateInitLateStaticFieldStub(/*is_final=*/false, /*is_shared=*/true);
}
void StubCodeCompiler::GenerateInitSharedLateFinalStaticFieldStub() {
GenerateInitLateStaticFieldStub(/*is_final=*/true, /*shared=*/true);
}
void StubCodeCompiler::GenerateInitInstanceFieldStub() {
+4 -3
View File
@@ -188,9 +188,10 @@ class StubCodeCompiler {
// stubs. Check architecture-specific version for inputs/outputs.
static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n);
// Common function for generating InitLateStaticField and
// InitLateFinalStaticField stubs.
void GenerateInitLateStaticFieldStub(bool is_final);
// Common function for generating InitLateStaticField,
// InitLateFinalStaticField, InitSharedLateStaticField,
// InitSharedLateFinalStaticField,
void GenerateInitLateStaticFieldStub(bool is_final, bool is_shared);
// Common function for generating InitLateInstanceField and
// InitLateFinalInstanceField stubs.
+6 -1
View File
@@ -55,6 +55,7 @@ intptr_t FieldTable::FieldOffsetFor(intptr_t field_id) {
bool FieldTable::Register(const Field& field, intptr_t expected_field_id) {
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(is_shared_ == field.is_shared());
ASSERT(is_ready_to_use_);
if (free_head_ < 0) {
@@ -119,7 +120,11 @@ void FieldTable::Grow(intptr_t new_capacity) {
// via store to table_.
reinterpret_cast<AcqRelAtomic<ObjectPtr*>*>(&table_)->store(new_table);
if (isolate_ != nullptr && isolate_->mutator_thread() != nullptr) {
isolate_->mutator_thread()->field_table_values_ = table_;
if (is_shared_) {
isolate_->mutator_thread()->shared_field_table_values_ = table_;
} else {
isolate_->mutator_thread()->field_table_values_ = table_;
}
}
}
+7 -2
View File
@@ -22,14 +22,15 @@ class FieldInvalidator;
class FieldTable {
public:
explicit FieldTable(Isolate* isolate)
explicit FieldTable(Isolate* isolate, bool is_shared = false)
: top_(0),
capacity_(0),
free_head_(-1),
table_(nullptr),
old_tables_(new MallocGrowableArray<ObjectPtr*>()),
isolate_(isolate),
is_ready_to_use_(isolate == nullptr) {}
is_ready_to_use_(isolate == nullptr),
is_shared_(is_shared) {}
~FieldTable();
@@ -118,6 +119,10 @@ class FieldTable {
// fields.
bool is_ready_to_use_ = false;
// Is this the shared field table? Need to know what is the isolate's property
// that have to be updated.
bool is_shared_ = false;
DISALLOW_COPY_AND_ASSIGN(FieldTable);
};
+28 -10
View File
@@ -351,6 +351,7 @@ IsolateGroup::IsolateGroup(std::shared_ptr<IsolateGroupSource> source,
heap_(nullptr),
saved_unlinked_calls_(Array::null()),
initial_field_table_(new FieldTable(/*isolate=*/nullptr)),
shared_field_table_(new FieldTable(/*isolate=*/nullptr, /*shared=*/true)),
#if !defined(DART_PRECOMPILED_RUNTIME)
background_compiler_(new BackgroundCompiler(this)),
#endif
@@ -786,6 +787,18 @@ void IsolateGroup::RegisterStaticField(const Field& field,
ASSERT(program_lock()->IsCurrentThreadWriter());
ASSERT(field.is_static());
if (field.is_shared()) {
GcSafepointOperationScope scope(Thread::Current());
if (shared_field_table()->Register(field)) {
for (auto isolate : isolates_) {
isolate->mutator_thread()->shared_field_table_values_ =
shared_field_table()->table();
}
}
const intptr_t field_id = field.field_id();
shared_field_table()->SetAt(field_id, initial_value.ptr());
return;
}
const bool need_to_grow_backing_store =
initial_field_table()->Register(field);
const intptr_t field_id = field.field_id();
@@ -821,16 +834,20 @@ void IsolateGroup::FreeStaticField(const Field& field) {
#endif
const intptr_t field_id = field.field_id();
initial_field_table()->Free(field_id);
ForEachIsolate([&](Isolate* isolate) {
auto field_table = isolate->field_table();
// The isolate might've just been created and is now participating in
// the reload request inside `IsolateGroup::RegisterIsolate()`.
// At that point it doesn't have the field table setup yet.
if (field_table->IsReadyToUse()) {
field_table->Free(field_id);
}
});
if (field.is_shared()) {
shared_field_table()->Free(field_id);
} else {
initial_field_table()->Free(field_id);
ForEachIsolate([&](Isolate* isolate) {
auto field_table = isolate->field_table();
// The isolate might've just been created and is now participating in
// the reload request inside `IsolateGroup::RegisterIsolate()`.
// At that point it doesn't have the field table setup yet.
if (field_table->IsReadyToUse()) {
field_table->Free(field_id);
}
});
}
}
Isolate* IsolateGroup::EnterTemporaryIsolate() {
@@ -2889,6 +2906,7 @@ void IsolateGroup::VisitSharedPointers(ObjectPointerVisitor* visitor) {
}
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&saved_unlinked_calls_));
initial_field_table()->VisitObjectPointers(visitor);
shared_field_table()->VisitObjectPointers(visitor);
// Visit the boxed_field_list_.
// 'boxed_field_list_' access via mutator and background compilation threads
+9
View File
@@ -733,6 +733,14 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
initial_field_table_ = field_table;
}
FieldTable* shared_field_table() const { return shared_field_table_.get(); }
std::shared_ptr<FieldTable> shared_field_table_shareable() {
return shared_field_table_;
}
void set_shared_field_table(std::shared_ptr<FieldTable> field_table) {
shared_field_table_ = field_table;
}
MutatorThreadPool* thread_pool() { return thread_pool_.get(); }
void RegisterClass(const Class& cls);
@@ -847,6 +855,7 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
intptr_t dispatch_table_snapshot_size_ = 0;
ArrayPtr saved_unlinked_calls_;
std::shared_ptr<FieldTable> initial_field_table_;
std::shared_ptr<FieldTable> shared_field_table_;
uint32_t isolate_group_flags_ = 0;
NOT_IN_PRECOMPILED(std::unique_ptr<BackgroundCompiler> background_compiler_);
+29
View File
@@ -24,6 +24,7 @@
#include "vm/service_isolate.h"
#include "vm/symbols.h"
#include "vm/thread.h"
#include "vm/version.h"
namespace dart {
namespace kernel {
@@ -36,6 +37,24 @@ namespace kernel {
static const char* const kVMServiceIOLibraryUri = "dart:vmservice_io";
static bool IsMainOrDevChannel() {
return strstr("|main|dev|", Version::Channel()) != nullptr;
}
static bool is_experimental_shared_data_enabled = false;
static void EnableExperimentSharedData(bool value) {
if (value && !IsMainOrDevChannel()) {
FATAL(
"Shared memory multithreading in only available for "
"experimentation in dev or main");
}
is_experimental_shared_data_enabled = value;
}
DEFINE_FLAG_HANDLER(EnableExperimentSharedData,
experimental_shared_data,
"Enable experiment to share data between isolates.");
class SimpleExpressionConverter {
public:
SimpleExpressionConverter(TranslationHelper* translation_helper,
@@ -1051,6 +1070,7 @@ void KernelLoader::FinishTopLevelClassLoading(
field.set_has_pragma(HasPragma::decode(pragma_bits));
field.set_is_extension_member(is_extension_member);
field.set_is_extension_type_member(is_extension_type_member);
field.set_is_shared(SharedPragma::decode(pragma_bits));
const AbstractType& type = T.BuildType(); // read type.
field.SetFieldType(type);
ReadInferredType(field, field_offset + library_kernel_offset_);
@@ -1460,6 +1480,7 @@ void KernelLoader::FinishClassLoading(const Class& klass,
field_helper.IsGenericCovariantImpl());
field.set_is_extension_member(is_extension_member);
field.set_is_extension_type_member(is_extension_type_member);
field.set_is_shared(SharedPragma::decode(pragma_bits));
ReadInferredType(field, field_offset + library_kernel_offset_);
CheckForInitializer(field);
// Static fields with initializers are implicitly late.
@@ -1755,6 +1776,14 @@ void KernelLoader::ReadVMAnnotations(intptr_t annotation_count,
if (constant_reader.IsStringConstant(name_index, "vm:ffi:native")) {
*pragma_bits = FfiNativePragma::update(true, *pragma_bits);
}
if (constant_reader.IsStringConstant(name_index, "vm:shared")) {
if (!is_experimental_shared_data_enabled) {
FATAL(
"Encountered vm:shared when functionality is disabled. "
"Pass --experimental-shared-data");
}
*pragma_bits = SharedPragma::update(true, *pragma_bits);
}
}
} else {
helper_.SkipExpression();
+1
View File
@@ -235,6 +235,7 @@ class KernelLoader : public ValueObject {
BitField<uint32_t, bool, IsolateUnsendablePragma::kNextBit, 1>;
using FfiNativePragma =
BitField<uint32_t, bool, DeeplyImmutablePragma::kNextBit, 1>;
using SharedPragma = BitField<uint32_t, bool, FfiNativePragma::kNextBit, 1>;
void FinishTopLevelClassLoading(const Class& toplevel_class,
const Library& library,
+6 -2
View File
@@ -12146,11 +12146,12 @@ const char* Field::ToCString() const {
const char* kF1 = is_late() ? " late" : "";
const char* kF2 = is_final() ? " final" : "";
const char* kF3 = is_const() ? " const" : "";
const char* kF4 = is_shared() ? " shared" : "";
const char* field_name = String::Handle(name()).ToCString();
const Class& cls = Class::Handle(Owner());
const char* cls_name = String::Handle(cls.Name()).ToCString();
return OS::SCreate(Thread::Current()->zone(), "Field <%s.%s>:%s%s%s%s",
cls_name, field_name, kF0, kF1, kF2, kF3);
return OS::SCreate(Thread::Current()->zone(), "Field <%s.%s>:%s%s%s%s%s",
cls_name, field_name, kF0, kF1, kF2, kF3, kF4);
}
// Build a closure object that gets (or sets) the contents of a static
@@ -12407,6 +12408,7 @@ ObjectPtr Field::StaticConstFieldValue() const {
// We can safely cache the value of the static const field in the initial
// field table.
ASSERT(!is_shared());
auto& value = Object::Handle(
zone, initial_field_table->At(field_id(), /*concurrent_use=*/true));
if (value.ptr() == Object::sentinel().ptr()) {
@@ -12428,6 +12430,7 @@ ObjectPtr Field::StaticConstFieldValue() const {
void Field::SetStaticConstFieldValue(const Instance& value,
bool assert_initializing_store) const {
ASSERT(is_static());
ASSERT(!is_shared());
auto thread = Thread::Current();
auto initial_field_table = thread->isolate_group()->initial_field_table();
@@ -12747,6 +12750,7 @@ 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());
+7
View File
@@ -4462,6 +4462,11 @@ class Field : public Object {
set_kind_bits(GenericCovariantImplBit::update(value, untag()->kind_bits_));
}
void set_is_shared(bool value) const {
set_kind_bits(SharedBit::update(value, untag()->kind_bits_));
}
bool is_shared() const { return SharedBit::decode(kind_bits()); }
intptr_t kernel_offset() const {
#if defined(DART_PRECOMPILED_RUNTIME)
return 0;
@@ -4831,6 +4836,7 @@ class Field : public Object {
kIsExtensionTypeMemberBit,
kNeedsLoadGuardBit,
kHasInitializerBit,
kSharedBit,
};
class ConstBit : public BitField<uint16_t, bool, kConstBit, 1> {};
class StaticBit : public BitField<uint16_t, bool, kStaticBit, 1> {};
@@ -4857,6 +4863,7 @@ class Field : public Object {
: public BitField<uint16_t, bool, kNeedsLoadGuardBit, 1> {};
class HasInitializerBit
: public BitField<uint16_t, bool, kHasInitializerBit, 1> {};
class SharedBit : public BitField<uint16_t, bool, kSharedBit, 1> {};
// Force this field's guard to be dynamic and deoptimize dependent code.
void ForceDynamicGuardedCidAndLength() const;
+4
View File
@@ -259,6 +259,8 @@ class ObjectPointerVisitor;
RW(Code, init_instance_field_stub) \
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) \
@@ -368,6 +370,8 @@ class ObjectPointerVisitor;
DO(init_instance_field_stub, InitInstanceField) \
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) \
+2
View File
@@ -148,6 +148,8 @@ namespace dart {
V(InitInstanceField) \
V(InitLateInstanceField) \
V(InitLateFinalInstanceField) \
V(InitSharedLateStaticField) \
V(InitSharedLateFinalStaticField) \
V(Throw) \
V(ReThrow) \
V(AssertBoolean) \
+4
View File
@@ -339,6 +339,7 @@ void Thread::AssertEmptyThreadInvariants() {
// Avoid running these asserts for `vm-isolate`.
if (active_stacktrace_.untag() != 0) {
ASSERT(field_table_values_ == nullptr);
ASSERT(shared_field_table_values_ == nullptr);
ASSERT(global_object_pool_ == Object::null());
#define CHECK_REUSABLE_HANDLE(object) ASSERT(object##_handle_->IsNull());
REUSABLE_HANDLE_LIST(CHECK_REUSABLE_HANDLE)
@@ -1401,6 +1402,8 @@ void Thread::SetupDartMutatorStateDependingOnSnapshot(IsolateGroup* group) {
#undef INIT_ENTRY_POINT
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
shared_field_table_values_ = group->shared_field_table()->table();
}
void Thread::ResetDartMutatorState(Isolate* isolate) {
@@ -1410,6 +1413,7 @@ void Thread::ResetDartMutatorState(Isolate* isolate) {
is_unwind_in_progress_ = false;
field_table_values_ = nullptr;
shared_field_table_values_ = nullptr;
ONLY_IN_PRECOMPILED(global_object_pool_ = ObjectPool::null());
ONLY_IN_PRECOMPILED(dispatch_table_array_ = nullptr);
}
+8
View File
@@ -544,6 +544,10 @@ class Thread : public ThreadState {
return OFFSET_OF(Thread, field_table_values_);
}
static intptr_t shared_field_table_values_offset() {
return OFFSET_OF(Thread, shared_field_table_values_);
}
bool IsDartMutatorThread() const {
return scheduled_dart_mutator_isolate_ != nullptr;
}
@@ -1177,6 +1181,7 @@ class Thread : public ThreadState {
uword end_ = 0;
const uword* dispatch_table_array_ = nullptr;
ObjectPtr* field_table_values_ = nullptr;
ObjectPtr* shared_field_table_values_ = nullptr;
// Offsets up to this point can all fit in a byte on X64. All of the above
// fields are very abundantly accessed from code. Thus, keeping them first
@@ -1319,6 +1324,9 @@ class Thread : public ThreadState {
ErrorPtr sticky_error_;
ObjectPtr* field_table_values() const { return field_table_values_; }
ObjectPtr* shared_field_table_values() const {
return shared_field_table_values_;
}
// Reusable handles support.
#define REUSABLE_HANDLE_FIELDS(object) object* object##_handle_;
+1
View File
@@ -165,6 +165,7 @@ void ThreadRegistry::ReturnToFreelistLocked(Thread* thread) {
ASSERT(thread->isolate_ == nullptr);
ASSERT(thread->isolate_group_ == nullptr);
ASSERT(thread->field_table_values_ == nullptr);
ASSERT(thread->shared_field_table_values_ == nullptr);
ASSERT(threads_lock()->IsOwnedByCurrentThread());
// Add thread to the free list.
thread->next_ = free_list_;
+2
View File
@@ -15,12 +15,14 @@ class Version : public AllStatic {
static const char* SnapshotString();
static const char* CommitString();
static const char* SdkHash();
static const char* Channel();
private:
static const char* str_;
static const char* snapshot_hash_;
static const char* commit_;
static const char* git_short_hash_;
static const char* channel_;
};
} // namespace dart
+5
View File
@@ -24,6 +24,10 @@ const char* Version::SdkHash() {
return git_short_hash_;
}
const char* Version::Channel() {
return channel_;
}
const char* Version::snapshot_hash_ = "{{SNAPSHOT_HASH}}";
const char* Version::str_ =
"{{VERSION_STR}} ({{CHANNEL}}) ({{COMMIT_TIME}})"
@@ -35,5 +39,6 @@ const char* Version::str_ =
kTargetArchitectureName "\"";
const char* Version::commit_ = "{{VERSION_STR}}";
const char* Version::git_short_hash_ = "{{GIT_HASH}}";
const char* Version::channel_ = "{{CHANNEL}}";
} // namespace dart