Skip to content

Commit 6e7bf5e

Browse files
mralephcommit-bot@chromium.org
authored andcommitted
[vm] Dynamically track exactness of the field's static type on X64.
We say that field's static type G<T0, ..., Tn> is exact if for any value that can be loaded from this field, its runtime type T is such that T at G has type arguments exactly equal to <T0, ..., Tn>. Know if field's static type is exact allows us to apply optimizations that require knowing type arguments e.g. - we can fold o.f.:type_arguments to a constant value if we know that o.f is trivially exact; - for method invocations o.f.m(...) we can skip argument type checks on the callee if we know that o.f is invariant (this optimization will be enabled once multiple entry points CLs will land). Bug: #31798 Change-Id: Id565046d45a842625d41feb002b65db48451034c Reviewed-on: https://dart-review.googlesource.com/69969 Commit-Queue: Vyacheslav Egorov <[email protected]> Reviewed-by: Alexander Markov <[email protected]>
1 parent 37056cc commit 6e7bf5e

19 files changed

+692
-43
lines changed

runtime/vm/clustered_snapshot.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,7 @@ class FieldSerializationCluster : public SerializationCluster {
10521052
s->WriteTokenPosition(field->ptr()->end_token_pos_);
10531053
s->WriteCid(field->ptr()->guarded_cid_);
10541054
s->WriteCid(field->ptr()->is_nullable_);
1055+
s->Write<int8_t>(field->ptr()->static_type_exactness_state_);
10551056
#if !defined(DART_PRECOMPILED_RUNTIME)
10561057
s->Write<int32_t>(field->ptr()->kernel_offset_);
10571058
#endif
@@ -1103,6 +1104,7 @@ class FieldDeserializationCluster : public DeserializationCluster {
11031104
field->ptr()->end_token_pos_ = d->ReadTokenPosition();
11041105
field->ptr()->guarded_cid_ = d->ReadCid();
11051106
field->ptr()->is_nullable_ = d->ReadCid();
1107+
field->ptr()->static_type_exactness_state_ = d->Read<int8_t>();
11061108
#if !defined(DART_PRECOMPILED_RUNTIME)
11071109
field->ptr()->kernel_offset_ = d->Read<int32_t>();
11081110
#endif
@@ -1124,6 +1126,8 @@ class FieldDeserializationCluster : public DeserializationCluster {
11241126
field.set_guarded_list_length(Field::kNoFixedLength);
11251127
field.set_guarded_list_length_in_object_offset(
11261128
Field::kUnknownLengthOffset);
1129+
field.set_static_type_exactness_state(
1130+
StaticTypeExactnessState::NotTracking());
11271131
}
11281132
} else {
11291133
for (intptr_t i = start_index_; i < stop_index_; i++) {

runtime/vm/compiler/backend/constant_propagator.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ void ConstantPropagator::VisitGuardFieldClass(GuardFieldClassInstr* instr) {}
243243

244244
void ConstantPropagator::VisitGuardFieldLength(GuardFieldLengthInstr* instr) {}
245245

246+
void ConstantPropagator::VisitGuardFieldType(GuardFieldTypeInstr* instr) {}
247+
246248
void ConstantPropagator::VisitCheckSmi(CheckSmiInstr* instr) {}
247249

248250
void ConstantPropagator::VisitTailCall(TailCallInstr* instr) {}

runtime/vm/compiler/backend/il.cc

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,10 @@ bool GuardFieldLengthInstr::AttributesEqual(Instruction* other) const {
879879
return field().raw() == other->AsGuardFieldLength()->field().raw();
880880
}
881881

882+
bool GuardFieldTypeInstr::AttributesEqual(Instruction* other) const {
883+
return field().raw() == other->AsGuardFieldType()->field().raw();
884+
}
885+
882886
bool AssertAssignableInstr::AttributesEqual(Instruction* other) const {
883887
AssertAssignableInstr* other_assert = other->AsAssertAssignable();
884888
ASSERT(other_assert != NULL);
@@ -2647,6 +2651,15 @@ Definition* LoadFieldInstr::Canonicalize(FlowGraph* flow_graph) {
26472651
}
26482652
} else if (CreateArrayInstr* create_array = array->AsCreateArray()) {
26492653
return create_array->element_type()->definition();
2654+
} else if (LoadFieldInstr* load_array = array->AsLoadField()) {
2655+
const Field* field = load_array->field();
2656+
// For trivially exact fields we know that type arguments match
2657+
// static type arguments exactly.
2658+
if ((field != nullptr) &&
2659+
field->static_type_exactness_state().IsTriviallyExact()) {
2660+
return flow_graph->GetConstant(TypeArguments::Handle(
2661+
AbstractType::Handle(field->type()).arguments()));
2662+
}
26502663
}
26512664
}
26522665

@@ -2693,31 +2706,64 @@ Definition* AssertAssignableInstr::Canonicalize(FlowGraph* flow_graph) {
26932706
// be located in the unreachable part of the graph (e.g.
26942707
// it might be dominated by CheckClass that always fails).
26952708
// This means that the code below must guard against such possibility.
2696-
ConstantInstr* constant_instantiator_type_args =
2697-
instantiator_type_arguments()->definition()->AsConstant();
2698-
ConstantInstr* constant_function_type_args =
2699-
function_type_arguments()->definition()->AsConstant();
2700-
if ((constant_instantiator_type_args != NULL) &&
2701-
(constant_function_type_args != NULL)) {
2702-
ASSERT(constant_instantiator_type_args->value().IsNull() ||
2703-
constant_instantiator_type_args->value().IsTypeArguments());
2704-
ASSERT(constant_function_type_args->value().IsNull() ||
2705-
constant_function_type_args->value().IsTypeArguments());
2709+
Zone* Z = Thread::Current()->zone();
27062710

2707-
Zone* Z = Thread::Current()->zone();
2708-
const TypeArguments& instantiator_type_args = TypeArguments::Handle(
2709-
Z,
2710-
TypeArguments::RawCast(constant_instantiator_type_args->value().raw()));
2711+
const TypeArguments* instantiator_type_args = nullptr;
2712+
const TypeArguments* function_type_args = nullptr;
27112713

2712-
const TypeArguments& function_type_args = TypeArguments::Handle(
2713-
Z, TypeArguments::RawCast(constant_function_type_args->value().raw()));
2714+
if (instantiator_type_arguments()->BindsToConstant()) {
2715+
const Object& val = instantiator_type_arguments()->BoundConstant();
2716+
instantiator_type_args = (val.raw() == TypeArguments::null())
2717+
? &TypeArguments::null_type_arguments()
2718+
: &TypeArguments::Cast(val);
2719+
}
2720+
2721+
if (function_type_arguments()->BindsToConstant()) {
2722+
const Object& val = function_type_arguments()->BoundConstant();
2723+
function_type_args =
2724+
(val.raw() == TypeArguments::null())
2725+
? &TypeArguments::null_type_arguments()
2726+
: &TypeArguments::Cast(function_type_arguments()->BoundConstant());
2727+
}
2728+
2729+
// If instantiator_type_args are not constant try to match the pattern
2730+
// obj.field.:type_arguments where field's static type exactness state
2731+
// tells us that all values stored in the field have exact superclass.
2732+
// In this case we know the prefix of the actual type arguments vector
2733+
// and can try to instantiate the type using just the prefix.
2734+
//
2735+
// Note: TypeParameter::InstantiateFrom returns an error if we try
2736+
// to instantiate it from a vector that is too short.
2737+
if (instantiator_type_args == nullptr) {
2738+
if (LoadFieldInstr* load_type_args =
2739+
instantiator_type_arguments()->definition()->AsLoadField()) {
2740+
if (load_type_args->native_field() != nullptr &&
2741+
load_type_args->native_field()->kind() ==
2742+
NativeFieldDesc::kTypeArguments) {
2743+
if (LoadFieldInstr* load_field = load_type_args->instance()
2744+
->definition()
2745+
->OriginalDefinition()
2746+
->AsLoadField()) {
2747+
if (load_field->field() != nullptr &&
2748+
load_field->field()
2749+
->static_type_exactness_state()
2750+
.IsHasExactSuperClass()) {
2751+
instantiator_type_args = &TypeArguments::Handle(
2752+
Z, AbstractType::Handle(Z, load_field->field()->type())
2753+
.arguments());
2754+
}
2755+
}
2756+
}
2757+
}
2758+
}
27142759

2760+
if ((instantiator_type_args != nullptr) && (function_type_args != nullptr)) {
27152761
Error& bound_error = Error::Handle(Z);
27162762

27172763
AbstractType& new_dst_type = AbstractType::Handle(
2718-
Z, dst_type().InstantiateFrom(instantiator_type_args,
2719-
function_type_args, kAllFree,
2720-
&bound_error, NULL, NULL, Heap::kOld));
2764+
Z, dst_type().InstantiateFrom(
2765+
*instantiator_type_args, *function_type_args, kAllFree,
2766+
&bound_error, nullptr, nullptr, Heap::kOld));
27212767
if (new_dst_type.IsMalformedOrMalbounded() || !bound_error.IsNull()) {
27222768
return this;
27232769
}
@@ -3318,6 +3364,11 @@ Instruction* GuardFieldLengthInstr::Canonicalize(FlowGraph* flow_graph) {
33183364
return this;
33193365
}
33203366

3367+
Instruction* GuardFieldTypeInstr::Canonicalize(FlowGraph* flow_graph) {
3368+
return field().static_type_exactness_state().NeedsFieldGuard() ? this
3369+
: nullptr;
3370+
}
3371+
33213372
Instruction* CheckSmiInstr::Canonicalize(FlowGraph* flow_graph) {
33223373
return (value()->Type()->ToCid() == kSmiCid) ? NULL : this;
33233374
}

runtime/vm/compiler/backend/il.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ struct InstrAttrs {
580580
/*We could be more precise about when these 2 instructions can trigger GC.*/ \
581581
M(GuardFieldClass, _) \
582582
M(GuardFieldLength, _) \
583+
M(GuardFieldType, _) \
583584
M(IfThenElse, kNoGC) \
584585
M(MaterializeObject, _) \
585586
M(TestSmi, kNoGC) \
@@ -4297,6 +4298,29 @@ class GuardFieldLengthInstr : public GuardFieldInstr {
42974298
DISALLOW_COPY_AND_ASSIGN(GuardFieldLengthInstr);
42984299
};
42994300

4301+
// For a field of static type G<T0, ..., Tn> and a stored value of runtime
4302+
// type T checks that type arguments of T at G exactly match <T0, ..., Tn>
4303+
// and updates guarded state (RawField::static_type_exactness_state_)
4304+
// accordingly.
4305+
//
4306+
// See StaticTypeExactnessState for more information.
4307+
class GuardFieldTypeInstr : public GuardFieldInstr {
4308+
public:
4309+
GuardFieldTypeInstr(Value* value, const Field& field, intptr_t deopt_id)
4310+
: GuardFieldInstr(value, field, deopt_id) {
4311+
CheckField(field);
4312+
}
4313+
4314+
DECLARE_INSTRUCTION(GuardFieldType)
4315+
4316+
virtual Instruction* Canonicalize(FlowGraph* flow_graph);
4317+
4318+
virtual bool AttributesEqual(Instruction* other) const;
4319+
4320+
private:
4321+
DISALLOW_COPY_AND_ASSIGN(GuardFieldTypeInstr);
4322+
};
4323+
43004324
class LoadStaticFieldInstr : public TemplateDefinition<1, NoThrow> {
43014325
public:
43024326
LoadStaticFieldInstr(Value* field_value, TokenPosition token_pos)

runtime/vm/compiler/backend/il_arm.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,6 +2013,16 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
20132013
}
20142014
}
20152015

2016+
LocationSummary* GuardFieldTypeInstr::MakeLocationSummary(Zone* zone,
2017+
bool opt) const {
2018+
UNREACHABLE();
2019+
return nullptr;
2020+
}
2021+
2022+
void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
2023+
UNREACHABLE();
2024+
}
2025+
20162026
class BoxAllocationSlowPath : public TemplateSlowPathCode<Instruction> {
20172027
public:
20182028
BoxAllocationSlowPath(Instruction* instruction,

runtime/vm/compiler/backend/il_arm64.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,6 +1572,16 @@ static void LoadValueCid(FlowGraphCompiler* compiler,
15721572
__ Bind(&done);
15731573
}
15741574

1575+
LocationSummary* GuardFieldTypeInstr::MakeLocationSummary(Zone* zone,
1576+
bool opt) const {
1577+
UNREACHABLE();
1578+
return nullptr;
1579+
}
1580+
1581+
void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
1582+
UNREACHABLE();
1583+
}
1584+
15751585
LocationSummary* GuardFieldClassInstr::MakeLocationSummary(Zone* zone,
15761586
bool opt) const {
15771587
const intptr_t kNumInputs = 1;

runtime/vm/compiler/backend/il_dbc.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ DECLARE_FLAG(int, optimization_counter_threshold);
3838
M(TruncDivMod) \
3939
M(GuardFieldClass) \
4040
M(GuardFieldLength) \
41+
M(GuardFieldType) \
4142
M(IfThenElse) \
4243
M(ExtractNthOutput) \
4344
M(BinaryUint32Op) \

runtime/vm/compiler/backend/il_ia32.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,16 @@ void StoreIndexedInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
14381438
}
14391439
}
14401440

1441+
LocationSummary* GuardFieldTypeInstr::MakeLocationSummary(Zone* zone,
1442+
bool opt) const {
1443+
UNREACHABLE();
1444+
return nullptr;
1445+
}
1446+
1447+
void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
1448+
UNREACHABLE();
1449+
}
1450+
14411451
LocationSummary* GuardFieldClassInstr::MakeLocationSummary(Zone* zone,
14421452
bool opt) const {
14431453
const intptr_t kNumInputs = 1;

runtime/vm/compiler/backend/il_printer.cc

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -651,16 +651,8 @@ void LoadFieldInstr::PrintOperandsTo(BufferFormatter* f) const {
651651
f->Print(", %" Pd, offset_in_bytes());
652652

653653
if (field() != nullptr) {
654-
f->Print(" {%s}", String::Handle(field()->name()).ToCString());
655-
const char* expected = "?";
656-
if (field()->guarded_cid() != kIllegalCid) {
657-
const Class& cls = Class::Handle(
658-
Isolate::Current()->class_table()->At(field()->guarded_cid()));
659-
expected = String::Handle(cls.Name()).ToCString();
660-
}
661-
662-
f->Print(" [%s %s]", field()->is_nullable() ? "nullable" : "non-nullable",
663-
expected);
654+
f->Print(" {%s} %s", String::Handle(field()->name()).ToCString(),
655+
field()->GuardedPropertiesAsCString());
664656
}
665657

666658
if (native_field() != nullptr) {

runtime/vm/compiler/backend/il_x64.cc

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,10 +1646,14 @@ void GuardFieldClassInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
16461646
__ j(EQUAL, &ok);
16471647

16481648
// Check if the tracked state of the guarded field can be initialized
1649-
// inline. If the field needs length check we fall through to runtime
1650-
// which is responsible for computing offset of the length field
1651-
// based on the class id.
1652-
if (!field().needs_length_check()) {
1649+
// inline. If the field needs length check or requires type arguments and
1650+
// class hierarchy processing for exactness tracking then we fall through
1651+
// into runtime which is responsible for computing offset of the length
1652+
// field based on the class id.
1653+
const bool is_complicated_field =
1654+
field().needs_length_check() ||
1655+
field().static_type_exactness_state().IsUninitialized();
1656+
if (!is_complicated_field) {
16531657
// Uninitialized field can be handled inline. Check if the
16541658
// field is still unitialized.
16551659
__ cmpw(field_cid_operand, Immediate(kIllegalCid));
@@ -1805,6 +1809,87 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
18051809
}
18061810
}
18071811

1812+
LocationSummary* GuardFieldTypeInstr::MakeLocationSummary(Zone* zone,
1813+
bool opt) const {
1814+
const intptr_t kNumInputs = 1;
1815+
const intptr_t kNumTemps = 1;
1816+
LocationSummary* summary = new (zone)
1817+
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
1818+
summary->set_in(0, Location::RequiresRegister());
1819+
summary->set_temp(0, Location::RequiresRegister());
1820+
return summary;
1821+
}
1822+
1823+
void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
1824+
// Should never emit GuardFieldType for fields that are marked as NotTracking.
1825+
ASSERT(field().static_type_exactness_state().IsTracking());
1826+
if (!field().static_type_exactness_state().NeedsFieldGuard()) {
1827+
// Nothing to do: we only need to perform checks for trivially invariant
1828+
// fields. If optimizing Canonicalize pass should have removed
1829+
// this instruction.
1830+
if (Compiler::IsBackgroundCompilation()) {
1831+
Compiler::AbortBackgroundCompilation(
1832+
deopt_id(),
1833+
"GuardFieldTypeInstr: field state changed during compilation");
1834+
}
1835+
ASSERT(!compiler->is_optimizing());
1836+
return;
1837+
}
1838+
1839+
Label* deopt =
1840+
compiler->is_optimizing()
1841+
? compiler->AddDeoptStub(deopt_id(), ICData::kDeoptGuardField)
1842+
: NULL;
1843+
1844+
Label ok;
1845+
1846+
const Register value_reg = locs()->in(0).reg();
1847+
const Register temp = locs()->temp(0).reg();
1848+
1849+
// Skip null values for nullable fields.
1850+
if (!compiler->is_optimizing() || field().is_nullable()) {
1851+
__ CompareObject(value_reg, Object::Handle());
1852+
__ j(EQUAL, &ok);
1853+
}
1854+
1855+
// Get the state.
1856+
__ LoadObject(temp, field());
1857+
__ movsxb(temp,
1858+
FieldAddress(temp, Field::static_type_exactness_state_offset()));
1859+
1860+
if (!compiler->is_optimizing()) {
1861+
// Check if field requires checking (it is in unitialized or trivially
1862+
// exact state).
1863+
__ cmpq(temp, Immediate(StaticTypeExactnessState::kUninitialized));
1864+
__ j(LESS, &ok);
1865+
}
1866+
1867+
Label call_runtime;
1868+
if (field().static_type_exactness_state().IsUninitialized()) {
1869+
// Can't initialize the field state inline in optimized code.
1870+
__ cmpq(temp, Immediate(StaticTypeExactnessState::kUninitialized));
1871+
__ j(EQUAL, compiler->is_optimizing() ? deopt : &call_runtime);
1872+
}
1873+
1874+
// At this point temp is known to be type arguments offset in words.
1875+
__ movq(temp, FieldAddress(value_reg, temp, TIMES_8, 0));
1876+
__ CompareObject(temp, TypeArguments::ZoneHandle(
1877+
AbstractType::Handle(field().type()).arguments()));
1878+
if (deopt != nullptr) {
1879+
__ j(NOT_EQUAL, deopt);
1880+
} else {
1881+
__ j(EQUAL, &ok);
1882+
1883+
__ Bind(&call_runtime);
1884+
__ PushObject(field());
1885+
__ pushq(value_reg);
1886+
__ CallRuntime(kUpdateFieldCidRuntimeEntry, 2);
1887+
__ Drop(2);
1888+
}
1889+
1890+
__ Bind(&ok);
1891+
}
1892+
18081893
LocationSummary* StoreInstanceFieldInstr::MakeLocationSummary(Zone* zone,
18091894
bool opt) const {
18101895
const intptr_t kNumInputs = 2;

0 commit comments

Comments
 (0)