[vm/compiler] Allocation sinking of arrays and typed data objects

JIT mode: ParticleSystemUpdate +44-52%
JIT mode with null safety: ParticleSystemUpdate +40-44%

In AOT mode a few methods from package:vector_math are not inlined
in ParticleSystemUpdate benchmarks, which prevents allocation
sinking optimization.

Closes https://github.com/dart-lang/sdk/issues/43228

Change-Id: I28d5816e643e4cfffe4b4fb7db5db5a3aa72499c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/164805
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Alexander Markov 2020-10-16 20:05:02 +00:00 committed by commit-bot@chromium.org
parent 8cfaeebaa4
commit dea3fa7b8c
18 changed files with 911 additions and 197 deletions

View file

@ -993,9 +993,8 @@ Representation LoadFieldInstr::representation() const {
AllocateUninitializedContextInstr::AllocateUninitializedContextInstr(
TokenPosition token_pos,
intptr_t num_context_variables)
: token_pos_(token_pos),
num_context_variables_(num_context_variables),
identity_(AliasIdentity::Unknown()) {
: TemplateAllocation(token_pos),
num_context_variables_(num_context_variables) {
// This instruction is not used in AOT for code size reasons.
ASSERT(!CompilerState::Current().is_aot());
}
@ -1244,6 +1243,15 @@ const Object& Value::BoundConstant() const {
return constant->value();
}
bool Value::BindsToSmiConstant() const {
return BindsToConstant() && BoundConstant().IsSmi();
}
intptr_t Value::BoundSmiConstant() const {
ASSERT(BindsToSmiConstant());
return Smi::Cast(BoundConstant()).Value();
}
GraphEntryInstr::GraphEntryInstr(const ParsedFunction& parsed_function,
intptr_t osr_id)
: GraphEntryInstr(parsed_function,
@ -2745,6 +2753,7 @@ bool LoadFieldInstr::IsImmutableLengthLoad() const {
case Slot::Kind::kPointerBase_data_field:
case Slot::Kind::kType_arguments:
case Slot::Kind::kTypeArgumentsIndex:
case Slot::Kind::kArrayElement:
case Slot::Kind::kUnhandledException_exception:
case Slot::Kind::kUnhandledException_stacktrace:
return false;
@ -5658,14 +5667,10 @@ bool TestCidsInstr::AttributesEqual(Instruction* other) const {
return true;
}
static bool BindsToSmiConstant(Value* value) {
return value->BindsToConstant() && value->BoundConstant().IsSmi();
}
bool IfThenElseInstr::Supports(ComparisonInstr* comparison,
Value* v1,
Value* v2) {
bool is_smi_result = BindsToSmiConstant(v1) && BindsToSmiConstant(v2);
bool is_smi_result = v1->BindsToSmiConstant() && v2->BindsToSmiConstant();
if (comparison->IsStrictCompare()) {
// Strict comparison with number checks calls a stub and is not supported
// by if-conversion.

View file

@ -163,6 +163,12 @@ class Value : public ZoneAllocated {
// Assert if BindsToConstant() is false, otherwise returns the constant value.
const Object& BoundConstant() const;
// Return true if the value represents Smi constant.
bool BindsToSmiConstant() const;
// Return value of represented Smi constant.
intptr_t BoundSmiConstant() const;
// Return true if storing the value into a heap object requires applying the
// write barrier. Can change the reaching type of the Value or other Values
// in the same chain of redefinitions.
@ -505,6 +511,7 @@ struct InstrAttrs {
#define FOR_EACH_ABSTRACT_INSTRUCTION(M) \
M(Allocation, _) \
M(ArrayAllocation, _) \
M(BinaryIntegerOp, _) \
M(BlockEntry, _) \
M(BoxInteger, _) \
@ -5954,8 +5961,16 @@ class InstanceOfInstr : public TemplateDefinition<3, Throws> {
// either reside in new space or be in the store buffer.
class AllocationInstr : public Definition {
public:
explicit AllocationInstr(intptr_t deopt_id = DeoptId::kNone)
: Definition(deopt_id) {}
explicit AllocationInstr(TokenPosition token_pos,
intptr_t deopt_id = DeoptId::kNone)
: Definition(deopt_id),
token_pos_(token_pos),
identity_(AliasIdentity::Unknown()) {}
virtual TokenPosition token_pos() const { return token_pos_; }
virtual AliasIdentity Identity() const { return identity_; }
virtual void SetIdentity(AliasIdentity identity) { identity_ = identity; }
// TODO(sjindel): Update these conditions when the incremental write barrier
// is added.
@ -5964,14 +5979,18 @@ class AllocationInstr : public Definition {
DEFINE_INSTRUCTION_TYPE_CHECK(Allocation);
private:
const TokenPosition token_pos_;
AliasIdentity identity_;
DISALLOW_COPY_AND_ASSIGN(AllocationInstr);
};
template <intptr_t N, typename ThrowsTrait>
class TemplateAllocation : public AllocationInstr {
public:
explicit TemplateAllocation(intptr_t deopt_id = DeoptId::kNone)
: AllocationInstr(deopt_id), inputs_() {}
explicit TemplateAllocation(TokenPosition token_pos,
intptr_t deopt_id = DeoptId::kNone)
: AllocationInstr(token_pos, deopt_id), inputs_() {}
virtual intptr_t InputCount() const { return N; }
virtual Value* InputAt(intptr_t i) const { return inputs_[i]; }
@ -5993,10 +6012,9 @@ class AllocateObjectInstr : public AllocationInstr {
AllocateObjectInstr(TokenPosition token_pos,
const Class& cls,
Value* type_arguments = nullptr)
: token_pos_(token_pos),
: AllocationInstr(token_pos),
cls_(cls),
type_arguments_(type_arguments),
identity_(AliasIdentity::Unknown()),
closure_function_(Function::ZoneHandle()) {
ASSERT((cls.NumTypeArguments() > 0) == (type_arguments != nullptr));
if (type_arguments != nullptr) {
@ -6008,7 +6026,6 @@ class AllocateObjectInstr : public AllocationInstr {
virtual CompileType ComputeType() const;
const Class& cls() const { return cls_; }
virtual TokenPosition token_pos() const { return token_pos_; }
Value* type_arguments() const { return type_arguments_; }
const Function& closure_function() const { return closure_function_; }
@ -6030,9 +6047,6 @@ class AllocateObjectInstr : public AllocationInstr {
virtual bool HasUnknownSideEffects() const { return false; }
virtual AliasIdentity Identity() const { return identity_; }
virtual void SetIdentity(AliasIdentity identity) { identity_ = identity; }
virtual bool WillAllocateNewOrRemembered() const {
return WillAllocateNewOrRemembered(cls());
}
@ -6052,10 +6066,8 @@ class AllocateObjectInstr : public AllocationInstr {
type_arguments_ = value;
}
const TokenPosition token_pos_;
const Class& cls_;
Value* type_arguments_;
AliasIdentity identity_;
Function& closure_function_;
DISALLOW_COPY_AND_ASSIGN(AllocateObjectInstr);
@ -6070,7 +6082,6 @@ class AllocateUninitializedContextInstr
DECLARE_INSTRUCTION(AllocateUninitializedContext)
virtual CompileType ComputeType() const;
virtual TokenPosition token_pos() const { return token_pos_; }
intptr_t num_context_variables() const { return num_context_variables_; }
virtual bool ComputeCanDeoptimize() const { return false; }
@ -6082,15 +6093,10 @@ class AllocateUninitializedContextInstr
num_context_variables_);
}
virtual AliasIdentity Identity() const { return identity_; }
virtual void SetIdentity(AliasIdentity identity) { identity_ = identity; }
PRINT_OPERANDS_TO_SUPPORT
private:
const TokenPosition token_pos_;
const intptr_t num_context_variables_;
AliasIdentity identity_;
DISALLOW_COPY_AND_ASSIGN(AllocateUninitializedContextInstr);
};
@ -6100,15 +6106,17 @@ class AllocateUninitializedContextInstr
// It does not produce any real code only deoptimization information.
class MaterializeObjectInstr : public Definition {
public:
MaterializeObjectInstr(AllocateObjectInstr* allocation,
MaterializeObjectInstr(AllocationInstr* allocation,
const Class& cls,
intptr_t num_elements,
const ZoneGrowableArray<const Slot*>& slots,
ZoneGrowableArray<Value*>* values)
: allocation_(allocation),
cls_(allocation->cls()),
num_variables_(-1),
cls_(cls),
num_elements_(num_elements),
slots_(slots),
values_(values),
locations_(NULL),
locations_(nullptr),
visited_for_liveness_(false),
registers_remapped_(false) {
ASSERT(slots_.length() == values_->length());
@ -6118,28 +6126,10 @@ class MaterializeObjectInstr : public Definition {
}
}
MaterializeObjectInstr(AllocateUninitializedContextInstr* allocation,
const ZoneGrowableArray<const Slot*>& slots,
ZoneGrowableArray<Value*>* values)
: allocation_(allocation),
cls_(Class::ZoneHandle(Object::context_class())),
num_variables_(allocation->num_context_variables()),
slots_(slots),
values_(values),
locations_(NULL),
visited_for_liveness_(false),
registers_remapped_(false) {
ASSERT(slots_.length() == values_->length());
for (intptr_t i = 0; i < InputCount(); i++) {
InputAt(i)->set_instruction(this);
InputAt(i)->set_use_index(i);
}
}
Definition* allocation() const { return allocation_; }
AllocationInstr* allocation() const { return allocation_; }
const Class& cls() const { return cls_; }
intptr_t num_variables() const { return num_variables_; }
intptr_t num_elements() const { return num_elements_; }
intptr_t FieldOffsetAt(intptr_t i) const {
return slots_[i]->offset_in_bytes();
@ -6183,9 +6173,9 @@ class MaterializeObjectInstr : public Definition {
(*values_)[i] = value;
}
Definition* allocation_;
AllocationInstr* allocation_;
const Class& cls_;
intptr_t num_variables_;
intptr_t num_elements_;
const ZoneGrowableArray<const Slot*>& slots_;
ZoneGrowableArray<Value*>* values_;
Location* locations_;
@ -6196,15 +6186,51 @@ class MaterializeObjectInstr : public Definition {
DISALLOW_COPY_AND_ASSIGN(MaterializeObjectInstr);
};
class CreateArrayInstr : public TemplateAllocation<2, Throws> {
class ArrayAllocationInstr : public AllocationInstr {
public:
explicit ArrayAllocationInstr(TokenPosition token_pos, intptr_t deopt_id)
: AllocationInstr(token_pos, deopt_id) {}
virtual Value* num_elements() const = 0;
bool HasConstantNumElements() const {
return num_elements()->BindsToSmiConstant();
}
intptr_t GetConstantNumElements() const {
return num_elements()->BoundSmiConstant();
}
DEFINE_INSTRUCTION_TYPE_CHECK(ArrayAllocation);
private:
DISALLOW_COPY_AND_ASSIGN(ArrayAllocationInstr);
};
template <intptr_t N, typename ThrowsTrait>
class TemplateArrayAllocation : public ArrayAllocationInstr {
public:
explicit TemplateArrayAllocation(TokenPosition token_pos, intptr_t deopt_id)
: ArrayAllocationInstr(token_pos, deopt_id), inputs_() {}
virtual intptr_t InputCount() const { return N; }
virtual Value* InputAt(intptr_t i) const { return inputs_[i]; }
virtual bool MayThrow() const { return ThrowsTrait::kCanThrow; }
protected:
EmbeddedArray<Value*, N> inputs_;
private:
virtual void RawSetInputAt(intptr_t i, Value* value) { inputs_[i] = value; }
};
class CreateArrayInstr : public TemplateArrayAllocation<2, Throws> {
public:
CreateArrayInstr(TokenPosition token_pos,
Value* element_type,
Value* num_elements,
intptr_t deopt_id)
: TemplateAllocation(deopt_id),
token_pos_(token_pos),
identity_(AliasIdentity::Unknown()) {
: TemplateArrayAllocation(token_pos, deopt_id) {
SetInputAt(kElementTypePos, element_type);
SetInputAt(kLengthPos, num_elements);
}
@ -6214,9 +6240,8 @@ class CreateArrayInstr : public TemplateAllocation<2, Throws> {
DECLARE_INSTRUCTION(CreateArray)
virtual CompileType ComputeType() const;
virtual TokenPosition token_pos() const { return token_pos_; }
Value* element_type() const { return inputs_[kElementTypePos]; }
Value* num_elements() const { return inputs_[kLengthPos]; }
virtual Value* num_elements() const { return inputs_[kLengthPos]; }
// Throw needs environment, which is created only if instruction can
// deoptimize.
@ -6226,35 +6251,24 @@ class CreateArrayInstr : public TemplateAllocation<2, Throws> {
virtual bool HasUnknownSideEffects() const { return false; }
virtual AliasIdentity Identity() const { return identity_; }
virtual void SetIdentity(AliasIdentity identity) { identity_ = identity; }
virtual bool WillAllocateNewOrRemembered() const {
// Large arrays will use cards instead; cannot skip write barrier.
if (!num_elements()->BindsToConstant()) return false;
const Object& length = num_elements()->BoundConstant();
if (!length.IsSmi()) return false;
if (!HasConstantNumElements()) return false;
return compiler::target::WillAllocateNewOrRememberedArray(
Smi::Cast(length).Value());
GetConstantNumElements());
}
private:
const TokenPosition token_pos_;
AliasIdentity identity_;
DISALLOW_COPY_AND_ASSIGN(CreateArrayInstr);
};
class AllocateTypedDataInstr : public TemplateAllocation<1, Throws> {
class AllocateTypedDataInstr : public TemplateArrayAllocation<1, Throws> {
public:
AllocateTypedDataInstr(TokenPosition token_pos,
classid_t class_id,
Value* num_elements,
intptr_t deopt_id)
: TemplateAllocation(deopt_id),
token_pos_(token_pos),
class_id_(class_id),
identity_(AliasIdentity::Unknown()) {
: TemplateArrayAllocation(token_pos, deopt_id), class_id_(class_id) {
SetInputAt(kLengthPos, num_elements);
}
@ -6263,9 +6277,8 @@ class AllocateTypedDataInstr : public TemplateAllocation<1, Throws> {
DECLARE_INSTRUCTION(AllocateTypedData)
virtual CompileType ComputeType() const;
virtual TokenPosition token_pos() const { return token_pos_; }
classid_t class_id() const { return class_id_; }
Value* num_elements() const { return inputs_[kLengthPos]; }
virtual Value* num_elements() const { return inputs_[kLengthPos]; }
// Throw needs environment, which is created only if instruction can
// deoptimize.
@ -6275,18 +6288,13 @@ class AllocateTypedDataInstr : public TemplateAllocation<1, Throws> {
virtual bool HasUnknownSideEffects() const { return false; }
virtual AliasIdentity Identity() const { return identity_; }
virtual void SetIdentity(AliasIdentity identity) { identity_ = identity; }
virtual bool WillAllocateNewOrRemembered() const {
// No write barriers are generated for typed data accesses.
return false;
}
private:
const TokenPosition token_pos_;
classid_t class_id_;
AliasIdentity identity_;
const classid_t class_id_;
DISALLOW_COPY_AND_ASSIGN(AllocateTypedDataInstr);
};
@ -6460,7 +6468,15 @@ class LoadFieldInstr : public TemplateDefinition<1, Throws> {
virtual CompileType ComputeType() const;
virtual bool ComputeCanDeoptimize() const { return calls_initializer(); }
virtual bool HasUnknownSideEffects() const { return calls_initializer(); }
virtual bool HasUnknownSideEffects() const {
if (calls_initializer()) {
const Field& field = slot().field();
return field.needs_load_guard() || field.has_initializer();
}
return false;
}
virtual bool CanTriggerGC() const { return calls_initializer(); }
virtual bool MayThrow() const { return calls_initializer(); }
@ -6625,12 +6641,11 @@ class AllocateContextInstr : public TemplateAllocation<0, NoThrow> {
public:
AllocateContextInstr(TokenPosition token_pos,
const ZoneGrowableArray<const Slot*>& context_slots)
: token_pos_(token_pos), context_slots_(context_slots) {}
: TemplateAllocation(token_pos), context_slots_(context_slots) {}
DECLARE_INSTRUCTION(AllocateContext)
virtual CompileType ComputeType() const;
virtual TokenPosition token_pos() const { return token_pos_; }
const ZoneGrowableArray<const Slot*>& context_slots() const {
return context_slots_;
}
@ -6649,7 +6664,6 @@ class AllocateContextInstr : public TemplateAllocation<0, NoThrow> {
PRINT_OPERANDS_TO_SUPPORT
private:
const TokenPosition token_pos_;
const ZoneGrowableArray<const Slot*>& context_slots_;
DISALLOW_COPY_AND_ASSIGN(AllocateContextInstr);

View file

@ -2189,6 +2189,9 @@ bool FlowGraphDeserializer::ParseSlot(SExpList* list, const Slot** out) {
case Slot::Kind::kTypeArgumentsIndex:
*out = &Slot::GetTypeArgumentsIndexSlot(thread(), offset);
break;
case Slot::Kind::kArrayElement:
*out = &Slot::GetArrayElementSlot(thread(), offset);
break;
case Slot::Kind::kCapturedVariable:
StoreError(kind_sexp, "unhandled Slot kind");
return false;

View file

@ -2730,6 +2730,11 @@ void LoadFieldInstr::InferRange(RangeAnalysis* analysis, Range* range) {
UNREACHABLE();
break;
case Slot::Kind::kArrayElement:
// Should not be used in LoadField instructions.
UNREACHABLE();
break;
case Slot::Kind::kFunction_packed_fields:
*range = Range::Full(RepresentationToRangeSize(slot().representation()));
break;

View file

@ -10,6 +10,7 @@
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/loops.h"
#include "vm/hash_map.h"
#include "vm/object_store.h"
#include "vm/stack_frame.h"
namespace dart {
@ -1006,8 +1007,9 @@ class AliasedSet : public ZoneAllocated {
return true;
}
return (place->kind() == Place::kInstanceField) &&
!CanBeAliased(place->instance());
return ((place->kind() == Place::kInstanceField) ||
(place->kind() == Place::kConstantIndexed)) &&
(place->instance() != nullptr) && !CanBeAliased(place->instance());
}
// Returns true if there are direct loads from the given place.
@ -2061,6 +2063,40 @@ class LoadOptimizer : public ValueObject {
}
}
continue;
} else if (auto alloc = instr->AsCreateArray()) {
for (Value* use = alloc->input_use_list(); use != nullptr;
use = use->next_use()) {
// Look for all immediate loads/stores from this object.
if (use->use_index() != 0) {
continue;
}
intptr_t place_id = -1;
Definition* forward_def = nullptr;
if (auto load = use->instruction()->AsLoadField()) {
if (load->slot().IsTypeArguments()) {
place_id = GetPlaceId(load);
forward_def = alloc->element_type()->definition();
}
} else if (use->instruction()->IsLoadIndexed() ||
use->instruction()->IsStoreIndexed()) {
if (aliased_set_->CanBeAliased(alloc)) {
continue;
}
place_id = GetPlaceId(use->instruction());
if (aliased_set_->places()[place_id]->kind() !=
Place::kConstantIndexed) {
continue;
}
// Set initial value of array element to null.
forward_def = graph_->constant_null();
}
if (forward_def != nullptr) {
gen->Add(place_id);
if (out_values == nullptr) out_values = CreateBlockOutValues();
(*out_values)[place_id] = forward_def;
}
}
continue;
}
if (!IsLoadEliminationCandidate(defn)) {
@ -3003,10 +3039,22 @@ void DeadStoreElimination::Optimize(FlowGraph* graph) {
// Allocation Sinking
//
static bool IsValidLengthForAllocationSinking(
ArrayAllocationInstr* array_alloc) {
const intptr_t kMaxAllocationSinkingNumElements = 32;
if (!array_alloc->HasConstantNumElements()) {
return false;
}
const intptr_t length = array_alloc->GetConstantNumElements();
return (length >= 0) && (length <= kMaxAllocationSinkingNumElements);
}
// Returns true if the given instruction is an allocation that
// can be sunk by the Allocation Sinking pass.
static bool IsSupportedAllocation(Instruction* instr) {
return instr->IsAllocateObject() || instr->IsAllocateUninitializedContext();
return instr->IsAllocateObject() || instr->IsAllocateUninitializedContext() ||
(instr->IsArrayAllocation() &&
IsValidLengthForAllocationSinking(instr->AsArrayAllocation()));
}
enum SafeUseCheck { kOptimisticCheck, kStrictCheck };
@ -3032,12 +3080,13 @@ enum SafeUseCheck { kOptimisticCheck, kStrictCheck };
// optimistically and then checks each collected candidate strictly and unmarks
// invalid candidates transitively until only strictly valid ones remain.
static bool IsSafeUse(Value* use, SafeUseCheck check_type) {
ASSERT(IsSupportedAllocation(use->definition()));
if (use->instruction()->IsMaterializeObject()) {
return true;
}
StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
if (store != NULL) {
if (auto* store = use->instruction()->AsStoreInstanceField()) {
if (use == store->value()) {
Definition* instance = store->instance()->definition();
return IsSupportedAllocation(instance) &&
@ -3047,6 +3096,38 @@ static bool IsSafeUse(Value* use, SafeUseCheck check_type) {
return true;
}
if (auto* store = use->instruction()->AsStoreIndexed()) {
if (use == store->index()) {
return false;
}
if (use == store->array()) {
if (!store->index()->BindsToSmiConstant()) {
return false;
}
const intptr_t index = store->index()->BoundSmiConstant();
if (index < 0 || index >= use->definition()
->AsArrayAllocation()
->GetConstantNumElements()) {
return false;
}
if (auto* alloc_typed_data = use->definition()->AsAllocateTypedData()) {
if (store->class_id() != alloc_typed_data->class_id() ||
!store->aligned() ||
store->index_scale() != compiler::target::Instance::ElementSizeFor(
alloc_typed_data->class_id())) {
return false;
}
}
}
if (use == store->value()) {
Definition* instance = store->array()->definition();
return IsSupportedAllocation(instance) &&
((check_type == kOptimisticCheck) ||
instance->Identity().IsAllocationSinkingCandidate());
}
return true;
}
return false;
}
@ -3071,13 +3152,26 @@ static bool IsAllocationSinkingCandidate(Definition* alloc,
// If the given use is a store into an object then return an object we are
// storing into.
static Definition* StoreInto(Value* use) {
StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
if (store != NULL) {
static Definition* StoreDestination(Value* use) {
if (auto store = use->instruction()->AsStoreInstanceField()) {
return store->instance()->definition();
}
if (auto store = use->instruction()->AsStoreIndexed()) {
return store->array()->definition();
}
return nullptr;
}
return NULL;
// If the given instruction is a load from an object, then return an object
// we are loading from.
static Definition* LoadSource(Definition* instr) {
if (auto load = instr->AsLoadField()) {
return load->instance()->definition();
}
if (auto load = instr->AsLoadIndexed()) {
return load->array()->definition();
}
return nullptr;
}
// Remove the given allocation from the graph. It is not observable.
@ -3106,10 +3200,6 @@ void AllocationSinking::EliminateAllocation(Definition* alloc) {
#endif
ASSERT(alloc->input_use_list() == NULL);
alloc->RemoveFromGraph();
if (alloc->ArgumentCount() > 0) {
ASSERT(alloc->ArgumentCount() == 1);
ASSERT(!alloc->HasPushArguments());
}
}
// Find allocation instructions that can be potentially eliminated and
@ -3186,23 +3276,23 @@ void AllocationSinking::NormalizeMaterializations() {
}
// We transitively insert materializations at each deoptimization exit that
// might see the given allocation (see ExitsCollector). Some of this
// might see the given allocation (see ExitsCollector). Some of these
// materializations are not actually used and some fail to compute because
// they are inserted in the block that is not dominated by the allocation.
// Remove them unused materializations from the graph.
// Remove unused materializations from the graph.
void AllocationSinking::RemoveUnusedMaterializations() {
intptr_t j = 0;
for (intptr_t i = 0; i < materializations_.length(); i++) {
MaterializeObjectInstr* mat = materializations_[i];
if ((mat->input_use_list() == NULL) && (mat->env_use_list() == NULL)) {
if ((mat->input_use_list() == nullptr) &&
(mat->env_use_list() == nullptr)) {
// Check if this materialization failed to compute and remove any
// unforwarded loads. There were no loads from any allocation sinking
// candidate in the beginning so it is safe to assume that any encountered
// load was inserted by CreateMaterializationAt.
for (intptr_t i = 0; i < mat->InputCount(); i++) {
LoadFieldInstr* load = mat->InputAt(i)->definition()->AsLoadField();
if ((load != NULL) &&
(load->instance()->definition() == mat->allocation())) {
Definition* load = mat->InputAt(i)->definition();
if (LoadSource(load) == mat->allocation()) {
load->ReplaceUsesWith(flow_graph_->constant_null());
load->RemoveFromGraph();
}
@ -3263,14 +3353,16 @@ void AllocationSinking::DiscoverFailedCandidates() {
alloc->set_env_use_list(NULL);
for (Value* use = alloc->input_use_list(); use != NULL;
use = use->next_use()) {
if (use->instruction()->IsLoadField()) {
LoadFieldInstr* load = use->instruction()->AsLoadField();
if (use->instruction()->IsLoadField() ||
use->instruction()->IsLoadIndexed()) {
Definition* load = use->instruction()->AsDefinition();
load->ReplaceUsesWith(flow_graph_->constant_null());
load->RemoveFromGraph();
} else {
ASSERT(use->instruction()->IsMaterializeObject() ||
use->instruction()->IsPhi() ||
use->instruction()->IsStoreInstanceField());
use->instruction()->IsStoreInstanceField() ||
use->instruction()->IsStoreIndexed());
}
}
} else {
@ -3312,17 +3404,34 @@ void AllocationSinking::Optimize() {
// v_1 <- LoadField(v_0, field_1)
// ...
// v_N <- LoadField(v_0, field_N)
// v_{N+1} <- MaterializeObject(field_1 = v_1, ..., field_N = v_{N})
// v_{N+1} <- MaterializeObject(field_1 = v_1, ..., field_N = v_N)
//
// For typed data objects materialization looks like this:
// v_1 <- LoadIndexed(v_0, index_1)
// ...
// v_N <- LoadIndexed(v_0, index_N)
// v_{N+1} <- MaterializeObject([index_1] = v_1, ..., [index_N] = v_N)
//
// For arrays materialization looks like this:
// v_1 <- LoadIndexed(v_0, index_1)
// ...
// v_N <- LoadIndexed(v_0, index_N)
// v_{N+1} <- LoadField(v_0, Array.type_arguments)
// v_{N+2} <- MaterializeObject([index_1] = v_1, ..., [index_N] = v_N,
// type_arguments = v_{N+1})
//
for (intptr_t i = 0; i < candidates_.length(); i++) {
InsertMaterializations(candidates_[i]);
}
// Run load forwarding to eliminate LoadField instructions inserted above.
// Run load forwarding to eliminate LoadField/LoadIndexed instructions
// inserted above.
//
// All loads will be successfully eliminated because:
// a) they use fields (not offsets) and thus provide precise aliasing
// a) they use fields/constant indices and thus provide precise aliasing
// information
// b) candidate does not escape and thus its fields is not affected by
// external effects from calls.
// b) candidate does not escape and thus its fields/elements are not
// affected by external effects from calls.
LoadOptimizer::OptimizeGraph(flow_graph_);
NormalizeMaterializations();
@ -3426,23 +3535,57 @@ void AllocationSinking::CreateMaterializationAt(
// instruction.
Instruction* load_point = FirstMaterializationAt(exit);
// Insert load instruction for every field.
// Insert load instruction for every field and element.
for (auto slot : slots) {
LoadFieldInstr* load =
new (Z) LoadFieldInstr(new (Z) Value(alloc), *slot, alloc->token_pos());
Definition* load = nullptr;
if (slot->IsArrayElement()) {
intptr_t array_cid, index;
if (alloc->IsCreateArray()) {
array_cid = kArrayCid;
index =
compiler::target::Array::index_at_offset(slot->offset_in_bytes());
} else if (auto alloc_typed_data = alloc->AsAllocateTypedData()) {
array_cid = alloc_typed_data->class_id();
index = slot->offset_in_bytes() /
compiler::target::Instance::ElementSizeFor(array_cid);
} else {
UNREACHABLE();
}
load = new (Z) LoadIndexedInstr(
new (Z) Value(alloc),
new (Z) Value(
flow_graph_->GetConstant(Smi::ZoneHandle(Z, Smi::New(index)))),
/*index_unboxed=*/false,
/*index_scale=*/compiler::target::Instance::ElementSizeFor(array_cid),
array_cid, kAlignedAccess, DeoptId::kNone, alloc->token_pos());
} else {
load = new (Z)
LoadFieldInstr(new (Z) Value(alloc), *slot, alloc->token_pos());
}
flow_graph_->InsertBefore(load_point, load, nullptr, FlowGraph::kValue);
values->Add(new (Z) Value(load));
}
MaterializeObjectInstr* mat = nullptr;
if (alloc->IsAllocateObject()) {
mat = new (Z)
MaterializeObjectInstr(alloc->AsAllocateObject(), slots, values);
const Class* cls = nullptr;
intptr_t num_elements = -1;
if (auto instr = alloc->AsAllocateObject()) {
cls = &(instr->cls());
} else if (auto instr = alloc->AsAllocateUninitializedContext()) {
cls = &Class::ZoneHandle(Object::context_class());
num_elements = instr->num_context_variables();
} else if (auto instr = alloc->AsCreateArray()) {
cls = &Class::ZoneHandle(
flow_graph_->isolate()->object_store()->array_class());
num_elements = instr->GetConstantNumElements();
} else if (auto instr = alloc->AsAllocateTypedData()) {
cls = &Class::ZoneHandle(
flow_graph_->isolate()->class_table()->At(instr->class_id()));
num_elements = instr->GetConstantNumElements();
} else {
ASSERT(alloc->IsAllocateUninitializedContext());
mat = new (Z) MaterializeObjectInstr(
alloc->AsAllocateUninitializedContext(), slots, values);
UNREACHABLE();
}
MaterializeObjectInstr* mat = new (Z) MaterializeObjectInstr(
alloc->AsAllocation(), *cls, num_elements, slots, values);
flow_graph_->InsertBefore(exit, mat, nullptr, FlowGraph::kValue);
@ -3497,7 +3640,7 @@ void AllocationSinking::ExitsCollector::Collect(Definition* alloc) {
// this object.
for (Value* use = alloc->input_use_list(); use != NULL;
use = use->next_use()) {
Definition* obj = StoreInto(use);
Definition* obj = StoreDestination(use);
if ((obj != NULL) && (obj != alloc)) {
AddInstruction(&worklist_, obj);
}
@ -3521,14 +3664,27 @@ void AllocationSinking::ExitsCollector::CollectTransitively(Definition* alloc) {
}
void AllocationSinking::InsertMaterializations(Definition* alloc) {
// Collect all fields that are written for this instance.
// Collect all fields and array elements that are written for this instance.
auto slots = new (Z) ZoneGrowableArray<const Slot*>(5);
for (Value* use = alloc->input_use_list(); use != NULL;
use = use->next_use()) {
StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
if ((store != NULL) && (store->instance()->definition() == alloc)) {
AddSlot(slots, store->slot());
if (StoreDestination(use) == alloc) {
if (auto store = use->instruction()->AsStoreInstanceField()) {
AddSlot(slots, store->slot());
} else if (auto store = use->instruction()->AsStoreIndexed()) {
const intptr_t index = store->index()->BoundSmiConstant();
intptr_t offset = -1;
if (alloc->IsCreateArray()) {
offset = compiler::target::Array::element_offset(index);
} else if (alloc->IsAllocateTypedData()) {
offset = index * store->index_scale();
} else {
UNREACHABLE();
}
AddSlot(slots,
Slot::GetArrayElementSlot(flow_graph_->thread(), offset));
}
}
}
@ -3538,6 +3694,13 @@ void AllocationSinking::InsertMaterializations(Definition* alloc) {
alloc_object->cls()));
}
}
if (auto create_array = alloc->AsCreateArray()) {
AddSlot(slots,
Slot::GetTypeArgumentsSlotFor(
flow_graph_->thread(),
Class::Handle(
Z, flow_graph_->isolate()->object_store()->array_class())));
}
// Collect all instructions that mention this object in the environment.
exits_collector_.CollectTransitively(alloc);

View file

@ -1114,6 +1114,181 @@ ISOLATE_UNIT_TEST_CASE(LoadOptimizer_RedundantInitializerCallInLoop) {
EXPECT(load_field_in_loop2->calls_initializer());
}
ISOLATE_UNIT_TEST_CASE(AllocationSinking_Arrays) {
const char* kScript = R"(
import 'dart:typed_data';
class Vector2 {
final Float64List _v2storage;
@pragma('vm:prefer-inline')
Vector2.zero() : _v2storage = Float64List(2);
@pragma('vm:prefer-inline')
factory Vector2(double x, double y) => Vector2.zero()..setValues(x, y);
@pragma('vm:prefer-inline')
factory Vector2.copy(Vector2 other) => Vector2.zero()..setFrom(other);
@pragma('vm:prefer-inline')
Vector2 clone() => Vector2.copy(this);
@pragma('vm:prefer-inline')
void setValues(double x_, double y_) {
_v2storage[0] = x_;
_v2storage[1] = y_;
}
@pragma('vm:prefer-inline')
void setFrom(Vector2 other) {
final otherStorage = other._v2storage;
_v2storage[1] = otherStorage[1];
_v2storage[0] = otherStorage[0];
}
@pragma('vm:prefer-inline')
Vector2 operator +(Vector2 other) => clone()..add(other);
@pragma('vm:prefer-inline')
void add(Vector2 arg) {
final argStorage = arg._v2storage;
_v2storage[0] = _v2storage[0] + argStorage[0];
_v2storage[1] = _v2storage[1] + argStorage[1];
}
@pragma('vm:prefer-inline')
double get x => _v2storage[0];
@pragma('vm:prefer-inline')
double get y => _v2storage[1];
}
@pragma('vm:never-inline')
String foo(double x) {
// All allocations in this function are eliminated by the compiler,
// except array allocation for string interpolation at the end.
List v1 = List.filled(2, null);
v1[0] = 1;
v1[1] = 'hi';
Vector2 v2 = new Vector2(1.0, 2.0);
Vector2 v3 = v2 + Vector2(x, x);
double sum = v3.x + v3.y;
return "v1: [${v1[0]},${v1[1]}], v2: [${v2.x},${v2.y}], v3: [${v3.x},${v3.y}], sum: $sum";
}
main() {
foo(42.0);
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
Invoke(root_library, "main");
const auto& function = Function::Handle(GetFunction(root_library, "foo"));
TestPipeline pipeline(function, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses({});
ASSERT(flow_graph != nullptr);
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
/* Flow graph to match:
4: CheckStackOverflow:8(stack=0, loop=0)
6: v590 <- UnboxedConstant(#1.0) T{_Double}
7: ParallelMove DS-8 <- xmm0
8: v592 <- UnboxedConstant(#2.0) T{_Double}
9: ParallelMove rax <- S+2, DS-7 <- xmm1
10: CheckClass:14(v2 Cids[1: _Double@0150898 etc. cid 54])
12: v526 <- Unbox:14(v2 T{_Double}) T{_Double}
14: ParallelMove xmm3 <- xmm0
14: v352 <- BinaryDoubleOp:22(+, v590, v526) T{_Double}
15: ParallelMove DS-6 <- xmm3
16: ParallelMove xmm4 <- xmm1
16: v363 <- BinaryDoubleOp:34(+, v592, v526) T{_Double}
17: ParallelMove DS-5 <- xmm4
18: ParallelMove xmm2 <- xmm3
18: v21 <- BinaryDoubleOp:28(+, v352, v363) T{_Double}
19: ParallelMove rbx <- C, r10 <- C, DS-4 <- xmm2
20: v24 <- CreateArray:30(v0, v23) T{_List}
21: ParallelMove rcx <- rax
22: ParallelMove S-3 <- rcx
22: StoreIndexed(v24, v6, v26, NoStoreBarrier)
24: StoreIndexed(v24, v7, v7, NoStoreBarrier)
26: StoreIndexed(v24, v3, v29, NoStoreBarrier)
28: StoreIndexed(v24, v30, v8, NoStoreBarrier)
30: StoreIndexed(v24, v33, v34, NoStoreBarrier)
31: ParallelMove xmm0 <- DS-8
32: v582 <- Box(v590) T{_Double}
33: ParallelMove rdx <- rcx, rax <- rax
34: StoreIndexed(v24, v35, v582)
36: StoreIndexed(v24, v38, v29, NoStoreBarrier)
37: ParallelMove xmm0 <- DS-7
38: v584 <- Box(v592) T{_Double}
39: ParallelMove rdx <- rcx, rax <- rax
40: StoreIndexed(v24, v39, v584)
42: StoreIndexed(v24, v42, v43, NoStoreBarrier)
43: ParallelMove xmm0 <- DS-6
44: v586 <- Box(v352) T{_Double}
45: ParallelMove rdx <- rcx, rax <- rax
46: StoreIndexed(v24, v44, v586)
48: StoreIndexed(v24, v47, v29, NoStoreBarrier)
49: ParallelMove xmm0 <- DS-5
50: v588 <- Box(v363) T{_Double}
51: ParallelMove rdx <- rcx, rax <- rax
52: StoreIndexed(v24, v48, v588)
54: StoreIndexed(v24, v51, v52, NoStoreBarrier)
55: ParallelMove xmm0 <- DS-4
56: v580 <- Box(v21) T{_Double}
57: ParallelMove rdx <- rcx, rax <- rax
58: StoreIndexed(v24, v53, v580)
59: ParallelMove rax <- rcx
60: v54 <- StringInterpolate:44(v24) T{String}
61: ParallelMove rax <- rax
62: Return:48(v54)
*/
CreateArrayInstr* create_array = nullptr;
StringInterpolateInstr* string_interpolate = nullptr;
ILMatcher cursor(flow_graph, entry, /*trace=*/true,
ParallelMovesHandling::kSkip);
RELEASE_ASSERT(cursor.TryMatch({
kMatchAndMoveFunctionEntry,
kMatchAndMoveCheckStackOverflow,
kMatchAndMoveUnboxedConstant,
kMatchAndMoveUnboxedConstant,
kMatchAndMoveCheckClass,
kMatchAndMoveUnbox,
kMatchAndMoveBinaryDoubleOp,
kMatchAndMoveBinaryDoubleOp,
kMatchAndMoveBinaryDoubleOp,
{kMatchAndMoveCreateArray, &create_array},
kMatchAndMoveStoreIndexed,
kMatchAndMoveStoreIndexed,
kMatchAndMoveStoreIndexed,
kMatchAndMoveStoreIndexed,
kMatchAndMoveStoreIndexed,
kMatchAndMoveBox,
kMatchAndMoveStoreIndexed,
kMatchAndMoveStoreIndexed,
kMatchAndMoveBox,
kMatchAndMoveStoreIndexed,
kMatchAndMoveStoreIndexed,
kMatchAndMoveBox,
kMatchAndMoveStoreIndexed,
kMatchAndMoveStoreIndexed,
kMatchAndMoveBox,
kMatchAndMoveStoreIndexed,
kMatchAndMoveStoreIndexed,
kMatchAndMoveBox,
kMatchAndMoveStoreIndexed,
{kMatchAndMoveStringInterpolate, &string_interpolate},
kMatchReturn,
}));
EXPECT(string_interpolate->value()->definition() == create_array);
}
#if !defined(TARGET_ARCH_IA32)
ISOLATE_UNIT_TEST_CASE(DelayAllocations_DelayAcrossCalls) {

View file

@ -58,6 +58,8 @@ const char* Slot::KindToCString(Kind k) {
#undef NATIVE_CASE
case Kind::kTypeArguments:
return "TypeArguments";
case Kind::kArrayElement:
return "ArrayElement";
case Kind::kCapturedVariable:
return "CapturedVariable";
case Kind::kDartField:
@ -81,6 +83,10 @@ bool Slot::ParseKind(const char* str, Kind* out) {
*out = Kind::kTypeArguments;
return true;
}
if (strcmp(str, "ArrayElement") == 0) {
*out = Kind::kArrayElement;
return true;
}
if (strcmp(str, "CapturedVariable") == 0) {
*out = Kind::kCapturedVariable;
return true;
@ -212,6 +218,14 @@ const Slot& Slot::GetTypeArgumentsIndexSlot(Thread* thread, intptr_t index) {
return SlotCache::Instance(thread).Canonicalize(slot);
}
const Slot& Slot::GetArrayElementSlot(Thread* thread,
intptr_t offset_in_bytes) {
const Slot& slot =
Slot(Kind::kArrayElement, IsNullableBit::encode(true), kDynamicCid,
offset_in_bytes, ":array_element", /*static_type=*/nullptr, kTagged);
return SlotCache::Instance(thread).Canonicalize(slot);
}
const Slot& Slot::Get(const Field& field,
const ParsedFunction* parsed_function) {
Thread* thread = Thread::Current();
@ -328,6 +342,7 @@ bool Slot::Equals(const Slot* other) const {
switch (kind_) {
case Kind::kTypeArguments:
case Kind::kTypeArgumentsIndex:
case Kind::kArrayElement:
return (offset_in_bytes_ == other->offset_in_bytes_);
case Kind::kCapturedVariable:

View file

@ -148,6 +148,10 @@ class Slot : public ZoneAllocated {
// A slot at a specific [index] in a [RawTypeArgument] vector.
kTypeArgumentsIndex,
// A slot corresponding to an array element at given offset.
// Only used during allocation sinking and in MaterializeObjectInstr.
kArrayElement,
// A slot within a Context object that contains a value of a captured
// local variable.
kCapturedVariable,
@ -176,6 +180,10 @@ class Slot : public ZoneAllocated {
// Returns a slot at a specific [index] in a [RawTypeArgument] vector.
static const Slot& GetTypeArgumentsIndexSlot(Thread* thread, intptr_t index);
// Returns a slot corresponding to an array element at [offset_in_bytes].
static const Slot& GetArrayElementSlot(Thread* thread,
intptr_t offset_in_bytes);
// Returns a slot that represents the given captured local variable.
static const Slot& GetContextVariableSlotFor(Thread* thread,
const LocalVariable& var);
@ -198,6 +206,7 @@ class Slot : public ZoneAllocated {
bool IsLocalVariable() const { return kind() == Kind::kCapturedVariable; }
bool IsTypeArguments() const { return kind() == Kind::kTypeArguments; }
bool IsArgumentOfType() const { return kind() == Kind::kTypeArgumentsIndex; }
bool IsArrayElement() const { return kind() == Kind::kArrayElement; }
const char* Name() const;

View file

@ -882,6 +882,11 @@ word Array::NextFieldOffset() {
return -kWordSize;
}
intptr_t Array::index_at_offset(intptr_t offset_in_bytes) {
return dart::Array::index_at_offset(
TranslateOffsetInWordsToHost(offset_in_bytes));
}
word GrowableObjectArray::NextFieldOffset() {
return -kWordSize;
}

View file

@ -553,6 +553,7 @@ class Array : public AllStatic {
static word type_arguments_offset();
static word length_offset();
static word element_offset(intptr_t index);
static intptr_t index_at_offset(intptr_t offset_in_bytes);
static word InstanceSize();
static word NextFieldOffset();

View file

@ -166,7 +166,7 @@ ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_Arrays) {
c = C();
}
array[0] = c;
return c;
return array;
}
main() { foo(10); }

View file

@ -224,23 +224,50 @@ void DeferredObject::Create() {
Class& cls = Class::Handle();
cls ^= GetClass();
if (cls.raw() == Object::context_class()) {
intptr_t num_variables = Smi::Cast(Object::Handle(GetLength())).Value();
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr("materializing context of length %" Pd " (%" Px ", %" Pd
" vars)\n",
num_variables, reinterpret_cast<uword>(args_), field_count_);
}
object_ = &Context::ZoneHandle(Context::New(num_variables));
switch (cls.id()) {
case kContextCid: {
const intptr_t num_variables =
Smi::Cast(Object::Handle(GetLength())).Value();
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(
"materializing context of length %" Pd " (%" Px ", %" Pd " vars)\n",
num_variables, reinterpret_cast<uword>(args_), field_count_);
}
object_ = &Context::ZoneHandle(Context::New(num_variables));
} break;
case kArrayCid: {
const intptr_t num_elements =
Smi::Cast(Object::Handle(GetLength())).Value();
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr("materializing array of length %" Pd " (%" Px ", %" Pd
" elements)\n",
num_elements, reinterpret_cast<uword>(args_),
field_count_);
}
object_ = &Array::ZoneHandle(Array::New(num_elements));
} break;
default:
if (IsTypedDataClassId(cls.id())) {
const intptr_t num_elements =
Smi::Cast(Object::Handle(GetLength())).Value();
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr("materializing typed data cid %" Pd " of length %" Pd
" (%" Px ", %" Pd " elements)\n",
cls.id(), num_elements, reinterpret_cast<uword>(args_),
field_count_);
}
object_ =
&TypedData::ZoneHandle(TypedData::New(cls.id(), num_elements));
} else {
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr("materializing instance of %s (%" Px ", %" Pd " fields)\n",
cls.ToCString(), reinterpret_cast<uword>(args_),
field_count_);
}
} else {
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(
"materializing instance of %s (%" Px ", %" Pd " fields)\n",
cls.ToCString(), reinterpret_cast<uword>(args_), field_count_);
}
object_ = &Instance::ZoneHandle(Instance::New(cls));
object_ = &Instance::ZoneHandle(Instance::New(cls));
}
}
}
@ -256,65 +283,178 @@ void DeferredObject::Fill() {
Class& cls = Class::Handle();
cls ^= GetClass();
if (cls.raw() == Object::context_class()) {
const Context& context = Context::Cast(*object_);
switch (cls.id()) {
case kContextCid: {
const Context& context = Context::Cast(*object_);
Smi& offset = Smi::Handle();
Object& value = Object::Handle();
Smi& offset = Smi::Handle();
Object& value = Object::Handle();
for (intptr_t i = 0; i < field_count_; i++) {
offset ^= GetFieldOffset(i);
if (offset.Value() == Context::parent_offset()) {
// Copy parent.
Context& parent = Context::Handle();
parent ^= GetValue(i);
context.set_parent(parent);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" ctx@parent (offset %" Pd ") <- %s\n",
offset.Value(), value.ToCString());
}
} else {
intptr_t context_index = ToContextIndex(offset.Value());
value = GetValue(i);
context.SetAt(context_index, value);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" ctx@%" Pd " (offset %" Pd ") <- %s\n",
context_index, offset.Value(), value.ToCString());
for (intptr_t i = 0; i < field_count_; i++) {
offset ^= GetFieldOffset(i);
if (offset.Value() == Context::parent_offset()) {
// Copy parent.
Context& parent = Context::Handle();
parent ^= GetValue(i);
context.set_parent(parent);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" ctx@parent (offset %" Pd ") <- %s\n",
offset.Value(), value.ToCString());
}
} else {
intptr_t context_index = ToContextIndex(offset.Value());
value = GetValue(i);
context.SetAt(context_index, value);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" ctx@%" Pd " (offset %" Pd ") <- %s\n",
context_index, offset.Value(), value.ToCString());
}
}
}
}
} else {
const Instance& obj = Instance::Cast(*object_);
} break;
case kArrayCid: {
const Array& array = Array::Cast(*object_);
Smi& offset = Smi::Handle();
Field& field = Field::Handle();
Object& value = Object::Handle();
const Array& offset_map = Array::Handle(cls.OffsetToFieldMap());
Smi& offset = Smi::Handle();
Object& value = Object::Handle();
for (intptr_t i = 0; i < field_count_; i++) {
offset ^= GetFieldOffset(i);
field ^= offset_map.At(offset.Value() / kWordSize);
value = GetValue(i);
if (!field.IsNull()) {
obj.SetField(field, value);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" %s <- %s\n",
String::Handle(field.name()).ToCString(),
value.ToCString());
}
} else {
// In addition to the type arguments vector we can also have lazy
// materialization of e.g. _ByteDataView objects which don't have
// explicit fields in Dart (all accesses to the fields are done via
// recognized native methods).
ASSERT(offset.Value() < cls.host_instance_size());
obj.SetFieldAtOffset(offset.Value(), value);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" null Field @ offset(%" Pd ") <- %s\n",
offset.Value(), value.ToCString());
for (intptr_t i = 0; i < field_count_; i++) {
offset ^= GetFieldOffset(i);
if (offset.Value() == Array::type_arguments_offset()) {
TypeArguments& type_args = TypeArguments::Handle();
type_args ^= GetValue(i);
array.SetTypeArguments(type_args);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" array@type_args (offset %" Pd ") <- %s\n",
offset.Value(), value.ToCString());
}
} else {
const intptr_t index = Array::index_at_offset(offset.Value());
value = GetValue(i);
array.SetAt(index, value);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" array@%" Pd " (offset %" Pd ") <- %s\n", index,
offset.Value(), value.ToCString());
}
}
}
}
} break;
default:
if (IsTypedDataClassId(cls.id())) {
const TypedData& typed_data = TypedData::Cast(*object_);
Smi& offset = Smi::Handle();
Object& value = Object::Handle();
const auto cid = cls.id();
for (intptr_t i = 0; i < field_count_; i++) {
offset ^= GetFieldOffset(i);
const intptr_t element_offset = offset.Value();
value = GetValue(i);
switch (cid) {
case kTypedDataInt8ArrayCid:
typed_data.SetInt8(
element_offset,
static_cast<int8_t>(Integer::Cast(value).AsInt64Value()));
break;
case kTypedDataUint8ArrayCid:
case kTypedDataUint8ClampedArrayCid:
typed_data.SetUint8(
element_offset,
static_cast<uint8_t>(Integer::Cast(value).AsInt64Value()));
break;
case kTypedDataInt16ArrayCid:
typed_data.SetInt16(
element_offset,
static_cast<int16_t>(Integer::Cast(value).AsInt64Value()));
break;
case kTypedDataUint16ArrayCid:
typed_data.SetUint16(
element_offset,
static_cast<uint16_t>(Integer::Cast(value).AsInt64Value()));
break;
case kTypedDataInt32ArrayCid:
typed_data.SetInt32(
element_offset,
static_cast<int32_t>(Integer::Cast(value).AsInt64Value()));
break;
case kTypedDataUint32ArrayCid:
typed_data.SetUint32(
element_offset,
static_cast<uint32_t>(Integer::Cast(value).AsInt64Value()));
break;
case kTypedDataInt64ArrayCid:
typed_data.SetInt64(element_offset,
Integer::Cast(value).AsInt64Value());
break;
case kTypedDataUint64ArrayCid:
typed_data.SetUint64(
element_offset,
static_cast<uint64_t>(Integer::Cast(value).AsInt64Value()));
break;
case kTypedDataFloat32ArrayCid:
typed_data.SetFloat32(
element_offset,
static_cast<float>(Double::Cast(value).value()));
break;
case kTypedDataFloat64ArrayCid:
typed_data.SetFloat64(element_offset,
Double::Cast(value).value());
break;
case kTypedDataFloat32x4ArrayCid:
typed_data.SetFloat32x4(element_offset,
Float32x4::Cast(value).value());
break;
case kTypedDataInt32x4ArrayCid:
typed_data.SetInt32x4(element_offset,
Int32x4::Cast(value).value());
break;
case kTypedDataFloat64x2ArrayCid:
typed_data.SetFloat64x2(element_offset,
Float64x2::Cast(value).value());
break;
default:
UNREACHABLE();
}
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" typed_data (offset %" Pd ") <- %s\n",
element_offset, value.ToCString());
}
}
} else {
const Instance& obj = Instance::Cast(*object_);
Smi& offset = Smi::Handle();
Field& field = Field::Handle();
Object& value = Object::Handle();
const Array& offset_map = Array::Handle(cls.OffsetToFieldMap());
for (intptr_t i = 0; i < field_count_; i++) {
offset ^= GetFieldOffset(i);
field ^= offset_map.At(offset.Value() / kWordSize);
value = GetValue(i);
if (!field.IsNull()) {
obj.SetField(field, value);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" %s <- %s\n",
String::Handle(field.name()).ToCString(),
value.ToCString());
}
} else {
// In addition to the type arguments vector we can also have lazy
// materialization of e.g. _ByteDataView objects which don't have
// explicit fields in Dart (all accesses to the fields are done via
// recognized native methods).
ASSERT(offset.Value() < cls.host_instance_size());
obj.SetFieldAtOffset(offset.Value(), value);
if (FLAG_trace_deoptimization_verbose) {
OS::PrintErr(" null Field @ offset(%" Pd ") <- %s\n",
offset.Value(), value.ToCString());
}
}
}
}
break;
}
}

View file

@ -201,7 +201,12 @@ class DeferredObject {
private:
enum {
kClassIndex = 0,
kLengthIndex, // Number of context variables for contexts, -1 otherwise.
// Number of context variables for contexts,
// number of elements for arrays and typed data objects,
// -1 otherwise.
kLengthIndex,
kFieldsStartIndex
};

View file

@ -1208,7 +1208,7 @@ intptr_t DeoptInfoBuilder::EmitMaterializationArguments(intptr_t dest_index) {
MaterializeObjectInstr* mat = materializations_[i];
// Class of the instance to allocate.
AddConstant(mat->cls(), dest_index++);
AddConstant(Smi::ZoneHandle(Smi::New(mat->num_variables())), dest_index++);
AddConstant(Smi::ZoneHandle(Smi::New(mat->num_elements())), dest_index++);
for (intptr_t i = 0; i < mat->InputCount(); i++) {
if (!mat->InputAt(i)->BindsToConstantNull()) {
// Emit offset-value pair.

View file

@ -11433,7 +11433,6 @@ void Script::TokenRangeAtLine(intptr_t line_number,
StringPtr Script::GetLine(intptr_t line_number, Heap::Space space) const {
const String& src = String::Handle(Source());
if (src.IsNull()) {
ASSERT(Dart::vm_snapshot_kind() == Snapshot::kFullAOT);
return Symbols::OptimizedOut().raw();
}
intptr_t relative_line_number = line_number - line_offset();

View file

@ -9725,6 +9725,11 @@ class Array : public Instance {
static intptr_t element_offset(intptr_t index) {
return OFFSET_OF_RETURNED_VALUE(ArrayLayout, data) + kWordSize * index;
}
static intptr_t index_at_offset(intptr_t offset_in_bytes) {
intptr_t index = (offset_in_bytes - data_offset()) / kWordSize;
ASSERT(index >= 0);
return index;
}
struct ArrayTraits {
static intptr_t elements_start_offset() { return Array::data_offset(); }

View file

@ -0,0 +1,85 @@
// Copyright (c) 2020, 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.
// VMOptions=--optimization-counter-threshold=100 --deterministic
// Tests allocation sinking of arrays and typed data objects.
import 'dart:typed_data';
import 'package:expect/expect.dart';
import 'dart:typed_data';
class Vector2 {
final Float64List _v2storage;
@pragma('vm:prefer-inline')
Vector2.zero() : _v2storage = Float64List(2);
@pragma('vm:prefer-inline')
factory Vector2(double x, double y) => Vector2.zero()..setValues(x, y);
@pragma('vm:prefer-inline')
factory Vector2.copy(Vector2 other) => Vector2.zero()..setFrom(other);
@pragma('vm:prefer-inline')
Vector2 clone() => Vector2.copy(this);
@pragma('vm:prefer-inline')
void setValues(double x_, double y_) {
_v2storage[0] = x_;
_v2storage[1] = y_;
}
@pragma('vm:prefer-inline')
void setFrom(Vector2 other) {
final otherStorage = other._v2storage;
_v2storage[1] = otherStorage[1];
_v2storage[0] = otherStorage[0];
}
@pragma('vm:prefer-inline')
Vector2 operator +(Vector2 other) => clone()..add(other);
@pragma('vm:prefer-inline')
void add(Vector2 arg) {
final argStorage = arg._v2storage;
_v2storage[0] = _v2storage[0] + argStorage[0];
_v2storage[1] = _v2storage[1] + argStorage[1];
}
@pragma('vm:prefer-inline')
double get x => _v2storage[0];
@pragma('vm:prefer-inline')
double get y => _v2storage[1];
}
@pragma('vm:never-inline')
String foo(double x, num doDeopt) {
// All allocations in this function are eliminated by the compiler,
// except array allocation for string interpolation at the end.
List v1 = List.filled(2, null);
v1[0] = 1;
v1[1] = 'hi';
Vector2 v2 = new Vector2(1.0, 2.0);
Vector2 v3 = v2 + Vector2(x, x);
double sum = v3.x + v3.y;
// Deoptimization is triggered here to materialize removed allocations.
doDeopt + 2;
return "v1: [${v1[0]},${v1[1]}], v2: [${v2.x},${v2.y}], v3: [${v3.x},${v3.y}], sum: $sum";
}
main() {
// Due to '--optimization-counter-threshold=100 --deterministic'
// foo() is optimized during the first 100 iterations.
// After that, on iteration 120 deoptimization is triggered by changed
// type of 'doDeopt'. That forces materialization of all objects which
// allocations were removed by optimizer.
for (int i = 0; i < 130; ++i) {
final num doDeopt = (i < 120 ? 1 : 2.0);
final result = foo(3.0, doDeopt);
Expect.equals("v1: [1,hi], v2: [1.0,2.0], v3: [4.0,5.0], sum: 9.0", result);
}
}

View file

@ -0,0 +1,85 @@
// Copyright (c) 2020, 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.
// VMOptions=--optimization-counter-threshold=100 --deterministic
// Tests allocation sinking of arrays and typed data objects.
import 'dart:typed_data';
import 'package:expect/expect.dart';
import 'dart:typed_data';
class Vector2 {
final Float64List _v2storage;
@pragma('vm:prefer-inline')
Vector2.zero() : _v2storage = Float64List(2);
@pragma('vm:prefer-inline')
factory Vector2(double x, double y) => Vector2.zero()..setValues(x, y);
@pragma('vm:prefer-inline')
factory Vector2.copy(Vector2 other) => Vector2.zero()..setFrom(other);
@pragma('vm:prefer-inline')
Vector2 clone() => Vector2.copy(this);
@pragma('vm:prefer-inline')
void setValues(double x_, double y_) {
_v2storage[0] = x_;
_v2storage[1] = y_;
}
@pragma('vm:prefer-inline')
void setFrom(Vector2 other) {
final otherStorage = other._v2storage;
_v2storage[1] = otherStorage[1];
_v2storage[0] = otherStorage[0];
}
@pragma('vm:prefer-inline')
Vector2 operator +(Vector2 other) => clone()..add(other);
@pragma('vm:prefer-inline')
void add(Vector2 arg) {
final argStorage = arg._v2storage;
_v2storage[0] = _v2storage[0] + argStorage[0];
_v2storage[1] = _v2storage[1] + argStorage[1];
}
@pragma('vm:prefer-inline')
double get x => _v2storage[0];
@pragma('vm:prefer-inline')
double get y => _v2storage[1];
}
@pragma('vm:never-inline')
String foo(double x, num doDeopt) {
// All allocations in this function are eliminated by the compiler,
// except array allocation for string interpolation at the end.
List v1 = List.filled(2, null);
v1[0] = 1;
v1[1] = 'hi';
Vector2 v2 = new Vector2(1.0, 2.0);
Vector2 v3 = v2 + Vector2(x, x);
double sum = v3.x + v3.y;
// Deoptimization is triggered here to materialize removed allocations.
doDeopt + 2;
return "v1: [${v1[0]},${v1[1]}], v2: [${v2.x},${v2.y}], v3: [${v3.x},${v3.y}], sum: $sum";
}
main() {
// Due to '--optimization-counter-threshold=100 --deterministic'
// foo() is optimized during the first 100 iterations.
// After that, on iteration 120 deoptimization is triggered by changed
// type of 'doDeopt'. That forces materialization of all objects which
// allocations were removed by optimizer.
for (int i = 0; i < 130; ++i) {
final num doDeopt = (i < 120 ? 1 : 2.0);
final result = foo(3.0, doDeopt);
Expect.equals("v1: [1,hi], v2: [1.0,2.0], v3: [4.0,5.0], sum: 9.0", result);
}
}