2565ca222f
AllocationSinking pass discovers non-escaping allocations that have no input uses other than uses in the stores into its own fields. Every environment use of such allocation is replaced by a state snapshot (MaterializeObject instruction) that describes the state of each initialized field in the object. State snapshots are computed through an additional round of load-forwarding. Once snapshots are computed allocations are removed from the graph. MaterializeObject instructions are not compiled into native code but produce deoptimization instructions instead that describe how object should be materialized at deoptimization. Deoptimization instructions now follow the following format: [mat obj #1]...[mat obj #N][ret addr][... mat arguments ...][... real frames ...] - the prefix describes each object to materialize on deopt via kMaterializeObject instruction; - actual values that are needed for materialization are emited as a part of bottom-most stack frame. This is done to simplify implementation: they need to be discoverable by a GC during materialization phase. At the end of deoptimization they will be removed from the stack; - normal stack slots can refer to materialized objects via kMaterializedObjectRef instruction. Additionally this change contains fixes in load-forwarding that are needed to guarantee that all artificial LoadField instructions inserted during AllocationSinking are correctly replaced with actual values. Limitations of the current implementation: - can't eliminate allocations that flow into phis but otherwise don't actually escape; - can't sink allocations out of loops; - allocation with type arguments are not handled. R=regis@google.com, srdjan@google.com, zra@google.com Review URL: https://codereview.chromium.org//14935005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@22485 260f80e4-7a28-3924-810f-c04153c831b5
351 lines
12 KiB
C++
351 lines
12 KiB
C++
// Copyright (c) 2012, 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.
|
|
|
|
#ifndef VM_FLOW_GRAPH_OPTIMIZER_H_
|
|
#define VM_FLOW_GRAPH_OPTIMIZER_H_
|
|
|
|
#include "vm/intermediate_language.h"
|
|
#include "vm/flow_graph.h"
|
|
|
|
namespace dart {
|
|
|
|
class CSEInstructionMap;
|
|
template <typename T> class GrowableArray;
|
|
class ParsedFunction;
|
|
|
|
class FlowGraphOptimizer : public FlowGraphVisitor {
|
|
public:
|
|
FlowGraphOptimizer(FlowGraph* flow_graph,
|
|
GrowableArray<const Field*>* guarded_fields)
|
|
: FlowGraphVisitor(flow_graph->reverse_postorder()),
|
|
flow_graph_(flow_graph),
|
|
guarded_fields_(guarded_fields) { }
|
|
virtual ~FlowGraphOptimizer() {}
|
|
|
|
FlowGraph* flow_graph() const { return flow_graph_; }
|
|
|
|
// Use ICData to optimize, replace or eliminate instructions.
|
|
void ApplyICData();
|
|
|
|
// Use propagated class ids to optimize, replace or eliminate instructions.
|
|
void ApplyClassIds();
|
|
|
|
// Optimize (a << b) & c pattern: if c is a positive Smi or zero, then the
|
|
// shift can be a truncating Smi shift-left and result is always Smi.
|
|
void TryOptimizeLeftShiftWithBitAndPattern();
|
|
|
|
// Returns true if any instructions were canonicalized away.
|
|
bool Canonicalize();
|
|
|
|
void EliminateDeadPhis();
|
|
|
|
void SelectRepresentations();
|
|
|
|
void InferSmiRanges();
|
|
|
|
// Remove environments from the instructions which do not deoptimize.
|
|
void EliminateEnvironments();
|
|
|
|
virtual void VisitStaticCall(StaticCallInstr* instr);
|
|
virtual void VisitInstanceCall(InstanceCallInstr* instr);
|
|
virtual void VisitRelationalOp(RelationalOpInstr* instr);
|
|
virtual void VisitEqualityCompare(EqualityCompareInstr* instr);
|
|
virtual void VisitBranch(BranchInstr* instr);
|
|
virtual void VisitStrictCompare(StrictCompareInstr* instr);
|
|
|
|
void InsertBefore(Instruction* next,
|
|
Instruction* instr,
|
|
Environment* env,
|
|
Definition::UseKind use_kind) {
|
|
flow_graph_->InsertBefore(next, instr, env, use_kind);
|
|
}
|
|
|
|
private:
|
|
// Attempt to build ICData for call using propagated class-ids.
|
|
bool TryCreateICData(InstanceCallInstr* call);
|
|
|
|
void SpecializePolymorphicInstanceCall(PolymorphicInstanceCallInstr* call);
|
|
|
|
intptr_t PrepareIndexedOp(InstanceCallInstr* call,
|
|
intptr_t class_id,
|
|
Definition** array,
|
|
Definition** index);
|
|
bool TryReplaceWithStoreIndexed(InstanceCallInstr* call);
|
|
void BuildStoreIndexed(InstanceCallInstr* call,
|
|
const ICData& value_check,
|
|
intptr_t class_id);
|
|
bool TryReplaceWithLoadIndexed(InstanceCallInstr* call);
|
|
|
|
bool TryReplaceWithBinaryOp(InstanceCallInstr* call, Token::Kind op_kind);
|
|
bool TryReplaceWithUnaryOp(InstanceCallInstr* call, Token::Kind op_kind);
|
|
|
|
bool TryInlineInstanceGetter(InstanceCallInstr* call);
|
|
bool TryInlineInstanceSetter(InstanceCallInstr* call,
|
|
const ICData& unary_ic_data);
|
|
|
|
bool TryInlineInstanceMethod(InstanceCallInstr* call);
|
|
bool TryInlineFloat32x4Method(InstanceCallInstr* call,
|
|
MethodRecognizer::Kind recognized_kind);
|
|
void ReplaceWithInstanceOf(InstanceCallInstr* instr);
|
|
void ReplaceWithTypeCast(InstanceCallInstr* instr);
|
|
|
|
LoadIndexedInstr* BuildStringCodeUnitAt(InstanceCallInstr* call,
|
|
intptr_t cid);
|
|
|
|
bool BuildByteArrayViewLoad(InstanceCallInstr* call,
|
|
intptr_t receiver_cid,
|
|
intptr_t view_cid);
|
|
bool BuildByteArrayViewStore(InstanceCallInstr* call,
|
|
intptr_t receiver_cid,
|
|
intptr_t view_cid);
|
|
void PrepareByteArrayViewOp(InstanceCallInstr* call,
|
|
intptr_t receiver_cid,
|
|
intptr_t view_cid,
|
|
Definition** array);
|
|
|
|
// Insert a check of 'to_check' determined by 'unary_checks'. If the
|
|
// check fails it will deoptimize to 'deopt_id' using the deoptimization
|
|
// environment 'deopt_environment'. The check is inserted immediately
|
|
// before 'insert_before'.
|
|
void AddCheckClass(Definition* to_check,
|
|
const ICData& unary_checks,
|
|
intptr_t deopt_id,
|
|
Environment* deopt_environment,
|
|
Instruction* insert_before);
|
|
|
|
// Insert a Smi check if needed.
|
|
void AddCheckSmi(Definition* to_check,
|
|
intptr_t deopt_id,
|
|
Environment* deopt_environment,
|
|
Instruction* insert_before);
|
|
|
|
// Add a class check for a call's first argument immediately before the
|
|
// call, using the call's IC data to determine the check, and the call's
|
|
// deopt ID and deoptimization environment if the check fails.
|
|
void AddReceiverCheck(InstanceCallInstr* call);
|
|
|
|
void ReplaceCall(Definition* call, Definition* replacement);
|
|
|
|
void InsertConversionsFor(Definition* def);
|
|
|
|
void InsertConversion(Representation from,
|
|
Representation to,
|
|
Value* use,
|
|
Instruction* insert_before,
|
|
Instruction* deopt_target);
|
|
|
|
bool InstanceCallNeedsClassCheck(InstanceCallInstr* call) const;
|
|
bool MethodExtractorNeedsClassCheck(InstanceCallInstr* call) const;
|
|
|
|
bool InlineFloat32x4Getter(InstanceCallInstr* call,
|
|
MethodRecognizer::Kind getter);
|
|
|
|
void InlineImplicitInstanceGetter(InstanceCallInstr* call);
|
|
void InlineArrayLengthGetter(InstanceCallInstr* call,
|
|
intptr_t length_offset,
|
|
bool is_immutable,
|
|
MethodRecognizer::Kind kind);
|
|
void InlineGrowableArrayCapacityGetter(InstanceCallInstr* call);
|
|
void InlineStringLengthGetter(InstanceCallInstr* call);
|
|
void InlineStringIsEmptyGetter(InstanceCallInstr* call);
|
|
|
|
RawBool* InstanceOfAsBool(const ICData& ic_data,
|
|
const AbstractType& type) const;
|
|
|
|
void ReplaceWithMathCFunction(InstanceCallInstr* call,
|
|
MethodRecognizer::Kind recognized_kind);
|
|
|
|
void HandleRelationalOp(RelationalOpInstr* comp);
|
|
|
|
// Visit an equality compare. The current instruction can be the
|
|
// comparison itself or a branch on the comparison.
|
|
template <typename T>
|
|
void HandleEqualityCompare(EqualityCompareInstr* comp,
|
|
T current_instruction);
|
|
|
|
static bool CanStrictifyEqualityCompare(EqualityCompareInstr* call);
|
|
|
|
template <typename T>
|
|
bool StrictifyEqualityCompare(EqualityCompareInstr* compare,
|
|
T current_instruction) const;
|
|
|
|
void OptimizeLeftShiftBitAndSmiOp(Definition* bit_and_instr,
|
|
Definition* left_instr,
|
|
Definition* right_instr);
|
|
|
|
void AddToGuardedFields(const Field& field);
|
|
|
|
FlowGraph* flow_graph_;
|
|
GrowableArray<const Field*>* guarded_fields_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(FlowGraphOptimizer);
|
|
};
|
|
|
|
|
|
// Loop invariant code motion.
|
|
class LICM : public ValueObject {
|
|
public:
|
|
explicit LICM(FlowGraph* flow_graph);
|
|
|
|
void Optimize();
|
|
|
|
private:
|
|
FlowGraph* flow_graph() const { return flow_graph_; }
|
|
|
|
void Hoist(ForwardInstructionIterator* it,
|
|
BlockEntryInstr* pre_header,
|
|
Instruction* current);
|
|
|
|
void TryHoistCheckSmiThroughPhi(ForwardInstructionIterator* it,
|
|
BlockEntryInstr* header,
|
|
BlockEntryInstr* pre_header,
|
|
CheckSmiInstr* current);
|
|
|
|
FlowGraph* const flow_graph_;
|
|
};
|
|
|
|
|
|
// A simple common subexpression elimination based
|
|
// on the dominator tree.
|
|
class DominatorBasedCSE : public AllStatic {
|
|
public:
|
|
// Return true, if the optimization changed the flow graph.
|
|
// False, if nothing changed.
|
|
static bool Optimize(FlowGraph* graph);
|
|
|
|
private:
|
|
static bool OptimizeRecursive(
|
|
FlowGraph* graph,
|
|
BlockEntryInstr* entry,
|
|
CSEInstructionMap* map);
|
|
};
|
|
|
|
|
|
// Sparse conditional constant propagation and unreachable code elimination.
|
|
// Assumes that use lists are computed and preserves them.
|
|
class ConstantPropagator : public FlowGraphVisitor {
|
|
public:
|
|
ConstantPropagator(FlowGraph* graph,
|
|
const GrowableArray<BlockEntryInstr*>& ignored);
|
|
|
|
static void Optimize(FlowGraph* graph);
|
|
|
|
// Only visit branches to optimize away unreachable blocks discovered
|
|
// by range analysis.
|
|
static void OptimizeBranches(FlowGraph* graph);
|
|
|
|
// Used to initialize the abstract value of definitions.
|
|
static RawObject* Unknown() { return Object::transition_sentinel().raw(); }
|
|
|
|
private:
|
|
void Analyze();
|
|
void VisitBranches();
|
|
void Transform();
|
|
|
|
void SetReachable(BlockEntryInstr* block);
|
|
void SetValue(Definition* definition, const Object& value);
|
|
|
|
// Assign the join (least upper bound) of a pair of abstract values to the
|
|
// first one.
|
|
void Join(Object* left, const Object& right);
|
|
|
|
bool IsUnknown(const Object& value) {
|
|
return value.raw() == unknown_.raw();
|
|
}
|
|
bool IsNonConstant(const Object& value) {
|
|
return value.raw() == non_constant_.raw();
|
|
}
|
|
bool IsConstant(const Object& value) {
|
|
return !IsNonConstant(value) && !IsUnknown(value);
|
|
}
|
|
|
|
virtual void VisitBlocks() { UNREACHABLE(); }
|
|
|
|
#define DECLARE_VISIT(type) virtual void Visit##type(type##Instr* instr);
|
|
FOR_EACH_INSTRUCTION(DECLARE_VISIT)
|
|
#undef DECLARE_VISIT
|
|
|
|
FlowGraph* graph_;
|
|
|
|
// Sentinels for unknown constant and non-constant values.
|
|
const Object& unknown_;
|
|
const Object& non_constant_;
|
|
|
|
// Analysis results. For each block, a reachability bit. Indexed by
|
|
// preorder number.
|
|
BitVector* reachable_;
|
|
|
|
// Definitions can move up the lattice twice, so we use a mark bit to
|
|
// indicate that they are already on the worklist in order to avoid adding
|
|
// them again. Indexed by SSA temp index.
|
|
BitVector* definition_marks_;
|
|
|
|
// Worklists of blocks and definitions.
|
|
GrowableArray<BlockEntryInstr*> block_worklist_;
|
|
GrowableArray<Definition*> definition_worklist_;
|
|
};
|
|
|
|
|
|
// Rewrite branches to eliminate materialization of boolean values after
|
|
// inlining, and to expose other optimizations (e.g., constant folding of
|
|
// branches, unreachable code elimination).
|
|
class BranchSimplifier : public AllStatic {
|
|
public:
|
|
static void Simplify(FlowGraph* flow_graph);
|
|
|
|
// Replace a target entry instruction with a join entry instruction. Does
|
|
// not update the original target's predecessors to point to the new block
|
|
// and does not replace the target in already computed block order lists.
|
|
static JoinEntryInstr* ToJoinEntry(TargetEntryInstr* target);
|
|
|
|
private:
|
|
// Match an instance of the pattern to rewrite. See the implementation
|
|
// for the patterns that are handled by this pass.
|
|
static bool Match(JoinEntryInstr* block);
|
|
|
|
// Duplicate a branch while replacing its comparison's left and right
|
|
// inputs.
|
|
static BranchInstr* CloneBranch(BranchInstr* branch,
|
|
Value* left,
|
|
Value* right);
|
|
};
|
|
|
|
|
|
// Rewrite diamond control flow patterns that materialize values to use more
|
|
// efficient branchless code patterns if such are supported on the current
|
|
// platform.
|
|
class IfConverter : public AllStatic {
|
|
public:
|
|
static void Simplify(FlowGraph* flow_graph);
|
|
};
|
|
|
|
|
|
class AllocationSinking : public ZoneAllocated {
|
|
public:
|
|
explicit AllocationSinking(FlowGraph* flow_graph)
|
|
: flow_graph_(flow_graph),
|
|
materializations_(5) { }
|
|
|
|
void Optimize();
|
|
|
|
void DetachMaterializations();
|
|
|
|
private:
|
|
void InsertMaterializations(AllocateObjectInstr* alloc);
|
|
|
|
void CreateMaterializationAt(
|
|
Instruction* exit,
|
|
AllocateObjectInstr* alloc,
|
|
const Class& cls,
|
|
const ZoneGrowableArray<const Field*>& fields);
|
|
|
|
FlowGraph* flow_graph_;
|
|
|
|
GrowableArray<MaterializeObjectInstr*> materializations_;
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // VM_FLOW_GRAPH_OPTIMIZER_H_
|