[vm/shared] Perform deeply-immutable initialization runtime check.
When an initial value is assigned into a class tagged as deeply-immutable, perform runtime check of that value. This is needed to support proper initialization of the closures as part of deeply-immutable classes. BUG=https://github.com/dart-lang/sdk/issues/61962 TEST=run_isolate_group_run_test Change-Id: I550746c0d22ca06ffb89959e8384cc9e6d28d590 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/468200 Commit-Queue: Alexander Aprelev <aam@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
fc3ce5a24e
commit
eb75c53d95
@@ -141,6 +141,8 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
int maxSourcePosition = 0;
|
||||
Member? dynModuleEntryPoint;
|
||||
|
||||
bool isInDeeplyImmutableClass = false;
|
||||
|
||||
BytecodeGenerator(
|
||||
ast.Component component,
|
||||
CoreTypes coreTypes,
|
||||
@@ -196,6 +198,9 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
|
||||
@override
|
||||
void visitClass(Class node) {
|
||||
isInDeeplyImmutableClass =
|
||||
pragmaParser.parsedPragmas<ParsedVmDeeplyImmutablePragma>(
|
||||
node.annotations).isNotEmpty;
|
||||
startMembers();
|
||||
visitList(node.constructors, this);
|
||||
visitList(node.procedures, this);
|
||||
@@ -323,6 +328,11 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
flags |= ClassDeclaration.hasAnnotationsFlag;
|
||||
if (annotations.hasPragma) {
|
||||
flags |= ClassDeclaration.hasPragmaFlag;
|
||||
if (pragmaParser
|
||||
.parsedPragmas<ParsedVmDeeplyImmutablePragma>(cls.annotations)
|
||||
.isNotEmpty) {
|
||||
flags |= ClassDeclaration.isDeeplyImmutableFlag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1046,6 +1056,10 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
? ffiLibraryIndex.getTopLevelProcedure('dart:ffi', '_ffiCall')
|
||||
: null;
|
||||
|
||||
late Procedure ensureDeeplyImmutable =
|
||||
libraryIndex.getTopLevelProcedure('dart:_internal',
|
||||
'_ensureDeeplyImmutable');
|
||||
|
||||
// Selector for implicit dynamic calls 'foo(...)' where
|
||||
// variable 'foo' has type 'dynamic'.
|
||||
late final implicitCallName = Name('implicit:call');
|
||||
@@ -1136,6 +1150,12 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
_generateNode(initializer);
|
||||
|
||||
final int cpIndex = cp.addInstanceField(field);
|
||||
if (isInDeeplyImmutableClass) {
|
||||
// TODO(dartbug.com/61078): Use static type to avoid runtime check.
|
||||
_genDirectCall(
|
||||
ensureDeeplyImmutable, objectTable.getArgDescHandle(1), 1);
|
||||
}
|
||||
|
||||
asm.emitStoreFieldTOS(cpIndex);
|
||||
|
||||
initializedFields.add(field);
|
||||
|
||||
@@ -100,6 +100,7 @@ class ClassDeclaration extends BytecodeDeclaration {
|
||||
static const isBaseClassFlag = 1 << 11;
|
||||
static const isInterfaceFlag = 1 << 12;
|
||||
static const isFinalFlag = 1 << 13;
|
||||
static const isDeeplyImmutableFlag = 1 << 14;
|
||||
|
||||
ObjectHandle? name;
|
||||
final int flags;
|
||||
@@ -200,6 +201,9 @@ class ClassDeclaration extends BytecodeDeclaration {
|
||||
if ((flags & hasPragmaFlag) != 0) {
|
||||
sb.write(', has-pragma');
|
||||
}
|
||||
if ((flags & isDeeplyImmutableFlag) != 0) {
|
||||
sb.write(', deeply-immutable');
|
||||
}
|
||||
if ((flags & hasSourcePositionsFlag) != 0) {
|
||||
sb.write(', pos = $position, end-pos = $endPosition');
|
||||
}
|
||||
|
||||
@@ -4666,7 +4666,7 @@ const MessageCode
|
||||
codeFfiDeeplyImmutableFieldsMustBeDeeplyImmutable = const MessageCode(
|
||||
"FfiDeeplyImmutableFieldsMustBeDeeplyImmutable",
|
||||
problemMessage:
|
||||
"""Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', and classes annotated with `@pragma('vm:deeply-immutable')`.""",
|
||||
"""Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', function types and classes annotated with `@pragma('vm:deeply-immutable')`.""",
|
||||
correctionMessage:
|
||||
"""Try changing the type of this field to a deeply immutable type or mark the type of this field as deeply immutable.""",
|
||||
);
|
||||
|
||||
@@ -5037,7 +5037,7 @@ ffiDeeplyImmutableClassesMustBeFinalOrSealed:
|
||||
ffiDeeplyImmutableFieldsMustBeDeeplyImmutable:
|
||||
# Used by dart:ffi
|
||||
parameters: none
|
||||
problemMessage: "Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', and classes annotated with `@pragma('vm:deeply-immutable')`."
|
||||
problemMessage: "Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', function types and classes annotated with `@pragma('vm:deeply-immutable')`."
|
||||
correctionMessage: 'Try changing the type of this field to a deeply immutable type or mark the type of this field as deeply immutable.'
|
||||
external: test/ffi_test.dart
|
||||
|
||||
|
||||
@@ -881,11 +881,11 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_AllocateArray_entry_point_offset => 0x2e8;
|
||||
@override
|
||||
int get Thread_DeoptimizeCopyFrame_entry_point_offset => 0x700;
|
||||
int get Thread_DeoptimizeCopyFrame_entry_point_offset => 0x708;
|
||||
@override
|
||||
int get Thread_active_exception_offset => 0x6c0;
|
||||
int get Thread_active_exception_offset => 0x6c8;
|
||||
@override
|
||||
int get Thread_active_stacktrace_offset => 0x6c8;
|
||||
int get Thread_active_stacktrace_offset => 0x6d0;
|
||||
@override
|
||||
int get Thread_array_write_barrier_entry_point_offset => 0x200;
|
||||
@override
|
||||
@@ -903,7 +903,7 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_allocate_object_slow_entry_point_offset => 0x230;
|
||||
@override
|
||||
int get Thread_api_top_scope_offset => 0x890;
|
||||
int get Thread_api_top_scope_offset => 0x898;
|
||||
@override
|
||||
int get Thread_async_exception_handler_stub_offset => 0x160;
|
||||
@override
|
||||
@@ -919,15 +919,15 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_call_to_runtime_stub_offset => 0xd8;
|
||||
@override
|
||||
int get Thread_dart_stream_offset => 0x8e8;
|
||||
int get Thread_dart_stream_offset => 0x8f0;
|
||||
@override
|
||||
int get Thread_dispatch_table_array_offset => 0x68;
|
||||
@override
|
||||
int get Thread_double_truncate_round_supported_offset => 0x898;
|
||||
int get Thread_double_truncate_round_supported_offset => 0x8a0;
|
||||
@override
|
||||
int get Thread_service_extension_stream_offset => 0x8f0;
|
||||
int get Thread_service_extension_stream_offset => 0x8f8;
|
||||
@override
|
||||
int get Thread_thread_locals_offset => 0x8f8;
|
||||
int get Thread_thread_locals_offset => 0x900;
|
||||
@override
|
||||
int get Thread_optimize_entry_offset => 0x258;
|
||||
@override
|
||||
@@ -945,7 +945,7 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_enter_safepoint_stub_offset => 0x1e0;
|
||||
@override
|
||||
int get Thread_execution_state_offset => 0x6e8;
|
||||
int get Thread_execution_state_offset => 0x6f0;
|
||||
@override
|
||||
int get Thread_exit_safepoint_stub_offset => 0x1e8;
|
||||
@override
|
||||
@@ -965,7 +965,7 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_float_zerow_address_offset => 0x2e0;
|
||||
@override
|
||||
int get Thread_global_object_pool_offset => 0x6d0;
|
||||
int get Thread_global_object_pool_offset => 0x6d8;
|
||||
@override
|
||||
int get Thread_interpret_call_entry_point_offset => 0x2a0;
|
||||
@override
|
||||
@@ -973,11 +973,11 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_invoke_dart_code_stub_offset => 0xc8;
|
||||
@override
|
||||
int get Thread_exit_through_ffi_offset => 0x6f8;
|
||||
int get Thread_exit_through_ffi_offset => 0x700;
|
||||
@override
|
||||
int get Thread_isolate_offset => 0x670;
|
||||
int get Thread_isolate_offset => 0x678;
|
||||
@override
|
||||
int get Thread_isolate_group_offset => 0x678;
|
||||
int get Thread_isolate_group_offset => 0x680;
|
||||
@override
|
||||
int get Thread_field_table_values_offset => 0x70;
|
||||
@override
|
||||
@@ -987,9 +987,9 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_lazy_specialize_type_test_stub_offset => 0x1d8;
|
||||
@override
|
||||
int get Thread_old_marking_stack_block_offset => 0x6a0;
|
||||
int get Thread_old_marking_stack_block_offset => 0x6a8;
|
||||
@override
|
||||
int get Thread_new_marking_stack_block_offset => 0x6a8;
|
||||
int get Thread_new_marking_stack_block_offset => 0x6b0;
|
||||
@override
|
||||
int get Thread_megamorphic_call_checked_entry_offset => 0x248;
|
||||
@override
|
||||
@@ -1045,15 +1045,15 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_resume_interpreter_adjusted_entry_point_offset => 0x280;
|
||||
@override
|
||||
int get Thread_resume_pc_offset => 0x6d8;
|
||||
int get Thread_resume_pc_offset => 0x6e0;
|
||||
@override
|
||||
int get Thread_saved_shadow_call_stack_offset => 0x6e0;
|
||||
int get Thread_saved_shadow_call_stack_offset => 0x6e8;
|
||||
@override
|
||||
int get Thread_safepoint_state_offset => 0x6f0;
|
||||
int get Thread_safepoint_state_offset => 0x6f8;
|
||||
@override
|
||||
int get Thread_shared_field_table_values_offset => 0x78;
|
||||
@override
|
||||
int get Thread_single_step_offset => 0x8c8;
|
||||
int get Thread_single_step_offset => 0x8d0;
|
||||
@override
|
||||
int get Thread_slow_type_test_stub_offset => 0x1d0;
|
||||
@override
|
||||
@@ -1061,9 +1061,9 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_stack_limit_offset => 0x48;
|
||||
@override
|
||||
int get Thread_saved_stack_limit_offset => 0x680;
|
||||
int get Thread_saved_stack_limit_offset => 0x688;
|
||||
@override
|
||||
int get Thread_stack_overflow_flags_offset => 0x688;
|
||||
int get Thread_stack_overflow_flags_offset => 0x690;
|
||||
@override
|
||||
int get Thread_stack_overflow_shared_with_fpu_regs_entry_point_offset =>
|
||||
0x240;
|
||||
@@ -1075,60 +1075,60 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_stack_overflow_shared_without_fpu_regs_stub_offset => 0x188;
|
||||
@override
|
||||
int get Thread_store_buffer_block_offset => 0x698;
|
||||
int get Thread_store_buffer_block_offset => 0x6a0;
|
||||
@override
|
||||
int get Thread_suspend_state_await_entry_point_offset => 0x620;
|
||||
int get Thread_suspend_state_await_entry_point_offset => 0x628;
|
||||
@override
|
||||
int get Thread_suspend_state_await_with_type_check_entry_point_offset =>
|
||||
0x628;
|
||||
0x630;
|
||||
@override
|
||||
int get Thread_suspend_state_init_async_entry_point_offset => 0x618;
|
||||
int get Thread_suspend_state_init_async_entry_point_offset => 0x620;
|
||||
@override
|
||||
int get Thread_suspend_state_return_async_entry_point_offset => 0x630;
|
||||
int get Thread_suspend_state_return_async_entry_point_offset => 0x638;
|
||||
@override
|
||||
int get Thread_suspend_state_return_async_not_future_entry_point_offset =>
|
||||
0x638;
|
||||
0x640;
|
||||
@override
|
||||
int get Thread_suspend_state_init_async_star_entry_point_offset => 0x640;
|
||||
int get Thread_suspend_state_init_async_star_entry_point_offset => 0x648;
|
||||
@override
|
||||
int get Thread_suspend_state_yield_async_star_entry_point_offset => 0x648;
|
||||
int get Thread_suspend_state_yield_async_star_entry_point_offset => 0x650;
|
||||
@override
|
||||
int get Thread_suspend_state_return_async_star_entry_point_offset => 0x650;
|
||||
int get Thread_suspend_state_return_async_star_entry_point_offset => 0x658;
|
||||
@override
|
||||
int get Thread_suspend_state_init_sync_star_entry_point_offset => 0x658;
|
||||
int get Thread_suspend_state_init_sync_star_entry_point_offset => 0x660;
|
||||
@override
|
||||
int get Thread_suspend_state_suspend_sync_star_at_start_entry_point_offset =>
|
||||
0x660;
|
||||
0x668;
|
||||
@override
|
||||
int get Thread_suspend_state_handle_exception_entry_point_offset => 0x668;
|
||||
int get Thread_suspend_state_handle_exception_entry_point_offset => 0x670;
|
||||
@override
|
||||
int get Thread_top_exit_frame_info_offset => 0x690;
|
||||
int get Thread_top_exit_frame_info_offset => 0x698;
|
||||
@override
|
||||
int get Thread_top_offset => 0x58;
|
||||
@override
|
||||
int get Thread_top_resource_offset => 0x20;
|
||||
@override
|
||||
int get Thread_unboxed_runtime_arg_offset => 0x8a0;
|
||||
int get Thread_unboxed_runtime_arg_offset => 0x8a8;
|
||||
@override
|
||||
int get Thread_vm_tag_offset => 0x6b8;
|
||||
int get Thread_vm_tag_offset => 0x6c0;
|
||||
@override
|
||||
int get Thread_write_barrier_entry_point_offset => 0x1f8;
|
||||
@override
|
||||
int get Thread_write_barrier_mask_offset => 0x50;
|
||||
@override
|
||||
int get Thread_next_task_id_offset => 0x8b0;
|
||||
int get Thread_next_task_id_offset => 0x8b8;
|
||||
@override
|
||||
int get Thread_random_offset => 0x8b8;
|
||||
int get Thread_random_offset => 0x8c0;
|
||||
@override
|
||||
int get Thread_jump_to_frame_entry_point_offset => 0x270;
|
||||
@override
|
||||
int get Thread_tsan_utils_offset => 0x8c0;
|
||||
int get Thread_tsan_utils_offset => 0x8c8;
|
||||
@override
|
||||
int get Thread_current_tag_offset => 0x8d8;
|
||||
int get Thread_current_tag_offset => 0x8e0;
|
||||
@override
|
||||
int get Thread_default_tag_offset => 0x8e0;
|
||||
int get Thread_default_tag_offset => 0x8e8;
|
||||
@override
|
||||
int get Thread_user_tag_offset => 0x8d0;
|
||||
int get Thread_user_tag_offset => 0x8d8;
|
||||
@override
|
||||
int get TsanUtils_setjmp_function_offset => 0x0;
|
||||
@override
|
||||
@@ -1231,7 +1231,6 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
List<int> get Code_entry_point_offset => [0x8, 0x18, 0x10, 0x20];
|
||||
@override
|
||||
List<int> get Thread_write_barrier_wrappers_thread_offset => [
|
||||
0x578,
|
||||
0x580,
|
||||
0x588,
|
||||
0x590,
|
||||
@@ -1246,17 +1245,18 @@ final class Arm64VMOffsets extends VMOffsets {
|
||||
0x5d8,
|
||||
0x5e0,
|
||||
0x5e8,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
0x5f0,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
0x5f8,
|
||||
-1,
|
||||
-1,
|
||||
0x600,
|
||||
-1,
|
||||
-1,
|
||||
0x608,
|
||||
0x610,
|
||||
0x618,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
@@ -1786,11 +1786,11 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_AllocateArray_entry_point_offset => 0x2e8;
|
||||
@override
|
||||
int get Thread_DeoptimizeCopyFrame_entry_point_offset => 0x700;
|
||||
int get Thread_DeoptimizeCopyFrame_entry_point_offset => 0x708;
|
||||
@override
|
||||
int get Thread_active_exception_offset => 0x6c0;
|
||||
int get Thread_active_exception_offset => 0x6c8;
|
||||
@override
|
||||
int get Thread_active_stacktrace_offset => 0x6c8;
|
||||
int get Thread_active_stacktrace_offset => 0x6d0;
|
||||
@override
|
||||
int get Thread_array_write_barrier_entry_point_offset => 0x200;
|
||||
@override
|
||||
@@ -1808,7 +1808,7 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_allocate_object_slow_entry_point_offset => 0x230;
|
||||
@override
|
||||
int get Thread_api_top_scope_offset => 0x890;
|
||||
int get Thread_api_top_scope_offset => 0x898;
|
||||
@override
|
||||
int get Thread_async_exception_handler_stub_offset => 0x160;
|
||||
@override
|
||||
@@ -1824,15 +1824,15 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_call_to_runtime_stub_offset => 0xd8;
|
||||
@override
|
||||
int get Thread_dart_stream_offset => 0x8e8;
|
||||
int get Thread_dart_stream_offset => 0x8f0;
|
||||
@override
|
||||
int get Thread_dispatch_table_array_offset => 0x68;
|
||||
@override
|
||||
int get Thread_double_truncate_round_supported_offset => 0x898;
|
||||
int get Thread_double_truncate_round_supported_offset => 0x8a0;
|
||||
@override
|
||||
int get Thread_service_extension_stream_offset => 0x8f0;
|
||||
int get Thread_service_extension_stream_offset => 0x8f8;
|
||||
@override
|
||||
int get Thread_thread_locals_offset => 0x8f8;
|
||||
int get Thread_thread_locals_offset => 0x900;
|
||||
@override
|
||||
int get Thread_optimize_entry_offset => 0x258;
|
||||
@override
|
||||
@@ -1850,7 +1850,7 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_enter_safepoint_stub_offset => 0x1e0;
|
||||
@override
|
||||
int get Thread_execution_state_offset => 0x6e8;
|
||||
int get Thread_execution_state_offset => 0x6f0;
|
||||
@override
|
||||
int get Thread_exit_safepoint_stub_offset => 0x1e8;
|
||||
@override
|
||||
@@ -1870,7 +1870,7 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_float_zerow_address_offset => 0x2e0;
|
||||
@override
|
||||
int get Thread_global_object_pool_offset => 0x6d0;
|
||||
int get Thread_global_object_pool_offset => 0x6d8;
|
||||
@override
|
||||
int get Thread_interpret_call_entry_point_offset => 0x2a0;
|
||||
@override
|
||||
@@ -1878,11 +1878,11 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_invoke_dart_code_stub_offset => 0xc8;
|
||||
@override
|
||||
int get Thread_exit_through_ffi_offset => 0x6f8;
|
||||
int get Thread_exit_through_ffi_offset => 0x700;
|
||||
@override
|
||||
int get Thread_isolate_offset => 0x670;
|
||||
int get Thread_isolate_offset => 0x678;
|
||||
@override
|
||||
int get Thread_isolate_group_offset => 0x678;
|
||||
int get Thread_isolate_group_offset => 0x680;
|
||||
@override
|
||||
int get Thread_field_table_values_offset => 0x70;
|
||||
@override
|
||||
@@ -1892,9 +1892,9 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_lazy_specialize_type_test_stub_offset => 0x1d8;
|
||||
@override
|
||||
int get Thread_old_marking_stack_block_offset => 0x6a0;
|
||||
int get Thread_old_marking_stack_block_offset => 0x6a8;
|
||||
@override
|
||||
int get Thread_new_marking_stack_block_offset => 0x6a8;
|
||||
int get Thread_new_marking_stack_block_offset => 0x6b0;
|
||||
@override
|
||||
int get Thread_megamorphic_call_checked_entry_offset => 0x248;
|
||||
@override
|
||||
@@ -1950,11 +1950,11 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_resume_interpreter_adjusted_entry_point_offset => 0x280;
|
||||
@override
|
||||
int get Thread_resume_pc_offset => 0x6d8;
|
||||
int get Thread_resume_pc_offset => 0x6e0;
|
||||
@override
|
||||
int get Thread_saved_shadow_call_stack_offset => 0x6e0;
|
||||
int get Thread_saved_shadow_call_stack_offset => 0x6e8;
|
||||
@override
|
||||
int get Thread_safepoint_state_offset => 0x6f0;
|
||||
int get Thread_safepoint_state_offset => 0x6f8;
|
||||
@override
|
||||
int get Thread_shared_field_table_values_offset => 0x78;
|
||||
@override
|
||||
@@ -1964,9 +1964,9 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_stack_limit_offset => 0x48;
|
||||
@override
|
||||
int get Thread_saved_stack_limit_offset => 0x680;
|
||||
int get Thread_saved_stack_limit_offset => 0x688;
|
||||
@override
|
||||
int get Thread_stack_overflow_flags_offset => 0x688;
|
||||
int get Thread_stack_overflow_flags_offset => 0x690;
|
||||
@override
|
||||
int get Thread_stack_overflow_shared_with_fpu_regs_entry_point_offset =>
|
||||
0x240;
|
||||
@@ -1978,60 +1978,60 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
@override
|
||||
int get Thread_stack_overflow_shared_without_fpu_regs_stub_offset => 0x188;
|
||||
@override
|
||||
int get Thread_store_buffer_block_offset => 0x698;
|
||||
int get Thread_store_buffer_block_offset => 0x6a0;
|
||||
@override
|
||||
int get Thread_suspend_state_await_entry_point_offset => 0x620;
|
||||
int get Thread_suspend_state_await_entry_point_offset => 0x628;
|
||||
@override
|
||||
int get Thread_suspend_state_await_with_type_check_entry_point_offset =>
|
||||
0x628;
|
||||
0x630;
|
||||
@override
|
||||
int get Thread_suspend_state_init_async_entry_point_offset => 0x618;
|
||||
int get Thread_suspend_state_init_async_entry_point_offset => 0x620;
|
||||
@override
|
||||
int get Thread_suspend_state_return_async_entry_point_offset => 0x630;
|
||||
int get Thread_suspend_state_return_async_entry_point_offset => 0x638;
|
||||
@override
|
||||
int get Thread_suspend_state_return_async_not_future_entry_point_offset =>
|
||||
0x638;
|
||||
0x640;
|
||||
@override
|
||||
int get Thread_suspend_state_init_async_star_entry_point_offset => 0x640;
|
||||
int get Thread_suspend_state_init_async_star_entry_point_offset => 0x648;
|
||||
@override
|
||||
int get Thread_suspend_state_yield_async_star_entry_point_offset => 0x648;
|
||||
int get Thread_suspend_state_yield_async_star_entry_point_offset => 0x650;
|
||||
@override
|
||||
int get Thread_suspend_state_return_async_star_entry_point_offset => 0x650;
|
||||
int get Thread_suspend_state_return_async_star_entry_point_offset => 0x658;
|
||||
@override
|
||||
int get Thread_suspend_state_init_sync_star_entry_point_offset => 0x658;
|
||||
int get Thread_suspend_state_init_sync_star_entry_point_offset => 0x660;
|
||||
@override
|
||||
int get Thread_suspend_state_suspend_sync_star_at_start_entry_point_offset =>
|
||||
0x660;
|
||||
0x668;
|
||||
@override
|
||||
int get Thread_suspend_state_handle_exception_entry_point_offset => 0x668;
|
||||
int get Thread_suspend_state_handle_exception_entry_point_offset => 0x670;
|
||||
@override
|
||||
int get Thread_top_exit_frame_info_offset => 0x690;
|
||||
int get Thread_top_exit_frame_info_offset => 0x698;
|
||||
@override
|
||||
int get Thread_top_offset => 0x58;
|
||||
@override
|
||||
int get Thread_top_resource_offset => 0x20;
|
||||
@override
|
||||
int get Thread_unboxed_runtime_arg_offset => 0x8a0;
|
||||
int get Thread_unboxed_runtime_arg_offset => 0x8a8;
|
||||
@override
|
||||
int get Thread_vm_tag_offset => 0x6b8;
|
||||
int get Thread_vm_tag_offset => 0x6c0;
|
||||
@override
|
||||
int get Thread_write_barrier_entry_point_offset => 0x1f8;
|
||||
@override
|
||||
int get Thread_write_barrier_mask_offset => 0x50;
|
||||
@override
|
||||
int get Thread_next_task_id_offset => 0x8b0;
|
||||
int get Thread_next_task_id_offset => 0x8b8;
|
||||
@override
|
||||
int get Thread_random_offset => 0x8b8;
|
||||
int get Thread_random_offset => 0x8c0;
|
||||
@override
|
||||
int get Thread_jump_to_frame_entry_point_offset => 0x270;
|
||||
@override
|
||||
int get Thread_tsan_utils_offset => 0x8c0;
|
||||
int get Thread_tsan_utils_offset => 0x8c8;
|
||||
@override
|
||||
int get Thread_current_tag_offset => 0x8d8;
|
||||
int get Thread_current_tag_offset => 0x8e0;
|
||||
@override
|
||||
int get Thread_default_tag_offset => 0x8e0;
|
||||
int get Thread_default_tag_offset => 0x8e8;
|
||||
@override
|
||||
int get Thread_user_tag_offset => 0x8d0;
|
||||
int get Thread_user_tag_offset => 0x8d8;
|
||||
@override
|
||||
int get TsanUtils_setjmp_function_offset => 0x0;
|
||||
@override
|
||||
@@ -2134,7 +2134,6 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
List<int> get Code_entry_point_offset => [0x8, 0x18, 0x10, 0x20];
|
||||
@override
|
||||
List<int> get Thread_write_barrier_wrappers_thread_offset => [
|
||||
0x578,
|
||||
0x580,
|
||||
0x588,
|
||||
0x590,
|
||||
@@ -2149,17 +2148,18 @@ final class Arm64ProductVMOffsets extends VMOffsets {
|
||||
0x5d8,
|
||||
0x5e0,
|
||||
0x5e8,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
0x5f0,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
0x5f8,
|
||||
-1,
|
||||
-1,
|
||||
0x600,
|
||||
-1,
|
||||
-1,
|
||||
0x608,
|
||||
0x610,
|
||||
0x618,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
@@ -2498,6 +2498,7 @@ enum StubCode {
|
||||
FfiAsyncCallbackSend,
|
||||
CheckIsolateFieldAccess,
|
||||
CheckedStoreIntoShared,
|
||||
EnsureDeeplyImmutable,
|
||||
UnknownDartCode,
|
||||
}
|
||||
|
||||
@@ -2584,6 +2585,7 @@ enum RuntimeEntry {
|
||||
ResumeInterpreter,
|
||||
InitializeSharedField,
|
||||
FatalError,
|
||||
EnsureDeeplyImmutable,
|
||||
}
|
||||
|
||||
enum LeafRuntimeEntry {
|
||||
|
||||
@@ -139,16 +139,6 @@ class DeeplyImmutableValidator {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
// but we allow it.
|
||||
// TODO(dartbug.com/61962): remove this once the bug is fixed.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// All instance fields should be non-late final and deeply immutable.
|
||||
for (final field in node.fields) {
|
||||
if (field.isStatic) {
|
||||
@@ -185,6 +175,11 @@ class DeeplyImmutableValidator {
|
||||
if (dartType is TypeParameterType) {
|
||||
return _isDeeplyImmutableDartType(dartType.bound);
|
||||
}
|
||||
if (dartType is FunctionType) {
|
||||
// Relies on dynamic check of whether closure actually captures only
|
||||
// deeply-immutable values.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ const kVmPlatformConstPragmaName = "vm:platform-const";
|
||||
const kVmPlatformConstIfPragmaName = "vm:platform-const-if";
|
||||
const kVmFfiNative = "vm:ffi:native";
|
||||
const kVmSharedPragmaName = "vm:shared";
|
||||
const kVmDeeplyImmutablePragmaName = "vm:deeply-immutable";
|
||||
|
||||
// Pragmas recognized by dart2wasm
|
||||
const kWasmEntryPointPragmaName = "wasm:entry-point";
|
||||
@@ -96,6 +97,10 @@ class ParsedVmSharedPragma implements ParsedPragma {
|
||||
const ParsedVmSharedPragma();
|
||||
}
|
||||
|
||||
class ParsedVmDeeplyImmutablePragma implements ParsedPragma {
|
||||
const ParsedVmDeeplyImmutablePragma();
|
||||
}
|
||||
|
||||
abstract class PragmaAnnotationParser {
|
||||
/// May return 'null' if the annotation does not represent a recognized
|
||||
/// @pragma.
|
||||
@@ -241,6 +246,8 @@ class ConstantPragmaAnnotationParser implements PragmaAnnotationParser {
|
||||
return const ParsedDynModuleEntryPointPragma();
|
||||
case kVmSharedPragmaName:
|
||||
return const ParsedVmSharedPragma();
|
||||
case kVmDeeplyImmutablePragmaName:
|
||||
return const ParsedVmDeeplyImmutablePragma();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ Deeply immutable instances can be shared across isolates within the same group.
|
||||
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.
|
||||
If an instance field has a deeply immutable type, it doesn't require any runtime checks for the values it gets. Instance fields of function types are also allowed in `@pragma('vm:deeply-immutable')` classes, but runtime checks will ensure that only immutable values are captured by the inititalizing values.
|
||||
|
||||
A list of immutable types:
|
||||
|
||||
@@ -36,8 +36,9 @@ instances can also be deeply immutable while their type is not deeply immutable:
|
||||
* `StackTrace` (can be implemented externally, not `final`)
|
||||
* `Type` (can be implemented externally, not `final`)
|
||||
* const object (the class can be deeply immutable)
|
||||
* function types (closures can capture arbritary contents)
|
||||
|
||||
This means users cannot mark classes with fields typed with these types as `@pragma('vm:deeply-immutable')`.
|
||||
This means users cannot mark classes with fields typed with these types as `@pragma('vm:deeply-immutable')`. Only exception is the function types, which are still allowed, but incur runtime-check.
|
||||
|
||||
## Shallowly immutable instances
|
||||
|
||||
@@ -50,8 +51,8 @@ The VM also has shallow immutability.
|
||||
|
||||
### 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.
|
||||
The `UntaggedObject::ShallowImmutableBit` and `UntaggedObject::DeeplyImmutableBit` track whether an instance is shallowly or deeply 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. During objects lifecycle if the object is inspected, it can get it shallow-immutable bit upgraded to deeply-immutable bit.
|
||||
|
||||
### Deeply immutable types
|
||||
|
||||
@@ -64,10 +65,10 @@ This bit can be set in two ways:
|
||||
|
||||
The `vm:deeply-immutable` pragma is added to classes of which their _type_ is deeply immutable.
|
||||
|
||||
This puts the following restrictions on these classes:
|
||||
This puts the following compile-time restrictions on these classes:
|
||||
|
||||
1. All instance fields must
|
||||
1. have a deeply immutable type,
|
||||
1. either have a deeply immutable or function type,
|
||||
2. be final, and
|
||||
3. be non-late.
|
||||
2. The class must be `final` or `sealed`.
|
||||
@@ -75,5 +76,9 @@ This puts the following restrictions on these classes:
|
||||
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).
|
||||
5. Context captured by closures must be deeply-immutable.
|
||||
|
||||
Compile-time restructions are enforced by [DeeplyImmutableValidator](../../pkg/vm/lib/transformations/ffi/deeply_immutable.dart).
|
||||
|
||||
Run-time checks are inserted to ensure that function-type instance fields are initialized with closure that only capture deeply-immutable values.
|
||||
|
||||
These restructions are enforced by [DeeplyImmutableValidator](../../pkg/vm/lib/transformations/ffi/deeply_immutable.dart).
|
||||
|
||||
@@ -127,8 +127,9 @@ DEFINE_FFI_NATIVE_ENTRY(IsolateGroup_runSync,
|
||||
|
||||
{
|
||||
DARTSCOPE(current_thread);
|
||||
FfiCallbackMetadata::EnsureTriviallyImmutable(
|
||||
current_thread->zone(), Object::Handle(Api::UnwrapHandle(closure)));
|
||||
auto& object =
|
||||
Object::Handle(current_thread->zone(), Api::UnwrapHandle(closure));
|
||||
object.EnsureDeeplyImmutable(current_thread->zone());
|
||||
}
|
||||
|
||||
Isolate* saved_isolate = current_thread->isolate();
|
||||
|
||||
@@ -619,6 +619,12 @@ DEFINE_NATIVE_ENTRY(Internal_loadDynamicModule, 0, 1) {
|
||||
#endif // defined(DART_DYNAMIC_MODULES)
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Internal_ensureDeeplyImmutable, 0, 1) {
|
||||
GET_NATIVE_ARGUMENT(Instance, value, arguments->NativeArgAt(0));
|
||||
value.EnsureDeeplyImmutable(zone);
|
||||
return value.ptr();
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(InvocationMirror_unpackTypeArguments, 0, 2) {
|
||||
const TypeArguments& type_arguments =
|
||||
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0));
|
||||
|
||||
@@ -30,6 +30,8 @@ class A<T> {
|
||||
dynamic m<H>(T a, H b) => this;
|
||||
}
|
||||
|
||||
List<String> list = <String>["Hello", "world"];
|
||||
|
||||
// When running with isolate groups enabled, we can share all of the following
|
||||
// objects.
|
||||
final sharableObjects = [
|
||||
@@ -123,8 +125,27 @@ final sharableObjects = [
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someDeeplyImmutable: null,
|
||||
somePointer: Pointer.fromAddress(0x8badf00d),
|
||||
someClosure: () => 31,
|
||||
),
|
||||
somePointer: Pointer.fromAddress(0xdeadbeef),
|
||||
someClosure: () => 42,
|
||||
),
|
||||
DeeplyImmutable(
|
||||
someString: 'someString',
|
||||
someNullableString: 'someString',
|
||||
someInt: 3,
|
||||
someDouble: 3.3,
|
||||
someBool: false,
|
||||
someNull: null,
|
||||
someInt32x4: Int32x4(0, 1, 2, 3),
|
||||
someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someDeeplyImmutable: null,
|
||||
somePointer: Pointer.fromAddress(0xdeadbeef),
|
||||
someClosure: () {
|
||||
list.add("kuka");
|
||||
return list;
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
@@ -282,6 +303,7 @@ final class DeeplyImmutable {
|
||||
final Float64x2 someFloat64x2;
|
||||
final DeeplyImmutable? someDeeplyImmutable;
|
||||
final Pointer somePointer;
|
||||
final Function() someClosure;
|
||||
|
||||
DeeplyImmutable({
|
||||
required this.someString,
|
||||
@@ -295,5 +317,6 @@ final class DeeplyImmutable {
|
||||
required this.someFloat64x2,
|
||||
this.someDeeplyImmutable,
|
||||
required this.somePointer,
|
||||
required this.someClosure,
|
||||
});
|
||||
}
|
||||
|
||||
+64
-61
@@ -168,7 +168,7 @@ class SerializationCluster : public ZoneAllocated {
|
||||
cid_(cid),
|
||||
target_instance_size_(target_instance_size),
|
||||
is_canonical_(is_canonical),
|
||||
is_immutable_(Object::ShouldHaveImmutabilityBitSet(cid)) {
|
||||
is_deeply_immutable_(Object::ShouldHaveDeeplyImmutabilityBitSet(cid)) {
|
||||
ASSERT(target_instance_size == kSizeVaries || target_instance_size >= 0);
|
||||
}
|
||||
virtual ~SerializationCluster() {}
|
||||
@@ -190,7 +190,7 @@ class SerializationCluster : public ZoneAllocated {
|
||||
const char* name() const { return name_; }
|
||||
intptr_t cid() const { return cid_; }
|
||||
bool is_canonical() const { return is_canonical_; }
|
||||
bool is_immutable() const { return is_immutable_; }
|
||||
bool is_deeply_immutable() const { return is_deeply_immutable_; }
|
||||
intptr_t size() const { return size_; }
|
||||
intptr_t num_objects() const { return num_objects_; }
|
||||
|
||||
@@ -208,7 +208,7 @@ class SerializationCluster : public ZoneAllocated {
|
||||
const intptr_t cid_;
|
||||
const intptr_t target_instance_size_;
|
||||
const bool is_canonical_;
|
||||
const bool is_immutable_;
|
||||
const bool is_deeply_immutable_;
|
||||
intptr_t size_ = 0;
|
||||
intptr_t num_objects_ = 0;
|
||||
intptr_t target_memory_size_ = 0;
|
||||
@@ -218,10 +218,10 @@ class DeserializationCluster : public ZoneAllocated {
|
||||
public:
|
||||
explicit DeserializationCluster(const char* name,
|
||||
bool is_canonical = false,
|
||||
bool is_immutable = false)
|
||||
bool is_deeply_immutable = false)
|
||||
: name_(name),
|
||||
is_canonical_(is_canonical),
|
||||
is_immutable_(is_immutable),
|
||||
is_deeply_immutable_(is_deeply_immutable),
|
||||
start_index_(-1),
|
||||
stop_index_(-1) {}
|
||||
virtual ~DeserializationCluster() {}
|
||||
@@ -248,14 +248,14 @@ class DeserializationCluster : public ZoneAllocated {
|
||||
|
||||
const char* name() const { return name_; }
|
||||
bool is_canonical() const { return is_canonical_; }
|
||||
bool is_immutable() const { return is_immutable_; }
|
||||
bool is_deeply_immutable() const { return is_deeply_immutable_; }
|
||||
|
||||
protected:
|
||||
void ReadAllocFixedSize(Deserializer* deserializer, intptr_t instance_size);
|
||||
|
||||
const char* const name_;
|
||||
const bool is_canonical_;
|
||||
const bool is_immutable_;
|
||||
const bool is_deeply_immutable_;
|
||||
// The range of the ref array that belongs to this cluster.
|
||||
intptr_t start_index_;
|
||||
intptr_t stop_index_;
|
||||
@@ -687,13 +687,13 @@ class Deserializer : public ThreadStackResource {
|
||||
intptr_t size,
|
||||
bool is_canonical = false) {
|
||||
InitializeHeader(raw, cid, size, is_canonical,
|
||||
ShouldHaveImmutabilityBitSetCid(cid));
|
||||
Object::ShouldHaveDeeplyImmutabilityBitSet(cid));
|
||||
}
|
||||
static void InitializeHeader(ObjectPtr raw,
|
||||
intptr_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical,
|
||||
bool is_immutable);
|
||||
bool is_deeply_immutable);
|
||||
|
||||
// Reads raw data (for basic types).
|
||||
// sizeof(T) must be in {1,2,4,8}.
|
||||
@@ -887,7 +887,7 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
||||
intptr_t class_id,
|
||||
intptr_t size,
|
||||
bool is_canonical,
|
||||
bool is_immutable) {
|
||||
bool is_deeply_immutable) {
|
||||
ASSERT(Utils::IsAligned(size, kObjectAlignment));
|
||||
uword tags = 0;
|
||||
tags = UntaggedObject::ClassIdTag::update(class_id, tags);
|
||||
@@ -897,7 +897,9 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
||||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(true, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(false, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(is_immutable, tags);
|
||||
tags = UntaggedObject::ShallowImmutableBit::update(
|
||||
Object::ShouldHaveShallowImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(is_deeply_immutable, tags);
|
||||
raw->untag()->tags_ = tags;
|
||||
}
|
||||
|
||||
@@ -906,9 +908,10 @@ void SerializationCluster::WriteAndMeasureAlloc(Serializer* serializer) {
|
||||
intptr_t start_size = serializer->bytes_written();
|
||||
intptr_t start_data = serializer->GetDataSize();
|
||||
intptr_t start_objects = serializer->next_ref_index();
|
||||
uint32_t tags = UntaggedObject::ClassIdTag::encode(cid_) |
|
||||
UntaggedObject::CanonicalBit::encode(is_canonical()) |
|
||||
UntaggedObject::ImmutableBit::encode(is_immutable());
|
||||
uint32_t tags =
|
||||
UntaggedObject::ClassIdTag::encode(cid_) |
|
||||
UntaggedObject::CanonicalBit::encode(is_canonical()) |
|
||||
UntaggedObject::DeeplyImmutableBit::encode(is_deeply_immutable());
|
||||
serializer->Write<uint32_t>(tags);
|
||||
WriteAlloc(serializer);
|
||||
intptr_t stop_size = serializer->bytes_written();
|
||||
@@ -4555,9 +4558,9 @@ class AbstractInstanceDeserializationCluster : public DeserializationCluster {
|
||||
protected:
|
||||
explicit AbstractInstanceDeserializationCluster(const char* name,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: DeserializationCluster(name, is_canonical, is_immutable),
|
||||
: DeserializationCluster(name, is_canonical, is_deeply_immutable),
|
||||
is_root_unit_(is_root_unit) {}
|
||||
|
||||
const bool is_root_unit_;
|
||||
@@ -4584,11 +4587,11 @@ class InstanceDeserializationCluster
|
||||
public:
|
||||
explicit InstanceDeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Instance",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~InstanceDeserializationCluster() {}
|
||||
@@ -4611,7 +4614,7 @@ class InstanceDeserializationCluster
|
||||
|
||||
const intptr_t cid = cid_;
|
||||
const bool mark_canonical = is_root_unit_ && is_canonical();
|
||||
const bool is_immutable = is_immutable_;
|
||||
const bool is_deeply_immutable = is_deeply_immutable_;
|
||||
intptr_t next_field_offset = next_field_offset_in_words_
|
||||
<< kCompressedWordSizeLog2;
|
||||
intptr_t instance_size = Object::RoundedAllocationSize(
|
||||
@@ -4621,7 +4624,7 @@ class InstanceDeserializationCluster
|
||||
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
|
||||
InstancePtr instance = static_cast<InstancePtr>(d.Ref(id));
|
||||
Deserializer::InitializeHeader(instance, cid, instance_size,
|
||||
mark_canonical, is_immutable);
|
||||
mark_canonical, is_deeply_immutable);
|
||||
intptr_t offset = Instance::NextFieldOffset();
|
||||
while (offset < next_field_offset) {
|
||||
if (unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) {
|
||||
@@ -5248,11 +5251,11 @@ class ClosureDeserializationCluster
|
||||
: public AbstractInstanceDeserializationCluster {
|
||||
public:
|
||||
explicit ClosureDeserializationCluster(bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Closure",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit) {}
|
||||
~ClosureDeserializationCluster() {}
|
||||
|
||||
@@ -5345,12 +5348,10 @@ class MintSerializationCluster : public SerializationCluster {
|
||||
class MintDeserializationCluster
|
||||
: public AbstractInstanceDeserializationCluster {
|
||||
public:
|
||||
explicit MintDeserializationCluster(bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_root_unit)
|
||||
explicit MintDeserializationCluster(bool is_canonical, bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("int",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
/*is_deeply_immutable=*/true,
|
||||
is_root_unit) {}
|
||||
~MintDeserializationCluster() {}
|
||||
|
||||
@@ -5417,14 +5418,12 @@ class DoubleSerializationCluster : public SerializationCluster {
|
||||
class DoubleDeserializationCluster
|
||||
: public AbstractInstanceDeserializationCluster {
|
||||
public:
|
||||
explicit DoubleDeserializationCluster(bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_root_unit)
|
||||
explicit DoubleDeserializationCluster(bool is_canonical, bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("double",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
/*is_deeply_immutable=*/true,
|
||||
is_root_unit) {
|
||||
ASSERT(Object::ShouldHaveImmutabilityBitSet(kDoubleCid));
|
||||
ASSERT(Object::ShouldHaveDeeplyImmutabilityBitSet(kDoubleCid));
|
||||
}
|
||||
~DoubleDeserializationCluster() {}
|
||||
|
||||
@@ -5492,11 +5491,10 @@ class Simd128DeserializationCluster
|
||||
public:
|
||||
explicit Simd128DeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Simd128",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
/*is_deeply_immutable=*/true,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~Simd128DeserializationCluster() {}
|
||||
@@ -5511,7 +5509,7 @@ class Simd128DeserializationCluster
|
||||
Deserializer::Local d(d_);
|
||||
const intptr_t cid = cid_;
|
||||
const bool mark_canonical = is_root_unit_ && is_canonical();
|
||||
const bool is_immutable = ShouldHaveImmutabilityBitSetCid(cid);
|
||||
const bool is_immutable = Object::ShouldHaveDeeplyImmutabilityBitSet(cid);
|
||||
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
|
||||
ObjectPtr vector = d.Ref(id);
|
||||
Deserializer::InitializeHeader(vector, cid, Int32x4::InstanceSize(),
|
||||
@@ -5641,11 +5639,11 @@ class RecordDeserializationCluster
|
||||
: public AbstractInstanceDeserializationCluster {
|
||||
public:
|
||||
explicit RecordDeserializationCluster(bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Record",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit) {}
|
||||
~RecordDeserializationCluster() {}
|
||||
|
||||
@@ -6278,11 +6276,11 @@ class MapDeserializationCluster
|
||||
public:
|
||||
explicit MapDeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Map",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~MapDeserializationCluster() {}
|
||||
@@ -6355,11 +6353,11 @@ class SetDeserializationCluster
|
||||
public:
|
||||
explicit SetDeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Set",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~SetDeserializationCluster() {}
|
||||
@@ -6497,11 +6495,11 @@ class ArrayDeserializationCluster
|
||||
public:
|
||||
explicit ArrayDeserializationCluster(intptr_t cid,
|
||||
bool is_canonical,
|
||||
bool is_immutable,
|
||||
bool is_deeply_immutable,
|
||||
bool is_root_unit)
|
||||
: AbstractInstanceDeserializationCluster("Array",
|
||||
is_canonical,
|
||||
is_immutable,
|
||||
is_deeply_immutable,
|
||||
is_root_unit),
|
||||
cid_(cid) {}
|
||||
~ArrayDeserializationCluster() {}
|
||||
@@ -6521,7 +6519,7 @@ class ArrayDeserializationCluster
|
||||
|
||||
const intptr_t cid = cid_;
|
||||
const bool stamp_canonical = is_root_unit_ && is_canonical();
|
||||
const bool is_immutable = ShouldHaveImmutabilityBitSetCid(cid);
|
||||
const bool is_immutable = Object::ShouldHaveDeeplyImmutabilityBitSet(cid);
|
||||
for (intptr_t id = start_index_, n = stop_index_; id < n; id++) {
|
||||
ArrayPtr array = static_cast<ArrayPtr>(d.Ref(id));
|
||||
const intptr_t length = d.ReadUnsigned();
|
||||
@@ -9013,11 +9011,12 @@ DeserializationCluster* Deserializer::ReadCluster() {
|
||||
const uint32_t tags = Read<uint32_t>();
|
||||
const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
|
||||
const bool is_canonical = UntaggedObject::CanonicalBit::decode(tags);
|
||||
const bool is_immutable = UntaggedObject::ImmutableBit::decode(tags);
|
||||
const bool is_deeply_immutable =
|
||||
UntaggedObject::DeeplyImmutableBit::decode(tags);
|
||||
Zone* Z = zone_;
|
||||
if (cid >= kNumPredefinedCids || cid == kInstanceCid) {
|
||||
return new (Z) InstanceDeserializationCluster(
|
||||
cid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
cid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
}
|
||||
if (IsTypedDataViewClassId(cid)) {
|
||||
ASSERT(!is_canonical);
|
||||
@@ -9151,25 +9150,28 @@ DeserializationCluster* Deserializer::ReadCluster() {
|
||||
return new (Z)
|
||||
TypeParameterDeserializationCluster(is_canonical, !is_non_root_unit_);
|
||||
case kClosureCid:
|
||||
return new (Z) ClosureDeserializationCluster(is_canonical, is_immutable,
|
||||
!is_non_root_unit_);
|
||||
return new (Z) ClosureDeserializationCluster(
|
||||
is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kMintCid:
|
||||
return new (Z) MintDeserializationCluster(is_canonical, is_immutable,
|
||||
!is_non_root_unit_);
|
||||
RELEASE_ASSERT(is_deeply_immutable);
|
||||
return new (Z)
|
||||
MintDeserializationCluster(is_canonical, !is_non_root_unit_);
|
||||
case kDoubleCid:
|
||||
return new (Z) DoubleDeserializationCluster(is_canonical, is_immutable,
|
||||
!is_non_root_unit_);
|
||||
RELEASE_ASSERT(is_deeply_immutable);
|
||||
return new (Z)
|
||||
DoubleDeserializationCluster(is_canonical, !is_non_root_unit_);
|
||||
case kInt32x4Cid:
|
||||
case kFloat32x4Cid:
|
||||
case kFloat64x2Cid:
|
||||
return new (Z) Simd128DeserializationCluster(
|
||||
cid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
RELEASE_ASSERT(is_deeply_immutable);
|
||||
return new (Z)
|
||||
Simd128DeserializationCluster(cid, is_canonical, !is_non_root_unit_);
|
||||
case kGrowableObjectArrayCid:
|
||||
ASSERT(!is_canonical);
|
||||
return new (Z) GrowableObjectArrayDeserializationCluster();
|
||||
case kRecordCid:
|
||||
return new (Z) RecordDeserializationCluster(is_canonical, is_immutable,
|
||||
!is_non_root_unit_);
|
||||
return new (Z) RecordDeserializationCluster(
|
||||
is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kStackTraceCid:
|
||||
ASSERT(!is_canonical);
|
||||
return new (Z) StackTraceDeserializationCluster();
|
||||
@@ -9184,19 +9186,20 @@ DeserializationCluster* Deserializer::ReadCluster() {
|
||||
UNREACHABLE();
|
||||
case kConstMapCid:
|
||||
return new (Z) MapDeserializationCluster(
|
||||
kConstMapCid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
kConstMapCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kSetCid:
|
||||
// We do not have mutable hash sets in snapshots.
|
||||
UNREACHABLE();
|
||||
case kConstSetCid:
|
||||
return new (Z) SetDeserializationCluster(
|
||||
kConstSetCid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
kConstSetCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kArrayCid:
|
||||
return new (Z) ArrayDeserializationCluster(
|
||||
kArrayCid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
kArrayCid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kImmutableArrayCid:
|
||||
return new (Z) ArrayDeserializationCluster(
|
||||
kImmutableArrayCid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
return new (Z)
|
||||
ArrayDeserializationCluster(kImmutableArrayCid, is_canonical,
|
||||
is_deeply_immutable, !is_non_root_unit_);
|
||||
case kWeakArrayCid:
|
||||
return new (Z) WeakArrayDeserializationCluster();
|
||||
case kStringCid:
|
||||
@@ -9207,7 +9210,7 @@ DeserializationCluster* Deserializer::ReadCluster() {
|
||||
CLASS_LIST_FFI_TYPE_MARKER(CASE_FFI_CID)
|
||||
#undef CASE_FFI_CID
|
||||
return new (Z) InstanceDeserializationCluster(
|
||||
cid, is_canonical, is_immutable, !is_non_root_unit_);
|
||||
cid, is_canonical, is_deeply_immutable, !is_non_root_unit_);
|
||||
case kDeltaEncodedTypedDataCid:
|
||||
return new (Z) DeltaEncodedTypedDataDeserializationCluster();
|
||||
default:
|
||||
|
||||
@@ -279,6 +279,7 @@ namespace dart {
|
||||
V(Internal_deoptimizeFunctionsOnStack, 0) \
|
||||
V(Internal_allocateObjectInstructionsStart, 0) \
|
||||
V(Internal_allocateObjectInstructionsEnd, 0) \
|
||||
V(Internal_ensureDeeplyImmutable, 1) \
|
||||
V(InvocationMirror_unpackTypeArguments, 2) \
|
||||
V(NoSuchMethodError_existingMethodSignature, 3) \
|
||||
V(ThreadLocal_allocateId, 0) \
|
||||
|
||||
@@ -2311,6 +2311,7 @@ void BytecodeReaderHelper::ReadClassDeclaration(const Class& cls) {
|
||||
const int kIsBaseClassFlag = 1 << 11;
|
||||
const int kIsInterfaceFlag = 1 << 12;
|
||||
const int kIsFinalFlag = 1 << 13;
|
||||
const int kIsDeeplyImmutableFlag = 1 << 14;
|
||||
|
||||
// Class is allocated when reading library declaration in
|
||||
// BytecodeReaderHelper::ReadLibraryDeclaration.
|
||||
@@ -2370,6 +2371,9 @@ void BytecodeReaderHelper::ReadClassDeclaration(const Class& cls) {
|
||||
if ((flags & kIsFinalFlag) != 0) {
|
||||
cls.set_is_final();
|
||||
}
|
||||
if ((flags & kIsDeeplyImmutableFlag) != 0) {
|
||||
cls.set_is_deeply_immutable(true);
|
||||
}
|
||||
|
||||
intptr_t num_type_arguments = 0;
|
||||
if ((flags & kHasTypeArgumentsFlag) != 0) {
|
||||
|
||||
@@ -599,13 +599,6 @@ inline bool IsShallowlyImmutableCid(intptr_t predefined_cid) {
|
||||
IsUnmodifiableTypedDataViewClassId(predefined_cid);
|
||||
}
|
||||
|
||||
// See documentation on ImmutableBit in raw_object.h
|
||||
inline bool ShouldHaveImmutabilityBitSetCid(intptr_t predefined_cid) {
|
||||
ASSERT(predefined_cid < kNumPredefinedCids);
|
||||
return IsDeeplyImmutableCid(predefined_cid) ||
|
||||
IsShallowlyImmutableCid(predefined_cid);
|
||||
}
|
||||
|
||||
inline bool IsFfiTypeClassId(intptr_t index) {
|
||||
switch (index) {
|
||||
case kPointerCid:
|
||||
|
||||
@@ -287,6 +287,9 @@ void ConstantPropagator::VisitGuardFieldLength(GuardFieldLengthInstr* instr) {}
|
||||
|
||||
void ConstantPropagator::VisitGuardFieldType(GuardFieldTypeInstr* instr) {}
|
||||
|
||||
void ConstantPropagator::VisitCheckFieldImmutability(
|
||||
CheckFieldImmutabilityInstr* instr) {}
|
||||
|
||||
void ConstantPropagator::VisitCheckSmi(CheckSmiInstr* instr) {}
|
||||
|
||||
void ConstantPropagator::VisitTailCall(TailCallInstr* instr) {}
|
||||
|
||||
@@ -359,8 +359,8 @@ class FieldAccessErrorSlowPath : public ThrowErrorSlowPathCode {
|
||||
class CheckedStoreIntoSharedSlowPath
|
||||
: public TemplateSlowPathCode<StoreStaticFieldInstr> {
|
||||
public:
|
||||
explicit CheckedStoreIntoSharedSlowPath(StoreStaticFieldInstr* instruction,
|
||||
Register value)
|
||||
CheckedStoreIntoSharedSlowPath(StoreStaticFieldInstr* instruction,
|
||||
Register value)
|
||||
: TemplateSlowPathCode(instruction), value_(value) {}
|
||||
|
||||
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
||||
@@ -375,6 +375,20 @@ class CheckedStoreIntoSharedSlowPath
|
||||
Register value() const { return value_; }
|
||||
};
|
||||
|
||||
class EnsureDeeplyImmutableSlowPath : public TemplateSlowPathCode<Instruction> {
|
||||
public:
|
||||
EnsureDeeplyImmutableSlowPath(CheckFieldImmutabilityInstr* instruction,
|
||||
Register value)
|
||||
: TemplateSlowPathCode(instruction), value_(value) {}
|
||||
|
||||
virtual void EmitNativeCode(FlowGraphCompiler* compiler);
|
||||
|
||||
private:
|
||||
Register value_;
|
||||
|
||||
Register value() const { return value_; }
|
||||
};
|
||||
|
||||
enum class TypeTestOutcome { kConclusive, kNotConclusive };
|
||||
|
||||
class FlowGraphCompiler : public ValueObject {
|
||||
|
||||
@@ -1057,6 +1057,11 @@ bool GuardFieldTypeInstr::AttributesEqual(const Instruction& other) const {
|
||||
return field().ptr() == other.AsGuardFieldType()->field().ptr();
|
||||
}
|
||||
|
||||
bool CheckFieldImmutabilityInstr::AttributesEqual(
|
||||
const Instruction& other) const {
|
||||
return field().ptr() == other.AsCheckFieldImmutability()->field().ptr();
|
||||
}
|
||||
|
||||
Instruction* AssertSubtypeInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
// If all inputs needed to check instantiation are constant, instantiate the
|
||||
// sub and super type and remove the instruction if the subtype test succeeds.
|
||||
@@ -3944,6 +3949,10 @@ Instruction* GuardFieldTypeInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
Instruction* CheckFieldImmutabilityInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Instruction* CheckSmiInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
return (value()->Type()->ToCid() == kSmiCid) ? nullptr : this;
|
||||
}
|
||||
@@ -6437,6 +6446,34 @@ void CheckedStoreIntoSharedSlowPath::EmitNativeCode(
|
||||
__ Jump(exit_label());
|
||||
}
|
||||
|
||||
void EnsureDeeplyImmutableSlowPath::EmitNativeCode(
|
||||
FlowGraphCompiler* compiler) {
|
||||
__ Comment("EnsureDeeplyImmutableSlowPath");
|
||||
__ Bind(entry_label());
|
||||
|
||||
LocationSummary* locs = instruction()->locs();
|
||||
locs->live_registers()->Remove(locs->out(0));
|
||||
|
||||
compiler->SaveLiveRegisters(locs);
|
||||
|
||||
auto slow_path_env =
|
||||
compiler->SlowPathEnvironmentFor(instruction(), /*num_slow_path_args=*/0);
|
||||
|
||||
#if defined(TARGET_ARCH_IA32)
|
||||
__ MoveRegister(EnsureDeeplyImmutableStubABI::kValueReg, value());
|
||||
#else
|
||||
ASSERT(value() == EnsureDeeplyImmutableStubABI::kValueReg);
|
||||
#endif
|
||||
|
||||
compiler->GenerateStubCall(instruction()->source(),
|
||||
StubCode::EnsureDeeplyImmutable(),
|
||||
UntaggedPcDescriptors::kOther, locs,
|
||||
instruction()->deopt_id(), slow_path_env);
|
||||
|
||||
compiler->RestoreLiveRegisters(instruction()->locs());
|
||||
__ Jump(exit_label());
|
||||
}
|
||||
|
||||
void UnboxInstr::EmitLoadFromBoxWithDeopt(FlowGraphCompiler* compiler) {
|
||||
const intptr_t box_cid = BoxCid();
|
||||
ASSERT(box_cid != kSmiCid); // Should never reach here with Smi-able ints.
|
||||
|
||||
@@ -520,6 +520,7 @@ struct InstrAttrs {
|
||||
M(GuardFieldClass, _) \
|
||||
M(GuardFieldLength, _) \
|
||||
M(GuardFieldType, _) \
|
||||
M(CheckFieldImmutability, _) \
|
||||
M(IfThenElse, kNoGC) \
|
||||
M(MaterializeObject, _) \
|
||||
M(TestInt, kNoGC) \
|
||||
@@ -6608,6 +6609,55 @@ class GuardFieldTypeInstr : public GuardFieldInstr {
|
||||
DISALLOW_COPY_AND_ASSIGN(GuardFieldTypeInstr);
|
||||
};
|
||||
|
||||
// Throws if [value] is not immutable.
|
||||
//
|
||||
// This is used when it's impossible to conclude from the variable static type
|
||||
// whether value is immutable(in case of closures, for example).
|
||||
class CheckFieldImmutabilityInstr : public TemplateInstruction<1, Throws> {
|
||||
public:
|
||||
CheckFieldImmutabilityInstr(Value* value,
|
||||
const Field& field,
|
||||
intptr_t deopt_id)
|
||||
: TemplateInstruction(deopt_id), field_(field) {
|
||||
SetInputAt(0, value);
|
||||
CheckField(field);
|
||||
}
|
||||
|
||||
Value* value() const { return inputs_[0]; }
|
||||
enum {
|
||||
kValue = 0,
|
||||
};
|
||||
|
||||
const Field& field() const { return field_; }
|
||||
|
||||
DECLARE_INSTRUCTION(CheckFieldImmutability)
|
||||
|
||||
virtual bool ComputeCanDeoptimize() const { return false; }
|
||||
virtual bool ComputeCanDeoptimizeAfterCall() const {
|
||||
return !CompilerState::Current().is_aot();
|
||||
}
|
||||
virtual bool CanBecomeDeoptimizationTarget() const { return true; }
|
||||
|
||||
virtual bool AllowsCSE() const { return true; }
|
||||
virtual bool HasUnknownSideEffects() const { return false; }
|
||||
|
||||
virtual Instruction* Canonicalize(FlowGraph* flow_graph);
|
||||
|
||||
virtual bool AttributesEqual(const Instruction& other) const;
|
||||
|
||||
PRINT_OPERANDS_TO_SUPPORT
|
||||
|
||||
#define FIELD_LIST(F) F(const Field&, field_)
|
||||
|
||||
DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS(CheckFieldImmutabilityInstr,
|
||||
TemplateInstruction,
|
||||
FIELD_LIST)
|
||||
#undef FIELD_LIST
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(CheckFieldImmutabilityInstr);
|
||||
};
|
||||
|
||||
enum class SlowPathOnSentinelValue {
|
||||
kDoNothing,
|
||||
kThrowAccessError, // This is part of shared field implementation.
|
||||
|
||||
@@ -2776,6 +2776,39 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
|
||||
DEFINE_UNIMPLEMENTED_INSTRUCTION(GuardFieldTypeInstr)
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
__ ldrb(temp, compiler::FieldAddress(
|
||||
value, compiler::target::Object::tags_offset()));
|
||||
__ TestImmediate(temp,
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ b(slow_path->entry_label(), ZERO);
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* LoadCodeUnitsInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const bool might_box = (representation() == kTagged) && !can_pack_into_smi();
|
||||
@@ -2946,17 +2979,11 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
1 << compiler::target::UntaggedObject::kCanonicalBit);
|
||||
// If canonical bit is set, no need for runtime check.
|
||||
__ b(&allow_store, NOT_ZERO);
|
||||
__ TestImmediate(temp,
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit);
|
||||
__ TestImmediate(
|
||||
temp, 1 << compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ b(checked_store_into_shared_slow_path->entry_label(), ZERO);
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(temp, value);
|
||||
__ CompareImmediate(temp, kClosureCid);
|
||||
__ b(checked_store_into_shared_slow_path->entry_label(), EQ);
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
}
|
||||
@@ -6339,8 +6366,11 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ ldr(TMP, compiler::FieldAddress(locs()->in(0).reg(),
|
||||
compiler::target::Object::tags_offset()));
|
||||
// In the first byte.
|
||||
ASSERT(compiler::target::UntaggedObject::kImmutableBit < 8);
|
||||
__ TestImmediate(TMP, 1 << compiler::target::UntaggedObject::kImmutableBit);
|
||||
ASSERT(compiler::target::UntaggedObject::kDeeplyImmutableBit < 8);
|
||||
ASSERT(compiler::target::UntaggedObject::kShallowImmutableBit < 8);
|
||||
__ TestImmediate(
|
||||
TMP, 1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit);
|
||||
__ b(slow_path->entry_label(), NOT_ZERO);
|
||||
}
|
||||
|
||||
|
||||
@@ -2559,13 +2559,7 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
compiler::target::UntaggedObject::kCanonicalBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ tbz(checked_store_into_shared_slow_path->entry_label(), temp,
|
||||
compiler::target::UntaggedObject::kImmutableBit);
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(temp, value);
|
||||
__ CompareImmediate(temp, kClosureCid);
|
||||
__ b(checked_store_into_shared_slow_path->entry_label(), EQ);
|
||||
compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
@@ -2591,6 +2585,39 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
}
|
||||
}
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
__ ldr(temp,
|
||||
compiler::FieldAddress(value, compiler::target::Object::tags_offset()),
|
||||
compiler::kUnsignedByte);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ tbz(slow_path->entry_label(), temp,
|
||||
compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* InstanceOfInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 3;
|
||||
@@ -5337,9 +5364,12 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
compiler::target::Object::tags_offset()),
|
||||
compiler::kUnsignedByte);
|
||||
// In the first byte.
|
||||
ASSERT(compiler::target::UntaggedObject::kImmutableBit < 8);
|
||||
__ tbnz(slow_path->entry_label(), TMP,
|
||||
compiler::target::UntaggedObject::kImmutableBit);
|
||||
ASSERT(compiler::target::UntaggedObject::kDeeplyImmutableBit < 8);
|
||||
ASSERT(compiler::target::UntaggedObject::kShallowImmutableBit < 8);
|
||||
__ TestImmediate(
|
||||
TMP, 1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit);
|
||||
__ b(slow_path->entry_label(), NOT_ZERO);
|
||||
}
|
||||
|
||||
class Int64DivideSlowPath : public ThrowErrorSlowPathCode {
|
||||
|
||||
@@ -2102,6 +2102,41 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
}
|
||||
}
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
|
||||
__ movl(temp, compiler::FieldAddress(
|
||||
value, compiler::target::Object::tags_offset()));
|
||||
__ testl(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit));
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ j(ZERO, slow_path->entry_label(), compiler::Assembler::kNearJump);
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumTemps =
|
||||
@@ -2157,17 +2192,12 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
1 << compiler::target::UntaggedObject::kCanonicalBit));
|
||||
// If canonical bit is set, no need for runtime check.
|
||||
__ j(NOT_ZERO, &allow_store);
|
||||
__ testl(temp, compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit));
|
||||
__ testl(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit));
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ j(ZERO, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(temp, in);
|
||||
__ CompareImmediate(temp, kClosureCid);
|
||||
__ BranchIf(EQUAL, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
}
|
||||
@@ -5460,8 +5490,10 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ movl(temp,
|
||||
compiler::FieldAddress(locs()->in(0).reg(),
|
||||
compiler::target::Object::tags_offset()));
|
||||
__ testl(temp, compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit));
|
||||
__ testl(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit));
|
||||
__ j(NOT_ZERO, slow_path->entry_label());
|
||||
}
|
||||
|
||||
|
||||
@@ -959,6 +959,11 @@ void GuardFieldInstr::PrintOperandsTo(BaseTextBuffer* f) const {
|
||||
value()->PrintTo(f);
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::PrintOperandsTo(BaseTextBuffer* f) const {
|
||||
f->Printf("%s , ", String::Handle(field().name()).ToCString());
|
||||
value()->PrintTo(f);
|
||||
}
|
||||
|
||||
void StoreFieldInstr::PrintOperandsTo(BaseTextBuffer* f) const {
|
||||
instance()->PrintTo(f);
|
||||
f->Printf(" . %s = ", slot().Name());
|
||||
|
||||
@@ -2449,6 +2449,39 @@ static void LoadValueCid(FlowGraphCompiler* compiler,
|
||||
|
||||
DEFINE_UNIMPLEMENTED_INSTRUCTION(GuardFieldTypeInstr)
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
__ lbu(temp, compiler::FieldAddress(value,
|
||||
compiler::target::Object::tags_offset()));
|
||||
__ andi(temp, temp,
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ beqz(temp, slow_path->entry_label());
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* GuardFieldClassInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
@@ -2754,16 +2787,11 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ andi(temp, TMP, 1 << compiler::target::UntaggedObject::kCanonicalBit);
|
||||
// If canonical bit is set, no need for runtime check.
|
||||
__ bnez(temp, &allow_store);
|
||||
__ andi(temp, TMP, 1 << compiler::target::UntaggedObject::kImmutableBit);
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ andi(temp, TMP,
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit);
|
||||
// If deeply immutability bit is not set, go to runtime.
|
||||
__ beqz(temp, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(TMP, value);
|
||||
__ CompareImmediate(TMP, kClosureCid);
|
||||
__ BranchIf(EQ, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
}
|
||||
@@ -5486,8 +5514,11 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ lbu(TMP, compiler::FieldAddress(locs()->in(0).reg(),
|
||||
compiler::target::Object::tags_offset()));
|
||||
// In the first byte.
|
||||
ASSERT(compiler::target::UntaggedObject::kImmutableBit < 8);
|
||||
__ andi(TMP, TMP, 1 << compiler::target::UntaggedObject::kImmutableBit);
|
||||
ASSERT(compiler::target::UntaggedObject::kDeeplyImmutableBit < 8);
|
||||
ASSERT(compiler::target::UntaggedObject::kShallowImmutableBit < 8);
|
||||
__ andi(TMP, TMP,
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit);
|
||||
__ bnez(TMP, slow_path->entry_label());
|
||||
}
|
||||
|
||||
|
||||
@@ -2477,6 +2477,40 @@ void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
__ Bind(&ok);
|
||||
}
|
||||
|
||||
LocationSummary* CheckFieldImmutabilityInstr::MakeLocationSummary(
|
||||
Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 1;
|
||||
LocationSummary* summary = new (zone) LocationSummary(
|
||||
zone, kNumInputs, kNumTemps, LocationSummary::kCallOnSlowPath);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kValueReg));
|
||||
summary->set_temp(
|
||||
0, Location::RegisterLocation(EnsureDeeplyImmutableStubABI::kTempReg));
|
||||
return summary;
|
||||
}
|
||||
|
||||
void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
const Register value = locs()->in(0).reg();
|
||||
const Register temp = locs()->temp(0).reg();
|
||||
|
||||
auto slow_path = new EnsureDeeplyImmutableSlowPath(this, value);
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
|
||||
__ BranchIfSmi(value, slow_path->exit_label(),
|
||||
compiler::Assembler::kNearJump);
|
||||
__ movq(temp, compiler::FieldAddress(
|
||||
value, compiler::target::Object::tags_offset()));
|
||||
__ testq(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit));
|
||||
// If deep immutability bit is not set, go to runtime.
|
||||
__ j(ZERO, slow_path->entry_label());
|
||||
|
||||
__ Bind(slow_path->exit_label());
|
||||
}
|
||||
|
||||
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
@@ -2527,17 +2561,12 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
1 << compiler::target::UntaggedObject::kCanonicalBit));
|
||||
// If canonical bit is set, no need for runtime check.
|
||||
__ j(NOT_ZERO, &allow_store);
|
||||
__ testq(temp, compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit));
|
||||
// If immutability bit is not set, go to runtime.
|
||||
__ testq(temp,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit));
|
||||
// If deep immutability bit is not set, go to runtime.
|
||||
__ j(ZERO, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
// If immutability bit is set, skip runtime unless it's a Closure
|
||||
// (see raw_object.h ImmutableBit description for deep vs shallow).
|
||||
__ LoadClassId(temp, value);
|
||||
__ CompareImmediate(temp, kClosureCid);
|
||||
__ BranchIf(EQUAL, checked_store_into_shared_slow_path->entry_label());
|
||||
|
||||
__ Bind(&allow_store);
|
||||
}
|
||||
}
|
||||
@@ -5649,8 +5678,10 @@ void CheckWritableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
compiler->AddSlowPathCode(slow_path);
|
||||
__ movq(TMP, compiler::FieldAddress(locs()->in(0).reg(),
|
||||
compiler::target::Object::tags_offset()));
|
||||
__ testq(TMP, compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kImmutableBit));
|
||||
__ testq(TMP,
|
||||
compiler::Immediate(
|
||||
1 << compiler::target::UntaggedObject::kDeeplyImmutableBit |
|
||||
1 << compiler::target::UntaggedObject::kShallowImmutableBit));
|
||||
__ j(NOT_ZERO, slow_path->entry_label());
|
||||
}
|
||||
|
||||
|
||||
@@ -597,7 +597,8 @@ Fragment BaseFlowGraphBuilder::StoreField(
|
||||
|
||||
Fragment BaseFlowGraphBuilder::StoreFieldGuarded(
|
||||
const Field& field,
|
||||
StoreFieldInstr::Kind kind /* = StoreFieldInstr::Kind::kOther */) {
|
||||
StoreFieldInstr::Kind kind /* = StoreFieldInstr::Kind::kOther */,
|
||||
bool requires_immutability_check /* = false */) {
|
||||
Fragment instructions;
|
||||
const Field& field_clone = MayCloneField(Z, field);
|
||||
if (IG->use_field_guards()) {
|
||||
@@ -632,6 +633,13 @@ Fragment BaseFlowGraphBuilder::StoreFieldGuarded(
|
||||
new (Z) GuardFieldTypeInstr(Pop(), field_clone, GetNextDeoptId());
|
||||
}
|
||||
}
|
||||
if (requires_immutability_check) {
|
||||
ASSERT(kind == StoreFieldInstr::Kind::kInitializing);
|
||||
LocalVariable* store_expression = MakeTemporary();
|
||||
instructions += LoadLocal(store_expression);
|
||||
instructions <<= new (Z)
|
||||
CheckFieldImmutabilityInstr(Pop(), field_clone, GetNextDeoptId());
|
||||
}
|
||||
instructions +=
|
||||
StoreNativeField(Slot::Get(field_clone, parsed_function_), kind);
|
||||
return instructions;
|
||||
|
||||
@@ -263,7 +263,8 @@ class BaseFlowGraphBuilder {
|
||||
StoreBarrierType emit_store_barrier = kEmitStoreBarrier);
|
||||
Fragment StoreFieldGuarded(
|
||||
const Field& field,
|
||||
StoreFieldInstr::Kind kind = StoreFieldInstr::Kind::kOther);
|
||||
StoreFieldInstr::Kind kind = StoreFieldInstr::Kind::kOther,
|
||||
bool requires_immutability_check = false);
|
||||
Fragment LoadStaticField(const Field& field, bool calls_initializer);
|
||||
Fragment RedefinitionWithType(const AbstractType& type);
|
||||
Fragment ReachabilityFence();
|
||||
|
||||
@@ -178,8 +178,11 @@ Fragment StreamingFlowGraphBuilder::BuildFieldInitializer(
|
||||
if (only_for_side_effects) {
|
||||
instructions += Drop();
|
||||
} else {
|
||||
const auto& klass = Class::Handle(field.Owner());
|
||||
// TODO(dartbug.com/61078): Use static type to avoid runtime check.
|
||||
instructions += flow_graph_builder_->StoreFieldGuarded(
|
||||
field, StoreFieldInstr::Kind::kInitializing);
|
||||
field, StoreFieldInstr::Kind::kInitializing,
|
||||
klass.is_deeply_immutable());
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@@ -368,8 +368,10 @@ uword MakeTagWordForNewSpaceObject(classid_t cid, uword instance_size) {
|
||||
dart::UntaggedObject::NewOrEvacuationCandidateBit::encode(true) |
|
||||
dart::UntaggedObject::AlwaysSetBit::encode(true) |
|
||||
dart::UntaggedObject::NotMarkedBit::encode(true) |
|
||||
dart::UntaggedObject::ImmutableBit::encode(
|
||||
dart::Object::ShouldHaveImmutabilityBitSet(cid));
|
||||
dart::UntaggedObject::ShallowImmutableBit::encode(
|
||||
dart::Object::ShouldHaveShallowImmutabilityBitSet(cid)) |
|
||||
dart::UntaggedObject::DeeplyImmutableBit::encode(
|
||||
dart::Object::ShouldHaveDeeplyImmutabilityBitSet(cid));
|
||||
}
|
||||
|
||||
word Object::tags_offset() {
|
||||
@@ -391,8 +393,11 @@ const word UntaggedObject::kOldAndNotRememberedBit =
|
||||
const word UntaggedObject::kNotMarkedBit =
|
||||
dart::UntaggedObject::NotMarkedBit::shift();
|
||||
|
||||
const word UntaggedObject::kImmutableBit =
|
||||
dart::UntaggedObject::ImmutableBit::shift();
|
||||
const word UntaggedObject::kShallowImmutableBit =
|
||||
dart::UntaggedObject::ShallowImmutableBit::shift();
|
||||
|
||||
const word UntaggedObject::kDeeplyImmutableBit =
|
||||
dart::UntaggedObject::DeeplyImmutableBit::shift();
|
||||
|
||||
const word UntaggedObject::kSizeTagPos =
|
||||
dart::UntaggedObject::SizeTagBits::shift();
|
||||
|
||||
@@ -422,7 +422,8 @@ class UntaggedObject : public AllStatic {
|
||||
static const word kNewOrEvacuationCandidateBit;
|
||||
static const word kOldAndNotRememberedBit;
|
||||
static const word kNotMarkedBit;
|
||||
static const word kImmutableBit;
|
||||
static const word kShallowImmutableBit;
|
||||
static const word kDeeplyImmutableBit;
|
||||
static const word kSizeTagPos;
|
||||
static const word kSizeTagSize;
|
||||
static const word kClassIdTagPos;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1881,6 +1881,15 @@ void StubCodeCompiler::GenerateCheckedStoreIntoSharedStub() {
|
||||
__ Ret();
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateEnsureDeeplyImmutableStub() {
|
||||
__ EnterStubFrame();
|
||||
__ PushRegister(EnsureDeeplyImmutableStubABI::kValueReg);
|
||||
__ CallRuntime(kEnsureDeeplyImmutableRuntimeEntry, /*argument_count=*/1);
|
||||
__ Drop(1);
|
||||
__ LeaveStubFrame();
|
||||
__ Ret();
|
||||
}
|
||||
|
||||
static intptr_t SuspendStateFpOffset() {
|
||||
return compiler::target::frame_layout.FrameSlotForVariableIndex(
|
||||
SuspendState::kSuspendStateVarIndex) *
|
||||
|
||||
@@ -573,6 +573,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = R0;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = R0;
|
||||
static constexpr Register kTempReg = R1;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -411,6 +411,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = R0;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = R0;
|
||||
static constexpr Register kTempReg = R1;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -301,6 +301,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = EAX;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = EAX;
|
||||
static constexpr Register kTempReg = ECX;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -455,6 +455,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = A0;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = A0;
|
||||
static constexpr Register kTempReg = T1;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -377,6 +377,12 @@ struct CheckedStoreIntoSharedStubABI {
|
||||
static constexpr Register kResultReg = RAX;
|
||||
};
|
||||
|
||||
// ABI for EnsureDeeplyImmutableStub.
|
||||
struct EnsureDeeplyImmutableStubABI {
|
||||
static constexpr Register kValueReg = RAX;
|
||||
static constexpr Register kTempReg = R10;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
|
||||
@@ -3896,7 +3896,7 @@ static Dart_Handle NewExternalTypedData(Thread* thread,
|
||||
callback);
|
||||
}
|
||||
if (unmodifiable) {
|
||||
result.SetImmutable(); // Can pass by reference.
|
||||
result.SetDeeplyImmutable(); // Can pass by reference.
|
||||
const intptr_t view_cid = cid - kTypedDataCidRemainderExternal +
|
||||
kTypedDataCidRemainderUnmodifiable;
|
||||
result = TypedDataView::New(view_cid, ExternalTypedData::Cast(result), 0,
|
||||
@@ -3922,7 +3922,7 @@ static Dart_Handle NewExternalByteData(Thread* thread,
|
||||
const ExternalTypedData& array =
|
||||
Api::UnwrapExternalTypedDataHandle(zone, ext_data);
|
||||
if (unmodifiable) {
|
||||
array.SetImmutable(); // Can pass by reference.
|
||||
array.SetDeeplyImmutable(); // Can pass by reference.
|
||||
}
|
||||
return Api::NewHandle(
|
||||
thread, TypedDataView::New(unmodifiable ? kUnmodifiableByteDataViewCid
|
||||
|
||||
@@ -352,96 +352,6 @@ PersistentHandle* FfiCallbackMetadata::CreatePersistentHandle(
|
||||
return handle;
|
||||
}
|
||||
|
||||
class WorkSet : public StackResource {
|
||||
public:
|
||||
explicit WorkSet(Thread* thread, Zone* zone)
|
||||
: StackResource(thread),
|
||||
thread_(thread),
|
||||
list_(GrowableObjectArray::Handle(zone)) {
|
||||
ASSERT(thread->forward_table_new() == nullptr);
|
||||
set_ = new WeakTable();
|
||||
list_ = GrowableObjectArray::New(16);
|
||||
list_pos_ = 0;
|
||||
thread->set_forward_table_new(set_);
|
||||
}
|
||||
|
||||
~WorkSet() { thread_->set_forward_table_new(nullptr); }
|
||||
|
||||
void Add(const Object& object) {
|
||||
if (!IsMarked(object.ptr())) {
|
||||
Mark(object.ptr());
|
||||
list_.Add(object);
|
||||
}
|
||||
}
|
||||
|
||||
bool Take(Object* object) {
|
||||
if (list_pos_ >= list_.Length()) {
|
||||
return false;
|
||||
}
|
||||
*object = list_.At(list_pos_++);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsMarked(ObjectPtr object) {
|
||||
return set_->GetValueExclusive(object) != 0;
|
||||
}
|
||||
void Mark(ObjectPtr object) { set_->SetValueExclusive(object, 1); }
|
||||
|
||||
Thread* thread_;
|
||||
WeakTable* set_;
|
||||
GrowableObjectArray& list_;
|
||||
intptr_t list_pos_;
|
||||
};
|
||||
|
||||
void FfiCallbackMetadata::EnsureTriviallyImmutable(Zone* zone,
|
||||
const Object& object) {
|
||||
WorkSet workset(Thread::Current(), zone);
|
||||
workset.Add(object);
|
||||
|
||||
Object& current = Object::Handle(zone);
|
||||
Function& function = Function::Handle(zone);
|
||||
Object& obj = Object::Handle(zone);
|
||||
while (workset.Take(¤t)) {
|
||||
if (current.IsSmi() || current.IsNull() || current.IsCanonical()) {
|
||||
continue;
|
||||
}
|
||||
if (current.IsClosure()) {
|
||||
const Closure& closure = Closure::Cast(current);
|
||||
function = closure.function();
|
||||
if (!function.IsImplicitClosureFunction() &&
|
||||
!function.captures_only_final_not_late_vars()) {
|
||||
Exceptions::ThrowArgumentError(String::Handle(String::New(
|
||||
"Only final not-late variables can be captured by isolate "
|
||||
"group callbacks.")));
|
||||
UNREACHABLE();
|
||||
}
|
||||
obj = closure.RawContext();
|
||||
workset.Add(obj);
|
||||
continue;
|
||||
}
|
||||
if (current.IsImmutable()) {
|
||||
continue;
|
||||
}
|
||||
if (IsTypedDataBaseClassId(current.GetClassId())) {
|
||||
continue;
|
||||
}
|
||||
if (current.IsContext()) {
|
||||
const Context& context = Context::Cast(current);
|
||||
// Iterate through all elements of the context.
|
||||
for (intptr_t i = 0; i < context.num_variables(); i++) {
|
||||
obj = context.At(i);
|
||||
workset.Add(obj);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Exceptions::ThrowArgumentError(String::Handle(
|
||||
String::NewFormatted("Only trivially-immutable values are allowed: %s.",
|
||||
current.ToCString())));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateLocalFfiCallback(
|
||||
Isolate* isolate,
|
||||
IsolateGroup* isolate_group,
|
||||
@@ -469,7 +379,7 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateLocalFfiCallback(
|
||||
|
||||
if (function.GetFfiCallbackKind() ==
|
||||
FfiCallbackKind::kIsolateGroupBoundClosureCallback) {
|
||||
EnsureTriviallyImmutable(zone, closure);
|
||||
closure.EnsureDeeplyImmutable(zone);
|
||||
}
|
||||
|
||||
handle = CreatePersistentHandle(
|
||||
|
||||
@@ -343,8 +343,6 @@ class FfiCallbackMetadata {
|
||||
#error What architecture?
|
||||
#endif
|
||||
|
||||
static void EnsureTriviallyImmutable(Zone* zone, const Object& object);
|
||||
|
||||
// Visible for testing.
|
||||
#if defined(TESTING)
|
||||
|
||||
|
||||
@@ -661,7 +661,8 @@ static constexpr uword kReadOnlyGCBits =
|
||||
uword ImageWriter::GetMarkedTags(classid_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical /* = false */,
|
||||
bool is_immutable /* = false */) {
|
||||
bool is_shallow_immutable /* = false */,
|
||||
bool is_deeply_immutable /* = false */) {
|
||||
// UntaggedObject::SizeTag expects a size divisible by kObjectAlignment and
|
||||
// checks this in debug mode, but the size on the target machine may not be
|
||||
// divisible by the host machine's object alignment if they differ.
|
||||
@@ -682,7 +683,8 @@ uword ImageWriter::GetMarkedTags(classid_t cid,
|
||||
return kReadOnlyGCBits | UntaggedObject::ClassIdTag::encode(cid) |
|
||||
UntaggedObject::SizeTag::encode(adjusted_size) |
|
||||
UntaggedObject::CanonicalBit::encode(is_canonical) |
|
||||
UntaggedObject::ImmutableBit::encode(is_immutable);
|
||||
UntaggedObject::ShallowImmutableBit::encode(is_shallow_immutable) |
|
||||
UntaggedObject::DeeplyImmutableBit::encode(is_deeply_immutable);
|
||||
}
|
||||
|
||||
uword ImageWriter::GetMarkedTags(const Object& obj) {
|
||||
|
||||
@@ -516,7 +516,8 @@ class ImageWriter : public ValueObject {
|
||||
static uword GetMarkedTags(classid_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical = false,
|
||||
bool is_immutable = false);
|
||||
bool is_shallow_immutable = false,
|
||||
bool is_deeply_immutable = false);
|
||||
static uword GetMarkedTags(const Object& obj);
|
||||
|
||||
void DumpInstructionStats();
|
||||
|
||||
@@ -243,8 +243,10 @@ DART_FORCE_INLINE static ObjectPtr InitializeHeader(uword addr,
|
||||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(is_old, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(!is_old, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(
|
||||
Object::ShouldHaveImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::ShallowImmutableBit::update(
|
||||
Object::ShouldHaveShallowImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(
|
||||
Object::ShouldHaveDeeplyImmutabilityBitSet(class_id), tags);
|
||||
#if defined(HASH_IN_OBJECT_HEADER)
|
||||
tags = UntaggedObject::HashTag::update(0, tags);
|
||||
#endif
|
||||
|
||||
@@ -1591,7 +1591,7 @@ class TypedDataViewMessageDeserializationCluster
|
||||
data = ExternalTypedData::New(
|
||||
backing_cid, reinterpret_cast<uint8_t*>(finalizable_data.data),
|
||||
length);
|
||||
data.SetImmutable(); // Can pass by reference.
|
||||
data.SetDeeplyImmutable(); // Can pass by reference.
|
||||
intptr_t external_size = length * element_size;
|
||||
data.AddFinalizer(finalizable_data.peer, finalizable_data.callback,
|
||||
external_size);
|
||||
|
||||
@@ -118,14 +118,15 @@ class Deserializer : public ThreadStackResource {
|
||||
intptr_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical = false) {
|
||||
InitializeHeader(raw, cid, size, is_canonical,
|
||||
ShouldHaveImmutabilityBitSetCid(cid));
|
||||
InitializeHeader(raw, cid, size, is_canonical, IsShallowlyImmutableCid(cid),
|
||||
IsDeeplyImmutableCid(cid));
|
||||
}
|
||||
static void InitializeHeader(ObjectPtr raw,
|
||||
intptr_t cid,
|
||||
intptr_t size,
|
||||
bool is_canonical,
|
||||
bool is_immutable);
|
||||
bool is_shallow_immutable,
|
||||
bool is_deeply_immutable);
|
||||
|
||||
// Reads raw data (for basic types).
|
||||
// sizeof(T) must be in {1,2,4,8}.
|
||||
@@ -240,7 +241,8 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
||||
intptr_t class_id,
|
||||
intptr_t size,
|
||||
bool is_canonical,
|
||||
bool is_immutable) {
|
||||
bool is_shallow_immutable,
|
||||
bool is_deeply_immutable) {
|
||||
ASSERT(Utils::IsAligned(size, kObjectAlignment));
|
||||
uword tags = 0;
|
||||
tags = UntaggedObject::ClassIdTag::update(class_id, tags);
|
||||
@@ -250,7 +252,9 @@ void Deserializer::InitializeHeader(ObjectPtr raw,
|
||||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(true, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(false, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(is_immutable, tags);
|
||||
tags =
|
||||
UntaggedObject::ShallowImmutableBit::update(is_shallow_immutable, tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(is_deeply_immutable, tags);
|
||||
raw->untag()->tags_ = tags;
|
||||
}
|
||||
|
||||
|
||||
+129
-5
@@ -1767,6 +1767,121 @@ void Object::RegisterPrivateClass(const Class& cls,
|
||||
lib.AddClass(cls);
|
||||
}
|
||||
|
||||
class WorkSet : public StackResource {
|
||||
public:
|
||||
explicit WorkSet(Thread* thread, Zone* zone)
|
||||
: StackResource(thread),
|
||||
thread_(thread),
|
||||
list_(GrowableObjectArray::Handle(zone)),
|
||||
stack_(GrowableObjectArray::Handle(zone)) {
|
||||
ASSERT(thread->forward_table_new() == nullptr);
|
||||
set_ = new WeakTable();
|
||||
list_ = GrowableObjectArray::New(16);
|
||||
stack_ = GrowableObjectArray::New(16);
|
||||
thread->set_forward_table_new(set_);
|
||||
}
|
||||
|
||||
~WorkSet() {
|
||||
thread_->set_forward_table_new(nullptr);
|
||||
set_ = nullptr;
|
||||
}
|
||||
|
||||
void Add(const Object& object) {
|
||||
if (!IsMarked(object.ptr())) {
|
||||
Mark(object.ptr());
|
||||
list_.Add(object);
|
||||
}
|
||||
}
|
||||
|
||||
bool TakeAndPush(Object* object) {
|
||||
if (list_.Length() == 0) {
|
||||
return false;
|
||||
}
|
||||
*object = list_.RemoveLast();
|
||||
stack_.Add(*object); // Push
|
||||
return true;
|
||||
}
|
||||
|
||||
void PopAndProcessCompletedClosuresAndContexts(Object* object) {
|
||||
if (stack_.Length() > 0) {
|
||||
*object = stack_.RemoveLast(); // Pop
|
||||
// Are we done processing nested context or closures?
|
||||
while (stack_.Length() > 0) {
|
||||
*object = stack_.At(stack_.Length() - 1);
|
||||
if (object->IsContext()) {
|
||||
stack_.RemoveLast();
|
||||
} else if (object->IsClosure()) {
|
||||
object->SetDeeplyImmutable();
|
||||
stack_.RemoveLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t StackLength() { return stack_.Length(); }
|
||||
|
||||
private:
|
||||
bool IsMarked(ObjectPtr object) {
|
||||
return set_->GetValueExclusive(object) != 0;
|
||||
}
|
||||
void Mark(ObjectPtr object) { set_->SetValueExclusive(object, 1); }
|
||||
|
||||
Thread* thread_;
|
||||
WeakTable* set_;
|
||||
GrowableObjectArray& list_;
|
||||
// Stack of objects that are currently being processed
|
||||
GrowableObjectArray& stack_;
|
||||
};
|
||||
|
||||
void Object::EnsureDeeplyImmutable(Zone* zone) const {
|
||||
WorkSet workset(Thread::Current(), zone);
|
||||
workset.Add(*this);
|
||||
|
||||
Object& current = Object::Handle(zone);
|
||||
Function& function = Function::Handle(zone);
|
||||
Object& obj = Object::Handle(zone);
|
||||
|
||||
while (workset.TakeAndPush(¤t)) {
|
||||
if (current.IsSmi() || current.IsNull() || current.IsCanonical() ||
|
||||
current.IsDeeplyImmutable() ||
|
||||
IsTypedDataBaseClassId(current.GetClassId())) {
|
||||
workset.PopAndProcessCompletedClosuresAndContexts(&obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current.IsClosure()) {
|
||||
const Closure& closure = Closure::Cast(current);
|
||||
function = closure.function();
|
||||
if (!function.IsImplicitClosureFunction() &&
|
||||
!function.captures_only_final_not_late_vars()) {
|
||||
Exceptions::ThrowArgumentError(String::Handle(String::New(
|
||||
"Only final not-late variables can be captured here.")));
|
||||
UNREACHABLE();
|
||||
}
|
||||
obj = closure.RawContext();
|
||||
workset.Add(obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current.IsContext()) {
|
||||
const Context& context = Context::Cast(current);
|
||||
// Iterate through all elements of the context.
|
||||
for (intptr_t i = 0; i < context.num_variables(); i++) {
|
||||
obj = context.At(i);
|
||||
workset.Add(obj);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Exceptions::ThrowArgumentError(String::Handle(
|
||||
String::NewFormatted("Only trivially-immutable values are allowed: %s.",
|
||||
current.ToCString())));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
RELEASE_ASSERT(workset.StackLength() == 0);
|
||||
}
|
||||
|
||||
// Initialize a new isolate from source or from a snapshot.
|
||||
//
|
||||
// There are three possibilities:
|
||||
@@ -2741,9 +2856,16 @@ StringPtr Object::DictionaryName() const {
|
||||
return String::null();
|
||||
}
|
||||
|
||||
bool Object::ShouldHaveImmutabilityBitSet(classid_t class_id) {
|
||||
bool Object::ShouldHaveShallowImmutabilityBitSet(classid_t class_id) {
|
||||
if (class_id < kNumPredefinedCids) {
|
||||
return ShouldHaveImmutabilityBitSetCid(class_id);
|
||||
return IsShallowlyImmutableCid(class_id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Object::ShouldHaveDeeplyImmutabilityBitSet(classid_t class_id) {
|
||||
if (class_id < kNumPredefinedCids) {
|
||||
return IsDeeplyImmutableCid(class_id);
|
||||
} else {
|
||||
return Class::IsDeeplyImmutable(
|
||||
IsolateGroup::Current()->class_table()->At(class_id));
|
||||
@@ -2852,8 +2974,10 @@ void Object::InitializeObject(uword address,
|
||||
tags = UntaggedObject::NotMarkedBit::update(true, tags);
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(is_old, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(!is_old, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(
|
||||
Object::ShouldHaveImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::ShallowImmutableBit::update(
|
||||
Object::ShouldHaveShallowImmutabilityBitSet(class_id), tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(
|
||||
Object::ShouldHaveDeeplyImmutabilityBitSet(class_id), tags);
|
||||
#if defined(HASH_IN_OBJECT_HEADER)
|
||||
tags = UntaggedObject::HashTag::update(0, tags);
|
||||
#endif
|
||||
@@ -13497,7 +13621,7 @@ void Field::SetStaticValue(const Object& value) const {
|
||||
ASSERT(id >= 0);
|
||||
|
||||
if (FLAG_experimental_shared_data && is_shared()) {
|
||||
FfiCallbackMetadata::EnsureTriviallyImmutable(thread->zone(), value);
|
||||
value.EnsureDeeplyImmutable(thread->zone());
|
||||
}
|
||||
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
|
||||
if (is_shared()) {
|
||||
|
||||
+17
-4
@@ -352,9 +352,19 @@ class Object {
|
||||
bool IsCanonical() const { return ptr()->untag()->IsCanonical(); }
|
||||
void SetCanonical() const { ptr()->untag()->SetCanonical(); }
|
||||
void ClearCanonical() const { ptr()->untag()->ClearCanonical(); }
|
||||
bool IsImmutable() const { return ptr()->untag()->IsImmutable(); }
|
||||
void SetImmutable() const { ptr()->untag()->SetImmutable(); }
|
||||
void ClearImmutable() const { ptr()->untag()->ClearImmutable(); }
|
||||
bool IsShallowImmutable() const {
|
||||
return ptr()->untag()->IsShallowImmutable();
|
||||
}
|
||||
void SetShallowImmutable() const { ptr()->untag()->SetShallowImmutable(); }
|
||||
void ClearShallowImmutable() const {
|
||||
ptr()->untag()->ClearShallowImmutable();
|
||||
}
|
||||
bool IsDeeplyImmutable() const { return ptr()->untag()->IsDeeplyImmutable(); }
|
||||
void SetDeeplyImmutable() const { ptr()->untag()->SetDeeplyImmutable(); }
|
||||
void ClearDeeplyImmutable() const { ptr()->untag()->ClearDeeplyImmutable(); }
|
||||
bool IsImmutable() const {
|
||||
return IsShallowImmutable() || IsDeeplyImmutable();
|
||||
}
|
||||
intptr_t GetClassId() const { return ptr()->GetClassId(); }
|
||||
inline ClassPtr clazz() const;
|
||||
static intptr_t tags_offset() { return OFFSET_OF(UntaggedObject, tags_); }
|
||||
@@ -722,7 +732,10 @@ class Object {
|
||||
kNo,
|
||||
};
|
||||
|
||||
static bool ShouldHaveImmutabilityBitSet(classid_t class_id);
|
||||
static bool ShouldHaveShallowImmutabilityBitSet(classid_t class_id);
|
||||
static bool ShouldHaveDeeplyImmutabilityBitSet(classid_t class_id);
|
||||
|
||||
void EnsureDeeplyImmutable(Zone* zone) const;
|
||||
|
||||
protected:
|
||||
friend ObjectPtr AllocateObject(intptr_t, intptr_t, intptr_t);
|
||||
|
||||
@@ -151,8 +151,11 @@ static bool CanShareObject(ObjectPtr obj, uword tags) {
|
||||
if ((tags & UntaggedObject::CanonicalBit::mask_in_place()) != 0) {
|
||||
return true;
|
||||
}
|
||||
const auto cid = UntaggedObject::ClassIdTag::decode(tags);
|
||||
if ((tags & UntaggedObject::ImmutableBit::mask_in_place()) != 0) {
|
||||
if ((tags & UntaggedObject::DeeplyImmutableBit::mask_in_place()) != 0) {
|
||||
return true;
|
||||
}
|
||||
if ((tags & UntaggedObject::ShallowImmutableBit::mask_in_place()) != 0) {
|
||||
const auto cid = UntaggedObject::ClassIdTag::decode(tags);
|
||||
if (IsUnmodifiableTypedDataViewClassId(cid)) {
|
||||
// Unmodifiable typed data views may have mutable backing stores.
|
||||
return TypedDataView::RawCast(obj)
|
||||
@@ -235,8 +238,10 @@ void SetNewSpaceTaggingWord(ObjectPtr to, classid_t cid, uint32_t size) {
|
||||
tags = UntaggedObject::OldAndNotRememberedBit::update(false, tags);
|
||||
tags = UntaggedObject::CanonicalBit::update(false, tags);
|
||||
tags = UntaggedObject::NewOrEvacuationCandidateBit::update(true, tags);
|
||||
tags = UntaggedObject::ImmutableBit::update(
|
||||
Object::ShouldHaveImmutabilityBitSet(cid), tags);
|
||||
tags = UntaggedObject::ShallowImmutableBit::update(
|
||||
Object::ShouldHaveShallowImmutabilityBitSet(cid), tags);
|
||||
tags = UntaggedObject::DeeplyImmutableBit::update(
|
||||
Object::ShouldHaveDeeplyImmutabilityBitSet(cid), tags);
|
||||
#if defined(HASH_IN_OBJECT_HEADER)
|
||||
tags = UntaggedObject::HashTag::update(0, tags);
|
||||
#endif
|
||||
|
||||
+16
-5
@@ -226,12 +226,15 @@ class UntaggedObject {
|
||||
// The bit is also used to make typed data stores efficient. 2.a.
|
||||
//
|
||||
// See also Class::kIsDeeplyImmutableBit.
|
||||
using ImmutableBit =
|
||||
using ShallowImmutableBit =
|
||||
BitField<decltype(tags_), bool, OldAndNotRememberedBit::kNextBit>;
|
||||
|
||||
using DeeplyImmutableBit =
|
||||
BitField<decltype(tags_), bool, ShallowImmutableBit::kNextBit>;
|
||||
|
||||
// The rest of the initial byte is currently reserved, so the next bitfield
|
||||
// starts at the byte boundary.
|
||||
COMPILE_ASSERT(ImmutableBit::kNextBit <= kBitsPerInt8);
|
||||
COMPILE_ASSERT(DeeplyImmutableBit::kNextBit <= kBitsPerInt8);
|
||||
using SizeTagBits = BitField<decltype(tags_), intptr_t, kBitsPerInt8, 4>;
|
||||
|
||||
// Encodes the object size in the tag in units of object alignment.
|
||||
@@ -357,9 +360,17 @@ class UntaggedObject {
|
||||
void SetCanonical() { tags_.UpdateBool<CanonicalBit>(true); }
|
||||
void ClearCanonical() { tags_.UpdateBool<CanonicalBit>(false); }
|
||||
|
||||
bool IsImmutable() const { return tags_.Read<ImmutableBit>(); }
|
||||
void SetImmutable() { tags_.UpdateBool<ImmutableBit>(true); }
|
||||
void ClearImmutable() { tags_.UpdateBool<ImmutableBit>(false); }
|
||||
bool IsShallowImmutable() const { return tags_.Read<ShallowImmutableBit>(); }
|
||||
void SetShallowImmutable() { tags_.UpdateBool<ShallowImmutableBit>(true); }
|
||||
void ClearShallowImmutable() { tags_.UpdateBool<ShallowImmutableBit>(false); }
|
||||
|
||||
bool IsDeeplyImmutable() const { return tags_.Read<DeeplyImmutableBit>(); }
|
||||
void SetDeeplyImmutable() { tags_.UpdateBool<DeeplyImmutableBit>(true); }
|
||||
void ClearDeeplyImmutable() { tags_.UpdateBool<DeeplyImmutableBit>(false); }
|
||||
|
||||
bool IsImmutable() const {
|
||||
return IsShallowImmutable() || IsDeeplyImmutable();
|
||||
}
|
||||
|
||||
bool InVMIsolateHeap() const;
|
||||
|
||||
|
||||
@@ -4687,7 +4687,7 @@ DEFINE_RUNTIME_ENTRY(CheckedStoreIntoShared, 2) {
|
||||
const Field& field = Field::CheckedHandle(zone, arguments.ArgAt(0));
|
||||
const Instance& value = Instance::CheckedHandle(zone, arguments.ArgAt(1));
|
||||
|
||||
FfiCallbackMetadata::EnsureTriviallyImmutable(zone, value);
|
||||
value.EnsureDeeplyImmutable(zone);
|
||||
|
||||
field.SetStaticValue(value);
|
||||
arguments.SetReturn(field);
|
||||
@@ -5245,6 +5245,13 @@ DEFINE_RUNTIME_ENTRY(InitializeSharedField, 1) {
|
||||
arguments.SetReturn(result);
|
||||
}
|
||||
|
||||
// Throw if the value is not immutable.
|
||||
// Arg0: Value to check.
|
||||
DEFINE_RUNTIME_ENTRY(EnsureDeeplyImmutable, 1) {
|
||||
const Instance& value = Instance::CheckedHandle(zone, arguments.ArgAt(0));
|
||||
value.EnsureDeeplyImmutable(zone);
|
||||
}
|
||||
|
||||
#if defined(USING_MEMORY_SANITIZER)
|
||||
extern "C" void dart_msan_read1(void* addr) {
|
||||
__msan_check_mem_is_initialized(addr, 1);
|
||||
|
||||
@@ -89,7 +89,8 @@ namespace dart {
|
||||
V(InvokeNoSuchMethod) \
|
||||
V(ResumeInterpreter) \
|
||||
V(InitializeSharedField) \
|
||||
V(FatalError)
|
||||
V(FatalError) \
|
||||
V(EnsureDeeplyImmutable)
|
||||
|
||||
// Note: Leaf runtime function have C linkage, so they cannot pass C++ struct
|
||||
// values like ObjectPtr.
|
||||
|
||||
@@ -193,6 +193,7 @@ namespace dart {
|
||||
V(FfiAsyncCallbackSend) \
|
||||
V(CheckIsolateFieldAccess) \
|
||||
V(CheckedStoreIntoShared) \
|
||||
V(EnsureDeeplyImmutable) \
|
||||
V(UnknownDartCode)
|
||||
|
||||
} // namespace dart
|
||||
|
||||
@@ -488,3 +488,6 @@ abstract interface class IsolateGroup {
|
||||
final _toStringVisiting = FinalThreadLocal<List<Object>>(() => <Object>[]);
|
||||
@patch
|
||||
List<Object> get toStringVisiting => _toStringVisiting.value;
|
||||
|
||||
@pragma("vm:external-name", "Internal_ensureDeeplyImmutable")
|
||||
external _ensureDeeplyImmutable(value);
|
||||
|
||||
@@ -44,6 +44,10 @@ main(List<String> args) {
|
||||
|
||||
testFailToAssignClosureWithListToSharedVar();
|
||||
|
||||
testCapturesClassWithDeeplyImmutableClosure();
|
||||
testFailToCaptureClassWithNonFinalCapturingClosure();
|
||||
testCapturesClassWithFinalCapturingClosure();
|
||||
|
||||
testFailToPrint();
|
||||
|
||||
testFailToIsolateGroupRunSyncThrows();
|
||||
@@ -266,6 +270,61 @@ void testFailToAssignClosureWithListToSharedVar() {
|
||||
print(getList);
|
||||
}
|
||||
|
||||
///
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class FooWithFunc {
|
||||
final int Function() func;
|
||||
final int kuka;
|
||||
|
||||
FooWithFunc(this.func, this.kuka);
|
||||
}
|
||||
|
||||
void testCapturesClassWithDeeplyImmutableClosure() {
|
||||
final foo = FooWithFunc(() => 42, 43);
|
||||
final result = IsolateGroup.runSync(() {
|
||||
return foo.func();
|
||||
});
|
||||
Expect.equals(42, result);
|
||||
}
|
||||
|
||||
@pragma('vm:shared')
|
||||
bool skipAccessAtRuntime = false;
|
||||
|
||||
void testFailToCaptureClassWithNonFinalCapturingClosure() {
|
||||
var closureValue = 42;
|
||||
|
||||
// Construction of this "deeply-immutable" object should actually fail at
|
||||
// runtime due to closure closing over non-final [closureValue] variable.
|
||||
Expect.throws(
|
||||
() => FooWithFunc(() => skipAccessAtRuntime ? -1 : closureValue, 43),
|
||||
(e) => e is Error && e.toString().contains("Only final not-late"),
|
||||
);
|
||||
}
|
||||
|
||||
void testCapturesClassWithFinalCapturingClosure() {
|
||||
final finalClosureValue = 43;
|
||||
final foo = FooWithFunc(
|
||||
() => skipAccessAtRuntime ? -1 : finalClosureValue,
|
||||
44,
|
||||
);
|
||||
|
||||
skipAccessAtRuntime = true;
|
||||
Expect.equals(
|
||||
-1,
|
||||
IsolateGroup.runSync(() {
|
||||
return foo.func();
|
||||
}),
|
||||
);
|
||||
|
||||
skipAccessAtRuntime = false;
|
||||
Expect.equals(
|
||||
43,
|
||||
IsolateGroup.runSync(() {
|
||||
return foo.func();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
void testFailToPrint() {
|
||||
Expect.throws(
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
|
||||
// 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.
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
// 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.
|
||||
|
||||
// These checks are not implemented in the analyzer. If we ever decide to
|
||||
// implement the static checks in the analyzer, move this test into the
|
||||
// static_checks subdir to prevent analyzer errors showing up in the IDE.
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
void main() {
|
||||
testInstantiateDeeplyImmutable();
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class EmptyClass {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class1 {
|
||||
Class1(this.a);
|
||||
|
||||
int a;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have final non-late instance fields.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class2 {
|
||||
late final int a;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have final non-late instance fields.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class3 {
|
||||
Class3(this.a);
|
||||
|
||||
final int a;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class4 {
|
||||
// Static fields are not part of the instance.
|
||||
static late final int a;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class5 {
|
||||
// External fields are defined as setter/getter pairs.
|
||||
external int a;
|
||||
}
|
||||
|
||||
final class NotDeeplyImmutable {
|
||||
late int a;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class6 {
|
||||
Class6(this.a);
|
||||
|
||||
final NotDeeplyImmutable a;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', function types and classes annotated with `@pragma('vm:deeply-immutable')`.
|
||||
}
|
||||
|
||||
void testInstantiateDeeplyImmutable() {
|
||||
Class7(
|
||||
someString: 'someString',
|
||||
someNullableString: 'someString',
|
||||
someInt: 3,
|
||||
someDouble: 3.3,
|
||||
someBool: false,
|
||||
someNull: null,
|
||||
someInt32x4: Int32x4(0, 1, 2, 3),
|
||||
someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someClass7: Class7(
|
||||
someString: 'someString',
|
||||
someInt: 3,
|
||||
someDouble: 3.3,
|
||||
someBool: false,
|
||||
someNull: null,
|
||||
someInt32x4: Int32x4(0, 1, 2, 3),
|
||||
someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someClass7: null,
|
||||
somePointer: Pointer.fromAddress(0x8badf00d),
|
||||
),
|
||||
somePointer: Pointer.fromAddress(0xdeadbeef),
|
||||
);
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class7 {
|
||||
final String someString;
|
||||
final String? someNullableString;
|
||||
final int someInt;
|
||||
final double someDouble;
|
||||
final bool someBool;
|
||||
final Null someNull;
|
||||
final Int32x4 someInt32x4;
|
||||
final Float32x4 someFloat32x4;
|
||||
final Float64x2 someFloat64x2;
|
||||
final Class7? someClass7;
|
||||
final Pointer somePointer;
|
||||
|
||||
// Note that UnmodifiableUint8ListView has been deprecated. Which means there
|
||||
// currently is no way to intentionally have a typed data as a field in a
|
||||
// class which is deeply immutable.
|
||||
// See: https://github.com/dart-lang/sdk/issues/53218.
|
||||
|
||||
// Note that RegExp, SendPort, and Capability can be implemented. So fields
|
||||
// are not allowed to be of these types either.
|
||||
|
||||
Class7({
|
||||
required this.someString,
|
||||
this.someNullableString,
|
||||
required this.someInt,
|
||||
required this.someDouble,
|
||||
required this.someBool,
|
||||
required this.someNull,
|
||||
required this.someInt32x4,
|
||||
required this.someFloat32x4,
|
||||
required this.someFloat64x2,
|
||||
required this.someClass7,
|
||||
required this.somePointer,
|
||||
});
|
||||
}
|
||||
|
||||
void testInstantiateImmutableHierarchy() {
|
||||
Class8(animal: Cat(numberOfLegs: 4, averageNumberOfMeowsPerDay: 42.0));
|
||||
Class8(animal: Dog(numberOfLegs: 4, averageNumberOfWoofsPerDay: 1337.0));
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Animal {
|
||||
final int numberOfLegs;
|
||||
|
||||
Animal({required this.numberOfLegs});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Cat extends Animal {
|
||||
final double averageNumberOfMeowsPerDay;
|
||||
|
||||
Cat({required super.numberOfLegs, required this.averageNumberOfMeowsPerDay});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Dog extends Animal {
|
||||
final double averageNumberOfWoofsPerDay;
|
||||
|
||||
Dog({required super.numberOfLegs, required this.averageNumberOfWoofsPerDay});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class8 {
|
||||
final Animal animal;
|
||||
|
||||
Class8({required this.animal});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
abstract final class DeeplyImmutableInterface {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class9 implements DeeplyImmutableInterface {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class10 implements DeeplyImmutableInterface {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
sealed class Class11 {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
class NotSealedOrFinalClass {}
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must be final or sealed.
|
||||
|
||||
final class Class12 extends DeeplyImmutableInterface {}
|
||||
// ^
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
|
||||
final class Class13 implements DeeplyImmutableInterface {
|
||||
// ^
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class14<T extends DeeplyImmutableInterface> {
|
||||
final T deeplyImmutable;
|
||||
|
||||
Class14({required this.deeplyImmutable});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class15<T extends NotDeeplyImmutable> {
|
||||
final T notDeeplyImmutable;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', function types and classes annotated with `@pragma('vm:deeply-immutable')`.
|
||||
|
||||
Class15({required this.notDeeplyImmutable});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
abstract mixin class Class17 {
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must be final or sealed.
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import 'dart:isolate';
|
||||
final class Foo {
|
||||
dynamic myMutableField;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', and classes annotated with `@pragma('vm:deeply-immutable')`.
|
||||
// [cfe] Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', function types and classes annotated with `@pragma('vm:deeply-immutable')`.
|
||||
// [cfe] Deeply immutable classes must only have final non-late instance fields.
|
||||
}
|
||||
|
||||
|
||||
@@ -9,29 +9,54 @@
|
||||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
void main() {
|
||||
testInstantiateDeeplyImmutable();
|
||||
testOnlyFinalCanBeCaptured();
|
||||
testOnlyTriviallyImmutableCanBeCaptured();
|
||||
testCaptureGlobal();
|
||||
testCaptureEmptyContext();
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class DeeplyImmutableFoo {
|
||||
final Function() closure;
|
||||
|
||||
DeeplyImmutableFoo(this.closure);
|
||||
}
|
||||
|
||||
testOnlyFinalCanBeCaptured() {
|
||||
List<String> list = <String>["Hello", "world"];
|
||||
Expect.throws(() {
|
||||
final foo = DeeplyImmutableFoo(() { list.add("abc"); return list; } );
|
||||
print(foo.closure());
|
||||
}, (e) => e is ArgumentError && e.toString().contains("Only final not-late"));
|
||||
}
|
||||
|
||||
testOnlyTriviallyImmutableCanBeCaptured() {
|
||||
final List<String> list = <String>["Hello", "world"];
|
||||
Expect.throws(() {
|
||||
final foo = DeeplyImmutableFoo(() { list.add("abc"); return list; } );
|
||||
print(foo.closure());
|
||||
}, (e) => e is ArgumentError &&
|
||||
e.toString().contains("Only trivially-immutable values"));
|
||||
}
|
||||
|
||||
final List<String> globalList = <String>["Hello", "world"];
|
||||
testCaptureGlobal() {
|
||||
final foo = DeeplyImmutableFoo(() { globalList.add("abc"); return globalList; });
|
||||
print(foo.closure());
|
||||
}
|
||||
|
||||
testCaptureEmptyContext() {
|
||||
final foo = DeeplyImmutableFoo(() => 42);
|
||||
print(foo.closure());
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class EmptyClass {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class1 {
|
||||
Class1(this.a);
|
||||
|
||||
int a;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have final non-late instance fields.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class2 {
|
||||
late final int a;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have final non-late instance fields.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class3 {
|
||||
Class3(this.a);
|
||||
@@ -55,15 +80,6 @@ final class NotDeeplyImmutable {
|
||||
late int a;
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class6 {
|
||||
Class6(this.a);
|
||||
|
||||
final NotDeeplyImmutable a;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', and classes annotated with `@pragma('vm:deeply-immutable')`.
|
||||
}
|
||||
|
||||
void testInstantiateDeeplyImmutable() {
|
||||
Class7(
|
||||
someString: 'someString',
|
||||
@@ -86,8 +102,10 @@ void testInstantiateDeeplyImmutable() {
|
||||
someFloat64x2: Float64x2(4.4, 5.5),
|
||||
someClass7: null,
|
||||
somePointer: Pointer.fromAddress(0x8badf00d),
|
||||
someClosure: () => 31,
|
||||
),
|
||||
somePointer: Pointer.fromAddress(0xdeadbeef),
|
||||
someClosure: () => 42
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,6 +122,7 @@ final class Class7 {
|
||||
final Float64x2 someFloat64x2;
|
||||
final Class7? someClass7;
|
||||
final Pointer somePointer;
|
||||
final Function() someClosure;
|
||||
|
||||
// Note that UnmodifiableUint8ListView has been deprecated. Which means there
|
||||
// currently is no way to intentionally have a typed data as a field in a
|
||||
@@ -125,6 +144,7 @@ final class Class7 {
|
||||
required this.someFloat64x2,
|
||||
required this.someClass7,
|
||||
required this.somePointer,
|
||||
required this.someClosure,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,38 +193,9 @@ final class Class10 implements DeeplyImmutableInterface {}
|
||||
@pragma('vm:deeply-immutable')
|
||||
sealed class Class11 {}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
class NotSealedOrFinalClass {}
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must be final or sealed.
|
||||
|
||||
final class Class12 extends DeeplyImmutableInterface {}
|
||||
// ^
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
|
||||
final class Class13 implements DeeplyImmutableInterface {
|
||||
// ^
|
||||
// [cfe] Subtypes of deeply immutable classes must be deeply immutable.
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class14<T extends DeeplyImmutableInterface> {
|
||||
final T deeplyImmutable;
|
||||
|
||||
Class14({required this.deeplyImmutable});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
final class Class15<T extends NotDeeplyImmutable> {
|
||||
final T notDeeplyImmutable;
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must only have deeply immutable instance fields. Deeply immutable types include 'int', 'double', 'bool', 'String', 'Pointer', 'Float32x4', 'Float64x2', 'Int32x4', and classes annotated with `@pragma('vm:deeply-immutable')`.
|
||||
|
||||
Class15({required this.notDeeplyImmutable});
|
||||
}
|
||||
|
||||
@pragma('vm:deeply-immutable')
|
||||
abstract mixin class Class17 {}
|
||||
// ^
|
||||
// [cfe] Deeply immutable classes must be final or sealed.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user