7cd8fda37e
Also load/store canonical hashes in the heap for non-empty TypedData instances in the same manner as canonical hashes for Arrays. TEST=ci (refactoring only) Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try Change-Id: I54274b558fa9f0c8e304198b18cb3f0e9c3e0dfb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/504600 Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
967 lines
35 KiB
C++
967 lines
35 KiB
C++
// Copyright (c) 2013, 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 RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_H_
|
|
#define RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_H_
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
#error "AOT runtime should not use compiler sources (including header files)"
|
|
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
#include <utility>
|
|
|
|
#include "vm/bit_vector.h"
|
|
#include "vm/compiler/backend/dart_calling_conventions.h"
|
|
#include "vm/compiler/backend/il.h"
|
|
#include "vm/growable_array.h"
|
|
#include "vm/hash_map.h"
|
|
#include "vm/parser.h"
|
|
#include "vm/thread.h"
|
|
|
|
namespace dart {
|
|
|
|
class LoopHierarchy;
|
|
class VariableLivenessAnalysis;
|
|
|
|
namespace compiler {
|
|
class GraphIntrinsifier;
|
|
}
|
|
|
|
class BlockIterator : public ValueObject {
|
|
public:
|
|
explicit BlockIterator(const GrowableArray<BlockEntryInstr*>& block_order)
|
|
: block_order_(block_order), current_(0) {}
|
|
|
|
BlockIterator(const BlockIterator& other)
|
|
: ValueObject(),
|
|
block_order_(other.block_order_),
|
|
current_(other.current_) {}
|
|
|
|
void Advance() {
|
|
ASSERT(!Done());
|
|
current_++;
|
|
}
|
|
|
|
bool Done() const { return current_ >= block_order_.length(); }
|
|
|
|
BlockEntryInstr* Current() const { return block_order_[current_]; }
|
|
|
|
private:
|
|
const GrowableArray<BlockEntryInstr*>& block_order_;
|
|
intptr_t current_;
|
|
};
|
|
|
|
class ConstantAndRepresentation {
|
|
public:
|
|
ConstantAndRepresentation(const Object& constant, Representation rep)
|
|
: constant_(constant),
|
|
representation_(rep),
|
|
hash_(ComputeHash(constant)) {}
|
|
|
|
const Object& constant() const { return constant_; }
|
|
Representation representation() const { return representation_; }
|
|
uword hash() const { return hash_; }
|
|
|
|
private:
|
|
static inline uword ComputeHash(const Object& constant) {
|
|
// Caveat: a null might be hiding inside a handle which overrides
|
|
// CanonicalizeHash() and does not check for |null| before computing the
|
|
// hash. Thus doing Instance::Cast(constant).CanonicalizeHash() and
|
|
// Instance::Handle(constant.ptr()).CanonicalizeHash() will produce
|
|
// different results. To work-around this problem check for null first.
|
|
if (constant.IsNull()) {
|
|
return kNullIdentityHash;
|
|
}
|
|
return constant.IsInstance() ? Instance::Cast(constant).CanonicalizeHash()
|
|
: Utils::WordHash(constant.GetClassId());
|
|
}
|
|
|
|
const Object& constant_;
|
|
Representation representation_;
|
|
uword hash_;
|
|
};
|
|
|
|
struct ConstantPoolTrait {
|
|
typedef ConstantInstr* Value;
|
|
typedef ConstantAndRepresentation Key;
|
|
typedef ConstantInstr* Pair;
|
|
|
|
static Key KeyOf(Pair kv) { return {kv->value(), kv->representation()}; }
|
|
|
|
static Value ValueOf(Pair kv) { return kv; }
|
|
|
|
static inline uword Hash(Key key) { return key.hash(); }
|
|
|
|
static inline bool IsKeyEqual(Pair kv, Key key) {
|
|
return (kv->value().ptr() == key.constant().ptr()) &&
|
|
(kv->representation() == key.representation());
|
|
}
|
|
};
|
|
|
|
struct PrologueInfo {
|
|
// The first blockid used for prologue building. This information can be used
|
|
// by the inliner for budget calculations: The prologue code falls away when
|
|
// inlining, so we should not include it in the budget.
|
|
intptr_t min_block_id;
|
|
|
|
// The last blockid used for prologue building. This information can be used
|
|
// by the inliner for budget calculations: The prologue code falls away when
|
|
// inlining, so we should not include it in the budget.
|
|
intptr_t max_block_id;
|
|
|
|
PrologueInfo(intptr_t min, intptr_t max)
|
|
: min_block_id(min), max_block_id(max) {}
|
|
|
|
bool Contains(intptr_t block_id) const {
|
|
return min_block_id <= block_id && block_id <= max_block_id;
|
|
}
|
|
};
|
|
|
|
struct InliningInfo {
|
|
// Maps inline_id_to_function[inline_id] -> function. Top scope
|
|
// function has inline_id 0. The map is populated by the inliner.
|
|
GrowableArray<const Function*> inline_id_to_function;
|
|
// Token position where inlining occurred.
|
|
GrowableArray<TokenPosition> inline_id_to_token_pos;
|
|
// For a given inlining-id(index) specifies the caller's inlining-id.
|
|
GrowableArray<intptr_t> caller_inline_id;
|
|
|
|
explicit InliningInfo(const Function* function) {
|
|
// Top scope function is at inlining id 0.
|
|
inline_id_to_function.Add(function);
|
|
// Top scope function has no caller (-1).
|
|
caller_inline_id.Add(-1);
|
|
// We do not add a token position for the top scope function to
|
|
// |inline_id_to_token_pos| because it is not (currently) inlined into
|
|
// another graph at a given token position. A side effect of this is that
|
|
// the length of |inline_id_to_function| and |caller_inline_id| is always
|
|
// larger than the length of |inline_id_to_token_pos| by one.
|
|
}
|
|
};
|
|
|
|
// Class to encapsulate the construction and manipulation of the flow graph.
|
|
class FlowGraph : public ZoneObject {
|
|
public:
|
|
enum class CompilationMode {
|
|
kUnoptimized,
|
|
kOptimized,
|
|
kIntrinsic,
|
|
};
|
|
|
|
FlowGraph(const ParsedFunction& parsed_function,
|
|
GraphEntryInstr* graph_entry,
|
|
intptr_t max_block_id,
|
|
PrologueInfo prologue_info,
|
|
CompilationMode compilation_mode);
|
|
|
|
// Function properties.
|
|
const ParsedFunction& parsed_function() const { return parsed_function_; }
|
|
const Function& function() const { return parsed_function_.function(); }
|
|
|
|
void Print(const char* phase = "unknown");
|
|
|
|
// The number of directly accessable parameters (above the frame pointer).
|
|
// All other parameters can only be indirectly loaded via metadata found in
|
|
// the arguments descriptor.
|
|
intptr_t num_direct_parameters() const { return num_direct_parameters_; }
|
|
|
|
// The number of variables (or boxes) which code can load from / store to.
|
|
// The SSA renaming will insert phi's for them (and only them - i.e. there
|
|
// will be no phi insertion for [LocalVariable]s pointing to the expression
|
|
// stack!).
|
|
intptr_t variable_count() const {
|
|
return num_direct_parameters_ + parsed_function_.num_stack_locals();
|
|
}
|
|
|
|
// The number of variables during OSR, which may include stack slots
|
|
// that pass in initial contents for the expression stack.
|
|
intptr_t osr_variable_count() const {
|
|
ASSERT(IsCompiledForOsr());
|
|
return variable_count() + graph_entry()->osr_entry()->stack_depth();
|
|
}
|
|
|
|
static Representation ParameterRepresentationAt(const Function& function,
|
|
intptr_t index);
|
|
|
|
static Representation ReturnRepresentationOf(const Function& function);
|
|
|
|
// The number of variables (or boxes) inside the functions frame - meaning
|
|
// below the frame pointer. This does not include the expression stack.
|
|
intptr_t num_stack_locals() const {
|
|
return parsed_function_.num_stack_locals();
|
|
}
|
|
|
|
bool IsIrregexpFunction() const { return function().IsIrregexpFunction(); }
|
|
|
|
LocalVariable* SuspendStateVar() const {
|
|
return parsed_function().suspend_state_var();
|
|
}
|
|
|
|
intptr_t SuspendStateEnvIndex() const { return EnvIndex(SuspendStateVar()); }
|
|
|
|
LocalVariable* CurrentContextVar() const {
|
|
return parsed_function().current_context_var();
|
|
}
|
|
|
|
intptr_t CurrentContextEnvIndex() const {
|
|
return EnvIndex(parsed_function().current_context_var());
|
|
}
|
|
|
|
intptr_t RawTypeArgumentEnvIndex() const {
|
|
return EnvIndex(parsed_function().RawTypeArgumentsVariable());
|
|
}
|
|
|
|
intptr_t ArgumentDescriptorEnvIndex() const {
|
|
return EnvIndex(parsed_function().arg_desc_var());
|
|
}
|
|
|
|
intptr_t EnvIndex(const LocalVariable* variable) const {
|
|
ASSERT(!variable->is_captured());
|
|
return num_direct_parameters_ - variable->index().value();
|
|
}
|
|
|
|
// Context and :suspend_state variables are never pruned and
|
|
// artificially kept alive.
|
|
bool IsImmortalVariable(intptr_t env_index) const {
|
|
return (env_index == CurrentContextEnvIndex()) ||
|
|
(SuspendStateVar() != nullptr &&
|
|
env_index == SuspendStateEnvIndex());
|
|
}
|
|
|
|
// Flow graph orders.
|
|
const GrowableArray<BlockEntryInstr*>& preorder() const { return preorder_; }
|
|
const GrowableArray<BlockEntryInstr*>& postorder() const {
|
|
return postorder_;
|
|
}
|
|
const GrowableArray<BlockEntryInstr*>& reverse_postorder() const {
|
|
return reverse_postorder_;
|
|
}
|
|
const GrowableArray<BlockEntryInstr*>& optimized_block_order() const {
|
|
return optimized_block_order_;
|
|
}
|
|
const GrowableArray<TryEntryInstr*>& try_entries() const {
|
|
return try_entries_;
|
|
}
|
|
TryEntryInstr* GetTryEntryByTryIndex(intptr_t try_index) const {
|
|
ASSERT(try_index < try_entries_.length());
|
|
return try_entries_[try_index];
|
|
}
|
|
CatchBlockEntryInstr* GetCatchBlockByTryIndex(intptr_t try_index) const {
|
|
return GetTryEntryByTryIndex(try_index)->catch_target();
|
|
}
|
|
intptr_t max_try_index() const { return max_try_index_; }
|
|
|
|
// In AOT these are guaranteed to be topologically sorted, but not in JIT.
|
|
GrowableArray<BlockEntryInstr*>* CodegenBlockOrder();
|
|
const GrowableArray<BlockEntryInstr*>* CodegenBlockOrder() const;
|
|
|
|
// Iterators.
|
|
BlockIterator reverse_postorder_iterator() const {
|
|
return BlockIterator(reverse_postorder());
|
|
}
|
|
BlockIterator postorder_iterator() const {
|
|
return BlockIterator(postorder());
|
|
}
|
|
|
|
void EnsureSSATempIndex(Definition* defn, Definition* replacement);
|
|
|
|
void ReplaceCurrentInstruction(ForwardInstructionIterator* iterator,
|
|
Instruction* current,
|
|
Instruction* replacement);
|
|
|
|
Instruction* CreateCheckClass(Definition* to_check,
|
|
const Cids& cids,
|
|
intptr_t deopt_id,
|
|
const InstructionSource& source);
|
|
|
|
bool ShouldOmitCheckBoundsIn(const Function& caller);
|
|
|
|
Instruction* AppendCheckBound(Instruction* cursor,
|
|
Definition* length,
|
|
Definition** index,
|
|
intptr_t deopt_id,
|
|
Environment* env);
|
|
|
|
void AddExactnessGuard(InstanceCallInstr* call, intptr_t receiver_cid);
|
|
|
|
intptr_t current_ssa_temp_index() const { return current_ssa_temp_index_; }
|
|
void set_current_ssa_temp_index(intptr_t index) {
|
|
current_ssa_temp_index_ = index;
|
|
}
|
|
|
|
intptr_t max_vreg() const {
|
|
return current_ssa_temp_index() * kMaxLocationCount;
|
|
}
|
|
|
|
enum class ToCheck { kNoCheck, kCheckNull, kCheckCid };
|
|
|
|
// Uses CHA to determine if the called method can be overridden.
|
|
// Return value indicates that the call needs no check at all,
|
|
// just a null check, or a full class check.
|
|
ToCheck CheckForInstanceCall(InstanceCallInstr* call,
|
|
UntaggedFunction::Kind kind) const;
|
|
|
|
Thread* thread() const { return thread_; }
|
|
Zone* zone() const { return thread()->zone(); }
|
|
IsolateGroup* isolate_group() const { return thread()->isolate_group(); }
|
|
|
|
intptr_t max_block_id() const { return max_block_id_; }
|
|
void set_max_block_id(intptr_t id) { max_block_id_ = id; }
|
|
intptr_t allocate_block_id() { return ++max_block_id_; }
|
|
|
|
GraphEntryInstr* graph_entry() const { return graph_entry_; }
|
|
|
|
ConstantInstr* constant_null() const { return constant_null_; }
|
|
|
|
ConstantInstr* constant_dead() const { return constant_dead_; }
|
|
|
|
void AllocateSSAIndex(Definition* def) {
|
|
def->set_ssa_temp_index(current_ssa_temp_index_);
|
|
current_ssa_temp_index_++;
|
|
}
|
|
|
|
intptr_t InstructionCount() const;
|
|
|
|
// Returns the definition for the object from the constant pool if
|
|
// one exists, otherwise returns nullptr.
|
|
ConstantInstr* GetExistingConstant(
|
|
const Object& object,
|
|
Representation representation = kTagged) const;
|
|
|
|
// Always returns a definition for the object from the constant pool,
|
|
// allocating one if it doesn't already exist.
|
|
ConstantInstr* GetConstant(const Object& object,
|
|
Representation representation = kTagged);
|
|
|
|
void AddToGraphInitialDefinitions(Definition* defn);
|
|
void AddToInitialDefinitions(BlockEntryWithInitialDefs* entry,
|
|
Definition* defn);
|
|
|
|
// Tries to create a constant definition with the given value which can be
|
|
// used to replace the given operation. Ensures that the representation of
|
|
// the replacement matches the representation of the original definition.
|
|
// If the given value can't be represented using matching representation
|
|
// then returns op itself.
|
|
Definition* TryCreateConstantReplacementFor(Definition* op,
|
|
const Object& value);
|
|
|
|
// Returns true if the given constant value can be represented in the given
|
|
// representation.
|
|
static bool IsConstantRepresentable(const Object& value,
|
|
Representation target_rep,
|
|
bool tagged_value_must_be_smi);
|
|
|
|
enum UseKind { kEffect, kValue };
|
|
|
|
void InsertBefore(Instruction* next,
|
|
Instruction* instr,
|
|
Environment* env,
|
|
UseKind use_kind) {
|
|
InsertAfter(next->previous(), instr, env, use_kind);
|
|
}
|
|
void InsertSpeculativeBefore(Instruction* next,
|
|
Instruction* instr,
|
|
Environment* env,
|
|
UseKind use_kind) {
|
|
InsertSpeculativeAfter(next->previous(), instr, env, use_kind);
|
|
}
|
|
void InsertAfter(Instruction* prev,
|
|
Instruction* instr,
|
|
Environment* env,
|
|
UseKind use_kind);
|
|
|
|
// Inserts a speculative [instr] after existing [prev] instruction.
|
|
//
|
|
// If the inserted [instr] deopts eagerly or lazily we will always continue in
|
|
// unoptimized code at before-call using the given [env].
|
|
//
|
|
// This is mainly used during inlining / call specializing when replacing
|
|
// calls with N specialized instructions where the inserted [1..N[
|
|
// instructions cannot continue in unoptimized code after-call since they
|
|
// would miss instructions following the one that lazy-deopted.
|
|
//
|
|
// For example specializing an instance call to an implicit field setter
|
|
//
|
|
// InstanceCall:<id>(v0, set:<name>, args = [v1])
|
|
//
|
|
// to
|
|
//
|
|
// v2 <- AssertAssignable:<id>(v1, ...)
|
|
// StoreField(v0, v2)
|
|
//
|
|
// If the [AssertAssignable] causes a lazy-deopt on return, we'll have to
|
|
// *re-try* the implicit setter call in unoptimized mode, i.e. lazy deopt to
|
|
// before-call (otherwise - if we continued after-call - the
|
|
// StoreField would not be performed).
|
|
void InsertSpeculativeAfter(Instruction* prev,
|
|
Instruction* instr,
|
|
Environment* env,
|
|
UseKind use_kind);
|
|
Instruction* AppendTo(Instruction* prev,
|
|
Instruction* instr,
|
|
Environment* env,
|
|
UseKind use_kind);
|
|
Instruction* AppendSpeculativeTo(Instruction* prev,
|
|
Instruction* instr,
|
|
Environment* env,
|
|
UseKind use_kind);
|
|
|
|
// Operations on the flow graph.
|
|
void ComputeSSA(ZoneGrowableArray<Definition*>* inlining_parameters);
|
|
|
|
// Verification method for debugging.
|
|
bool VerifyRedefinitions();
|
|
|
|
void DiscoverBlocks();
|
|
|
|
void MergeBlocks();
|
|
|
|
// Insert a redefinition of an original definition after prev and rename all
|
|
// dominated uses of the original. If an equivalent redefinition is already
|
|
// present, nothing is inserted.
|
|
// Returns the redefinition, if a redefinition was inserted, nullptr
|
|
// otherwise.
|
|
RedefinitionInstr* EnsureRedefinition(Instruction* prev,
|
|
Definition* original,
|
|
CompileType compile_type);
|
|
|
|
// Remove the redefinition instructions inserted to inhibit code motion.
|
|
void RemoveRedefinitions(bool keep_checks = false);
|
|
|
|
// Insert MoveArgument instructions and remove explicit def-use
|
|
// relations between calls and their arguments.
|
|
//
|
|
// Compute the maximum number of arguments.
|
|
void InsertMoveArguments();
|
|
|
|
// Copy deoptimization target from one instruction to another if we still
|
|
// have to keep deoptimization environment at gotos for LICM purposes.
|
|
void CopyDeoptTarget(Instruction* to, Instruction* from) {
|
|
if (is_licm_allowed()) {
|
|
to->InheritDeoptTarget(zone(), from);
|
|
}
|
|
}
|
|
|
|
// Returns true if every Goto in the graph is expected to have a
|
|
// deoptimization environment and can be used as deoptimization target
|
|
// for hoisted instructions.
|
|
bool is_licm_allowed() const { return licm_allowed_; }
|
|
|
|
// Stop preserving environments on Goto instructions. LICM is not allowed
|
|
// after this point.
|
|
void disallow_licm() { licm_allowed_ = false; }
|
|
|
|
// Returns true if mismatch in input/output representations is allowed.
|
|
bool unmatched_representations_allowed() const {
|
|
return unmatched_representations_allowed_;
|
|
}
|
|
|
|
// After the last SelectRepresentations pass all further transformations
|
|
// should maintain matching input/output representations.
|
|
void disallow_unmatched_representations() {
|
|
unmatched_representations_allowed_ = false;
|
|
}
|
|
|
|
// Returns true if this flow graph was built for a huge method
|
|
// and certain optimizations should be disabled.
|
|
bool is_huge_method() const { return huge_method_; }
|
|
// Mark this flow graph as huge and disable certain optimizations.
|
|
void mark_huge_method() { huge_method_ = true; }
|
|
|
|
PrologueInfo prologue_info() const { return prologue_info_; }
|
|
|
|
// Computes the loop hierarchy of the flow graph on demand.
|
|
const LoopHierarchy& GetLoopHierarchy() {
|
|
if (loop_hierarchy_ == nullptr) {
|
|
loop_hierarchy_ = ComputeLoops();
|
|
}
|
|
return loop_hierarchy();
|
|
}
|
|
|
|
const LoopHierarchy& loop_hierarchy() const { return *loop_hierarchy_; }
|
|
|
|
// Resets the loop hierarchy of the flow graph. Use this to
|
|
// force a recomputation of loop detection by the next call
|
|
// to GetLoopHierarchy() (note that this does not immediately
|
|
// reset the loop_info fields of block entries, although
|
|
// these will be overwritten by that next call).
|
|
void ResetLoopHierarchy() {
|
|
loop_hierarchy_ = nullptr;
|
|
loop_invariant_loads_ = nullptr;
|
|
}
|
|
|
|
// Per loop header invariant loads sets. Each set contains load id for
|
|
// those loads that are not affected by anything in the loop and can be
|
|
// hoisted out. Sets are computed by LoadOptimizer.
|
|
ZoneGrowableArray<BitVector*>* loop_invariant_loads() const {
|
|
return loop_invariant_loads_;
|
|
}
|
|
void set_loop_invariant_loads(
|
|
ZoneGrowableArray<BitVector*>* loop_invariant_loads) {
|
|
loop_invariant_loads_ = loop_invariant_loads;
|
|
}
|
|
|
|
bool IsCompiledForOsr() const { return graph_entry()->IsCompiledForOsr(); }
|
|
|
|
BitVector* captured_parameters() const { return captured_parameters_; }
|
|
|
|
intptr_t inlining_id() const { return inlining_id_; }
|
|
void set_inlining_id(intptr_t value) { inlining_id_ = value; }
|
|
|
|
InliningInfo& inlining_info() { return inlining_info_; }
|
|
const InliningInfo& inlining_info() const { return inlining_info_; }
|
|
|
|
// Returns true if any instructions were canonicalized away.
|
|
bool Canonicalize();
|
|
|
|
// Attaches new ICData's to static/instance calls which don't already have
|
|
// them.
|
|
void PopulateWithICData(const Function& function);
|
|
|
|
void SelectRepresentations();
|
|
|
|
// Remove environments from the instructions which do not deoptimize.
|
|
void EliminateEnvironments();
|
|
|
|
// Extract typed data payloads prior to any LoadIndexed, StoreIndexed, or
|
|
// MemoryCopy instruction where the incoming typed data array(s) are not
|
|
// proven to be internal typed data objects at compile time.
|
|
//
|
|
// Once this is done, no intra-block code motion should be performed.
|
|
void ExtractNonInternalTypedDataPayloads();
|
|
|
|
// Insert calls informing the sanitizers about loads and stores as separate IL
|
|
// instructions. Separate IL instructions allow for much less spilling via the
|
|
// regular register alloctor compared to internal spilling within the load or
|
|
// store instructions. This transformation must happen very late in the
|
|
// pipeline, after no more loads or store can be added or moved around.
|
|
void AddAsanMsanInstrumentation();
|
|
void AddTsanInstrumentation();
|
|
|
|
bool IsReceiver(Definition* def) const;
|
|
|
|
// 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.
|
|
// Merge instructions (only per basic-block).
|
|
void TryOptimizePatterns();
|
|
|
|
// Replaces uses that are dominated by dom of 'def' with 'other'.
|
|
// Note: uses that occur at instruction dom itself are not dominated by it.
|
|
static void RenameDominatedUses(Definition* def,
|
|
Instruction* dom,
|
|
Definition* other);
|
|
|
|
// Renames uses of redefined values to make sure that uses of redefined
|
|
// values that are dominated by a redefinition are renamed.
|
|
void RenameUsesDominatedByRedefinitions();
|
|
|
|
bool should_print() const { return should_print_; }
|
|
const uint8_t* compiler_pass_filters() const {
|
|
return compiler_pass_filters_;
|
|
}
|
|
|
|
bool should_reorder_blocks() const { return should_reorder_blocks_; }
|
|
|
|
bool should_omit_check_bounds() const { return should_omit_check_bounds_; }
|
|
|
|
//
|
|
// High-level utilities.
|
|
//
|
|
|
|
// Logical-AND (for use in short-circuit diamond).
|
|
struct LogicalAnd {
|
|
LogicalAnd(ConditionInstr* x, ConditionInstr* y) : oper1(x), oper2(y) {}
|
|
ConditionInstr* oper1;
|
|
ConditionInstr* oper2;
|
|
};
|
|
|
|
// Constructs a diamond control flow at the instruction, inheriting
|
|
// properties from inherit and using the given compare. Returns the
|
|
// join (and true/false blocks in out parameters). Updates dominance
|
|
// relation, but not the succ/pred ordering on block.
|
|
JoinEntryInstr* NewDiamond(Instruction* instruction,
|
|
Instruction* inherit,
|
|
ConditionInstr* condition,
|
|
TargetEntryInstr** block_true,
|
|
TargetEntryInstr** block_false);
|
|
|
|
// As above, but with a short-circuit on two comparisons.
|
|
JoinEntryInstr* NewDiamond(Instruction* instruction,
|
|
Instruction* inherit,
|
|
const LogicalAnd& condition,
|
|
TargetEntryInstr** block_true,
|
|
TargetEntryInstr** block_false);
|
|
|
|
// Adds a 2-way phi.
|
|
PhiInstr* AddPhi(JoinEntryInstr* join, Definition* d1, Definition* d2);
|
|
|
|
// SSA transformation methods and fields.
|
|
void ComputeDominators(GrowableArray<BitVector*>* dominance_frontier);
|
|
|
|
void CreateCommonConstants();
|
|
|
|
const TypedData& coverage_array() const { return *coverage_array_; }
|
|
void set_coverage_array(const TypedData& array) { coverage_array_ = &array; }
|
|
|
|
// Renumbers SSA values and basic blocks to make numbering dense.
|
|
// Preserves order among block ids.
|
|
//
|
|
// Also collects definitions which are detached from the flow graph
|
|
// but still referenced (currently only MaterializeObject instructions
|
|
// can be detached).
|
|
void CompactSSA(ZoneGrowableArray<Definition*>* detached_defs = nullptr);
|
|
|
|
// Maximum number of word-sized slots needed for outgoing arguments.
|
|
intptr_t max_argument_slot_count() const {
|
|
RELEASE_ASSERT(max_argument_slot_count_ >= 0);
|
|
return max_argument_slot_count_;
|
|
}
|
|
void set_max_argument_slot_count(intptr_t count) {
|
|
RELEASE_ASSERT(max_argument_slot_count_ == -1);
|
|
max_argument_slot_count_ = count;
|
|
}
|
|
|
|
const std::pair<Location, Representation>& GetDirectParameterInfoAt(
|
|
intptr_t i) {
|
|
return direct_parameter_locations_[i];
|
|
}
|
|
|
|
static intptr_t ComputeLocationsOfFixedParameters(
|
|
Zone* zone,
|
|
const Function& function,
|
|
bool should_assign_stack_locations = false,
|
|
compiler::ParameterInfoArray* parameter_info = nullptr);
|
|
|
|
static intptr_t ComputeArgumentsSizeInWords(const Function& function,
|
|
intptr_t arguments_count);
|
|
|
|
static constexpr CompilationMode CompilationModeFrom(bool is_optimizing) {
|
|
return is_optimizing ? CompilationMode::kOptimized
|
|
: CompilationMode::kUnoptimized;
|
|
}
|
|
|
|
// If either IsExternalPayloadClassId([cid]) or
|
|
// IsExternalPayloadClassId(array()->Type()->ToCid()) is true and
|
|
// [array] (an input of [instr]) is tagged, inserts a load of the array
|
|
// payload as an untagged pointer and rebinds [array] to the new load.
|
|
//
|
|
// Otherwise does not change the flow graph.
|
|
//
|
|
// Returns whether any changes were made to the flow graph.
|
|
bool ExtractExternalUntaggedPayload(Instruction* instr,
|
|
Value* array,
|
|
classid_t cid);
|
|
|
|
private:
|
|
friend class FlowGraphCompiler; // TODO(ajcbik): restructure
|
|
friend class FlowGraphChecker;
|
|
friend class IfConverter;
|
|
friend class BranchSimplifier;
|
|
friend class ConstantPropagator;
|
|
friend class DeadCodeElimination;
|
|
friend class compiler::GraphIntrinsifier;
|
|
|
|
void CompressPath(intptr_t start_index,
|
|
intptr_t current_index,
|
|
GrowableArray<intptr_t>* parent,
|
|
GrowableArray<intptr_t>* label);
|
|
|
|
void AddSyntheticPhis(BlockEntryInstr* block);
|
|
|
|
void Rename(GrowableArray<PhiInstr*>* live_phis,
|
|
VariableLivenessAnalysis* variable_liveness,
|
|
ZoneGrowableArray<Definition*>* inlining_parameters);
|
|
void RenameRecursive(BlockEntryInstr* block_entry,
|
|
GrowableArray<Definition*>* env,
|
|
GrowableArray<PhiInstr*>* live_phis,
|
|
VariableLivenessAnalysis* variable_liveness,
|
|
ZoneGrowableArray<Definition*>* inlining_parameters);
|
|
#if defined(DEBUG)
|
|
// Validates no phis are missing on join entry instructions.
|
|
void ValidatePhis();
|
|
#endif // defined(DEBUG)
|
|
|
|
void PopulateEnvironmentFromFunctionEntry(
|
|
FunctionEntryInstr* function_entry,
|
|
GrowableArray<Definition*>* env,
|
|
GrowableArray<PhiInstr*>* live_phis,
|
|
VariableLivenessAnalysis* variable_liveness,
|
|
ZoneGrowableArray<Definition*>* inlining_parameters);
|
|
|
|
void PopulateEnvironmentFromOsrEntry(OsrEntryInstr* osr_entry,
|
|
GrowableArray<Definition*>* env);
|
|
|
|
void PopulateEnvironmentFromCatchEntry(CatchBlockEntryInstr* catch_entry,
|
|
GrowableArray<Definition*>* env);
|
|
|
|
void AttachEnvironment(Instruction* instr, GrowableArray<Definition*>* env);
|
|
|
|
void InsertPhis(const GrowableArray<BlockEntryInstr*>& preorder,
|
|
VariableLivenessAnalysis& variable_liveness,
|
|
const GrowableArray<BitVector*>& dom_frontier,
|
|
GrowableArray<PhiInstr*>* live_phis);
|
|
void AddCatchEntryParameter(intptr_t var_index,
|
|
CatchBlockEntryInstr* catch_entry);
|
|
void InsertCatchBlockParams(const GrowableArray<BlockEntryInstr*>& preorder,
|
|
VariableLivenessAnalysis& variable_liveness);
|
|
|
|
void RemoveDeadPhis(GrowableArray<PhiInstr*>* live_phis);
|
|
|
|
void ReplacePredecessor(BlockEntryInstr* old_block,
|
|
BlockEntryInstr* new_block);
|
|
|
|
// Finds the blocks in the natural loop for the back edge m->n. The
|
|
// algorithm is described in "Advanced Compiler Design & Implementation"
|
|
// (Muchnick) p192. Returns a BitVector indexed by block pre-order
|
|
// number where each bit indicates membership in the loop.
|
|
BitVector* FindLoopBlocks(BlockEntryInstr* m, BlockEntryInstr* n) const;
|
|
|
|
// Finds the natural loops in the flow graph and attaches the loop
|
|
// information to each entry block. Returns the loop hierarchy.
|
|
LoopHierarchy* ComputeLoops() const;
|
|
|
|
void InsertConversionsFor(Definition* def);
|
|
void ConvertUse(Value* use, Representation from);
|
|
void InsertConversion(Representation from,
|
|
Representation to,
|
|
Value* use,
|
|
bool is_environment_use);
|
|
|
|
// Insert allocation of a record instance for [def]
|
|
// which returns an unboxed record.
|
|
void InsertRecordBoxing(Definition* def);
|
|
|
|
void ComputeIsReceiver(PhiInstr* phi) const;
|
|
void ComputeIsReceiverRecursive(PhiInstr* phi,
|
|
GrowableArray<PhiInstr*>* unmark) const;
|
|
|
|
void OptimizeLeftShiftBitAndSmiOp(
|
|
ForwardInstructionIterator* current_iterator,
|
|
Definition* bit_and_instr,
|
|
Definition* left_instr,
|
|
Definition* right_instr);
|
|
|
|
void TryMergeTruncDivMod(GrowableArray<BinarySmiOpInstr*>* merge_candidates);
|
|
|
|
void AppendExtractNthOutputForMerged(Definition* instr,
|
|
intptr_t ix,
|
|
Representation rep,
|
|
intptr_t cid);
|
|
|
|
void ExtractUntaggedPayload(Instruction* instr,
|
|
Value* array,
|
|
const Slot& slot,
|
|
InnerPointerAccess access);
|
|
|
|
void ExtractNonInternalTypedDataPayload(Instruction* instr,
|
|
Value* array,
|
|
classid_t cid);
|
|
|
|
Thread* thread_;
|
|
|
|
// DiscoverBlocks computes parent_ and assigned_vars_ which are then used
|
|
// if/when computing SSA.
|
|
GrowableArray<intptr_t> parent_;
|
|
|
|
intptr_t current_ssa_temp_index_;
|
|
intptr_t max_block_id_;
|
|
|
|
// Flow graph fields.
|
|
const ParsedFunction& parsed_function_;
|
|
intptr_t num_direct_parameters_;
|
|
compiler::ParameterInfoArray direct_parameter_locations_;
|
|
GraphEntryInstr* graph_entry_;
|
|
GrowableArray<BlockEntryInstr*> preorder_;
|
|
GrowableArray<BlockEntryInstr*> postorder_;
|
|
GrowableArray<BlockEntryInstr*> reverse_postorder_;
|
|
GrowableArray<BlockEntryInstr*> optimized_block_order_;
|
|
// Try entries indexed by try-index
|
|
GrowableArray<TryEntryInstr*> try_entries_;
|
|
intptr_t max_try_index_ = -1;
|
|
ConstantInstr* constant_null_;
|
|
ConstantInstr* constant_dead_;
|
|
|
|
bool licm_allowed_;
|
|
bool unmatched_representations_allowed_ = true;
|
|
bool huge_method_ = false;
|
|
const bool should_reorder_blocks_;
|
|
|
|
const PrologueInfo prologue_info_;
|
|
|
|
// Loop related fields.
|
|
LoopHierarchy* loop_hierarchy_;
|
|
ZoneGrowableArray<BitVector*>* loop_invariant_loads_;
|
|
|
|
DirectChainedHashMap<ConstantPoolTrait> constant_instr_pool_;
|
|
BitVector* captured_parameters_;
|
|
|
|
// Inlining related fields.
|
|
intptr_t inlining_id_;
|
|
InliningInfo inlining_info_;
|
|
|
|
bool should_print_;
|
|
const bool should_omit_check_bounds_;
|
|
uint8_t* compiler_pass_filters_ = nullptr;
|
|
|
|
intptr_t max_argument_slot_count_ = -1;
|
|
|
|
const TypedData* coverage_array_ = &TypedData::empty_coverage_array();
|
|
};
|
|
|
|
class LivenessAnalysis : public ValueObject {
|
|
public:
|
|
LivenessAnalysis(const FlowGraph* flow_graph, const intptr_t variable_count);
|
|
|
|
void Analyze();
|
|
|
|
virtual ~LivenessAnalysis() {}
|
|
|
|
BitVector* GetLiveInSetAt(intptr_t postorder_number) const {
|
|
return live_in_[postorder_number];
|
|
}
|
|
|
|
BitVector* GetLiveOutSetAt(intptr_t postorder_number) const {
|
|
return live_out_[postorder_number];
|
|
}
|
|
|
|
BitVector* GetLiveInSet(BlockEntryInstr* block) const {
|
|
return GetLiveInSetAt(block->postorder_number());
|
|
}
|
|
|
|
BitVector* GetKillSet(BlockEntryInstr* block) const {
|
|
return kill_[block->postorder_number()];
|
|
}
|
|
|
|
BitVector* GetLiveOutSet(BlockEntryInstr* block) const {
|
|
return GetLiveOutSetAt(block->postorder_number());
|
|
}
|
|
|
|
// Print results of liveness analysis.
|
|
void Dump();
|
|
|
|
protected:
|
|
// Compute initial values for live-out, kill and live-in sets.
|
|
//
|
|
// It's also responsible for populating [does_block_have_throw] set.
|
|
virtual void ComputeInitialSets() = 0;
|
|
|
|
// Update live-out set for the given block: live-out should contain
|
|
// all values that are live-in for block's successors.
|
|
// Returns true if live-out set was changed.
|
|
bool UpdateLiveOut(const BlockEntryInstr& instr);
|
|
|
|
// Update live-in set for the given block: live-in should contain
|
|
// all values that are live-out from the block and are not defined
|
|
// by this block.
|
|
// Returns true if live-in set was changed.
|
|
bool UpdateLiveIn(const BlockEntryInstr& instr);
|
|
|
|
// Perform fix-point iteration updating live-out and live-in sets
|
|
// for blocks until they stop changing.
|
|
void ComputeLiveInAndLiveOutSets();
|
|
|
|
Zone* zone() const { return zone_; }
|
|
|
|
Zone* zone_;
|
|
|
|
const FlowGraph* flow_graph_;
|
|
|
|
const intptr_t variable_count_;
|
|
|
|
const GrowableArray<BlockEntryInstr*>& postorder_;
|
|
|
|
// Live-out sets for each block. They contain indices of variables
|
|
// that are live out from this block. That is values that were (1) either
|
|
// defined in this block or live into it, and (2) that are used in some
|
|
// successor block.
|
|
GrowableArray<BitVector*> live_out_;
|
|
|
|
// Kill sets for each block. They contain indices of variables that
|
|
// are defined by this block.
|
|
GrowableArray<BitVector*> kill_;
|
|
|
|
// Live-in sets for each block. They contain indices of variables
|
|
// that are used by this block or its successors.
|
|
GrowableArray<BitVector*> live_in_;
|
|
|
|
// Given block's postoroder index indicates whether block has any
|
|
// instructions that throw.
|
|
BitVector does_block_have_throw;
|
|
};
|
|
|
|
template <typename T, typename E>
|
|
class Worklist : public ValueObject {
|
|
public:
|
|
Worklist(FlowGraph* flow_graph, intptr_t initial_capacity)
|
|
: elements_(initial_capacity),
|
|
contains_vector_(
|
|
new BitVector(flow_graph->zone(), T::FirstUnusedId(flow_graph))) {}
|
|
|
|
void Add(E* e) {
|
|
if (!Contains(e)) {
|
|
elements_.Add(e);
|
|
contains_vector_->Add(T::IdOf(e));
|
|
}
|
|
}
|
|
|
|
bool Contains(E* e) const {
|
|
const intptr_t id = T::IdOf(e);
|
|
return id >= 0 && contains_vector_->Contains(id);
|
|
}
|
|
|
|
bool IsEmpty() const { return elements_.is_empty(); }
|
|
|
|
E* RemoveLast() {
|
|
E* e = elements_.RemoveLast();
|
|
contains_vector_->Remove(T::IdOf(e));
|
|
return e;
|
|
}
|
|
|
|
BitVector* contains_vector() const { return contains_vector_; }
|
|
|
|
void Clear() {
|
|
elements_.TruncateTo(0);
|
|
contains_vector_->Clear();
|
|
}
|
|
|
|
protected:
|
|
GrowableArray<E*> elements_;
|
|
BitVector* contains_vector_;
|
|
};
|
|
|
|
class DefinitionWorklist : public Worklist<DefinitionWorklist, Definition> {
|
|
public:
|
|
DefinitionWorklist(FlowGraph* flow_graph, intptr_t initial_capacity)
|
|
: Worklist(flow_graph, initial_capacity) {}
|
|
|
|
const GrowableArray<Definition*>& definitions() const { return elements_; }
|
|
|
|
static intptr_t FirstUnusedId(FlowGraph* flow_graph) {
|
|
return flow_graph->current_ssa_temp_index();
|
|
}
|
|
|
|
static intptr_t IdOf(Definition* defn) { return defn->ssa_temp_index(); }
|
|
};
|
|
|
|
class BlockEntryWorklist
|
|
: public Worklist<BlockEntryWorklist, BlockEntryInstr> {
|
|
public:
|
|
BlockEntryWorklist(FlowGraph* flow_graph, intptr_t initial_capacity)
|
|
: Worklist(flow_graph, initial_capacity) {}
|
|
|
|
const GrowableArray<BlockEntryInstr*>& blocks() const { return elements_; }
|
|
|
|
static intptr_t FirstUnusedId(FlowGraph* flow_graph) {
|
|
return flow_graph->preorder().length();
|
|
}
|
|
|
|
static intptr_t IdOf(BlockEntryInstr* block) {
|
|
return block->preorder_number();
|
|
}
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_H_
|