[vm] Introduce pragma vm:deeply-immutable
This CL introduces a way to mark all instances of a class as deeply immutable. In order to statically verify that all instances of a deeply immutable class are immutable, a deeply immutable classes must have the following properties: 1. All instance fields must 1. have a deeply immutable type, 2. be final, and 3. be non-late. 2. The class must be `final` or `sealed`. This ensures no non-deeply-immutable subtypes are added by external code. 3. All subtypes must be deeply immutable. This ensures 1.1 can be trusted. 4. The super type must be deeply immutable (except for Object). Note that instances of some classes in the VM are deeply immutable while their class cannot be marked immutable. * SendPort, Capability, RegExp, and StackTrace are not `final` and can be implemented by external code. * UnmodifiableTypedDataViews do not have a public type. (It was recently deprecated.) See runtime/docs/deeply_immutable.md for more details. Use case: This enables attaching a `Dart_FinalizableHandle` to a deeply immutable object and the deeply immutable object with other isolates in the same isolate group. (Note that `NativeFinalizer`s live in an isolate, and not an isolate group. So this should currently _not_ be used with `NativeFinalizer`s. See https://github.com/dart-lang/sdk/issues/55062 for making a `NativeFinalizer.shared(` that would live in an isolate group instead of in an isolate.) Implementation details: Before this CL, the `ImmutableBit` in the object header was only ever set to true for predefined class ids (and for const objects). After this CL, the bit can also be set to true for non const instances of user-defined classes. The object allocation and initialization code has been changed to deal with this new case. The immutability of a class is saved in the class state bits. On object allocation and initialization the immutability bit is read from the class for non-predefined class ids. TEST=runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart TEST=runtime/vm/isolate_reload_test.cc TEST=tests/lib/isolate/deeply_immutable_* Bug: https://github.com/dart-lang/sdk/issues/55120 Bug: https://github.com/dart-lang/sdk/issues/54885 Change-Id: Ib97fe589cb4f81673cb928c93e3093838d82132d Cq-Include-Trybots: luci.dart.try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-x64-try,vm-kernel-linux-debug-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-ubsan-linux-release-x64-try Cq-Include-Trybots: dart-internal/g3.dart-internal.try:g3-cbuild-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/354902 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
committed by
Commit Queue
parent
094202bb91
commit
8de00e2137
@@ -0,0 +1,79 @@
|
||||
# Deeply immutable instances and types
|
||||
|
||||
The Dart VM has a concept of deeply immutable instances.
|
||||
|
||||
Deeply immutable instances can be shared across isolates within the same group.
|
||||
|
||||
## Deeply immutable types
|
||||
|
||||
A deeply immutable type is a type for which all instances that have this type are deeply immutable.
|
||||
|
||||
This is useful for static checks on classes annotated `@pragma('vm:deeply-immutable')`.
|
||||
All the instance fields of such classes must have a deeply immutable type.
|
||||
|
||||
A list of immutable types:
|
||||
|
||||
* `bool`
|
||||
* `double`
|
||||
* `int`
|
||||
* `Null`
|
||||
* `String`
|
||||
* `Float32x4`
|
||||
* `Float64x2`
|
||||
* `Int32x4`
|
||||
* `Pointer`
|
||||
* classes annotated with `@pragma('vm:deeply-immutable')`
|
||||
* type parameters bound by a deeply immutable type
|
||||
|
||||
## Deeply immutable instances without a deeply immutable type
|
||||
|
||||
In addition to instances from deeply immutable types,
|
||||
instances can also be deeply immutable while their type is not deeply immutable:
|
||||
|
||||
* `SendPort` (implemented externally `package:isolate`, so cannot be `final` https://github.com/dart-lang/sdk/issues/54885#issuecomment-1967329435)
|
||||
* `Capability` (has `SendPort` as subtype so cannot be `final`)
|
||||
* `RegExp` (can be implemented externally, not `final`)
|
||||
* `StackTrace` (can be implemented externally, not `final`)
|
||||
* `Type` (can be implemented externally, not `final`)
|
||||
* const object (the class can be deeply immutable)
|
||||
|
||||
This means users cannot mark classes with fields typed with these types as `@pragma('vm:deeply-immutable')`.
|
||||
|
||||
## Shallowly immutable instances
|
||||
|
||||
The VM also has shallow immutability.
|
||||
|
||||
* unmodifiable typed data views (the backing view might not be immutable)
|
||||
* closures (the context might not be empty)
|
||||
|
||||
## Implementation details
|
||||
|
||||
### Deeply and shallowly immutable instances
|
||||
|
||||
The `UntaggedObject::ImmutableBit` tracks whether an instance is deeply or shallowly immutable at runtime.
|
||||
For shallow immutable objects, the VM needs to know the layout and what to check when to check for to check deep immutability at runtime.
|
||||
|
||||
### Deeply immutable types
|
||||
|
||||
The `Class::is_deeply_immutable` tracks whether all instances of a class are deeply immutable.
|
||||
|
||||
This bit can be set in two ways:
|
||||
|
||||
1. For recognized classes, in the VM initialization.
|
||||
2. For classes with a Dart source, with the `vm:deeply-immutable` pragma.
|
||||
|
||||
The `vm:deeply-immutable` pragma is added to classes of which their _type_ is deeply immutable.
|
||||
|
||||
This puts the following restrictions on these classes:
|
||||
|
||||
1. All instance fields must
|
||||
1. have a deeply immutable type,
|
||||
2. be final, and
|
||||
3. be non-late.
|
||||
2. The class must be `final` or `sealed`.
|
||||
This ensures no non-deeply-immutable subtypes are added by external code.
|
||||
3. All subtypes must be deeply immutable.
|
||||
This ensures 1.1. can be trusted.
|
||||
4. The super type must be deeply immutable (except for Object).
|
||||
|
||||
These restructions are enforced by [DeeplyImmutableValidator](../../pkg/vm/lib/transformations/ffi/deeply_immutable.dart).
|
||||
@@ -19,6 +19,7 @@ These pragmas are part of the VM's API and are safe for use in external code.
|
||||
| `weak-tearoff-reference` | [Declaring a static weak reference intrinsic method.](compiler/pragmas_recognized_by_compiler.md#declaring-a-static-weak-reference-intrinsic-method) |
|
||||
| `vm:isolate-unsendable` | Marks a class, instances of which won't be allowed to be passed through ports or sent between isolates. |
|
||||
| `vm:awaiter-link` | [Specifying variable to follow for awaiter stack unwinding](awaiter_stack_traces.md) |
|
||||
| `vm:deeply-immutable` | [Specifying a class and all its subtypes are deeply immutable](deeply_immutable.md) |
|
||||
|
||||
## Unsafe pragmas for general use
|
||||
|
||||
|
||||
Reference in New Issue
Block a user