[vm/shared] Skip runtime deep-immutability check for statically known types.

Fixes https://github.com/dart-lang/sdk/issues/61078
TEST=StreamingFlowGraphBuilder_DeeplyImmutableTypeCheck*

Change-Id: I4116d5f51f3247d50d4e7fc0f87e04238a6e3151
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/474563
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Alexander Aprelev
2026-01-22 12:53:50 -08:00
committed by Commit Queue
parent e76c7d64cd
commit af7c390044
11 changed files with 386 additions and 120 deletions
@@ -36,9 +36,25 @@ void validateLibraries(
}
}
class _CheckResult {
final bool isImmutable;
final bool requiresRuntimeCheck;
const _CheckResult({
this.isImmutable = false,
this.requiresRuntimeCheck = false,
});
}
/// Implements the `vm:deeply-immutable` semantics.
class DeeplyImmutableValidator {
static const vmDeeplyImmutable = "vm:deeply-immutable";
late final InstanceConstant vmDeeplyImmutableConstant =
InstanceConstant(coreTypes.pragmaClass.reference, [], {
coreTypes.pragmaName.fieldReference: StringConstant(vmDeeplyImmutable),
coreTypes.pragmaOptions.fieldReference: NullConstant(),
});
static const vmShared = "vm:shared";
final CoreTypes coreTypes;
final DiagnosticReporter diagnosticReporter;
@@ -67,12 +83,32 @@ class DeeplyImmutableValidator {
for (final cls in library.classes) {
visitClass(cls);
}
for (final field in library.fields) {
if (_isVmSharedField(field)) {
addDeeplyImmutableAnnotationIfNeeded(field);
}
}
}
void visitClass(Class node) {
_validateDeeplyImmutable(node);
}
// pragma("vm:deeply-immutable") on a field indicates that the field static
// type guarantees that it always have deeply-immutable value, therefore
// at a runtime there is no need to check the value being assigned to the
// field.
// This pragma is added only for "vm:shared" static fields and to all fields
// of "vm:deeply-immutable" class because those are the only ones that are
// sensitive to having deeply-immutable values in them.
_CheckResult addDeeplyImmutableAnnotationIfNeeded(Field field) {
final checkResult = _isDeeplyImmutableDartType(field.type);
if (checkResult.isImmutable && !checkResult.requiresRuntimeCheck) {
field.addAnnotation(ConstantExpression(vmDeeplyImmutableConstant));
}
return checkResult;
}
bool _isOrExtendsNativeFieldWrapper1Class(Class? node) {
while (node != null && node != nativeFieldWrapperClass1Class) {
node = node.superclass;
@@ -99,6 +135,11 @@ class DeeplyImmutableValidator {
);
}
}
for (final field in node.fields) {
if (field.isStatic && _isVmSharedField(field)) {
addDeeplyImmutableAnnotationIfNeeded(field);
}
}
return;
}
@@ -141,11 +182,12 @@ class DeeplyImmutableValidator {
// All instance fields should be non-late final and deeply immutable.
for (final field in node.fields) {
final checkResult = addDeeplyImmutableAnnotationIfNeeded(field);
if (field.isStatic) {
// Static fields are not part of instances.
continue;
}
if (!_isDeeplyImmutableDartType(field.type)) {
if (!checkResult.isImmutable) {
diagnosticReporter.report(
codeFfiDeeplyImmutableFieldsMustBeDeeplyImmutable,
field.fileOffset,
@@ -164,13 +206,16 @@ class DeeplyImmutableValidator {
}
}
bool _isDeeplyImmutableDartType(DartType dartType) {
_CheckResult _isDeeplyImmutableDartType(DartType dartType) {
if (dartType is NullType) {
return true;
return _CheckResult(isImmutable: true, requiresRuntimeCheck: false);
}
if (dartType is InterfaceType) {
final classNode = dartType.classNode;
return _isDeeplyImmutableClass(classNode);
return _CheckResult(
isImmutable: _isDeeplyImmutableClass(classNode),
requiresRuntimeCheck: false,
);
}
if (dartType is TypeParameterType) {
return _isDeeplyImmutableDartType(dartType.bound);
@@ -178,9 +223,9 @@ class DeeplyImmutableValidator {
if (dartType is FunctionType) {
// Relies on dynamic check of whether closure actually captures only
// deeply-immutable values.
return true;
return _CheckResult(isImmutable: true, requiresRuntimeCheck: true);
}
return false;
return _CheckResult(isImmutable: false, requiresRuntimeCheck: false);
}
bool _isDeeplyImmutableClass(Class node) {
@@ -197,4 +242,19 @@ class DeeplyImmutableValidator {
}
return false;
}
bool _isVmSharedField(Field node) {
for (final annotation in node.annotations) {
if (annotation is ConstantExpression) {
final constant = annotation.constant;
if (constant is InstanceConstant &&
constant.classNode == pragmaClass &&
constant.fieldValues[pragmaName.fieldReference] ==
StringConstant(vmShared)) {
return true;
}
}
}
return false;
}
}
@@ -0,0 +1,85 @@
// Copyright (c) 2026, 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.
// Verifies that deeply-immutable runtme type check is omitted when types
// are statically known.
import 'package:expect/expect.dart';
import 'package:vm/testing/il_matchers.dart';
@pragma('vm:deeply-immutable')
final class FunctionFoo {
final void Function(int) baz;
FunctionFoo(this.baz);
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
runtime_check_is_present() {
final ff = FunctionFoo((int x) {
print(x);
});
ff.baz(42);
}
@pragma('vm:deeply-immutable')
final class IntFoo {
final int baz;
IntFoo(this.baz);
}
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
runtime_check_is_omitted() {
IntFoo(42);
}
void main() {
runtime_check_is_present();
runtime_check_is_omitted();
}
void extractAllInstructions(dynamic data, List<String> into) {
if (data is Map) {
for (var entry in data.entries) {
if (entry.key == "o" && entry.value is String) {
into.add(entry.value);
} else {
extractAllInstructions(entry.value, into);
}
}
} else if (data is List) {
for (var entry in data) {
extractAllInstructions(entry, into);
}
} else {
if (data is int || data is String) {
// ok
} else {
print("Notice: Unhandled data: ${data.runtimeType}: $data");
}
}
}
bool hasCheckFieldImmutability(FlowGraph graph) {
List<String> ils = [];
extractAllInstructions(graph.blocks(), ils);
for (String il in ils) {
if (il == "CheckFieldImmutability") return true;
}
return false;
}
void matchIL$runtime_check_is_present(FlowGraph graph) {
Expect.isTrue(
hasCheckFieldImmutability(graph),
"should have immutability checks",
);
}
void matchIL$runtime_check_is_omitted(FlowGraph graph) {
Expect.isFalse(
hasCheckFieldImmutability(graph),
"should not have immutability checks",
);
}
+25 -22
View File
@@ -2933,8 +2933,10 @@ void LoadCodeUnitsInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps =
FLAG_experimental_shared_data && field().is_shared() ? 2 : 1;
const bool need_extra_temp = FLAG_experimental_shared_data &&
field().is_shared() &&
!field().has_deeply_immutable_type();
const intptr_t kNumTemps = need_extra_temp ? 2 : 1;
const bool can_call_to_throw = FLAG_experimental_shared_data;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps,
@@ -2943,7 +2945,7 @@ LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
locs->set_in(
0, Location::RegisterLocation(CheckedStoreIntoSharedStubABI::kValueReg));
locs->set_temp(0, Location::RequiresRegister());
if (FLAG_experimental_shared_data && field().is_shared()) {
if (need_extra_temp) {
locs->set_temp(1, Location::RegisterLocation(
CheckedStoreIntoSharedStubABI::kFieldReg));
}
@@ -2965,26 +2967,26 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ LoadIsolate(temp);
__ BranchIfZero(temp, slow_path->entry_label());
} else {
// TODO(dartbug.com/61078): use field static type information to decide
// whether the following value check is needed or not.
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, value);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
if (!field().has_deeply_immutable_type()) {
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, value);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
compiler::Label allow_store;
__ BranchIfSmi(value, &allow_store, compiler::Assembler::kNearJump);
__ ldr(temp, compiler::FieldAddress(
value, compiler::target::Object::tags_offset()));
__ TestImmediate(temp,
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::kDeeplyImmutableBit);
// If immutability bit is not set, go to runtime.
__ b(checked_store_into_shared_slow_path->entry_label(), ZERO);
compiler::Label allow_store;
__ BranchIfSmi(value, &allow_store, compiler::Assembler::kNearJump);
__ ldr(temp, compiler::FieldAddress(
value, compiler::target::Object::tags_offset()));
__ TestImmediate(temp,
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::kDeeplyImmutableBit);
// If immutability bit is not set, go to runtime.
__ b(checked_store_into_shared_slow_path->entry_label(), ZERO);
__ Bind(&allow_store);
__ Bind(&allow_store);
}
}
}
@@ -3004,7 +3006,8 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
compiler::target::FieldTable::OffsetOf(field()));
}
if (FLAG_experimental_shared_data && field().is_shared()) {
if (FLAG_experimental_shared_data && field().is_shared() &&
!field().has_deeply_immutable_type()) {
__ Bind(checked_store_into_shared_slow_path->exit_label());
}
}
+25 -22
View File
@@ -2510,8 +2510,10 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps =
FLAG_experimental_shared_data && field().is_shared() ? 2 : 1;
const bool need_extra_temp = FLAG_experimental_shared_data &&
field().is_shared() &&
!field().has_deeply_immutable_type();
const intptr_t kNumTemps = need_extra_temp ? 2 : 1;
const bool can_call_to_throw = FLAG_experimental_shared_data;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps,
@@ -2520,7 +2522,7 @@ LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
locs->set_in(
0, Location::RegisterLocation(CheckedStoreIntoSharedStubABI::kValueReg));
locs->set_temp(0, Location::RequiresRegister());
if (FLAG_experimental_shared_data && field().is_shared()) {
if (need_extra_temp) {
locs->set_temp(1, Location::RegisterLocation(
CheckedStoreIntoSharedStubABI::kFieldReg));
}
@@ -2542,26 +2544,26 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ LoadIsolate(temp);
__ BranchIfZero(temp, slow_path->entry_label());
} else {
// TODO(dartbug.com/61078): use field static type information to decide
// whether the following value check is needed or not.
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, value);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
if (!field().has_deeply_immutable_type()) {
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, value);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
compiler::Label allow_store;
__ BranchIfSmi(value, &allow_store, compiler::Assembler::kNearJump);
__ ldr(temp,
compiler::FieldAddress(value,
compiler::target::Object::tags_offset()),
compiler::kUnsignedByte);
// If canonical bit is set, no need for runtime check.
__ tbnz(&allow_store, temp,
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::kDeeplyImmutableBit);
compiler::Label allow_store;
__ BranchIfSmi(value, &allow_store, compiler::Assembler::kNearJump);
__ ldr(temp,
compiler::FieldAddress(value,
compiler::target::Object::tags_offset()),
compiler::kUnsignedByte);
// If canonical bit is set, no need for runtime check.
__ tbnz(&allow_store, temp,
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::kDeeplyImmutableBit);
__ Bind(&allow_store);
__ Bind(&allow_store);
}
}
}
@@ -2580,7 +2582,8 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
compiler::target::FieldTable::OffsetOf(field()));
}
if (FLAG_experimental_shared_data && field().is_shared()) {
if (FLAG_experimental_shared_data && field().is_shared() &&
!field().has_deeply_immutable_type()) {
__ Bind(checked_store_into_shared_slow_path->exit_label());
}
}
+28 -23
View File
@@ -2139,8 +2139,10 @@ void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumTemps =
FLAG_experimental_shared_data && field().is_shared() ? 2 : 1;
const bool need_extra_temp = FLAG_experimental_shared_data &&
field().is_shared() &&
!field().has_deeply_immutable_type();
const intptr_t kNumTemps = need_extra_temp ? 2 : 1;
const bool can_call_to_throw = FLAG_experimental_shared_data;
LocationSummary* locs = new (zone)
LocationSummary(zone, 1, kNumTemps,
@@ -2149,7 +2151,7 @@ LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
locs->set_in(0, value()->NeedsWriteBarrier() ? Location::WritableRegister()
: Location::RequiresRegister());
locs->set_temp(0, Location::RequiresRegister());
if (FLAG_experimental_shared_data && field().is_shared()) {
if (need_extra_temp) {
locs->set_temp(1, Location::RegisterLocation(
CheckedStoreIntoSharedStubABI::kFieldReg));
}
@@ -2177,28 +2179,30 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ LoadIsolate(temp);
__ BranchIfZero(temp, slow_path->entry_label());
} else {
// TODO(dartbug.com/61078): use field static type information to decide
// whether the following value check is needed or not.
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, in);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
if (!field().has_deeply_immutable_type()) {
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, in);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
compiler::Label allow_store;
__ BranchIfSmi(in, &allow_store, compiler::Assembler::kNearJump);
compiler::Label allow_store;
__ BranchIfSmi(in, &allow_store, compiler::Assembler::kNearJump);
__ movl(temp, compiler::FieldAddress(
in, compiler::target::Object::tags_offset()));
__ testl(temp, compiler::Immediate(
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::kDeeplyImmutableBit));
// If immutability bit is not set, go to runtime.
__ j(ZERO, checked_store_into_shared_slow_path->entry_label());
__ movl(temp, compiler::FieldAddress(
in, compiler::target::Object::tags_offset()));
__ testl(temp,
compiler::Immediate(
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::kDeeplyImmutableBit));
// If immutability bit is not set, go to runtime.
__ j(ZERO, checked_store_into_shared_slow_path->entry_label());
__ Bind(&allow_store);
__ Bind(&allow_store);
}
}
}
@@ -2219,7 +2223,8 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
in);
}
if (FLAG_experimental_shared_data && field().is_shared()) {
if (FLAG_experimental_shared_data && field().is_shared() &&
!field().has_deeply_immutable_type()) {
__ Bind(checked_store_into_shared_slow_path->exit_label());
}
}
+25 -22
View File
@@ -2742,8 +2742,9 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps =
FLAG_experimental_shared_data && field().is_shared() ? 1 : 0;
const bool need_temp = FLAG_experimental_shared_data && field().is_shared() &&
!field().has_deeply_immutable_type();
const intptr_t kNumTemps = need_temp ? 1 : 0;
const bool can_call_to_throw = FLAG_experimental_shared_data;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps,
@@ -2751,7 +2752,7 @@ LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
: LocationSummary::kNoCall);
locs->set_in(
0, Location::RegisterLocation(CheckedStoreIntoSharedStubABI::kValueReg));
if (FLAG_experimental_shared_data && field().is_shared()) {
if (need_temp) {
locs->set_temp(0, Location::RegisterLocation(
CheckedStoreIntoSharedStubABI::kFieldReg));
}
@@ -2772,27 +2773,28 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ LoadIsolate(TMP);
__ BranchIfZero(TMP, slow_path->entry_label());
} else {
const Register temp = locs()->temp(0).reg();
if (!field().has_deeply_immutable_type()) {
const Register temp = locs()->temp(0).reg();
// TODO(dartbug.com/61078): use field static type information to decide
// whether the following value check is needed or not.
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, value);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, value);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
compiler::Label allow_store;
__ BranchIfSmi(value, &allow_store, compiler::Assembler::kNearJump);
__ lbu(TMP, compiler::FieldAddress(
value, compiler::target::Object::tags_offset()));
__ 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::kDeeplyImmutableBit);
// If deeply immutability bit is not set, go to runtime.
__ beqz(temp, checked_store_into_shared_slow_path->entry_label());
compiler::Label allow_store;
__ BranchIfSmi(value, &allow_store, compiler::Assembler::kNearJump);
__ lbu(TMP, compiler::FieldAddress(
value, compiler::target::Object::tags_offset()));
__ 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::kDeeplyImmutableBit);
// If deeply immutability bit is not set, go to runtime.
__ beqz(temp, checked_store_into_shared_slow_path->entry_label());
__ Bind(&allow_store);
__ Bind(&allow_store);
}
}
}
@@ -2811,7 +2813,8 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
compiler::target::FieldTable::OffsetOf(field()));
}
if (FLAG_experimental_shared_data && field().is_shared()) {
if (FLAG_experimental_shared_data && field().is_shared() &&
!field().has_deeply_immutable_type()) {
__ Bind(checked_store_into_shared_slow_path->exit_label());
}
}
+28 -23
View File
@@ -2514,8 +2514,10 @@ void CheckFieldImmutabilityInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps =
FLAG_experimental_shared_data && field().is_shared() ? 2 : 1;
const bool need_extra_temp = FLAG_experimental_shared_data &&
field().is_shared() &&
!field().has_deeply_immutable_type();
const intptr_t kNumTemps = need_extra_temp ? 2 : 1;
const bool can_call_to_throw = FLAG_experimental_shared_data;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps,
@@ -2524,7 +2526,7 @@ LocationSummary* StoreStaticFieldInstr::MakeLocationSummary(Zone* zone,
locs->set_in(
0, Location::RegisterLocation(CheckedStoreIntoSharedStubABI::kValueReg));
locs->set_temp(0, Location::RequiresRegister());
if (FLAG_experimental_shared_data && field().is_shared()) {
if (need_extra_temp) {
locs->set_temp(1, Location::RegisterLocation(
CheckedStoreIntoSharedStubABI::kFieldReg));
}
@@ -2547,27 +2549,29 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ LoadIsolate(temp);
__ BranchIfZero(temp, slow_path->entry_label());
} else {
// TODO(dartbug.com/61078): use field static type information to decide
// whether the following value check is needed or not.
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, value);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
if (!field().has_deeply_immutable_type()) {
checked_store_into_shared_slow_path =
new CheckedStoreIntoSharedSlowPath(this, value);
compiler->AddSlowPathCode(checked_store_into_shared_slow_path);
compiler::Label allow_store;
__ BranchIfSmi(value, &allow_store, compiler::Assembler::kNearJump);
__ movq(temp, compiler::FieldAddress(
value, compiler::target::Object::tags_offset()));
__ testq(temp, compiler::Immediate(
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::kDeeplyImmutableBit));
// If deep immutability bit is not set, go to runtime.
__ j(ZERO, checked_store_into_shared_slow_path->entry_label());
compiler::Label allow_store;
__ BranchIfSmi(value, &allow_store, compiler::Assembler::kNearJump);
__ movq(temp, compiler::FieldAddress(
value, compiler::target::Object::tags_offset()));
__ testq(temp,
compiler::Immediate(
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::kDeeplyImmutableBit));
// If deep immutability bit is not set, go to runtime.
__ j(ZERO, checked_store_into_shared_slow_path->entry_label());
__ Bind(&allow_store);
__ Bind(&allow_store);
}
}
}
@@ -2588,7 +2592,8 @@ void StoreStaticFieldInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
value);
}
if (FLAG_experimental_shared_data && field().is_shared()) {
if (FLAG_experimental_shared_data && field().is_shared() &&
!field().has_deeply_immutable_type()) {
__ Bind(checked_store_into_shared_slow_path->exit_label());
}
}
@@ -179,10 +179,9 @@ Fragment StreamingFlowGraphBuilder::BuildFieldInitializer(
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,
klass.is_deeply_immutable());
klass.is_deeply_immutable() && !field.has_deeply_immutable_type());
}
return instructions;
}
@@ -394,4 +394,92 @@ ISOLATE_UNIT_TEST_CASE(
EXPECT_EQ(0xFEEDFEED, Integer::Cast(const_value->value()).Value());
}
ISOLATE_UNIT_TEST_CASE(
StreamingFlowGraphBuilder_DeeplyImmutableTypeCheckPresent) {
const char* kScript = R"(
@pragma("vm:deeply-immutable")
final class Foo {
final void Function(int) baz;
Foo(this.baz);
}
@pragma("vm:entry-point", "call")
test() {
Foo((int x) { x = x + 1; } );
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
EXPECT(ClassFinalizer::ProcessPendingClasses());
const Class& foo = Class::Handle(GetClass(root_library, "Foo"));
const auto& error = foo.EnsureIsFinalized(thread);
const auto& constructor = Function::Handle(
foo.LookupConstructor(String::Handle(String::New("Foo."))));
EXPECT(error == Error::null());
Invoke(root_library, "test");
TestPipeline pipeline(constructor, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
ILMatcher cursor(flow_graph, entry);
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
kMatchAndMoveGuardFieldClass,
kMatchAndMoveCheckFieldImmutability,
kMatchAndMoveStoreField,
}));
}
ISOLATE_UNIT_TEST_CASE(
StreamingFlowGraphBuilder_DeeplyImmutableTypeCheckOmitted) {
const char* kScript = R"(
@pragma("vm:deeply-immutable")
final class Foo {
final int baz;
Foo(this.baz);
}
@pragma("vm:entry-point", "call")
test() {
Foo(42);
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
EXPECT(ClassFinalizer::ProcessPendingClasses());
const Class& foo = Class::Handle(GetClass(root_library, "Foo"));
const auto& error = foo.EnsureIsFinalized(thread);
const auto& constructor = Function::Handle(
foo.LookupConstructor(String::Handle(String::New("Foo."))));
EXPECT(error == Error::null());
Invoke(root_library, "test");
TestPipeline pipeline(constructor, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({
CompilerPass::kComputeSSA,
});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
ILMatcher cursor(flow_graph, entry);
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMoveDebugStepChecks,
kMatchAndMoveGuardFieldClass,
kMatchAndMoveStoreField,
}));
}
} // namespace dart
+4
View File
@@ -1023,6 +1023,8 @@ void KernelLoader::FinishTopLevelClassLoading(
field.set_is_shared(SharedPragma::decode(pragma_bits));
field.set_is_no_sanitize_thread(
NoSanitizeThreadPragma::decode(pragma_bits));
field.set_has_deeply_immutable_type(
DeeplyImmutablePragma::decode(pragma_bits));
const AbstractType& type = T.BuildType(); // read type.
field.SetFieldType(type);
ReadInferredType(field, field_offset + library_kernel_offset_);
@@ -1453,6 +1455,8 @@ void KernelLoader::FinishClassLoading(const Class& klass,
field.set_is_shared(SharedPragma::decode(pragma_bits));
field.set_is_no_sanitize_thread(
NoSanitizeThreadPragma::decode(pragma_bits));
field.set_has_deeply_immutable_type(
DeeplyImmutablePragma::decode(pragma_bits));
ReadInferredType(field, field_offset + library_kernel_offset_);
CheckForInitializer(field);
// Static fields with initializers are implicitly late.
+11
View File
@@ -4584,6 +4584,13 @@ class Field : public Object {
return untag()->kind_bits_.Read<NoSanitizeThreadBit>();
}
void set_has_deeply_immutable_type(bool value) const {
untag()->kind_bits_.UpdateBool<HasDeeplyImmutableTypeBit>(value);
}
bool has_deeply_immutable_type() const {
return untag()->kind_bits_.Read<HasDeeplyImmutableTypeBit>();
}
#if defined(DART_DYNAMIC_MODULES)
bool is_declared_in_bytecode() const;
#else
@@ -4997,6 +5004,10 @@ class Field : public Object {
HasInitializerBit::kNextBit>;
using NoSanitizeThreadBit =
BitField<decltype(UntaggedField::kind_bits_), bool, SharedBit::kNextBit>;
using HasDeeplyImmutableTypeBit =
BitField<decltype(UntaggedField::kind_bits_),
bool,
NoSanitizeThreadBit::kNextBit>;
// Force this field's guard to be dynamic and deoptimize dependent code.
void ForceDynamicGuardedCidAndLength() const;