mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[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:
parent
8cfaeebaa4
commit
dea3fa7b8c
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_Arrays) {
|
|||
c = C();
|
||||
}
|
||||
array[0] = c;
|
||||
return c;
|
||||
return array;
|
||||
}
|
||||
|
||||
main() { foo(10); }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(); }
|
||||
|
|
85
tests/language/vm/allocation_sinking_arrays_test.dart
Normal file
85
tests/language/vm/allocation_sinking_arrays_test.dart
Normal 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);
|
||||
}
|
||||
}
|
85
tests/language_2/vm/allocation_sinking_arrays_test.dart
Normal file
85
tests/language_2/vm/allocation_sinking_arrays_test.dart
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue