Skip to content

Commit

Permalink
[vm] Dynamically track exactness of the field's static type on X64.
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
mraleph authored and [email protected] committed Aug 17, 2018
1 parent 37056cc commit 6e7bf5e
Show file tree
Hide file tree
Showing 19 changed files with 692 additions and 43 deletions.
4 changes: 4 additions & 0 deletions runtime/vm/clustered_snapshot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,7 @@ class FieldSerializationCluster : public SerializationCluster {
s->WriteTokenPosition(field->ptr()->end_token_pos_);
s->WriteCid(field->ptr()->guarded_cid_);
s->WriteCid(field->ptr()->is_nullable_);
s->Write<int8_t>(field->ptr()->static_type_exactness_state_);
#if !defined(DART_PRECOMPILED_RUNTIME)
s->Write<int32_t>(field->ptr()->kernel_offset_);
#endif
Expand Down Expand Up @@ -1103,6 +1104,7 @@ class FieldDeserializationCluster : public DeserializationCluster {
field->ptr()->end_token_pos_ = d->ReadTokenPosition();
field->ptr()->guarded_cid_ = d->ReadCid();
field->ptr()->is_nullable_ = d->ReadCid();
field->ptr()->static_type_exactness_state_ = d->Read<int8_t>();
#if !defined(DART_PRECOMPILED_RUNTIME)
field->ptr()->kernel_offset_ = d->Read<int32_t>();
#endif
Expand All @@ -1124,6 +1126,8 @@ class FieldDeserializationCluster : public DeserializationCluster {
field.set_guarded_list_length(Field::kNoFixedLength);
field.set_guarded_list_length_in_object_offset(
Field::kUnknownLengthOffset);
field.set_static_type_exactness_state(
StaticTypeExactnessState::NotTracking());
}
} else {
for (intptr_t i = start_index_; i < stop_index_; i++) {
Expand Down
2 changes: 2 additions & 0 deletions runtime/vm/compiler/backend/constant_propagator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ void ConstantPropagator::VisitGuardFieldClass(GuardFieldClassInstr* instr) {}

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

void ConstantPropagator::VisitGuardFieldType(GuardFieldTypeInstr* instr) {}

void ConstantPropagator::VisitCheckSmi(CheckSmiInstr* instr) {}

void ConstantPropagator::VisitTailCall(TailCallInstr* instr) {}
Expand Down
89 changes: 70 additions & 19 deletions runtime/vm/compiler/backend/il.cc
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,10 @@ bool GuardFieldLengthInstr::AttributesEqual(Instruction* other) const {
return field().raw() == other->AsGuardFieldLength()->field().raw();
}

bool GuardFieldTypeInstr::AttributesEqual(Instruction* other) const {
return field().raw() == other->AsGuardFieldType()->field().raw();
}

bool AssertAssignableInstr::AttributesEqual(Instruction* other) const {
AssertAssignableInstr* other_assert = other->AsAssertAssignable();
ASSERT(other_assert != NULL);
Expand Down Expand Up @@ -2647,6 +2651,15 @@ Definition* LoadFieldInstr::Canonicalize(FlowGraph* flow_graph) {
}
} else if (CreateArrayInstr* create_array = array->AsCreateArray()) {
return create_array->element_type()->definition();
} else if (LoadFieldInstr* load_array = array->AsLoadField()) {
const Field* field = load_array->field();
// For trivially exact fields we know that type arguments match
// static type arguments exactly.
if ((field != nullptr) &&
field->static_type_exactness_state().IsTriviallyExact()) {
return flow_graph->GetConstant(TypeArguments::Handle(
AbstractType::Handle(field->type()).arguments()));
}
}
}

Expand Down Expand Up @@ -2693,31 +2706,64 @@ Definition* AssertAssignableInstr::Canonicalize(FlowGraph* flow_graph) {
// be located in the unreachable part of the graph (e.g.
// it might be dominated by CheckClass that always fails).
// This means that the code below must guard against such possibility.
ConstantInstr* constant_instantiator_type_args =
instantiator_type_arguments()->definition()->AsConstant();
ConstantInstr* constant_function_type_args =
function_type_arguments()->definition()->AsConstant();
if ((constant_instantiator_type_args != NULL) &&
(constant_function_type_args != NULL)) {
ASSERT(constant_instantiator_type_args->value().IsNull() ||
constant_instantiator_type_args->value().IsTypeArguments());
ASSERT(constant_function_type_args->value().IsNull() ||
constant_function_type_args->value().IsTypeArguments());
Zone* Z = Thread::Current()->zone();

Zone* Z = Thread::Current()->zone();
const TypeArguments& instantiator_type_args = TypeArguments::Handle(
Z,
TypeArguments::RawCast(constant_instantiator_type_args->value().raw()));
const TypeArguments* instantiator_type_args = nullptr;
const TypeArguments* function_type_args = nullptr;

const TypeArguments& function_type_args = TypeArguments::Handle(
Z, TypeArguments::RawCast(constant_function_type_args->value().raw()));
if (instantiator_type_arguments()->BindsToConstant()) {
const Object& val = instantiator_type_arguments()->BoundConstant();
instantiator_type_args = (val.raw() == TypeArguments::null())
? &TypeArguments::null_type_arguments()
: &TypeArguments::Cast(val);
}

if (function_type_arguments()->BindsToConstant()) {
const Object& val = function_type_arguments()->BoundConstant();
function_type_args =
(val.raw() == TypeArguments::null())
? &TypeArguments::null_type_arguments()
: &TypeArguments::Cast(function_type_arguments()->BoundConstant());
}

// If instantiator_type_args are not constant try to match the pattern
// obj.field.:type_arguments where field's static type exactness state
// tells us that all values stored in the field have exact superclass.
// In this case we know the prefix of the actual type arguments vector
// and can try to instantiate the type using just the prefix.
//
// Note: TypeParameter::InstantiateFrom returns an error if we try
// to instantiate it from a vector that is too short.
if (instantiator_type_args == nullptr) {
if (LoadFieldInstr* load_type_args =
instantiator_type_arguments()->definition()->AsLoadField()) {
if (load_type_args->native_field() != nullptr &&
load_type_args->native_field()->kind() ==
NativeFieldDesc::kTypeArguments) {
if (LoadFieldInstr* load_field = load_type_args->instance()
->definition()
->OriginalDefinition()
->AsLoadField()) {
if (load_field->field() != nullptr &&
load_field->field()
->static_type_exactness_state()
.IsHasExactSuperClass()) {
instantiator_type_args = &TypeArguments::Handle(
Z, AbstractType::Handle(Z, load_field->field()->type())
.arguments());
}
}
}
}
}

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

AbstractType& new_dst_type = AbstractType::Handle(
Z, dst_type().InstantiateFrom(instantiator_type_args,
function_type_args, kAllFree,
&bound_error, NULL, NULL, Heap::kOld));
Z, dst_type().InstantiateFrom(
*instantiator_type_args, *function_type_args, kAllFree,
&bound_error, nullptr, nullptr, Heap::kOld));
if (new_dst_type.IsMalformedOrMalbounded() || !bound_error.IsNull()) {
return this;
}
Expand Down Expand Up @@ -3318,6 +3364,11 @@ Instruction* GuardFieldLengthInstr::Canonicalize(FlowGraph* flow_graph) {
return this;
}

Instruction* GuardFieldTypeInstr::Canonicalize(FlowGraph* flow_graph) {
return field().static_type_exactness_state().NeedsFieldGuard() ? this
: nullptr;
}

Instruction* CheckSmiInstr::Canonicalize(FlowGraph* flow_graph) {
return (value()->Type()->ToCid() == kSmiCid) ? NULL : this;
}
Expand Down
24 changes: 24 additions & 0 deletions runtime/vm/compiler/backend/il.h
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ struct InstrAttrs {
/*We could be more precise about when these 2 instructions can trigger GC.*/ \
M(GuardFieldClass, _) \
M(GuardFieldLength, _) \
M(GuardFieldType, _) \
M(IfThenElse, kNoGC) \
M(MaterializeObject, _) \
M(TestSmi, kNoGC) \
Expand Down Expand Up @@ -4297,6 +4298,29 @@ class GuardFieldLengthInstr : public GuardFieldInstr {
DISALLOW_COPY_AND_ASSIGN(GuardFieldLengthInstr);
};

// For a field of static type G<T0, ..., Tn> and a stored value of runtime
// type T checks that type arguments of T at G exactly match <T0, ..., Tn>
// and updates guarded state (RawField::static_type_exactness_state_)
// accordingly.
//
// See StaticTypeExactnessState for more information.
class GuardFieldTypeInstr : public GuardFieldInstr {
public:
GuardFieldTypeInstr(Value* value, const Field& field, intptr_t deopt_id)
: GuardFieldInstr(value, field, deopt_id) {
CheckField(field);
}

DECLARE_INSTRUCTION(GuardFieldType)

virtual Instruction* Canonicalize(FlowGraph* flow_graph);

virtual bool AttributesEqual(Instruction* other) const;

private:
DISALLOW_COPY_AND_ASSIGN(GuardFieldTypeInstr);
};

class LoadStaticFieldInstr : public TemplateDefinition<1, NoThrow> {
public:
LoadStaticFieldInstr(Value* field_value, TokenPosition token_pos)
Expand Down
10 changes: 10 additions & 0 deletions runtime/vm/compiler/backend/il_arm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,16 @@ void GuardFieldLengthInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
}
}

LocationSummary* GuardFieldTypeInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
UNREACHABLE();
return nullptr;
}

void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
UNREACHABLE();
}

class BoxAllocationSlowPath : public TemplateSlowPathCode<Instruction> {
public:
BoxAllocationSlowPath(Instruction* instruction,
Expand Down
10 changes: 10 additions & 0 deletions runtime/vm/compiler/backend/il_arm64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,16 @@ static void LoadValueCid(FlowGraphCompiler* compiler,
__ Bind(&done);
}

LocationSummary* GuardFieldTypeInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
UNREACHABLE();
return nullptr;
}

void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
UNREACHABLE();
}

LocationSummary* GuardFieldClassInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
Expand Down
1 change: 1 addition & 0 deletions runtime/vm/compiler/backend/il_dbc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ DECLARE_FLAG(int, optimization_counter_threshold);
M(TruncDivMod) \
M(GuardFieldClass) \
M(GuardFieldLength) \
M(GuardFieldType) \
M(IfThenElse) \
M(ExtractNthOutput) \
M(BinaryUint32Op) \
Expand Down
10 changes: 10 additions & 0 deletions runtime/vm/compiler/backend/il_ia32.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,16 @@ void StoreIndexedInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
}
}

