class ArenaStack; class CodeGenerator; class GraphChecker; class HBasicBlock; class HCondition; class HConstructorFence; class HCurrentMethod; class HDoubleConstant; class HEnvironment; class HFloatConstant; class HGraphBuilder; class HGraphVisitor; class HInstruction; class HIntConstant; class HInvoke; class HLongConstant; class HNullConstant; class HParameterValue; class HPhi; class HSuspendCheck; class HTryBoundary; class HVecCondition; class FieldInfo; class LiveInterval; class LocationSummary; class ProfilingInfo; class SlowPathCode; class SsaBuilder;
namespace mirror { class DexCache;
} // namespace mirror
// The maximum (meaningful) distance (31) that can be used in an integer shift/rotate operation. static constexpr int32_t kMaxIntShiftDistance = 0x1f; // The maximum (meaningful) distance (63) that can be used in a long shift/rotate operation. static constexpr int32_t kMaxLongShiftDistance = 0x3f;
inlinebool IsSameDexFile(const DexFile& lhs, const DexFile& rhs) { // For the purposes of the compiler, the dex files must actually be the same object // if we want to safely treat them as the same. This is especially important for JIT // as custom class loaders can open the same underlying file (or memory) multiple // times and provide different class resolution but no two class loaders should ever // use the same DexFile object - doing so is an unsupported hack that can lead to // all sorts of weird failures. return &lhs == &rhs;
}
enum IfCondition { // All types.
kCondEQ, // ==
kCondNE, // != // Signed integers and floating-point numbers.
kCondLT, // <
kCondLE, // <=
kCondGT, // >
kCondGE, // >= // Unsigned integers.
kCondB, // <
kCondBE, // <=
kCondA, // >
kCondAE, // >= // First and last aliases.
kCondFirst = kCondEQ,
kCondLast = kCondAE,
};
// Stores try/catch information for basic blocks. // Note that HGraph is constructed so that catch blocks cannot simultaneously // be try blocks. class TryCatchInformation final : public ArenaObject<kArenaAllocTryCatchInfo> { public: // Try block information constructor. explicit TryCatchInformation(const HTryBoundary& try_entry)
: try_entry_(&try_entry),
catch_dex_file_(nullptr),
catch_type_index_(dex::TypeIndex::Invalid()) {
DCHECK(try_entry_ != nullptr);
}
// Insert `this` between `predecessor` and `successor. This method // preserves the indices, and will update the first edge found between // `predecessor` and `successor`. void InsertBetween(HBasicBlock* predecessor, HBasicBlock* successor) {
size_t predecessor_index = successor->GetPredecessorIndexOf(predecessor);
size_t successor_index = predecessor->GetSuccessorIndexOf(successor);
successor->predecessors_[predecessor_index] = this;
predecessor->successors_[successor_index] = this;
successors_.push_back(successor);
predecessors_.push_back(predecessor);
}
// Returns whether the first occurrence of `predecessor` in the list of // predecessors is at index `idx`. bool IsFirstIndexOfPredecessor(HBasicBlock* predecessor, size_t idx) const {
DCHECK_EQ(GetPredecessors()[idx], predecessor); return GetPredecessorIndexOf(predecessor) == idx;
}
// Create a new block between this block and its predecessors. The new block // is added to the graph, all predecessor edges are relinked to it and an edge // is created to `this`. Returns the new empty block. Reverse post order or // loop and try/catch information are not updated.
HBasicBlock* CreateImmediateDominator();
// Split the block into two blocks just before `cursor`. Returns the newly // created, latter block. Note that this method will add the block to the // graph, create a Goto at the end of the former block and will create an edge // between the blocks. It will not, however, update the reverse post order or // loop and try/catch information.
HBasicBlock* SplitBefore(HInstruction* cursor);
// Split the block into two blocks just before `cursor`. Returns the newly // created block. Note that this method just updates raw block information, // like predecessors, successors, dominators, and instruction list. It does not // update the graph, reverse post order, loop information, nor make sure the // blocks are consistent (for example ending with a control flow instruction).
HBasicBlock* SplitBeforeForInlining(HInstruction* cursor);
// Similar to `SplitBeforeForInlining` but does it after `cursor`.
HBasicBlock* SplitAfterForInlining(HInstruction* cursor);
// Merge `other` at the end of `this`. Successors and dominated blocks of // `other` are changed to be successors and dominated blocks of `this`. Note // that this method does not update the graph, reverse post order, loop // information, nor make sure the blocks are consistent (for example ending // with a control flow instruction). void MergeWithInlined(HBasicBlock* other);
// Replace `this` with `other`. Predecessors, successors, and dominated blocks // of `this` are moved to `other`. // Note that this method does not update the graph, reverse post order, loop // information, nor make sure the blocks are consistent (for example ending // with a control flow instruction). void ReplaceWith(HBasicBlock* other);
// Merges the instructions of `other` at the end of `this`. void MergeInstructionsWith(HBasicBlock* other);
// Merge `other` at the end of `this`. This method updates loops, reverse post // order, links to predecessors, successors, dominators and deletes the block // from the graph. The two blocks must be successive, i.e. `this` the only // predecessor of `other` and vice versa. void MergeWith(HBasicBlock* other);
// Take the single successor's other predecessors and merge `HPhis`. This block must // contain only a single `HGoto` instruction and an arbitrary number of `HPhi`s. // This function does not update dominance information. void TakeGotoBlockSuccessorsOtherPredecessorsAndMergePhis();
// Disconnects `this` from all its predecessors, successors and dominator, // removes it from all loops it is included in and eventually from the graph. // The block must not dominate any other block. Predecessors and successors // are safely updated. void DisconnectAndDelete();
// Disconnects `this` from all its successors and updates their phis, if the successors have them. // If `visited` is provided, it will use the information to know if a successor is reachable and // skip updating those phis. void DisconnectFromSuccessors(BitVectorView<const size_t> visited = {});
// Removes the catch phi uses of the instructions in `this`, and then remove the instruction // itself. If `building_dominator_tree` is true, it will not remove the instruction as user, since // we do it in a previous step. This is a special case for building up the dominator tree: we want // to eliminate uses before inputs but we don't have domination information, so we remove all // connections from input/uses first before removing any instruction. // This method assumes the instructions have been removed from all users with the exception of // catch phis because of missing exceptional edges in the graph. void RemoveCatchPhiUsesAndInstruction(bool building_dominator_tree);
void AddInstruction(HInstruction* instruction); // Insert `instruction` before/after an existing instruction `cursor`. void InsertInstructionBefore(HInstruction* instruction, HInstruction* cursor); void InsertInstructionAfter(HInstruction* instruction, HInstruction* cursor); // Replace phi `initial` with `replacement` within this block. void ReplaceAndRemovePhiWith(HPhi* initial, HPhi* replacement); // Replace instruction `initial` with `replacement` within this block. void ReplaceAndRemoveInstructionWith(HInstruction* initial,
HInstruction* replacement); void AddPhi(HPhi* phi); void InsertPhiAfter(HPhi* instruction, HPhi* cursor); // RemoveInstruction and RemovePhi delete a given instruction from the respective // instruction list. With 'ensure_safety' set to true, it verifies that the // instruction is not in use and removes it from the use lists of its inputs. void RemoveInstruction(HInstruction* instruction, bool ensure_safety = true); void RemovePhi(HPhi* phi, bool ensure_safety = true); void RemoveInstructionOrPhi(HInstruction* instruction, bool ensure_safety = true);
// Returns the try entry that this block's successors should have. They will // be in the same try, unless the block ends in a try boundary. In that case, // the appropriate try entry will be returned. const HTryBoundary* ComputeTryEntryOfSuccessors() const;
bool HasThrowingInstructions() const;
// Returns whether this block dominates the blocked passed as parameter. bool Dominates(const HBasicBlock* block) const;
template <typename T> class HUseListNode : public ArenaObject<kArenaAllocUseListNode>, public IntrusiveForwardListNode<HUseListNode<T>> { public: // Get the instruction which has this use as one of the inputs.
T GetUser() const { return user_; } // Get the position of the input record that this use corresponds to.
size_t GetIndex() const { return index_; } // Set the position of the input record that this use corresponds to. void SetIndex(size_t index) { index_ = index; }
template <typename T> using HUseList = IntrusiveForwardList<HUseListNode<T>>;
// This class is used by HEnvironment and HInstruction classes to record the // instructions they use and pointers to the corresponding HUseListNodes kept // by the used instructions. template <typename T> class HUserRecord final : public ValueObject { public:
HUserRecord() : instruction_(nullptr), before_use_node_() {} explicit HUserRecord(HInstruction* instruction) : instruction_(instruction), before_use_node_() {}
private: // Instruction used by the user.
HInstruction* instruction_;
// Iterator before the corresponding entry in the use list kept by 'instruction_'. typename HUseList<T>::iterator before_use_node_;
};
// Helper class that extracts the input instruction from HUserRecord<HInstruction*>. // This is used for HInstruction::GetInputs() to return a container wrapper providing // HInstruction* values even though the underlying container has HUserRecord<>s. struct HInputExtractor {
HInstruction* operator()(HUserRecord<HInstruction*>& record) const { return record.GetInstruction();
} const HInstruction* operator()(const HUserRecord<HInstruction*>& record) const { return record.GetInstruction();
}
};
using HInputsRef = TransformArrayRef<HUserRecord<HInstruction*>, HInputExtractor>; using HConstInputsRef = TransformArrayRef<const HUserRecord<HInstruction*>, HInputExtractor>;
// Returns whether GC might happen across this instruction from the compiler perspective so // the next instruction in the IR would see that. // // See the SideEffect class comments. static SideEffects CanTriggerGC() { return SideEffects(1ULL << kCanTriggerGCBit);
}
// Returns whether the instruction must not be alive across a GC point. // // See the SideEffect class comments. static SideEffects DependsOnGC() { return SideEffects(1ULL << kDependsOnGCBit);
}
// Combines the side-effects of this and the other.
SideEffects Union(SideEffects other) const { return SideEffects(flags_ | other.flags_);
}
// Returns true if there are no side effects or dependencies. bool DoesNothing() const { return flags_ == 0u;
}
// Returns true if something is written. bool DoesAnyWrite() const { return (flags_ & kAllWrites) != 0u;
}
// Returns true if something is read. bool DoesAnyRead() const { return (flags_ & kAllReads) != 0u;
}
// Returns true if potentially everything is written and read // (every type and every kind of access). bool DoesAllReadWrite() const { return (flags_ & (kAllWrites | kAllReads)) == (kAllWrites | kAllReads);
}
// Returns true if `this` may read something written by `other`. bool MayDependOn(SideEffects other) const { const uint64_t depends_on_flags = (flags_ & kAllDependOnBits) >> kChangeBits; return (other.flags_ & depends_on_flags) != 0u;
}
// Returns string representation of flags (for debugging only). // Format: |x|DFJISCBZL|DFJISCBZL|y|DFJISCBZL|DFJISCBZL|
std::string ToString() const {
std::string flags = "|"; for (int s = kLastBit; s >= 0; s--) { bool current_bit_is_set = ((flags_ >> s) & 1) != 0; if ((s == kDependsOnGCBit) || (s == kCanTriggerGCBit)) { // This is a bit for the GC side effect. if (current_bit_is_set) {
flags += "GC";
}
flags += "|";
} else { // This is a bit for the array/field analysis. // The underscore character stands for the 'can trigger GC' bit. staticconstchar *kDebug = "LZBCSIJFDLZBCSIJFD_LZBCSIJFDLZBCSIJFD"; if (current_bit_is_set) {
flags += kDebug[s];
} if ((s == kFieldWriteOffset) || (s == kArrayWriteOffset) ||
(s == kFieldReadOffset) || (s == kArrayReadOffset)) {
flags += "|";
}
}
} return flags;
}
// Translates type to bit flag. The type must correspond to a Java type. static uint64_t TypeFlag(DataType::Type type, int offset) { int shift; switch (type) { case DataType::Type::kReference: shift = 0; break; case DataType::Type::kBool: shift = 1; break; case DataType::Type::kInt8: shift = 2; break; case DataType::Type::kUint16: shift = 3; break; case DataType::Type::kInt16: shift = 4; break; case DataType::Type::kInt32: shift = 5; break; case DataType::Type::kInt64: shift = 6; break; case DataType::Type::kFloat32: shift = 7; break; case DataType::Type::kFloat64: shift = 8; break; default:
LOG(FATAL) << "Unexpected data type " << type;
UNREACHABLE();
}
DCHECK_LE(kFieldWriteOffset, shift);
DCHECK_LT(shift, kArrayWriteOffset); return UINT64_C(1) << (shift + offset);
}
// Private constructor on direct flags value. explicit SideEffects(uint64_t flags) : flags_(flags) {}
uint64_t flags_;
};
// A HEnvironment object contains the values of virtual registers at a given location. class HEnvironment final : public ArenaObject<kArenaAllocEnvironment> { public: static HEnvironment* Create(ArenaAllocator* allocator,
size_t number_of_vregs,
ArtMethod* method,
uint32_t dex_pc,
HInstruction* holder) { // The storage for vreg records is allocated right after the `HEnvironment` itself.
static_assert(IsAligned<alignof(HUserRecord<HEnvironment*>)>(sizeof(HEnvironment)));
static_assert(IsAligned<alignof(HUserRecord<HEnvironment*>)>(ArenaAllocator::kAlignment));
size_t alloc_size = sizeof(HEnvironment) + number_of_vregs * sizeof(HUserRecord<HEnvironment*>); void* storage = allocator->Alloc(alloc_size, kArenaAllocEnvironment); returnnew (storage) HEnvironment(number_of_vregs, method, dex_pc, holder);
}
// Copy from `env`. If it's a loop phi for `loop_header`, copy the first // input to the loop phi instead. This is for inserting instructions that // require an environment (like HDeoptimization) in the loop pre-header. void CopyFromWithLoopPhiAdjustment(ArenaAllocator* allocator,
HEnvironment* env,
HBasicBlock* loop_header);
// Replaces the input at the position 'index' with the replacement; the replacement and old // input instructions' env_uses_ lists are adjusted. The function works similar to // HInstruction::ReplaceInput. void ReplaceInput(HInstruction* replacement, size_t index);
// Iterates over the Environments class HEnvironmentIterator final : public ValueObject { public: using iterator_category = std::forward_iterator_tag; using value_type = HEnvironment*; using difference_type = ptrdiff_t; using pointer = void; using reference = void;
static constexpr bool IsControlFlow(InstructionKind kind) { switch (kind) { case kExit: case kGoto: case kIf: case kPackedSwitch: case kReturn: case kReturnVoid: case kThrow: case kTryBoundary: #ifdefined(ART_ENABLE_CODEGEN_x86) case kX86PackedSwitch: #endif returntrue; default: returnfalse;
}
}
// Can the instruction throw? // TODO: We should rename to CanVisiblyThrow, as some instructions (like HNewInstance), // could throw OOME, but it is still OK to remove them if they are unused. virtualbool CanThrow() const { returnfalse; }
// Does the instruction always throw an exception unconditionally? virtualbool AlwaysThrows() const { returnfalse; } // Will this instruction only cause async exceptions if it causes any at all? virtualbool OnlyThrowsAsyncExceptions() const { returnfalse;
}
// Does not apply for all instructions, but having this at top level greatly // simplifies the null check elimination. // TODO: Consider merging can_be_null into ReferenceTypeInfo. virtualbool CanBeNull() const {
DCHECK_EQ(GetType(), DataType::Type::kReference) << "CanBeNull only applies to reference types"; returntrue;
}
// If this instruction will do an implicit null check, return the `HNullCheck` associated // with it. Otherwise return null.
HNullCheck* GetImplicitNullCheck() const { // Go over previous non-move instructions that are emitted at use site.
HInstruction* prev_not_move = GetPreviousDisregardingMoves(); while (prev_not_move != nullptr && prev_not_move->IsEmittedAtUseSite()) { if (prev_not_move->IsNullCheck()) { return prev_not_move->AsNullCheck();
}
prev_not_move = prev_not_move->GetPreviousDisregardingMoves();
} return nullptr;
}
// Sets the ReferenceTypeInfo. The RTI must be valid. void SetReferenceTypeInfo(ReferenceTypeInfo rti); // Same as above, but we only set it if it's valid. Otherwise, we don't change the current RTI. void SetReferenceTypeInfoIfValid(ReferenceTypeInfo rti);
void AddUseAt(ArenaAllocator* allocator, HInstruction* user, size_t index) {
DCHECK(user != nullptr);
HUseListNode<HInstruction*>* new_node = new (allocator) HUseListNode<HInstruction*>(user, index); // Note: `old_begin` remains valid across `push_front()`. auto old_begin = uses_.begin();
uses_.push_front(*new_node); // To speed up this code, we inline the // FixUpUserRecordsAfterUseInsertion( // old_begin != uses_.end() ? ++old_begin : old_begin); // to reduce branching as we know that we're going to fix up either one or two entries. auto new_begin = uses_.begin();
user->SetRawInputRecordAt(index, HUserRecord<HInstruction*>(this, uses_.before_begin())); if (old_begin != uses_.end()) {
HInstruction* old_begin_user = old_begin->GetUser();
size_t old_begin_index = old_begin->GetIndex();
old_begin_user->SetRawInputRecordAt(
old_begin_index, HUserRecord<HInstruction*>(this, new_begin));
}
}
void AddEnvUseAt(ArenaAllocator* allocator, HEnvironment* user, size_t index) {
DCHECK(user != nullptr);
HUseListNode<HEnvironment*>* new_node = new (allocator) HUseListNode<HEnvironment*>(user, index); // Note: `old_env_begin` remains valid across `push_front()`. auto old_env_begin = env_uses_.begin();
env_uses_.push_front(*new_node); // To speed up this code, we inline the // FixUpUserRecordsAfterEnvUseInsertion( // old_env_begin != env_uses_.end() ? ++old_env_begin : old_env_begin); // to reduce branching as we know that we're going to fix up either one or two entries. auto new_env_begin = env_uses_.begin();
user->GetVRegs()[index] = HUserRecord<HEnvironment*>(this, env_uses_.before_begin()); if (old_env_begin != env_uses_.end()) {
HEnvironment* old_env_begin_user = old_env_begin->GetUser();
size_t old_env_begin_index = old_env_begin->GetIndex();
old_env_begin_user->GetVRegs()[old_env_begin_index] =
HUserRecord<HEnvironment*>(this, new_env_begin);
}
}
bool IsRemovable() const { switch (GetKind()) { case kConstructorFence: case kMemoryBarrier: case kNop: case kParameterValue: case kSuspendCheck: // Control flow HInstructions. This has to be kept in sync with IsControlFlow. case kExit: case kGoto: case kIf: case kPackedSwitch: case kReturn: case kReturnVoid: case kThrow: case kTryBoundary: #ifdefined(ART_ENABLE_CODEGEN_x86) case kX86PackedSwitch: #endif #ifdefined(ART_ENABLE_CODEGEN_x86_64) case kX86Clear: #endif returnfalse; default:
DCHECK(!IsControlFlow()); return !DoesAnyWrite() && !CanThrow();
}
}
// Does this instruction dominate `other_instruction`? // Aborts if this instruction and `other_instruction` are different phis. bool Dominates(HInstruction* other_instruction) const;
// Same but with `strictly dominates` i.e. returns false if this instruction and // `other_instruction` are the same. bool StrictlyDominates(HInstruction* other_instruction) const;
// Set the environment of this instruction, copying it from `environment`. While // copying, the uses lists are being updated. void CopyEnvironmentFrom(HEnvironment* environment) {
DCHECK(environment_ == nullptr);
ArenaAllocator* allocator = GetBlock()->GetGraph()->GetAllocator();
environment_ = HEnvironment::Create(allocator, *environment, this);
environment_->CopyFrom(allocator, environment); if (environment->GetParent() != nullptr) {
environment_->SetAndCopyParentChain(allocator, environment->GetParent());
}
}
// Returns the number of entries in the environment. Typically, that is the // number of dex registers in a method. It could be more in case of inlining.
size_t EnvironmentSize() const;
// This is almost the same as doing `ReplaceWith()`. But in this helper, the // uses of this instruction by `other` are *not* updated. void ReplaceWithExceptInReplacementAtIndex(HInstruction* other, size_t use_index) {
ReplaceWith(other);
other->ReplaceInput(this, use_index);
}
// Move `this` before its first user and out of any loops. If there is no // out-of-loop user that dominates all other users, move the instruction // to the end of the out-of-loop common dominator of the user's blocks. // // This can be used only on non-throwing instructions with no side effects that // have at least one use but no environment uses. void MoveBeforeFirstUserAndOutOfLoops();
// Return a clone of the instruction if it is clonable (shallow copy by default, custom copy // if a custom copy-constructor is provided for a particular type). If IsClonable() is false for // the instruction then the behaviour of this function is undefined. // // Note: It is semantically valid to create a clone of the instruction only until // prepare_for_register_allocator phase as lifetime, intervals and codegen info are not // copied. // // Note: HEnvironment and some other fields are not copied and are set to default values, see // 'explicit HInstruction(const HInstruction& other)' for details. virtual HInstruction* Clone([[maybe_unused]] ArenaAllocator* arena) const {
LOG(FATAL) << "Cloning is not implemented for the instruction " <<
DebugName() << " " << GetId();
UNREACHABLE();
}
// Return whether instruction can be cloned (copied). virtualbool IsClonable() const { returnfalse; }
// Returns whether the instruction can be moved within the graph. // TODO: this method is used by LICM and GVN with possibly different // meanings? split and rename? virtualbool CanBeMoved() const { returnfalse; }
// Returns whether any data encoded in the two instructions is equal. // This method does not look at the inputs. Both instructions must be // of the same type, otherwise the method has undefined behavior. virtualbool InstructionDataEquals([[maybe_unused]] const HInstruction* other) const { returnfalse;
}
// Returns whether two instructions are equal, that is: // 1) They have the same type and contain the same data (InstructionDataEquals). // 2) Their inputs are identical. bool Equals(const HInstruction* other) const;
// Returns whether the code generation of the instruction will require to have access // to the current method. Such instructions are: // (1): Instructions that require an environment, as calling the runtime requires // to walk the stack and have the current method stored at a specific stack address. // (2): HCurrentMethod, potentially used by HInvokeStaticOrDirect, HLoadString, or HLoadClass // to access the dex cache. bool NeedsCurrentMethod() const { return NeedsEnvironment() || IsCurrentMethod();
}
// Does this instruction have any use in an environment before // control flow hits 'other'? bool HasAnyEnvironmentUseBefore(HInstruction* other);
// Remove all references to environment uses of this instruction. // The caller must ensure that this is safe to do. void RemoveEnvironmentUsers();
// Copy construction for the instruction (used for Clone function). // // Fields (e.g. lifetime, intervals and codegen info) associated with phases starting from // prepare_for_register_allocator are not copied (set to default values). // // Copy constructors must be provided for every HInstruction type; default copy constructor is // fine for most of them. However for some of the instructions a custom copy constructor must be // specified (when instruction has non-trivially copyable fields and must have a special behaviour // for copying them). explicit HInstruction(const HInstruction& other)
: previous_(nullptr),
next_(nullptr),
block_(nullptr),
dex_pc_(other.dex_pc_),
id_(-1),
ssa_index_(-1),
packed_fields_(other.packed_fields_),
environment_(nullptr),
locations_(nullptr),
live_interval_(nullptr),
lifetime_position_(kNoLifetime),
side_effects_(other.side_effects_),
reference_type_handle_(other.reference_type_handle_) {
}
private: using InstructionKindField =
BitField<InstructionKind, kFieldInstructionKind, kFieldInstructionKindSize>;
// An instruction gets an id when it is added to the graph. // It reflects creation order. A negative id means the instruction // has not been added to the graph. int id_;
// When doing liveness analysis, instructions that have uses get an SSA index. int ssa_index_;
// Packed fields.
uint32_t packed_fields_;
// List of instructions that have this instruction as input.
HUseList<HInstruction*> uses_;
// List of environments that contain this instruction.
HUseList<HEnvironment*> env_uses_;
// The environment associated with this instruction. Not null if the instruction // might jump out of the method.
HEnvironment* environment_;
// Set by the code generator.
LocationSummary* locations_;
// Set by the liveness analysis.
LiveInterval* live_interval_;
// Set by the liveness analysis, this is the position in a linear // order of blocks where this instruction's live interval start.
size_t lifetime_position_;
SideEffects side_effects_;
// The reference handle part of the reference type info. // The IsExact() flag is stored in packed fields. // TODO: for primitive types this should be marked as invalid.
ReferenceTypeInfo::TypeHandle reference_type_handle_;
// Forward declarations for friends template <typename InnerIter> struct HSTLInstructionIterator;
// Iterates over the instructions, while preserving the next instruction // in case the current instruction gets removed from the list by the user // of this iterator. class HInstructionIteratorPrefetchNext final : public ValueObject { public: explicit HInstructionIteratorPrefetchNext(const HInstructionList& instructions)
: instruction_(instructions.first_instruction_) {
next_ = Done() ? nullptr : instruction_->GetNext();
}
// Iterates over the instructions without saving the next instruction, // therefore handling changes in the graph potentially made by the user // of this iterator. class HInstructionIterator final : public ValueObject { public: explicit HInstructionIterator(const HInstructionList& instructions)
: instruction_(instructions.first_instruction_) {
}
template <typename InnerIter> struct HSTLInstructionIterator : public ValueObject { public: using iterator_category = std::forward_iterator_tag; using value_type = HInstruction*; using difference_type = ptrdiff_t; using pointer = void; using reference = void;
class HVariableInputSizeInstruction : public HInstruction { public:
ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() final { return ArrayRef<HUserRecord<HInstruction*>>(inputs_);
}
DEFINE_GET_INPUT_RECORDS_HELPERS(HVariableInputSizeInstruction);
template<size_t N, typename Base = HInstruction> class HExpression : public Base { public: template <typename... Args> explicit HExpression(Args&&... args)
: Base(std::forward<Args>(args)...), inputs_() {}
virtual ~HExpression() {}
ArrayRef<HUserRecord<HInstruction*>> GetInputRecords() final { return ArrayRef<HUserRecord<HInstruction*>>(inputs_);
}
DEFINE_GET_INPUT_RECORDS_HELPERS(HExpression);
// Represents dex's RETURN_VOID opcode. A HReturnVoid is a control flow // instruction that branches to the exit block. class HReturnVoid final : public HExpression<0> { public: explicit HReturnVoid(uint32_t dex_pc = kNoDexPc)
: HExpression(kReturnVoid, SideEffects::None(), dex_pc) {
}
// Represents dex's RETURN opcodes. A HReturn is a control flow // instruction that branches to the exit block. class HReturn final : public HExpression<1> { public: explicit HReturn(HInstruction* value, uint32_t dex_pc = kNoDexPc)
: HExpression(kReturn, SideEffects::None(), dex_pc) {
SetRawInputAt(0, value);
}
DECLARE_INSTRUCTION(Return);
protected:
DEFAULT_COPY_CONSTRUCTOR(Return);
};
class HPhi final : public HVariableInputSizeInstruction { public:
HPhi(ArenaAllocator* allocator,
uint32_t reg_number,
size_t number_of_inputs,
DataType::Type type,
uint32_t dex_pc = kNoDexPc)
: HVariableInputSizeInstruction(
kPhi,
ToPhiType(type),
SideEffects::None(),
dex_pc,
allocator,
number_of_inputs,
kArenaAllocPhiInputs),
reg_number_(reg_number) {
DCHECK_NE(GetType(), DataType::Type::kVoid); // Phis are constructed live and marked dead if conflicting or unused. // Individual steps of SsaBuilder should assume that if a phi has been // marked dead, it can be ignored and will be removed by SsaPhiElimination.
SetPackedFlag<kFlagIsLive>(true);
SetPackedFlag<kFlagCanBeNull>(true);
}
bool IsClonable() const override { returntrue; }
// Returns a type equivalent to the given `type`, but that a `HPhi` can hold. static DataType::Type ToPhiType(DataType::Type type) { return DataType::Kind(type);
}
// Returns the next equivalent phi (starting from the current one) or null if there is none. // An equivalent phi is a phi having the same dex register and type. // It assumes that phis with the same dex register are adjacent.
HPhi* GetNextEquivalentPhiWithSameType() {
HInstruction* next = GetNext(); while (next != nullptr && next->AsPhi()->GetRegNumber() == reg_number_) { if (next->GetType() == GetType()) { return next->AsPhi();
}
next = next->GetNext();
} return nullptr;
}
// The exit instruction is the only instruction of the exit block. // Instructions aborting the method (HThrow and HReturn) must branch to the // exit block. class HExit final : public HExpression<0> { public: explicit HExit(uint32_t dex_pc = kNoDexPc)
: HExpression(kExit, SideEffects::None(), dex_pc) {
}
DECLARE_INSTRUCTION(Exit);
protected:
DEFAULT_COPY_CONSTRUCTOR(Exit);
};
// Jumps from one block to another. class HGoto final : public HExpression<0> { public: explicit HGoto(uint32_t dex_pc = kNoDexPc)
: HExpression(kGoto, SideEffects::None(), dex_pc) {
}
class HConstant : public HExpression<0> { public: explicit HConstant(InstructionKind kind, DataType::Type type)
: HExpression(kind, type, SideEffects::None(), kNoDexPc) {
}
bool CanBeMoved() const override { returntrue; }
// Is this constant -1 in the arithmetic sense? virtualbool IsMinusOne() const { returnfalse; } // Is this constant 0 in the arithmetic sense? virtualbool IsArithmeticZero() const { returnfalse; } // Is this constant a 0-bit pattern? virtualbool IsZeroBitPattern() const { returnfalse; } // Is this constant 1 in the arithmetic sense? virtualbool IsOne() const { returnfalse; }
virtual uint64_t GetValueAsUint64() const = 0;
DECLARE_ABSTRACT_INSTRUCTION(Constant);
protected:
DEFAULT_COPY_CONSTRUCTOR(Constant);
};
class HNullConstant final : public HConstant { public: bool InstructionDataEquals([[maybe_unused]] const HInstruction* other) const override { returntrue;
}
// Constants of the type int. Those can be from Dex instructions, or // synthesized (for example with the if-eqz instruction). class HIntConstant final : public HConstant { public:
int32_t GetValue() const { return value_; }
// Integer constants are used to encode Boolean values as well, // where 1 means true and 0 means false. bool IsTrue() const { return GetValue() == 1; } bool IsFalse() const { return GetValue() == 0; }
// Only the SsaBuilder and HGraph can create floating-point constants. friendclass SsaBuilder; friendclass HGraph;
};
// Conditional branch. A block ending with an HIf instruction must have // two successors. class HIf final : public HExpression<1> { public: explicit HIf(HInstruction* input, uint32_t dex_pc = kNoDexPc)
: HExpression(kIf, SideEffects::None(), dex_pc),
true_count_(std::numeric_limits<uint16_t>::max()),
false_count_(std::numeric_limits<uint16_t>::max()) {
SetRawInputAt(0, input);
}
// Abstract instruction which marks the beginning and/or end of a try block and // links it to the respective exception handlers. Behaves the same as a Goto in // non-exceptional control flow. // Normal-flow successor is stored at index zero, exception handlers under // higher indices in no particular order. class HTryBoundary final : public HExpression<0> { public: enumclass BoundaryKind {
kEntry,
kExit,
kLast = kExit
};
// SideEffects::CanTriggerGC prevents instructions with SideEffects::DependOnGC to be alive // across the catch block entering edges as GC might happen during throwing an exception. // TryBoundary with BoundaryKind::kExit is conservatively used for that as there is no // HInstruction which a catch block must start from. explicit HTryBoundary(BoundaryKind kind, uint32_t dex_pc = kNoDexPc)
: HExpression(kTryBoundary,
(kind == BoundaryKind::kExit) ? SideEffects::CanTriggerGC()
: SideEffects::None(),
dex_pc) {
SetPackedField<BoundaryKindField>(kind);
}
// Returns the block's non-exceptional successor (index zero).
HBasicBlock* GetNormalFlowSuccessor() const { return GetBlock()->GetSuccessors()[0]; }
// Returns whether `handler` is among its exception handlers (non-zero index // successors). bool HasExceptionHandler(const HBasicBlock& handler) const {
DCHECK(handler.IsCatchBlock()); return GetBlock()->HasSuccessor(&handler, 1u /* Skip first successor. */);
}
// If not present already, adds `handler` to its block's list of exception // handlers. void AddExceptionHandler(HBasicBlock* handler) { if (!HasExceptionHandler(*handler)) {
GetBlock()->AddSuccessor(handler);
}
}
// Deoptimize to interpreter, upon checking a condition. class HDeoptimize final : public HVariableInputSizeInstruction { public: // Use this constructor when the `HDeoptimize` acts as a barrier, where no code can move // across.
HDeoptimize(ArenaAllocator* allocator,
HInstruction* cond,
DeoptimizationKind kind,
uint32_t dex_pc)
: HVariableInputSizeInstruction(
kDeoptimize,
SideEffects::All(),
dex_pc,
allocator, /* number_of_inputs= */ 1,
kArenaAllocMisc) {
SetPackedFlag<kFieldCanBeMoved>(false);
SetPackedField<DeoptimizeKindField>(kind);
SetRawInputAt(0, cond);
}
bool IsClonable() const override { returntrue; }
// Use this constructor when the `HDeoptimize` guards an instruction, and any user // that relies on the deoptimization to pass should have its input be the `HDeoptimize` // instead of `guard`. // We set CanTriggerGC to prevent any intermediate address to be live // at the point of the `HDeoptimize`.
HDeoptimize(ArenaAllocator* allocator,
HInstruction* cond,
HInstruction* guard,
DeoptimizationKind kind,
uint32_t dex_pc)
: HVariableInputSizeInstruction(
kDeoptimize,
guard->GetType(),
SideEffects::CanTriggerGC(),
dex_pc,
allocator, /* number_of_inputs= */ 2,
kArenaAllocMisc) {
SetPackedFlag<kFieldCanBeMoved>(true);
SetPackedField<DeoptimizeKindField>(kind);
SetRawInputAt(0, cond);
SetRawInputAt(1, guard);
}
// Represents a should_deoptimize flag. Currently used for CHA-based devirtualization. // The compiled code checks this flag value in a guard before devirtualized call and // if it's true, starts to do deoptimization. // It has a 4-byte slot on stack. // TODO: allocate a register for this flag. class HShouldDeoptimizeFlag final : public HVariableInputSizeInstruction { public: // CHA guards are only optimized in a separate pass and it has no side effects // with regard to other passes.
HShouldDeoptimizeFlag(ArenaAllocator* allocator, uint32_t dex_pc)
: HVariableInputSizeInstruction(kShouldDeoptimizeFlag,
DataType::Type::kInt32,
SideEffects::None(),
dex_pc,
allocator, 0,
kArenaAllocCHA) {
}
// We do all CHA guard elimination/motion in a single pass, after which there is no // further guard elimination/motion since a guard might have been used for justification // of the elimination of another guard. Therefore, we pretend this guard cannot be moved // to avoid other optimizations trying to move it. bool CanBeMoved() const override { returnfalse; }
// Represents the ArtMethod that was passed as a first argument to // the method. It is used by instructions that depend on it, like // instructions that work with the dex cache. class HCurrentMethod final : public HExpression<0> { public: explicit HCurrentMethod(DataType::Type type, uint32_t dex_pc = kNoDexPc)
: HExpression(kCurrentMethod, type, SideEffects::None(), dex_pc) {
}
// The index of the ArtMethod in the table. const size_t index_;
};
// PackedSwitch (jump table). A block ending with a PackedSwitch instruction will // have one successor for each entry in the switch table, and the final successor // will be the block containing the next Dex opcode. class HPackedSwitch final : public HExpression<1> { public:
HPackedSwitch(int32_t start_value,
uint32_t num_entries,
HInstruction* input,
uint32_t dex_pc = kNoDexPc)
: HExpression(kPackedSwitch, SideEffects::None(), dex_pc),
start_value_(start_value),
num_entries_(num_entries) {
SetRawInputAt(0, input);
}
HBasicBlock* GetDefaultBlock() const { // Last entry is the default block. return GetBlock()->GetSuccessors()[num_entries_];
}
DECLARE_INSTRUCTION(PackedSwitch);
// Try to statically evaluate `this` and return a HConstant // containing the result of this evaluation. If `this` cannot // be evaluated as a constant, return null.
HConstant* TryStaticEvaluation() const;
// Same but for `input` instead of GetInput().
HConstant* TryStaticEvaluation(HInstruction* input) const;
// Apply this operation to `x`. virtual HConstant* Evaluate([[maybe_unused]] HIntConstant* x) const {
LOG(FATAL) << DebugName() << " is not defined for int values";
UNREACHABLE();
} virtual HConstant* Evaluate([[maybe_unused]] HLongConstant* x) const {
LOG(FATAL) << DebugName() << " is not defined for long values";
UNREACHABLE();
} virtual HConstant* Evaluate([[maybe_unused]] HFloatConstant* x) const {
LOG(FATAL) << DebugName() << " is not defined for float values";
UNREACHABLE();
} virtual HConstant* Evaluate([[maybe_unused]] HDoubleConstant* x) const {
LOG(FATAL) << DebugName() << " is not defined for double values";
UNREACHABLE();
}
bool IsCommutative() const { switch (GetKind()) { case kAdd: case kAnd: case kEqual: case kMax: case kMin: case kMul: case kNotEqual: case kOr: case kXor: returntrue; default: returnfalse;
}
}
// Put constant on the right. // Returns whether order is changed. bool OrderInputsWithConstantOnTheRight() {
HInstruction* left = InputAt(0);
HInstruction* right = InputAt(1); if (left->IsConstant() && !right->IsConstant()) {
ReplaceInput(right, 0);
ReplaceInput(left, 1); returntrue;
} returnfalse;
}
// Order inputs by instruction id, but favor constant on the right side. // This helps GVN for commutative ops. void OrderInputs() {
DCHECK(IsCommutative());
HInstruction* left = InputAt(0);
HInstruction* right = InputAt(1); if (left == right || (!left->IsConstant() && right->IsConstant())) { return;
} if (OrderInputsWithConstantOnTheRight()) { return;
} // Order according to instruction id. if (left->GetId() > right->GetId()) {
ReplaceInput(right, 0);
ReplaceInput(left, 1);
}
}
// Try to statically evaluate `this` and return a HConstant // containing the result of this evaluation. If `this` cannot // be evaluated as a constant, return null.
HConstant* TryStaticEvaluation() const;
// Same but for `left` and `right` instead of GetLeft() and GetRight().
HConstant* TryStaticEvaluation(HInstruction* left, HInstruction* right) const;
// Apply this operation to `x` and `y`. virtual HConstant* Evaluate([[maybe_unused]] HNullConstant* x,
[[maybe_unused]] HNullConstant* y) const {
LOG(FATAL) << DebugName() << " is not defined for the (null, null) case.";
UNREACHABLE();
} virtual HConstant* Evaluate([[maybe_unused]] HIntConstant* x,
[[maybe_unused]] HIntConstant* y) const {
LOG(FATAL) << DebugName() << " is not defined for the (int, int) case.";
UNREACHABLE();
} virtual HConstant* Evaluate([[maybe_unused]] HLongConstant* x,
[[maybe_unused]] HLongConstant* y) const {
LOG(FATAL) << DebugName() << " is not defined for the (long, long) case.";
UNREACHABLE();
} virtual HConstant* Evaluate([[maybe_unused]] HLongConstant* x,
[[maybe_unused]] HIntConstant* y) const {
LOG(FATAL) << DebugName() << " is not defined for the (long, int) case.";
UNREACHABLE();
} virtual HConstant* Evaluate([[maybe_unused]] HFloatConstant* x,
[[maybe_unused]] HFloatConstant* y) const {
LOG(FATAL) << DebugName() << " is not defined for float values";
UNREACHABLE();
} virtual HConstant* Evaluate([[maybe_unused]] HDoubleConstant* x,
[[maybe_unused]] HDoubleConstant* y) const {
LOG(FATAL) << DebugName() << " is not defined for double values";
UNREACHABLE();
}
// Returns an input that can legally be used as the right input and is // constant, or null.
HConstant* GetConstantRight() const;
// If `GetConstantRight()` returns one of the input, this returns the other // one. Otherwise it returns null.
HInstruction* GetLeastConstantLeft() const;
// The comparison bias applies for floating point operations and indicates how NaN // comparisons are treated: enumclass ComparisonBias { // private marker to avoid generate-operator-out.py from processing.
kNoBias, // bias is not applicable (i.e. for long operation)
kGtBias, // return 1 for NaN comparisons
kLtBias, // return -1 for NaN comparisons
kLast = kLtBias
};
// For code generation purposes, returns whether this instruction is just before // `instruction`, and disregard moves in between. bool IsBeforeWhenDisregardMoves(HInstruction* instruction) const;
// Return an integer constant containing the result of a condition evaluated at compile time.
HIntConstant* MakeConstantCondition(bool value) const { return GetBlock()->GetGraph()->GetIntConstant(value);
}
DEFAULT_COPY_CONSTRUCTOR(Condition);
};
// Instruction to check if two inputs are equal to each other. class HEqual final : public HCondition { public:
HEqual(HInstruction* first, HInstruction* second, uint32_t dex_pc = kNoDexPc)
: HCondition(kEqual, first, second, dex_pc) {
}
HConstant* Evaluate([[maybe_unused]] HNullConstant* x,
[[maybe_unused]] HNullConstant* y) const override { return MakeConstantCondition(true);
}
HConstant* Evaluate(HIntConstant* x, HIntConstant* y) const override { return MakeConstantCondition(Compute(x->GetValue(), y->GetValue()));
} // In the following Evaluate methods, a HCompare instruction has // been merged into this HEqual instruction; evaluate it as // `Compare(x, y) == 0`.
HConstant* Evaluate(HLongConstant* x, HLongConstant* y) const override { return MakeConstantCondition(Compute(Compare(x->GetValue(), y->GetValue()), 0));
}
HConstant* Evaluate(HFloatConstant* x, HFloatConstant* y) const override { return MakeConstantCondition(Compute(CompareFP(x->GetValue(), y->GetValue()), 0));
}
HConstant* Evaluate(HDoubleConstant* x, HDoubleConstant* y) const override { return MakeConstantCondition(Compute(CompareFP(x->GetValue(), y->GetValue()), 0));
}
// Instruction to check how two inputs compare to each other. // Result is 0 if input0 == input1, 1 if input0 > input1, or -1 if input0 < input1. class HCompare final : public HBinaryOperation { public: // Note that `comparison_type` is the type of comparison performed // between the comparison's inputs, not the type of the instantiated // HCompare instruction (which is always DataType::Type::kInt).
HCompare(DataType::Type comparison_type,
HInstruction* first,
HInstruction* second,
ComparisonBias bias,
uint32_t dex_pc)
: HBinaryOperation(kCompare,
DataType::Type::kInt32,
first,
second,
SideEffectsForArchRuntimeCalls(comparison_type),
dex_pc) {
SetPackedField<ComparisonBiasField>(bias);
SetPackedField<ComparisonTypeField>(comparison_type);
}
template <typename T>
int32_t Compute(T x, T y) const { return x > y ? 1 : (x < y ? -1 : 0); }
HConstant* Evaluate(HIntConstant* x, HIntConstant* y) const override { // Note that there is no "cmp-int" Dex instruction so we shouldn't // reach this code path when processing a freshly built HIR // graph. However HCompare integer instructions can be synthesized // by the instruction simplifier to implement IntegerCompare and // IntegerSignum intrinsics, so we have to handle this case. const int32_t value = DataType::IsUnsignedType(GetComparisonType()) ?
Compute(x->GetValueAsUint64(), y->GetValueAsUint64()) :
Compute(x->GetValue(), y->GetValue()); return MakeConstantComparison(value);
}
HConstant* Evaluate(HLongConstant* x, HLongConstant* y) const override { const int32_t value = DataType::IsUnsignedType(GetComparisonType()) ?
Compute(x->GetValueAsUint64(), y->GetValueAsUint64()) :
Compute(x->GetValue(), y->GetValue()); return MakeConstantComparison(value);
}
HConstant* Evaluate(HFloatConstant* x, HFloatConstant* y) const override { return MakeConstantComparison(ComputeFP(x->GetValue(), y->GetValue()));
}
HConstant* Evaluate(HDoubleConstant* x, HDoubleConstant* y) const override { return MakeConstantComparison(ComputeFP(x->GetValue(), y->GetValue()));
}
// Does this compare instruction have a "gt bias" (vs an "lt bias")? // Only meaningful for floating-point comparisons. bool IsGtBias() const {
DCHECK(DataType::IsFloatingPointType(InputAt(0)->GetType())) << InputAt(0)->GetType(); return GetBias() == ComparisonBias::kGtBias;
}
static SideEffects SideEffectsForArchRuntimeCalls([[maybe_unused]] DataType::Type type) { // Comparisons do not require a runtime call in any back end. return SideEffects::None();
}
// Return an integer constant containing the result of a comparison evaluated at compile time.
HIntConstant* MakeConstantComparison(int32_t value) const {
DCHECK(value == -1 || value == 0 || value == 1) << value; return GetBlock()->GetGraph()->GetIntConstant(value);
}
enum IntrinsicNeedsEnvironment {
kNoEnvironment, // Intrinsic does not require an environment.
kNeedsEnvironment // Intrinsic requires an environment.
};
enum IntrinsicSideEffects {
kNoSideEffects, // Intrinsic does not have any heap memory side effects.
kReadSideEffects, // Intrinsic may read heap memory.
kWriteSideEffects, // Intrinsic may write heap memory.
kAllSideEffects // Intrinsic may read or write heap memory, or trigger GC.
};
enum IntrinsicExceptions {
kNoThrow, // Intrinsic does not throw any exceptions.
kCanThrow // Intrinsic may throw exceptions.
};
// Determines how to load an ArtMethod*. enumclass MethodLoadKind { // Use a String init ArtMethod* loaded from Thread entrypoints.
kStringInit,
// Use the method's own ArtMethod* loaded by the register allocator.
kRecursive,
// Use PC-relative boot image ArtMethod* address that will be known at link time. // Used for boot image methods referenced by boot image code.
kBootImageLinkTimePcRelative,
// Load from a boot image entry in the .data.img.rel.ro using a PC-relative load. // Used for app->boot calls with relocatable image.
kBootImageRelRo,
// Load from an app image entry in the .data.img.rel.ro using a PC-relative load. // Used for app image methods referenced by apps in AOT-compiled code.
kAppImageRelRo,
// Load from an entry in the .bss section using a PC-relative load. // Used for methods outside boot image referenced by AOT-compiled app and boot image code.
kBssEntry,
// Use ArtMethod* at a known address, embed the direct address in the code. // Used for for JIT-compiled calls.
kJitDirectAddress,
// Make a runtime call to resolve and call the method. This is the last-resort-kind // used when other kinds are unimplemented on a particular architecture.
kRuntimeCall,
};
// Determines the location of the code pointer of an invoke. enumclass CodePtrLocation { // Recursive call, use local PC-relative call instruction.
kCallSelf,
// Use native pointer from the Artmethod*. // Used for @CriticalNative to avoid going through the compiled stub. This call goes through // a special resolution stub if the class is not initialized or no native code is registered.
kCallCriticalNative,
// Use code pointer from the ArtMethod*. // Used when we don't know the target code. This is also the last-resort-kind used when // other kinds are unimplemented or impractical (i.e. slow) on a particular architecture.
kCallArtMethod,
};
// Return the number of arguments. This number can be lower than // the number of inputs returned by InputCount(), as some invoke // instructions (e.g. HInvokeStaticOrDirect) can have non-argument // inputs at the end of their list of inputs.
uint32_t GetNumberOfArguments() const { return number_of_arguments_; }
// Return the number of outgoing vregs.
uint32_t GetNumberOfOutVRegs() const { return number_of_out_vregs_; }
class HInvokePolymorphic final : public HInvoke { public:
HInvokePolymorphic(ArenaAllocator* allocator,
uint32_t number_of_arguments,
uint32_t number_of_out_vregs,
uint32_t number_of_other_inputs,
DataType::Type return_type,
uint32_t dex_pc,
MethodReference method_reference, // resolved_method is the ArtMethod object corresponding to the polymorphic // method (e.g. VarHandle.get), resolved using the class linker. It is needed // to pass intrinsic information to the HInvokePolymorphic node.
ArtMethod* resolved_method,
MethodReference resolved_method_reference,
dex::ProtoIndex proto_idx)
: HInvoke(kInvokePolymorphic,
allocator,
number_of_arguments,
number_of_out_vregs,
number_of_other_inputs,
return_type,
dex_pc,
method_reference,
resolved_method,
resolved_method_reference,
kPolymorphic, /* enable_intrinsic_opt= */ true),
proto_idx_(proto_idx),
needs_callsite_type_check_(true) {}
class HInvokeStaticOrDirect final : public HInvoke { public: // Requirements of this method call regarding the class // initialization (clinit) check of its declaring class. enumclass ClinitCheckRequirement { // private marker to avoid generate-operator-out.py from processing.
kNone, // Class already initialized.
kExplicit, // Static call having explicit clinit check as last input.
kImplicit, // Static call implicitly requiring a clinit check.
kLast = kImplicit
};
struct DispatchInfo {
MethodLoadKind method_load_kind;
CodePtrLocation code_ptr_location; // The method load data holds // - thread entrypoint offset for kStringInit method if this is a string init invoke. // Note that there are multiple string init methods, each having its own offset. // - the method address for kDirectAddress
uint64_t method_load_data;
};
HInvokeStaticOrDirect(ArenaAllocator* allocator,
uint32_t number_of_arguments,
uint32_t number_of_out_vregs,
DataType::Type return_type,
uint32_t dex_pc,
MethodReference method_reference,
ArtMethod* resolved_method,
DispatchInfo dispatch_info,
InvokeType invoke_type,
MethodReference resolved_method_reference,
ClinitCheckRequirement clinit_check_requirement, bool enable_intrinsic_opt)
: HInvoke(kInvokeStaticOrDirect,
allocator,
number_of_arguments,
number_of_out_vregs, // There is potentially one extra argument for the HCurrentMethod input, // and one other if the clinit check is explicit. These can be removed later.
(NeedsCurrentMethodInput(dispatch_info) ? 1u : 0u) +
(clinit_check_requirement == ClinitCheckRequirement::kExplicit ? 1u : 0u),
return_type,
dex_pc,
method_reference,
resolved_method,
resolved_method_reference,
invoke_type,
enable_intrinsic_opt),
dispatch_info_(dispatch_info) {
SetPackedField<ClinitCheckRequirementField>(clinit_check_requirement);
}
// Using the current method is the default and once we find a better // method load kind, we should not go back to using the current method.
DCHECK(had_current_method_input || !needs_current_method_input);
bool CanDoImplicitNullCheckOn([[maybe_unused]] HInstruction* obj) const override { // We do not access the method via object reference, so we cannot do an implicit null check. // TODO: for intrinsics we can generate implicit null checks. returnfalse;
}
bool CanBeNull() const override;
MethodLoadKind GetMethodLoadKind() const { return dispatch_info_.method_load_kind; }
CodePtrLocation GetCodePtrLocation() const { // We do CHA analysis after sharpening. When a method has CHA inlining, it // cannot call itself, as if the CHA optmization is invalid we want to make // sure the method is never executed again. So, while sharpening can return // kCallSelf, we bypass it here if there is a CHA optimization. if (dispatch_info_.code_ptr_location == CodePtrLocation::kCallSelf &&
GetBlock()->GetGraph()->HasShouldDeoptimizeFlag()) { return CodePtrLocation::kCallArtMethod;
} else { return dispatch_info_.code_ptr_location;
}
} bool IsRecursive() const { return GetMethodLoadKind() == MethodLoadKind::kRecursive; } bool IsStringInit() const { return GetMethodLoadKind() == MethodLoadKind::kStringInit; } bool HasMethodAddress() const { return GetMethodLoadKind() == MethodLoadKind::kJitDirectAddress; } bool HasPcRelativeMethodLoadKind() const { return IsPcRelativeMethodLoadKind(GetMethodLoadKind());
}
// Is this instruction a call to a static method? bool IsStatic() const { return GetInvokeType() == kStatic;
}
// Does this method load kind need the current method as an input? staticbool NeedsCurrentMethodInput(DispatchInfo dispatch_info) { return dispatch_info.method_load_kind == MethodLoadKind::kRecursive ||
dispatch_info.method_load_kind == MethodLoadKind::kRuntimeCall ||
dispatch_info.code_ptr_location == CodePtrLocation::kCallCriticalNative;
}
// Get the index of the current method input.
size_t GetCurrentMethodIndex() const {
DCHECK(HasCurrentMethodInput()); return GetCurrentMethodIndexUnchecked();
}
size_t GetCurrentMethodIndexUnchecked() const { return GetNumberOfArguments();
}
// Check if the method has a current method input. bool HasCurrentMethodInput() const { if (NeedsCurrentMethodInput(GetDispatchInfo())) {
DCHECK(InputAt(GetCurrentMethodIndexUnchecked()) == nullptr || // During argument setup.
InputAt(GetCurrentMethodIndexUnchecked())->IsCurrentMethod()); returntrue;
} else {
DCHECK(InputCount() == GetCurrentMethodIndexUnchecked() ||
InputAt(GetCurrentMethodIndexUnchecked()) == nullptr || // During argument setup.
!InputAt(GetCurrentMethodIndexUnchecked())->IsCurrentMethod()); returnfalse;
}
}
// Get the index of the special input.
size_t GetSpecialInputIndex() const {
DCHECK(HasSpecialInput()); return GetSpecialInputIndexUnchecked();
}
size_t GetSpecialInputIndexUnchecked() const { return GetNumberOfArguments() + (HasCurrentMethodInput() ? 1u : 0u);
}
// Check if the method has a special input. bool HasSpecialInput() const {
size_t other_inputs =
GetSpecialInputIndexUnchecked() + (IsStaticWithExplicitClinitCheck() ? 1u : 0u);
size_t input_count = InputCount();
DCHECK_LE(input_count - other_inputs, 1u) << other_inputs << " " << input_count; return other_inputs != input_count;
}
void AddSpecialInput(HInstruction* input) { // We allow only one special input.
DCHECK(!HasSpecialInput());
InsertInputAt(GetSpecialInputIndexUnchecked(), input);
}
// Remove the HClinitCheck or the replacement HLoadClass (set as last input by // PrepareForRegisterAllocation::VisitClinitCheck() in lieu of the initial HClinitCheck) // instruction; only relevant for static calls with explicit clinit check. void RemoveExplicitClinitCheck(ClinitCheckRequirement new_requirement) {
DCHECK(IsStaticWithExplicitClinitCheck());
size_t last_input_index = inputs_.size() - 1u;
HInstruction* last_input = inputs_.back().GetInstruction();
DCHECK(last_input != nullptr);
DCHECK(last_input->IsLoadClass() || last_input->IsClinitCheck()) << last_input->DebugName();
RemoveAsUserOfInput(last_input_index);
inputs_.pop_back();
SetPackedField<ClinitCheckRequirementField>(new_requirement);
DCHECK(!IsStaticWithExplicitClinitCheck());
}
// Is this a call to a static method whose declaring class has an // explicit initialization check in the graph? bool IsStaticWithExplicitClinitCheck() const { return IsStatic() && (GetClinitCheckRequirement() == ClinitCheckRequirement::kExplicit);
}
// Is this a call to a static method whose declaring class has an // implicit intialization check requirement? bool IsStaticWithImplicitClinitCheck() const { return IsStatic() && (GetClinitCheckRequirement() == ClinitCheckRequirement::kImplicit);
}
class HDiv final : public HBinaryOperation { public:
HDiv(DataType::Type result_type,
HInstruction* left,
HInstruction* right,
uint32_t dex_pc)
: HBinaryOperation(kDiv, result_type, left, right, SideEffects::None(), dex_pc) {
}
template <typename T>
T ComputeIntegral(T x, T y) const {
DCHECK(!DataType::IsFloatingPointType(GetType())) << GetType(); // Our graph structure ensures we never have 0 for `y` during // constant folding.
DCHECK_NE(y, 0); // Special case -1 to avoid getting a SIGFPE on x86(_64). return (y == -1) ? -x : x / y;
}
template <typename T>
T ComputeFP(T x, T y) const {
DCHECK(DataType::IsFloatingPointType(GetType())) << GetType(); return x / y;
}
class HRem final : public HBinaryOperation { public:
HRem(DataType::Type result_type,
HInstruction* left,
HInstruction* right,
uint32_t dex_pc)
: HBinaryOperation(kRem, result_type, left, right, SideEffects::None(), dex_pc) {
}
template <typename T>
T ComputeIntegral(T x, T y) const {
DCHECK(!DataType::IsFloatingPointType(GetType())) << GetType(); // Our graph structure ensures we never have 0 for `y` during // constant folding.
DCHECK_NE(y, 0); // Special case -1 to avoid getting a SIGFPE on x86(_64). return (y == -1) ? 0 : x % y;
}
template <typename T>
T ComputeFP(T x, T y) const {
DCHECK(DataType::IsFloatingPointType(GetType())) << GetType(); return std::fmod(x, y);
}
class HAbs final : public HUnaryOperation { public:
HAbs(DataType::Type result_type, HInstruction* input, uint32_t dex_pc = kNoDexPc)
: HUnaryOperation(kAbs, result_type, input, dex_pc) {}
// Evaluation for integral values. template <typename T> static T ComputeIntegral(T x) { return x < 0 ? -x : x;
}
// Evaluation for floating-point values. // Note, as a "quality of implementation", rather than pure "spec compliance", // we require that Math.abs() clears the sign bit (but changes nothing else) // for all floating-point numbers, including NaN (signaling NaN may become quiet though). // http://b/30758343 template <typename T, typename S> static T ComputeFP(T x) {
S bits = bit_cast<S, T>(x); return bit_cast<T, S>(bits & std::numeric_limits<S>::max());
}
class HDivZeroCheck final : public HExpression<1> { public: // `HDivZeroCheck` can trigger GC, as it may call the `ArithmeticException` // constructor. However it can only do it on a fatal slow path so execution never returns to the // instruction following the current one; thus 'SideEffects::None()' is used.
HDivZeroCheck(HInstruction* value, uint32_t dex_pc)
: HExpression(kDivZeroCheck, value->GetType(), SideEffects::None(), dex_pc) {
SetRawInputAt(0, value);
}
// The value of a parameter in this method. Its location depends on // the calling convention. class HParameterValue final : public HExpression<0> { public:
HParameterValue(const DexFile& dex_file,
dex::TypeIndex type_index,
uint8_t input_vreg_index,
DataType::Type parameter_type, bool is_this = false)
: HExpression(kParameterValue, parameter_type, SideEffects::None(), kNoDexPc),
dex_file_(dex_file),
type_index_(type_index),
input_vreg_index_(input_vreg_index) {
SetPackedFlag<kFlagIsThis>(is_this);
SetPackedFlag<kFlagCanBeNull>(!is_this);
}
private: // Whether or not the parameter value corresponds to 'this' argument. static constexpr size_t kFlagIsThis = kNumberOfGenericPackedBits; static constexpr size_t kFlagCanBeNull = kFlagIsThis + 1; static constexpr size_t kNumberOfParameterValuePackedBits = kFlagCanBeNull + 1;
static_assert(kNumberOfParameterValuePackedBits <= kMaxNumberOfPackedBits, "Too many packed fields.");
const DexFile& dex_file_; const dex::TypeIndex type_index_; // The index of this parameter in the parameters list. Must be less // than HGraph::number_of_in_vregs_. const uint8_t input_vreg_index_;
};
class HNot final : public HUnaryOperation { public:
HNot(DataType::Type result_type, HInstruction* input, uint32_t dex_pc = kNoDexPc)
: HUnaryOperation(kNot, result_type, input, dex_pc) {
}
class HTypeConversion final : public HExpression<1> { public: // Instantiate a type conversion of `input` to `result_type`.
HTypeConversion(DataType::Type result_type, HInstruction* input, uint32_t dex_pc = kNoDexPc)
: HExpression(kTypeConversion, result_type, SideEffects::None(), dex_pc) {
SetRawInputAt(0, input); // Invariant: We should never generate a conversion to a Boolean value.
DCHECK_NE(DataType::Type::kBool, result_type);
}
bool IsClonable() const override { returntrue; } bool CanBeMoved() const override { returntrue; } bool InstructionDataEquals([[maybe_unused]] const HInstruction* other) const override { returntrue;
} // Return whether the conversion is implicit. This includes conversion to the same type. bool IsImplicitConversion() const { return DataType::IsTypeConversionImplicit(GetInputType(), GetResultType());
}
// Try to statically evaluate the conversion and return a HConstant // containing the result. If the input cannot be converted, return nullptr.
HConstant* TryStaticEvaluation() const;
// Same but for `input` instead of GetInput().
HConstant* TryStaticEvaluation(HInstruction* input) const;
class HNullCheck final : public HExpression<1> { public: // `HNullCheck` can trigger GC, as it may call the `NullPointerException` // constructor. However it can only do it on a fatal slow path so execution never returns to the // instruction following the current one; thus 'SideEffects::None()' is used.
HNullCheck(HInstruction* value, uint32_t dex_pc)
: HExpression(kNullCheck, value->GetType(), SideEffects::None(), dex_pc) {
SetRawInputAt(0, value);
}
// Embeds an ArtField and all the information required by the compiler. We cache // that information to avoid requiring the mutator lock every time we need it. class FieldInfo final : public ValueObject { public:
FieldInfo(ArtField* field,
MemberOffset field_offset,
DataType::Type field_type, bool is_volatile,
uint32_t index,
uint16_t declaring_class_def_index, const DexFile& dex_file)
: field_(field),
field_offset_(field_offset),
field_type_(field_type),
is_volatile_(is_volatile),
index_(index),
declaring_class_def_index_(declaring_class_def_index),
dex_file_(dex_file) {}
bool CanBeNull() const override {
DCHECK_EQ(GetFieldType(), DataType::Type::kReference); // null-s are represented as NullConstant nodes and value_ does not hold null references // once it was set. return !HasConstantValue();
}
enumclass WriteBarrierKind { // Emit the write barrier. This write barrier is not being relied on so e.g. codegen can decide to // skip it if the value stored is null. This is the default behavior.
kEmitNotBeingReliedOn, // Emit the write barrier. This write barrier is being relied on and must be emitted.
kEmitBeingReliedOn, // Skip emitting the write barrier. This could be set because: // A) The write barrier is not needed (i.e. it is not a reference, or the value is the null // constant) // B) This write barrier was coalesced into another one so there's no need to emit it.
kDontEmit,
kLast = kDontEmit
};
std::ostream& operator<<(std::ostream& os, WriteBarrierKind rhs);
bool IsClonable() const override { returntrue; } bool CanBeMoved() const override { returntrue; } bool InstructionDataEquals([[maybe_unused]] const HInstruction* other) const override { returntrue;
} bool CanDoImplicitNullCheckOn([[maybe_unused]] HInstruction* obj) const override { // TODO: We can be smarter here. // Currently, unless the array is the result of NewArray, the array access is always // preceded by some form of null NullCheck necessary for the bounds check, usually // implicit null check on the ArrayLength input to BoundsCheck or Deoptimize for // dynamic BCE. There are cases when these could be removed to produce better code. // If we ever add optimizations to do so we should allow an implicit check here // (as long as the address falls in the first page). // // As an example of such fancy optimization, we could eliminate BoundsCheck for // a = cond ? new int[1] : null; // a[0]; // The Phi does not need bounds check for either input. returnfalse;
}
private: // We treat a String as an array, creating the HArrayGet from String.charAt() // intrinsic in the instruction simplifier. We can always determine whether // a particular HArrayGet is actually a String.charAt() by looking at the type // of the input but that requires holding the mutator lock, so we prefer to use // a flag, so that code generators don't need to do the locking. static constexpr size_t kFlagIsStringCharAt = kNumberOfGenericPackedBits; static constexpr size_t kNumberOfArrayGetPackedBits = kFlagIsStringCharAt + 1;
static_assert(kNumberOfArrayGetPackedBits <= HInstruction::kMaxNumberOfPackedBits, "Too many packed fields.");
};
class HArraySet final : public HExpression<3> { public:
HArraySet(HInstruction* array,
HInstruction* index,
HInstruction* value,
DataType::Type expected_component_type,
uint32_t dex_pc)
: HArraySet(array,
index,
value,
expected_component_type, // Make a best guess for side effects now, may be refined during SSA building.
ComputeSideEffects(GetComponentType(value->GetType(), expected_component_type)),
dex_pc) {
}
bool CanDoImplicitNullCheckOn([[maybe_unused]] HInstruction* obj) const override { // TODO: Same as for ArrayGet. returnfalse;
}
void ClearTypeCheck() {
SetPackedFlag<kFlagNeedsTypeCheck>(false); // Clear the `CanTriggerGC` flag too as we can only trigger a GC when doing a type check.
SetSideEffects(GetSideEffects().Exclusion(SideEffects::CanTriggerGC())); // Clear the environment too as we can only throw if we need a type check.
RemoveEnvironment();
}
static DataType::Type GetComponentType(DataType::Type value_type,
DataType::Type expected_component_type) { // The Dex format does not type floating point index operations. Since the // `expected_component_type` comes from SSA building and can therefore not // be correct, we also check what is the value type. If it is a floating // point type, we must use that type. return ((value_type == DataType::Type::kFloat32) || (value_type == DataType::Type::kFloat64))
? value_type
: expected_component_type;
}
void SetWriteBarrierKind(WriteBarrierKind kind) {
DCHECK(kind != WriteBarrierKind::kEmitNotBeingReliedOn)
<< "We shouldn't go back to the original value.";
DCHECK_IMPLIES(kind == WriteBarrierKind::kDontEmit,
GetWriteBarrierKind() != WriteBarrierKind::kEmitBeingReliedOn)
<< "If a write barrier was relied on by other write barriers, we cannot skip emitting it.";
SetPackedField<WriteBarrierKindField>(kind);
}
DECLARE_INSTRUCTION(ArraySet);
protected:
DEFAULT_COPY_CONSTRUCTOR(ArraySet);
private: static constexpr size_t kFieldExpectedComponentType = kNumberOfGenericPackedBits; static constexpr size_t kFieldExpectedComponentTypeSize =
MinimumBitsToStore(static_cast<size_t>(DataType::Type::kLast)); static constexpr size_t kFlagNeedsTypeCheck =
kFieldExpectedComponentType + kFieldExpectedComponentTypeSize; static constexpr size_t kFlagValueCanBeNull = kFlagNeedsTypeCheck + 1; // Cached information for the reference_type_info_ so that codegen // does not need to inspect the static type. static constexpr size_t kFlagStaticTypeOfArrayIsObjectArray = kFlagValueCanBeNull + 1; static constexpr size_t kWriteBarrierKind = kFlagStaticTypeOfArrayIsObjectArray + 1; static constexpr size_t kWriteBarrierKindSize =
MinimumBitsToStore(static_cast<size_t>(WriteBarrierKind::kLast)); static constexpr size_t kNumberOfArraySetPackedBits = kWriteBarrierKind + kWriteBarrierKindSize;
static_assert(kNumberOfArraySetPackedBits <= kMaxNumberOfPackedBits, "Too many packed fields."); using ExpectedComponentTypeField =
BitField<DataType::Type, kFieldExpectedComponentType, kFieldExpectedComponentTypeSize>;
using WriteBarrierKindField =
BitField<WriteBarrierKind, kWriteBarrierKind, kWriteBarrierKindSize>;
};
class HArrayLength final : public HExpression<1> { public:
HArrayLength(HInstruction* array, uint32_t dex_pc, bool is_string_length = false)
: HExpression(kArrayLength, DataType::Type::kInt32, SideEffects::None(), dex_pc) {
SetPackedFlag<kFlagIsStringLength>(is_string_length); // Note that arrays do not change length, so the instruction does not // depend on any write.
SetRawInputAt(0, array);
}
private: // We treat a String as an array, creating the HArrayLength from String.length() // or String.isEmpty() intrinsic in the instruction simplifier. We can always // determine whether a particular HArrayLength is actually a String.length() by // looking at the type of the input but that requires holding the mutator lock, so // we prefer to use a flag, so that code generators don't need to do the locking. static constexpr size_t kFlagIsStringLength = kNumberOfGenericPackedBits; static constexpr size_t kNumberOfArrayLengthPackedBits = kFlagIsStringLength + 1;
static_assert(kNumberOfArrayLengthPackedBits <= HInstruction::kMaxNumberOfPackedBits, "Too many packed fields.");
};
class HBoundsCheck final : public HExpression<2> { public: // `HBoundsCheck` can trigger GC, as it may call the `IndexOutOfBoundsException` // constructor. However it can only do it on a fatal slow path so execution never returns to the // instruction following the current one; thus 'SideEffects::None()' is used.
HBoundsCheck(HInstruction* index,
HInstruction* length,
uint32_t dex_pc, bool is_string_char_at = false)
: HExpression(kBoundsCheck, index->GetType(), SideEffects::None(), dex_pc) {
DCHECK_EQ(DataType::Type::kInt32, DataType::Kind(index->GetType()));
SetPackedFlag<kFlagIsStringCharAt>(is_string_char_at);
SetRawInputAt(0, index);
SetRawInputAt(1, length);
}
// True if the HSuspendCheck should not emit any code during codegen. It is // not possible to simply remove this instruction to disable codegen, as // other optimizations (e.g: CHAGuardVisitor::HoistGuard) depend on // HSuspendCheck being present in every loop. static constexpr size_t kFlagIsNoOp = kNumberOfGenericPackedBits; static constexpr size_t kNumberOfSuspendCheckPackedBits = kFlagIsNoOp + 1;
static_assert(kNumberOfSuspendCheckPackedBits <= HInstruction::kMaxNumberOfPackedBits, "Too many packed fields.");
private: // Only used for code generation, in order to share the same slow path between back edges // of a same loop.
SlowPathCode* slow_path_;
};
// Pseudo-instruction which doesn't generate any code. // If `emit_environment` is true, it can be used to generate an environment. It is used, for // example, to provide the native debugger with mapping information. It ensures that we can generate // line number and local variables at this point. class HNop : public HExpression<0> { public: explicit HNop(uint32_t dex_pc, bool needs_environment)
: HExpression<0>(kNop, SideEffects::None(), dex_pc), needs_environment_(needs_environment) {
}
/** *InstructiontoloadaClassobject.
*/ class HLoadClass final : public HInstruction { public: // Determines how to load the Class. enumclass LoadKind { // We cannot load this class. See HSharpening::SharpenLoadClass.
kInvalid = -1,
// Use the Class* from the method's own ArtMethod*.
kReferrersClass,
// Use PC-relative boot image Class* address that will be known at link time. // Used for boot image classes referenced by boot image code.
kBootImageLinkTimePcRelative,
// Load from a boot image entry in the .data.img.rel.ro using a PC-relative load. // Used for boot image classes referenced by apps in AOT-compiled code.
kBootImageRelRo,
// Load from an app image entry in the .data.img.rel.ro using a PC-relative load. // Used for app image classes referenced by apps in AOT-compiled code.
kAppImageRelRo,
// Load from an entry in the .bss section using a PC-relative load. // Used for classes outside boot image referenced by AOT-compiled app and boot image code.
kBssEntry,
// Load from an entry for public class in the .bss section using a PC-relative load. // Used for classes that were unresolved during AOT-compilation outside the literal // package of the compiling class. Such classes are accessible only if they are public // and the .bss entry shall therefore be filled only if the resolved class is public.
kBssEntryPublic,
// Load from an entry for package class in the .bss section using a PC-relative load. // Used for classes that were unresolved during AOT-compilation but within the literal // package of the compiling class. Such classes are accessible if they are public or // in the same package which, given the literal package match, requires only matching // defining class loader and the .bss entry shall therefore be filled only if at least // one of those conditions holds. Note that all code in an oat file belongs to classes // with the same defining class loader.
kBssEntryPackage,
// Use a known boot image Class* address, embedded in the code by the codegen. // Used for boot image classes referenced by apps in JIT-compiled code.
kJitBootImageAddress,
// Load from the root table associated with the JIT compiled method.
kJitTableAddress,
// Load using a simple runtime call. This is the fall-back load kind when // the codegen is unable to use another appropriate kind.
kRuntimeCall,
kLast = kRuntimeCall
};
HLoadClass(HCurrentMethod* current_method,
dex::TypeIndex type_index, const DexFile& dex_file,
Handle<mirror::Class> klass, bool is_referrers_class,
uint32_t dex_pc, bool needs_access_check)
: HInstruction(kLoadClass,
DataType::Type::kReference,
SideEffectsForArchRuntimeCalls(),
dex_pc),
special_input_(HUserRecord<HInstruction*>(current_method)),
type_index_(type_index),
dex_file_(dex_file),
klass_(klass) { // Referrers class should not need access check. We never inline unverified // methods so we can't possibly end up in this situation.
DCHECK_IMPLIES(is_referrers_class, !needs_access_check);
bool CanThrow() const override { return NeedsAccessCheck() ||
MustGenerateClinitCheck() || // If the class is in the boot or app image, the lookup in the runtime call cannot throw.
((GetLoadKind() == LoadKind::kRuntimeCall || NeedsBss()) && !IsInImage());
}
ReferenceTypeInfo GetLoadedClassRTI() { if (GetPackedFlag<kFlagValidLoadedClassRTI>()) { // Note: The is_exact flag from the return value should not be used. return ReferenceTypeInfo::CreateUnchecked(klass_, /* is_exact= */ true);
} else { return ReferenceTypeInfo::CreateInvalid();
}
}
// Loaded class RTI is marked as valid by RTP if the klass_ is admissible. void SetValidLoadedClassRTI() {
DCHECK(klass_ != nullptr);
SetPackedFlag<kFlagValidLoadedClassRTI>(true);
}
// The special input is the HCurrentMethod for kRuntimeCall or kReferrersClass. // For other load kinds it's empty or possibly some architecture-specific instruction // for PC-relative loads, i.e. kBssEntry* or kBootImageLinkTimePcRelative.
HUserRecord<HInstruction*> special_input_;
// A type index and dex file where the class can be accessed. The dex file can be: // - The compiling method's dex file if the class is defined there too. // - The compiling method's dex file if the class is referenced there. // - The dex file where the class is defined. When the load kind can only be // kBssEntry* or kRuntimeCall, we cannot emit code for this `HLoadClass`. const dex::TypeIndex type_index_; const DexFile& dex_file_;
// Note: defined outside class to see operator<<(., HLoadClass::LoadKind). inlinevoid HLoadClass::SetLoadKind(LoadKind load_kind) { // The load kind should be determined before inserting the instruction to the graph.
DCHECK(GetBlock() == nullptr);
DCHECK(GetEnvironment() == nullptr);
SetPackedField<LoadKindField>(load_kind); if (load_kind != LoadKind::kRuntimeCall && load_kind != LoadKind::kReferrersClass) {
special_input_ = HUserRecord<HInstruction*>(nullptr);
} if (!NeedsEnvironment()) {
SetSideEffects(SideEffects::None());
}
}
// Note: defined outside class to see operator<<(., HLoadClass::LoadKind). inlinevoid HLoadClass::AddSpecialInput(HInstruction* special_input) { // The special input is used for PC-relative loads on some architectures, // including literal pool loads, which are PC-relative too.
DCHECK(GetLoadKind() == LoadKind::kBootImageLinkTimePcRelative ||
GetLoadKind() == LoadKind::kBootImageRelRo ||
GetLoadKind() == LoadKind::kAppImageRelRo ||
GetLoadKind() == LoadKind::kBssEntry ||
GetLoadKind() == LoadKind::kBssEntryPublic ||
GetLoadKind() == LoadKind::kBssEntryPackage ||
GetLoadKind() == LoadKind::kJitBootImageAddress) << GetLoadKind();
DCHECK(special_input_.GetInstruction() == nullptr);
special_input_ = HUserRecord<HInstruction*>(special_input);
special_input->AddUseAt(GetBlock()->GetGraph()->GetAllocator(), this, 0);
}
class HLoadString final : public HInstruction { public: // Determines how to load the String. enumclass LoadKind { // Use PC-relative boot image String* address that will be known at link time. // Used for boot image strings referenced by boot image code.
kBootImageLinkTimePcRelative,
// Load from a boot image entry in the .data.img.rel.ro using a PC-relative load. // Used for boot image strings referenced by apps in AOT-compiled code.
kBootImageRelRo,
// Load from an app image entry in the .data.img.rel.ro using a PC-relative load. // Used for app image strings referenced by apps in AOT-compiled code.
kAppImageRelRo,
// Load from an entry in the .bss section using a PC-relative load. // Used for strings outside boot image referenced by AOT-compiled app and boot image code.
kBssEntry,
// Use a known boot image String* address, embedded in the code by the codegen. // Used for boot image strings referenced by apps in JIT-compiled code.
kJitBootImageAddress,
// Load from the root table associated with the JIT compiled method.
kJitTableAddress,
// Load using a simple runtime call. This is the fall-back load kind when // the codegen is unable to use another appropriate kind.
kRuntimeCall,
// Will call the runtime if we need to load the string through // the dex cache and the string is not guaranteed to be there yet. bool NeedsEnvironment() const override {
LoadKind load_kind = GetLoadKind(); if (load_kind == LoadKind::kBootImageLinkTimePcRelative ||
load_kind == LoadKind::kBootImageRelRo ||
load_kind == LoadKind::kAppImageRelRo ||
load_kind == LoadKind::kJitBootImageAddress ||
load_kind == LoadKind::kJitTableAddress) { returnfalse;
} returntrue;
}
// The special input is the HCurrentMethod for kRuntimeCall. // For other load kinds it's empty or possibly some architecture-specific instruction // for PC-relative loads, i.e. kBssEntry or kBootImageLinkTimePcRelative.
HUserRecord<HInstruction*> special_input_;
// Note: defined outside class to see operator<<(., HLoadString::LoadKind). inlinevoid HLoadString::SetLoadKind(LoadKind load_kind) { // The load kind should be determined before inserting the instruction to the graph.
DCHECK(GetBlock() == nullptr);
DCHECK(GetEnvironment() == nullptr);
DCHECK_EQ(GetLoadKind(), LoadKind::kRuntimeCall);
SetPackedField<LoadKindField>(load_kind); if (load_kind != LoadKind::kRuntimeCall) {
special_input_ = HUserRecord<HInstruction*>(nullptr);
} if (!NeedsEnvironment()) {
SetSideEffects(SideEffects::None());
}
}
// Note: defined outside class to see operator<<(., HLoadString::LoadKind). inlinevoid HLoadString::AddSpecialInput(HInstruction* special_input) { // The special input is used for PC-relative loads on some architectures, // including literal pool loads, which are PC-relative too.
DCHECK(GetLoadKind() == LoadKind::kBootImageLinkTimePcRelative ||
GetLoadKind() == LoadKind::kBootImageRelRo ||
GetLoadKind() == LoadKind::kAppImageRelRo ||
GetLoadKind() == LoadKind::kBssEntry ||
GetLoadKind() == LoadKind::kJitBootImageAddress) << GetLoadKind(); // HLoadString::GetInputRecords() returns an empty array at this point, // so use the GetInputRecords() from the base class to set the input record.
DCHECK(special_input_.GetInstruction() == nullptr);
special_input_ = HUserRecord<HInstruction*>(special_input);
special_input->AddUseAt(GetBlock()->GetGraph()->GetAllocator(), this, 0);
}
class HLoadMethodHandle final : public HInstruction { public:
HLoadMethodHandle(HCurrentMethod* current_method,
uint16_t method_handle_idx, const DexFile& dex_file,
uint32_t dex_pc)
: HInstruction(kLoadMethodHandle,
DataType::Type::kReference,
SideEffectsForArchRuntimeCalls(),
dex_pc),
special_input_(HUserRecord<HInstruction*>(current_method)),
method_handle_idx_(method_handle_idx),
dex_file_(dex_file) {
}
class HLoadMethodType final : public HInstruction { public: // Determines how to load the MethodType. enumclass LoadKind { // Load from an entry in the .bss section using a PC-relative load.
kBssEntry, // Load from the root table associated with the JIT compiled method.
kJitTableAddress, // Load using a single runtime call.
kRuntimeCall,
// Note: defined outside class to see operator<<(., HLoadMethodType::LoadKind). inlinevoid HLoadMethodType::SetLoadKind(LoadKind load_kind) { // The load kind should be determined before inserting the instruction to the graph.
DCHECK(GetBlock() == nullptr);
DCHECK(GetEnvironment() == nullptr);
DCHECK_EQ(GetLoadKind(), LoadKind::kRuntimeCall);
DCHECK_IMPLIES(GetLoadKind() == LoadKind::kJitTableAddress, GetMethodType() != nullptr);
SetPackedField<LoadKindField>(load_kind);
}
/** *PerformsaninitializationcheckonitsClassobjectinput.
*/ class HClinitCheck final : public HExpression<1> { public:
HClinitCheck(HLoadClass* constant, uint32_t dex_pc)
: HExpression(
kClinitCheck,
DataType::Type::kReference,
SideEffects::AllExceptGCDependency(), // Assume write/read on all fields/arrays.
dex_pc) {
SetRawInputAt(0, constant);
} // TODO: Make ClinitCheck clonable. bool CanBeMoved() const override { returntrue; } bool InstructionDataEquals([[maybe_unused]] const HInstruction* other) const override { returntrue;
}
bool NeedsEnvironment() const override { // May call runtime to initialize the class. returntrue;
}
WriteBarrierKind GetWriteBarrierKind() { return GetPackedField<WriteBarrierKindField>(); } void SetWriteBarrierKind(WriteBarrierKind kind) {
DCHECK(kind != WriteBarrierKind::kEmitNotBeingReliedOn)
<< "We shouldn't go back to the original value.";
DCHECK_IMPLIES(kind == WriteBarrierKind::kDontEmit,
GetWriteBarrierKind() != WriteBarrierKind::kEmitBeingReliedOn)
<< "If a write barrier was relied on by other write barriers, we cannot skip emitting it.";
SetPackedField<WriteBarrierKindField>(kind);
}
using WriteBarrierKindField =
BitField<WriteBarrierKind, kWriteBarrierKind, kWriteBarrierKindSize>;
};
class HStringBuilderAppend final : public HVariableInputSizeInstruction { public:
HStringBuilderAppend(HIntConstant* format,
uint32_t number_of_arguments,
uint32_t number_of_out_vregs, bool has_fp_args,
ArenaAllocator* allocator,
uint32_t dex_pc)
: HVariableInputSizeInstruction(
kStringBuilderAppend,
DataType::Type::kReference,
SideEffects::CanTriggerGC().Union( // The runtime call may read memory from inputs. It never writes outside // of the newly allocated result object or newly allocated helper objects, // except for float/double arguments where we reuse thread-local helper objects.
has_fp_args ? SideEffects::AllWritesAndReads() : SideEffects::AllReads()),
dex_pc,
allocator,
number_of_arguments + /* format */ 1u,
kArenaAllocInvokeInputs),
number_of_out_vregs_(number_of_out_vregs) {
DCHECK_GE(number_of_arguments, 1u); // There must be something to append.
SetRawInputAt(FormatIndex(), format);
}
// Return the number of arguments, excluding the format.
size_t GetNumberOfArguments() const {
DCHECK_GE(InputCount(), 1u); return InputCount() - 1u;
}
// Return the number of outgoing vregs.
uint32_t GetNumberOfOutVRegs() const { return number_of_out_vregs_; }
// Implicit part of move-exception which clears thread-local exception storage. // Must not be removed because the runtime expects the TLS to get cleared. class HClearException final : public HExpression<0> { public: explicit HClearException(uint32_t dex_pc = kNoDexPc)
: HExpression(kClearException, SideEffects::AllWrites(), dex_pc) {
}
/** *ImplementationstrategiesforthecodegeneratorofaHInstanceOf *or`HCheckCast`.
*/ enumclass TypeCheckKind { // private marker to avoid generate-operator-out.py from processing.
kUnresolvedCheck, // Check against an unresolved type.
kExactCheck, // Can do a single class compare.
kClassHierarchyCheck, // Can just walk the super class chain.
kAbstractClassCheck, // Can just walk the super class chain, starting one up.
kInterfaceCheck, // No optimization yet when checking against an interface.
kArrayObjectCheck, // Can just check if the array is not primitive.
kArrayCheck, // No optimization yet when checking against a generic array.
kBitstringCheck, // Compare the type check bitstring.
kLast = kArrayCheck
};
ReferenceTypeInfo GetTargetClassRTI() { if (GetPackedFlag<kFlagValidTargetClassRTI>()) { // Note: The is_exact flag from the return value should not be used. return ReferenceTypeInfo::CreateUnchecked(klass_, /* is_exact= */ true);
} else { return ReferenceTypeInfo::CreateInvalid();
}
}
// Target class RTI is marked as valid by RTP if the klass_ is admissible. void SetValidTargetClassRTI() {
DCHECK(klass_ != nullptr);
SetPackedFlag<kFlagValidTargetClassRTI>(true);
}
staticbool CanCallRuntime(TypeCheckKind check_kind) { // TODO: Re-evaluate now that mips codegen has been removed. return check_kind != TypeCheckKind::kExactCheck;
}
private: // Represents the top constraint that can_be_null_ cannot exceed (i.e. if this // is false then CanBeNull() cannot be true). static constexpr size_t kFlagUpperCanBeNull = kNumberOfGenericPackedBits; static constexpr size_t kFlagCanBeNull = kFlagUpperCanBeNull + 1; static constexpr size_t kNumberOfBoundTypePackedBits = kFlagCanBeNull + 1;
static_assert(kNumberOfBoundTypePackedBits <= kMaxNumberOfPackedBits, "Too many packed fields.");
// Encodes the most upper class that this instruction can have. In other words // it is always the case that GetUpperBound().IsSupertypeOf(GetReferenceType()). // It is used to bound the type in cases like: // if (x instanceof ClassX) { // // uper_bound_ will be ClassX // }
ReferenceTypeInfo upper_bound_;
};
// A constructor fence orders all prior stores to fields that could be accessed via a final field of // the specified object(s), with respect to any subsequent store that might "publish" // (i.e. make visible) the specified object to another thread. // // JLS 17.5.1 "Semantics of final fields" states that a freeze action happens // for all final fields (that were set) at the end of the invoked constructor. // // The constructor fence models the freeze actions for the final fields of an object // being constructed (semantically at the end of the constructor). Constructor fences // have a per-object affinity; two separate objects being constructed get two separate // constructor fences. // // (Note: that if calling a super-constructor or forwarding to another constructor, // the freezes would happen at the end of *that* constructor being invoked). // // The memory model guarantees that when the object being constructed is "published" after // constructor completion (i.e. escapes the current thread via a store), then any final field // writes must be observable on other threads (once they observe that publication). // // Further, anything written before the freeze, and read by dereferencing through the final field, // must also be visible (so final object field could itself have an object with non-final fields; // yet the freeze must also extend to them). // // Constructor example: // // class HasFinal { // final int field; Optimizing IR for <init>()V: // HasFinal() { // field = 123; HInstanceFieldSet(this, HasFinal.field, 123) // // freeze(this.field); HConstructorFence(this) // } HReturn // } // // HConstructorFence can serve double duty as a fence for new-instance/new-array allocations of // already-initialized classes; in that case the allocation must act as a "default-initializer" // of the object which effectively writes the class pointer "final field". // // For example, we can model default-initialiation as roughly the equivalent of the following: // // class Object { // private final Class header; // } // // Java code: Optimizing IR: // // T new_instance<T>() { // Object obj = allocate_memory(T.class.size); obj = HInvoke(art_quick_alloc_object, T) // obj.header = T.class; // header write is done by above call. // // freeze(obj.header) HConstructorFence(obj) // return (T)obj; // } // // See also: // * DexCompilationUnit::RequiresConstructorBarrier // * QuasiAtomic::ThreadFenceForConstructor // class HConstructorFence final : public HVariableInputSizeInstruction { // A fence has variable inputs because the inputs can be removed // after prepare_for_register_allocation phase. // (TODO: In the future a fence could freeze multiple objects // after merging two fences together.) public: // `fence_object` is the reference that needs to be protected for correct publication. // // It makes sense in the following situations: // * <init> constructors, it's the "this" parameter (i.e. HParameterValue, s.t. IsThis() == true). // * new-instance-like instructions, it's the return value (i.e. HNewInstance). // // After construction the `fence_object` becomes the 0th input. // This is not an input in a real sense, but just a convenient place to stash the information // about the associated object.
HConstructorFence(HInstruction* fence_object,
uint32_t dex_pc,
ArenaAllocator* allocator) // We strongly suspect there is not a more accurate way to describe the fine-grained reordering // constraints described in the class header. We claim that these SideEffects constraints // enforce a superset of the real constraints. // // The ordering described above is conservatively modeled with SideEffects as follows: // // * To prevent reordering of the publication stores: // ----> "Reads of objects" is the initial SideEffect. // * For every primitive final field store in the constructor: // ----> Union that field's type as a read (e.g. "Read of T") into the SideEffect. // * If there are any stores to reference final fields in the constructor: // ----> Use a more conservative "AllReads" SideEffect because any stores to any references // that are reachable from `fence_object` also need to be prevented for reordering // (and we do not want to do alias analysis to figure out what those stores are). // // In the implementation, this initially starts out as an "all reads" side effect; this is an // even more conservative approach than the one described above, and prevents all of the // above reordering without analyzing any of the instructions in the constructor. // // If in a later phase we discover that there are no writes to reference final fields, // we can refine the side effect to a smaller set of type reads (see above constraints).
: HVariableInputSizeInstruction(kConstructorFence,
SideEffects::AllReads(),
dex_pc,
allocator, /* number_of_inputs= */ 1,
kArenaAllocConstructorFenceInputs) {
DCHECK(fence_object != nullptr);
SetRawInputAt(0, fence_object);
}
// The object associated with this constructor fence. // // (Note: This will be null after the prepare_for_register_allocation phase, // as all constructor fence inputs are removed there).
HInstruction* GetFenceObject() const { return InputAt(0);
}
// Find all the HConstructorFence uses (`fence_use`) for `this` and: // - Delete `fence_use` from `this`'s use list. // - Delete `this` from `fence_use`'s inputs list. // - If the `fence_use` is dead, remove it from the graph. // // A fence is considered dead once it no longer has any uses // and all of the inputs are dead. // // This must *not* be called during/after prepare_for_register_allocation, // because that removes all the inputs to the fences but the fence is actually // still considered live. // // Returns how many HConstructorFence instructions were removed from graph. static size_t RemoveConstructorFences(HInstruction* instruction);
// Combine all inputs of `this` and `other` instruction and remove // `other` from the graph. // // Inputs are unique after the merge. // // Requirement: `this` must not be the same as `other. void Merge(HConstructorFence* other);
// Check if this constructor fence is protecting // an HNewInstance or HNewArray that is also the immediate // predecessor of `this`. // // If `ignore_inputs` is true, then the immediate predecessor doesn't need // to be one of the inputs of `this`. // // Returns the associated HNewArray or HNewInstance, // or null otherwise.
HInstruction* GetAssociatedAllocation(bool ignore_inputs = false);
// Instruction may go into runtime, so we need an environment. bool NeedsEnvironment() const override { returntrue; }
bool CanThrow() const override { // Verifier guarantees that monitor-exit cannot throw. // This is important because it allows the HGraphBuilder to remove // a dead throw-catch loop generated for `synchronized` blocks/methods. return IsEnter();
}
class HSelect final : public HExpression<3> { public:
HSelect(HInstruction* condition,
HInstruction* true_value,
HInstruction* false_value,
uint32_t dex_pc)
: HExpression(kSelect, HPhi::ToPhiType(true_value->GetType()), SideEffects::None(), dex_pc) {
DCHECK_EQ(HPhi::ToPhiType(true_value->GetType()), HPhi::ToPhiType(false_value->GetType()));
// First input must be `true_value` or `false_value` to allow codegens to // use the SameAsFirstInput allocation policy. We make it `false_value`, so // that architectures which implement HSelect as a conditional move also // will not need to invert the condition.
SetRawInputAt(0, false_value);
SetRawInputAt(1, true_value);
SetRawInputAt(2, condition);
}
// The parallel move resolver marks moves as "in-progress" by clearing the // destination (but not the source).
Location MarkPending() {
DCHECK(!IsPending());
Location dest = destination_;
destination_ = Location::NoLocation(); return dest;
}
// True if this blocks a move from the given location. bool Blocks(Location loc) const { return !IsEliminated() && source_.OverlapsWith(loc);
}
// A move is redundant if it's been eliminated, if its source and // destination are the same, or if its destination is unneeded. bool IsRedundant() const { return IsEliminated() || destination_.IsInvalid() || source_.Equals(destination_);
}
// We clear both operands to indicate move that's been eliminated. void Eliminate() {
source_ = destination_ = Location::NoLocation();
}
private:
Location source_;
Location destination_; // The type this move is for.
DataType::Type type_; // The instruction this move is assocatied with. Null when this move is // for moving an input in the expected locations of user (including a phi user). // This is only used in debug mode, to ensure we do not connect interval siblings // in the same parallel move.
HInstruction* instruction_;
};
private: // Specifies the bitwise operation, which will be then negated. const InstructionKind op_kind_;
};
// This instruction computes an intermediate address pointing in the 'middle' of an object. The // result pointer cannot be handled by GC, so extra care is taken to make sure that this value is // never used across anything that can trigger GC. // The result of this instruction is not a pointer in the sense of `DataType::Type::kreference`. // So we represent it by the type `DataType::Type::kInt`. class HIntermediateAddress final : public HExpression<2> { public:
HIntermediateAddress(HInstruction* base_address, HInstruction* offset, uint32_t dex_pc)
: HExpression(kIntermediateAddress,
DataType::Type::kInt32,
SideEffects::DependsOnGC(),
dex_pc) {
DCHECK_EQ(DataType::Size(DataType::Type::kInt32),
DataType::Size(DataType::Type::kReference))
<< "kPrimInt and kPrimNot have different sizes.";
SetRawInputAt(0, base_address);
SetRawInputAt(1, offset);
}
// Load a value from a constant table. The value of `index` must be below `num_entries`. // Used for optimizing `switch` statements that just feed a Phi with constants. class HLoadConstantTableEntry : public HInstruction { public:
HLoadConstantTableEntry(DataType::Type type,
HInstruction* index,
ArrayRef<const int64_t> entries,
ArenaAllocator* allocator,
uint32_t dex_pc = kNoDexPc)
: HInstruction(kLoadConstantTableEntry, type, SideEffects::None(), dex_pc),
entries_(allocator->AllocArray<int64_t>(entries.size(), kArenaAllocInstruction),
entries.size()) {
SetRawInputAt(0, index);
std::copy(entries.begin(), entries.end(), entries_.begin());
}
void AddSpecialInput(HInstruction* input) { // We allow only one special input.
DCHECK(!HasSpecialInput()); // Store the `input` directly. `SetRawInputAt()` cannot be used here because // `GetInputRecords()` does not include `inputs_[1]` while it contains null.
inputs_[1] = HUserRecord<HInstruction*>(input);
input->AddUseAt(GetBlock()->GetGraph()->GetAllocator(), this, 1u);
}
// Prevent this instruction from being moved. In particular, this instruction must not // move before the index range check created from the `HPackedSwitch`. bool CanBeMoved() const override { returnfalse; }
ALWAYS_INLINE void Dispatch(HInstruction* insn) {
HInstruction::InstructionKind kind; // Use `asm volatile` to prevent clang++ from optimizing the `kind = insn->GetKind()` // together with the `switch`. The simple expression can somehow derail the // `switch` optimization and result in a much worse compiled code. b/413605257 asmvolatile("" : "=r"(kind) : "0"(insn->GetKind()));
switch (kind) { #define DEFINE_DISPATCH_CASE(kind, super) \ case HInstruction::k##kind: \
Visit##kind(insn->As##kind()); \ break;
FOR_EACH_CONCRETE_INSTRUCTION(DEFINE_DISPATCH_CASE) #undef DEFINE_DISPATCH_CASE default: // Note: clang++ can optimize this `switch` to a virtual dispatch with indexed // load from the vtable using an adjusted `invoke->GetKind()` as the index. // However, a non-empty `default` or `case` causes clang++ to produce much // worse code, so we want to limit this check to debug builds only.
DCHECK(false) << "UNREACHABLE";
UNREACHABLE();
}
}
// Graph visitor class template that's using the Curiously Recurring Template Pattern to avoid // virtual dispatch in the visitor design pattern and allows inlining individual visit functions // if the compiler deems it beneficial. For further optimizations, see `Dispatch()`. template <typename T> class CRTPGraphVisitor { public: explicit CRTPGraphVisitor(HGraph* graph) : graph_(graph) {}
HGraph* GetGraph() const { return graph_; }
// The empty visit function that is the default target of visit method forwarding. void VisitInstruction([[maybe_unused]] HInstruction* instruction) {}
// Visit function declarations for both abstract and concrete instructions. These shall // not be defined. Instead, dispatch to these functions is forwarded, see `ForwardVisit()`. #define DECLARE_VISIT_INSTRUCTION(name, super) \ void Visit##name(H##name* instr);
FOR_EACH_INSTRUCTION(DECLARE_VISIT_INSTRUCTION) #undef DECLARE_VISIT_INSTRUCTION
// Dispatch the `insn` to the appropriate visit function based on `insn->GetKind()`. // // The template parameter `bool kReturnIsControlFlow`, if true, specifies that `Dispatch()` // shall return the compile-time evaluated result of `insn->IsControlFlow()` from each case // in the dispatch `switch`; `CRTPGraphVisitor::VisitNonPhiInstructions()` uses this for // additional optimization. Otherwise, it returns nothing (return type `void`). template <bool kReturnIsControlFlow = false>
ALWAYS_INLINE std::conditional_t<kReturnIsControlFlow, bool, void> Dispatch(HInstruction* insn) { // Evaluate target visit method for each instruction kind at compile time. If multiple // kinds redirect to the same visit function, select the first kind as `dispatch_kind`, // making the other cases in the second `switch` subject to dead code elimination. // Rely on jump-threading optimization to avoid the second `switch` and land directly // on the correct case based on the instruction kind dispatch from the first `switch`.
HInstruction::InstructionKind dispatch_kind = HInstruction::kLastInstructionKind; switch (insn->GetKind()) { #define DEFINE_CASE(kind, super) \ case HInstruction::k##kind: { \
constexpr auto visit = T::ForwardVisit(&T::Visit##kind); \ using I = decltype(ExtractInstructionType(visit)); \
static_assert(std::is_base_of_v<I, H##kind>); \
constexpr HInstruction::InstructionKind kDispatchKind = \
FindDispatchKind< \ /*kCheckIsControlFlow=*/ kReturnIsControlFlow, \
HInstruction::IsControlFlow(HInstruction::k##kind)>(visit); \
dispatch_kind = kDispatchKind; \ break; \
}
FOR_EACH_CONCRETE_INSTRUCTION(DEFINE_CASE) #undef DEFINE_CASE default:
DCHECK(false) << "UNREACHABLE"; // In debug build, check that this is unreachable.
UNREACHABLE();
} switch (dispatch_kind) { #define DEFINE_CASE(kind, super) \ case HInstruction::k##kind: { \
constexpr auto visit = T::ForwardVisit(&T::Visit##kind); \ using I = decltype(ExtractInstructionType(visit)); \
(down_cast<T*>(this)->*visit)(down_cast<I*>(insn)); \ if constexpr (kReturnIsControlFlow) { \ return HInstruction::IsControlFlow(HInstruction::k##kind); \
} else { \ return; \
} \
}
FOR_EACH_CONCRETE_INSTRUCTION(DEFINE_CASE) #undef DEFINE_CASE default:
LOG(FATAL) << "UNREACHABLE"; // Should be optimized away in both debug and release build.
UNREACHABLE();
}
}
// Visit the graph following basic block insertion order. void VisitInsertionOrder() { for (HBasicBlock* block : graph_->GetActiveBlocks()) {
down_cast<T*>(this)->VisitBasicBlock(block);
}
}
// Visit the graph following dominator tree reverse post-order.
ALWAYS_INLINE void VisitReversePostOrder() { for (HBasicBlock* block : graph_->GetReversePostOrder()) {
down_cast<T*>(this)->VisitBasicBlock(block);
}
}
// By default we visit block's instructions in normal (forward) order. // The derived class `T` can change that by providing replacement functions // `VisitBasicBlock()`, `VisitPhis()` or `VisitNonPhiInstructions()`.
ALWAYS_INLINE void VisitBasicBlock(HBasicBlock* block) {
down_cast<T*>(this)->VisitPhis(block);
down_cast<T*>(this)->VisitNonPhiInstructions(block);
}
protected:
ALWAYS_INLINE void VisitPhis(HBasicBlock* block) { static constexpr auto visit_phi = T::ForwardVisit(&T::VisitPhi); // Skip if `&T::VisitPhi` is forwarded to the empty `&CRTPGraphVisitor::VisitInstruction`. if constexpr (IsSameVisit(visit_phi, &CRTPGraphVisitor::VisitInstruction)) { return;
} for (HInstructionIteratorPrefetchNext it(block->GetPhis()); !it.Done(); it.Advance()) {
DCHECK(it.Current()->IsPhi());
(down_cast<T*>(this)->*visit_phi)(it.Current()->AsPhi());
}
}
ALWAYS_INLINE void VisitNonPhiInstructions(HBasicBlock* block) {
HInstruction* next = block->GetFirstInstruction();
DCHECK(next != nullptr); bool is_control_flow = false; do {
HInstruction* current = next;
DCHECK(!current->IsPhi());
next = current->GetNext(); // Each block ends with a control flow instruction, use that as the loop exit // condition. The `is_control_flow` is a Phi of compile-time constants in // `Dispatch<>()`, so this shall be optimized with jump-threading and visitors // for control-flow instructions shall be taken out of the loop. Empty visitors // for non-control-flow instructions shall be redirected to the start of the loop.
is_control_flow = Dispatch</*kReturnIsControlFlow=*/ true>(current);
DCHECK_EQ(is_control_flow, current->IsControlFlow()) << current->DebugName();
DCHECK_EQ(is_control_flow, next == nullptr) << current->DebugName(); // Visitors are not allowed to remove the next instruction from the block.
DCHECK_IMPLIES(next != nullptr, next->IsInBlock()) << current->DebugName();
} while (!is_control_flow);
}
// The default `ForwardVisit()` function template just returns the `visit` argument. // // Overloads for instruction visit functions declared directly in the `CRTPGraphVisitor` // are provided below and forward these functions to the visit functions for the // instruction superclass until we find one that's defined in or forwarded differently // by the derived class `T`, or reach the `VisitInstruction()`. // // Usually, the derived class `T` just defines visit functions for the instructions it // needs to process. However, it may also define its own replacement `ForwardVisit()` // functions that return member pointers to arbitrary visit functions that can take // relevant instructions by pointer (no need to call them `Visit*()`). template <typename U, typename I> static constexpr auto ForwardVisit(void (U::*visit)(I*)) { return visit;
}
template <typename U, typename I> static constexpr bool IsSameVisit(void (U::*visit)(I*), HInstruction::InstructionKind kind) { switch (kind) { #define DEFINE_CASE(kind, super) \ case HInstruction::k##kind: \ return IsSameVisit(visit, T::ForwardVisit(&T::Visit##kind));
FOR_EACH_CONCRETE_INSTRUCTION(DEFINE_CASE) #undef DEFINE_CASE default:
LOG(FATAL) << "Compile time error when executed in constexpr context.";
UNREACHABLE();
}
}
template <bool kCheckIsControlFlow, bool kIsControlFlow, typename U, typename I> static constexpr HInstruction::InstructionKind FindDispatchKind(void (U::*visit)(I*)) { for (uint32_t raw_kind = 0; raw_kind != HInstruction::kLastInstructionKind; ++raw_kind) {
HInstruction::InstructionKind kind = enum_cast<HInstruction::InstructionKind>(raw_kind); if (IsSameVisit(visit, kind) &&
(!kCheckIsControlFlow || kIsControlFlow == HInstruction::IsControlFlow(kind))) { return kind;
}
}
LOG(FATAL) << "Compile time error when executed in constexpr context.";
UNREACHABLE();
}
HGraph* graph_;
};
// Create a clone of the instruction, insert it into the graph; replace the old one with a new // and remove the old instruction.
HInstruction* ReplaceInstrOrPhiByClone(HInstruction* instr);
// Create a clone for each clonable instructions/phis and replace the original with the clone. // // Used for testing individual instruction cloner. class CloneAndReplaceInstructionVisitor final
: public CRTPGraphVisitor<CloneAndReplaceInstructionVisitor> { public: explicit CloneAndReplaceInstructionVisitor(HGraph* graph)
: CRTPGraphVisitor(graph), instr_replaced_by_clones_count_(0) {}
// Returns int64_t value of a properly typed constant. inline int64_t Int64FromConstant(HConstant* constant) { if (constant->IsIntConstant()) { return constant->AsIntConstant()->GetValue();
} elseif (constant->IsLongConstant()) { return constant->AsLongConstant()->GetValue();
} else {
DCHECK(constant->IsNullConstant()) << constant->DebugName(); return0;
}
}
// Returns true iff instruction is an integral constant (and sets value on success). inlinebool IsInt64AndGet(HInstruction* instruction, /*out*/ int64_t* value) { if (instruction->IsIntConstant()) {
*value = instruction->AsIntConstant()->GetValue(); returntrue;
} elseif (instruction->IsLongConstant()) {
*value = instruction->AsLongConstant()->GetValue(); returntrue;
} elseif (instruction->IsNullConstant()) {
*value = 0; returntrue;
} returnfalse;
}
// Returns true iff instruction is the given integral constant. inlinebool IsInt64Value(HInstruction* instruction, int64_t value) {
int64_t val = 0; return IsInt64AndGet(instruction, &val) && val == value;
}
// Returns true iff instruction is a zero bit pattern. inlinebool IsZeroBitPattern(HInstruction* instruction) { return instruction->IsConstant() && instruction->AsConstant()->IsZeroBitPattern();
}
// Detects an instruction that is >= 0. As long as the value is carried by // a single instruction, arithmetic wrap-around cannot occur. bool IsGEZero(HInstruction* instruction);
// // Helper functions that determine whether an instruction's environment must be // precise, i.e. whether the associated stack map should include vregister mappings. //
// Returns whether a graph requires precise HEnvironment. // // It is true in the following cases: // * Debuggable graph // when we want to observe the values / asynchronously deoptimize. // * Monitor operations // to allow dumping in a stack trace locked dex registers for non-debuggable code. inlinebool GraphNeedsPreciseEnvironment(HGraph* graph) { return graph->IsDebuggable() || graph->HasMonitorOperations();
}
// Returns whether instruction requires precise HEnvironment, independently from the graph // properties. // // It is true in the following cases: // * Deoptimization // when we need to obtain the values to restore actual vregisters for interpreter. // * On-stack-replacement (OSR) // when entering compiled for OSR code from the interpreter we need to initialize the compiled // code values with the values from the vregisters. Not all instructions in OSR mode // require a precise HEnvironment, only SuspendChecks in the non-inlined loops do. // * Method local catch blocks // a catch block must see the environment of the instruction from the same method that can // throw to this block. // * First instruction of a catch block // a catch block's first instruction is always Nop that requires precise HEnvironment as // it is used to emit catch block information. inlinebool InstructionNeedsPreciseEnvironment(HInstruction* instruction, bool osr) {
HBasicBlock* bb = instruction->GetBlock(); if (instruction->IsDeoptimize() || instruction->CanThrowIntoCatchBlock() || osr) { returntrue;
} if (bb->IsCatchBlock() && bb->GetFirstInstruction() == instruction) {
DCHECK(instruction->IsNop()); returntrue;
} returnfalse;
}
} // namespace art
#endif// ART_COMPILER_OPTIMIZING_NODES_H_
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.260Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-06-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.