LocationSummary* GuardFieldTypeInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
UNREACHABLE();
return nullptr;
}

void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
UNREACHABLE();
}

LocationSummary* GuardFieldClassInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
Expand Down
12 changes: 2 additions & 10 deletions runtime/vm/compiler/backend/il_printer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -651,16 +651,8 @@ void LoadFieldInstr::PrintOperandsTo(BufferFormatter* f) const {
f->Print(", %" Pd, offset_in_bytes());

if (field() != nullptr) {
f->Print(" {%s}", String::Handle(field()->name()).ToCString());
const char* expected = "?";
if (field()->guarded_cid() != kIllegalCid) {
const Class& cls = Class::Handle(
Isolate::Current()->class_table()->At(field()->guarded_cid()));
expected = String::Handle(cls.Name()).ToCString();
}

f->Print(" [%s %s]", field()->is_nullable() ? "nullable" : "non-nullable",
expected);
f->Print(" {%s} %s", String::Handle(field()->name()).ToCString(),
field()->GuardedPropertiesAsCString());
}

if (native_field() != nullptr) {
Expand Down
93 changes: 89 additions & 4 deletions runtime/vm/compiler/backend/il_x64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1646,10 +1646,14 @@ void GuardFieldClassInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ j(EQUAL, &ok);

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

LocationSummary* GuardFieldTypeInstr::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::kNoCall);
summary->set_in(0, Location::RequiresRegister());
summary->set_temp(0, Location::RequiresRegister());
return summary;
}

void GuardFieldTypeInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// Should never emit GuardFieldType for fields that are marked as NotTracking.
ASSERT(field().static_type_exactness_state().IsTracking());
if (!field().static_type_exactness_state().NeedsFieldGuard()) {
// Nothing to do: we only need to perform checks for trivially invariant
// fields. If optimizing Canonicalize pass should have removed
// this instruction.
if (Compiler::IsBackgroundCompilation()) {
Compiler::AbortBackgroundCompilation(
deopt_id(),
"GuardFieldTypeInstr: field state changed during compilation");
}
ASSERT(!compiler->is_optimizing());
return;
}

Label* deopt =
compiler->is_optimizing()
? compiler->AddDeoptStub(deopt_id(), ICData::kDeoptGuardField)
: NULL;

Label ok;

const Register value_reg = locs()->in(0).reg();
const Register temp = locs()->temp(0).reg();

// Skip null values for nullable fields.
if (!compiler->is_optimizing() || field().is_nullable()) {
__ CompareObject(value_reg, Object::Handle());
__ j(EQUAL, &ok);
}

// Get the state.
__ LoadObject(temp, field());
__ movsxb(temp,
FieldAddress(temp, Field::static_type_exactness_state_offset()));

if (!compiler->is_optimizing()) {
// Check if field requires checking (it is in unitialized or trivially
// exact state).
__ cmpq(temp, Immediate(StaticTypeExactnessState::kUninitialized));
__ j(LESS, &ok);
}

Label call_runtime;
if (field().static_type_exactness_state().IsUninitialized()) {
// Can't initialize the field state inline in optimized code.
__ cmpq(temp, Immediate(StaticTypeExactnessState::kUninitialized));
__ j(EQUAL, compiler->is_optimizing() ? deopt : &call_runtime);
}

// At this point temp is known to be type arguments offset in words.
__ movq(temp, FieldAddress(value_reg, temp, TIMES_8, 0));
__ CompareObject(temp, TypeArguments::ZoneHandle(
AbstractType::Handle(field().type()).arguments()));
if (deopt != nullptr) {
__ j(NOT_EQUAL, deopt);
} else {
__ j(EQUAL, &ok);

__ Bind(&call_runtime);
__ PushObject(field());
__ pushq(value_reg);
__ CallRuntime(kUpdateFieldCidRuntimeEntry, 2);
__ Drop(2);
}

__ Bind(&ok);
}

LocationSummary* StoreInstanceFieldInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 2;
Expand Down
Loading

0 comments on commit 6e7bf5e

Please sign in to comment.