blob: ad47f2eb6fc30bcf60551d8221603eb4a6589e2a [file] [log] [blame]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_COMPILER_OPTIMIZING_GRAPH_H_
#define ART_COMPILER_OPTIMIZING_GRAPH_H_
#include <algorithm>
#include <iosfwd>
#include <functional>
#include <optional>
#include <string>
#include "base/arena_allocator.h"
#include "base/arena_containers.h"
#include "base/arena_object.h"
#include "base/macros.h"
#include "base/stl_util.h"
#include "compilation_kind.h"
#include "data_type.h"
#include "dex/invoke_type.h"
#include "handle_cache.h"
#include "reference_type_info.h"
namespace art HIDDEN {
class BlockNamer;
class CodeGenerator;
class HBasicBlock;
class HConstant;
class HCurrentMethod;
class HDoubleConstant;
class HFloatConstant;
class HInstruction;
class HLongConstant;
class HIntConstant;
class HInvoke;
class HNullConstant;
class VariableSizedHandleScope;
enum GraphAnalysisResult {
kAnalysisSkipped,
kAnalysisInvalidBytecode,
kAnalysisFailThrowCatchLoop,
kAnalysisFailAmbiguousArrayOp,
kAnalysisFailIrreducibleLoopAndStringInit,
kAnalysisFailPhiEquivalentInOsr,
kAnalysisSuccess,
};
std::ostream& operator<<(std::ostream& os, GraphAnalysisResult ga);
// Control-flow graph of a method. Contains a list of basic blocks.
class HGraph : public ArenaObject<kArenaAllocGraph> {
public:
HGraph(ArenaAllocator* allocator,
ArenaStack* arena_stack,
VariableSizedHandleScope* handles,
const DexFile& dex_file,
uint32_t method_idx,
InstructionSet instruction_set,
InvokeType invoke_type,
bool dead_reference_safe = false,
bool debuggable = false,
CompilationKind compilation_kind = CompilationKind::kOptimized,
int start_instruction_id = 0)
: allocator_(allocator),
arena_stack_(arena_stack),
handle_cache_(handles),
blocks_(allocator->Adapter(kArenaAllocBlockList)),
reverse_post_order_(allocator->Adapter(kArenaAllocReversePostOrder)),
linear_order_(allocator->Adapter(kArenaAllocLinearOrder)),
entry_block_(nullptr),
exit_block_(nullptr),
number_of_vregs_(0),
number_of_in_vregs_(0),
temporaries_vreg_slots_(0),
has_bounds_checks_(false),
has_try_catch_(false),
has_monitor_operations_(false),
has_traditional_simd_(false),
has_predicated_simd_(false),
has_loops_(false),
has_irreducible_loops_(false),
has_direct_critical_native_call_(false),
has_always_throwing_invokes_(false),
dead_reference_safe_(dead_reference_safe),
debuggable_(debuggable),
current_instruction_id_(start_instruction_id),
dex_file_(dex_file),
method_idx_(method_idx),
invoke_type_(invoke_type),
in_ssa_form_(false),
number_of_cha_guards_(0),
instruction_set_(instruction_set),
cached_null_constant_(nullptr),
cached_int_constants_(std::less<int32_t>(), allocator->Adapter(kArenaAllocConstantsMap)),
cached_float_constants_(std::less<int32_t>(), allocator->Adapter(kArenaAllocConstantsMap)),
cached_long_constants_(std::less<int64_t>(), allocator->Adapter(kArenaAllocConstantsMap)),
cached_double_constants_(std::less<int64_t>(), allocator->Adapter(kArenaAllocConstantsMap)),
cached_current_method_(nullptr),
art_method_(nullptr),
compilation_kind_(compilation_kind),
useful_optimizing_(false),
cha_single_implementation_list_(allocator->Adapter(kArenaAllocCHA)) {
blocks_.reserve(kDefaultNumberOfBlocks);
}
std::ostream& Dump(std::ostream& os,
CodeGenerator* codegen,
std::optional<std::reference_wrapper<const BlockNamer>> namer = std::nullopt);
ArenaAllocator* GetAllocator() const { return allocator_; }
ArenaStack* GetArenaStack() const { return arena_stack_; }
HandleCache* GetHandleCache() { return &handle_cache_; }
const ArenaVector<HBasicBlock*>& GetBlocks() const { return blocks_; }
// An iterator to only blocks that are still actually in the graph (when
// blocks are removed they are replaced with 'nullptr' in GetBlocks to
// simplify block-id assignment and avoid memmoves in the block-list).
IterationRange<FilterNull<ArenaVector<HBasicBlock*>::const_iterator>> GetActiveBlocks() const {
return FilterOutNull(MakeIterationRange(GetBlocks()));
}
bool IsInSsaForm() const { return in_ssa_form_; }
void SetInSsaForm() { in_ssa_form_ = true; }
HBasicBlock* GetEntryBlock() const { return entry_block_; }
HBasicBlock* GetExitBlock() const { return exit_block_; }
bool HasExitBlock() const { return exit_block_ != nullptr; }
void SetEntryBlock(HBasicBlock* block) { entry_block_ = block; }
void SetExitBlock(HBasicBlock* block) { exit_block_ = block; }
void AddBlock(HBasicBlock* block);
void ComputeDominanceInformation();
void ClearDominanceInformation();
void ClearLoopInformation();
void FindBackEdges(/*out*/ BitVectorView<size_t> visited);
GraphAnalysisResult BuildDominatorTree();
GraphAnalysisResult RecomputeDominatorTree();
void SimplifyCFG();
void SimplifyCatchBlocks();
// Analyze all natural loops in this graph. Returns a code specifying that it
// was successful or the reason for failure. The method will fail if a loop
// is a throw-catch loop, i.e. the header is a catch block.
GraphAnalysisResult AnalyzeLoops() const;
// Iterate over blocks to compute try block membership. Needs reverse post
// order and loop information.
void ComputeTryBlockInformation();
// Inline this graph in `outer_graph`, replacing the given `invoke` instruction.
// Returns the instruction to replace the invoke expression or null if the
// invoke is for a void method. Note that the caller is responsible for replacing
// and removing the invoke instruction.
HInstruction* InlineInto(HGraph* outer_graph, HInvoke* invoke);
// Update the loop and try membership of `block`, which was spawned from `reference`.
// In case `reference` is a back edge, `replace_if_back_edge` notifies whether `block`
// should be the new back edge.
// `has_more_specific_try_catch_info` will be set to true when inlining a try catch.
void UpdateLoopAndTryInformationOfNewBlock(HBasicBlock* block,
HBasicBlock* reference,
bool replace_if_back_edge,
bool has_more_specific_try_catch_info = false);
// Need to add a couple of blocks to test if the loop body is entered and
// put deoptimization instructions, etc.
void TransformLoopHeaderForBCE(HBasicBlock* header);
// Adds a new loop directly after the loop with the given header and exit.
// Returns the new preheader.
HBasicBlock* TransformLoopForVectorization(HBasicBlock* header,
HBasicBlock* body,
HBasicBlock* exit);
// Removes `block` from the graph. Assumes `block` has been disconnected from
// other blocks and has no instructions or phis.
void DeleteDeadEmptyBlock(HBasicBlock* block);
// Splits the edge between `block` and `successor` while preserving the
// indices in the predecessor/successor lists. If there are multiple edges
// between the blocks, the lowest indices are used.
// Returns the new block which is empty and has the same dex pc as `successor`.
HBasicBlock* SplitEdge(HBasicBlock* block, HBasicBlock* successor);
void SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor);
void OrderLoopHeaderPredecessors(HBasicBlock* header);
// Transform a loop into a format with a single preheader.
//
// Each phi in the header should be split: original one in the header should only hold
// inputs reachable from the back edges and a single input from the preheader. The newly created
// phi in the preheader should collate the inputs from the original multiple incoming blocks.
//
// Loops in the graph typically have a single preheader, so this method is used to "repair" loops
// that no longer have this property.
void TransformLoopToSinglePreheaderFormat(HBasicBlock* header);
void SimplifyLoop(HBasicBlock* header);
ALWAYS_INLINE int32_t AllocateInstructionId();
int32_t GetCurrentInstructionId() const {
return current_instruction_id_;
}
void SetCurrentInstructionId(int32_t id) {
CHECK_GE(id, current_instruction_id_);
current_instruction_id_ = id;
}
void UpdateTemporariesVRegSlots(size_t slots) {
temporaries_vreg_slots_ = std::max(slots, temporaries_vreg_slots_);
}
size_t GetTemporariesVRegSlots() const {
DCHECK(!in_ssa_form_);
return temporaries_vreg_slots_;
}
void SetNumberOfVRegs(uint16_t number_of_vregs) {
number_of_vregs_ = number_of_vregs;
}
uint16_t GetNumberOfVRegs() const {
return number_of_vregs_;
}
void SetNumberOfInVRegs(uint16_t value) {
number_of_in_vregs_ = value;
}
uint16_t GetNumberOfInVRegs() const {
return number_of_in_vregs_;
}
uint16_t GetNumberOfLocalVRegs() const {
DCHECK(!in_ssa_form_);
return number_of_vregs_ - number_of_in_vregs_;
}
const ArenaVector<HBasicBlock*>& GetReversePostOrder() const {
return reverse_post_order_;
}
ArrayRef<HBasicBlock* const> GetReversePostOrderSkipEntryBlock() const {
DCHECK(GetReversePostOrder()[0] == entry_block_);
return ArrayRef<HBasicBlock* const>(GetReversePostOrder()).SubArray(1);
}
IterationRange<ArenaVector<HBasicBlock*>::const_reverse_iterator> GetPostOrder() const {
return ReverseRange(GetReversePostOrder());
}
const ArenaVector<HBasicBlock*>& GetLinearOrder() const {
return linear_order_;
}
IterationRange<ArenaVector<HBasicBlock*>::const_reverse_iterator> GetLinearPostOrder() const {
return ReverseRange(GetLinearOrder());
}
bool HasBoundsChecks() const {
return has_bounds_checks_;
}
void SetHasBoundsChecks(bool value) {
has_bounds_checks_ = value;
}
// Is the code known to be robust against eliminating dead references
// and the effects of early finalization?
bool IsDeadReferenceSafe() const { return dead_reference_safe_; }
void MarkDeadReferenceUnsafe() { dead_reference_safe_ = false; }
bool IsDebuggable() const { return debuggable_; }
// Returns a constant of the given type and value. If it does not exist
// already, it is created and inserted into the graph. This method is only for
// integral types.
HConstant* GetConstant(DataType::Type type, int64_t value);
// TODO: This is problematic for the consistency of reference type propagation
// because it can be created anytime after the pass and thus it will be left
// with an invalid type.
HNullConstant* GetNullConstant();
HIntConstant* GetIntConstant(int32_t value);
HLongConstant* GetLongConstant(int64_t value);
HFloatConstant* GetFloatConstant(float value);
HDoubleConstant* GetDoubleConstant(double value);
HCurrentMethod* GetCurrentMethod();
const DexFile& GetDexFile() const {
return dex_file_;
}
uint32_t GetMethodIdx() const {
return method_idx_;
}
// Get the method name (without the signature), e.g. "<init>"
const char* GetMethodName() const;
// Get the pretty method name (class + name + optionally signature).
std::string PrettyMethod(bool with_signature = true) const;
InvokeType GetInvokeType() const {
return invoke_type_;
}
InstructionSet GetInstructionSet() const {
return instruction_set_;
}
bool IsCompilingOsr() const { return compilation_kind_ == CompilationKind::kOsr; }
bool IsCompilingBaseline() const { return compilation_kind_ == CompilationKind::kBaseline; }
CompilationKind GetCompilationKind() const { return compilation_kind_; }
ArenaSet<ArtMethod*>& GetCHASingleImplementationList() {
return cha_single_implementation_list_;
}
// In case of OSR we intend to use SuspendChecks as an entry point to the
// function; for debuggable graphs we might deoptimize to interpreter from
// SuspendChecks. In these cases we should always generate code for them.
bool SuspendChecksAreAllowedToNoOp() const {
return !IsDebuggable() && !IsCompilingOsr();
}
void AddCHASingleImplementationDependency(ArtMethod* method) {
cha_single_implementation_list_.insert(method);
}
bool HasShouldDeoptimizeFlag() const {
return number_of_cha_guards_ != 0 || debuggable_;
}
bool HasTryCatch() const { return has_try_catch_; }
void SetHasTryCatch(bool value) { has_try_catch_ = value; }
bool HasMonitorOperations() const { return has_monitor_operations_; }
void SetHasMonitorOperations(bool value) { has_monitor_operations_ = value; }
bool HasTraditionalSIMD() { return has_traditional_simd_; }
void SetHasTraditionalSIMD(bool value) { has_traditional_simd_ = value; }
bool HasPredicatedSIMD() { return has_predicated_simd_; }
void SetHasPredicatedSIMD(bool value) { has_predicated_simd_ = value; }
bool HasSIMD() const { return has_traditional_simd_ || has_predicated_simd_; }
bool HasLoops() const { return has_loops_; }
void SetHasLoops(bool value) { has_loops_ = value; }
bool HasIrreducibleLoops() const { return has_irreducible_loops_; }
void SetHasIrreducibleLoops(bool value) { has_irreducible_loops_ = value; }
bool HasDirectCriticalNativeCall() const { return has_direct_critical_native_call_; }
void SetHasDirectCriticalNativeCall(bool value) { has_direct_critical_native_call_ = value; }
bool HasAlwaysThrowingInvokes() const { return has_always_throwing_invokes_; }
void SetHasAlwaysThrowingInvokes(bool value) { has_always_throwing_invokes_ = value; }
ArtMethod* GetArtMethod() const { return art_method_; }
void SetArtMethod(ArtMethod* method) { art_method_ = method; }
void SetProfilingInfo(ProfilingInfo* info) { profiling_info_ = info; }
ProfilingInfo* GetProfilingInfo() const { return profiling_info_; }
ReferenceTypeInfo GetInexactObjectRti() {
return ReferenceTypeInfo::Create(handle_cache_.GetObjectClassHandle(), /* is_exact= */ false);
}
uint32_t GetNumberOfCHAGuards() const { return number_of_cha_guards_; }
void SetNumberOfCHAGuards(uint32_t num) { number_of_cha_guards_ = num; }
void IncrementNumberOfCHAGuards() { number_of_cha_guards_++; }
void SetUsefulOptimizing() { useful_optimizing_ = true; }
bool IsUsefulOptimizing() const { return useful_optimizing_; }
private:
static const size_t kDefaultNumberOfBlocks = 8u;
void RemoveDeadBlocksInstructionsAsUsersAndDisconnect(BitVectorView<const size_t> visited) const;
void RemoveDeadBlocks(BitVectorView<const size_t> visited);
template <class InstructionType, typename ValueType>
InstructionType* CreateConstant(ValueType value,
ArenaSafeMap<ValueType, InstructionType*>* cache);
void InsertConstant(HConstant* instruction);
// Cache a float constant into the graph. This method should only be
// called by the SsaBuilder when creating "equivalent" instructions.
void CacheFloatConstant(HFloatConstant* constant);
// See CacheFloatConstant comment.
void CacheDoubleConstant(HDoubleConstant* constant);
ArenaAllocator* const allocator_;
ArenaStack* const arena_stack_;
HandleCache handle_cache_;
// List of blocks in insertion order.
ArenaVector<HBasicBlock*> blocks_;
// List of blocks to perform a reverse post order tree traversal.
ArenaVector<HBasicBlock*> reverse_post_order_;
// List of blocks to perform a linear order tree traversal. Unlike the reverse
// post order, this order is not incrementally kept up-to-date.
ArenaVector<HBasicBlock*> linear_order_;
HBasicBlock* entry_block_;
HBasicBlock* exit_block_;
// The number of virtual registers in this method. Contains the parameters.
uint16_t number_of_vregs_;
// The number of virtual registers used by parameters of this method.
uint16_t number_of_in_vregs_;
// Number of vreg size slots that the temporaries use (used in baseline compiler).
size_t temporaries_vreg_slots_;
// Flag whether there are bounds checks in the graph. We can skip
// BCE if it's false.
bool has_bounds_checks_;
// Flag whether there are try/catch blocks in the graph. We will skip
// try/catch-related passes if it's false.
bool has_try_catch_;
// Flag whether there are any HMonitorOperation in the graph. If yes this will mandate
// DexRegisterMap to be present to allow deadlock analysis for non-debuggable code.
bool has_monitor_operations_;
// Flags whether SIMD (traditional or predicated) instructions appear in the graph.
// If either is true, the code generators may have to be more careful spilling the wider
// contents of SIMD registers.
bool has_traditional_simd_;
bool has_predicated_simd_;
// Flag whether there are any loops in the graph. We can skip loop
// optimization if it's false.
bool has_loops_;
// Flag whether there are any irreducible loops in the graph.
bool has_irreducible_loops_;
// Flag whether there are any direct calls to native code registered
// for @CriticalNative methods.
bool has_direct_critical_native_call_;
// Flag whether the graph contains invokes that always throw.
bool has_always_throwing_invokes_;
// Is the code known to be robust against eliminating dead references
// and the effects of early finalization? If false, dead reference variables
// are kept if they might be visible to the garbage collector.
// Currently this means that the class was declared to be dead-reference-safe,
// the method accesses no reachability-sensitive fields or data, and the same
// is true for any methods that were inlined into the current one.
bool dead_reference_safe_;
// Indicates whether the graph should be compiled in a way that
// ensures full debuggability. If false, we can apply more
// aggressive optimizations that may limit the level of debugging.
const bool debuggable_;
// The current id to assign to a newly added instruction. See HInstruction.id_.
int32_t current_instruction_id_;
// The dex file from which the method is from.
const DexFile& dex_file_;
// The method index in the dex file.
const uint32_t method_idx_;
// If inlined, this encodes how the callee is being invoked.
const InvokeType invoke_type_;
// Whether the graph has been transformed to SSA form. Only used
// in debug mode to ensure we are not using properties only valid
// for non-SSA form (like the number of temporaries).
bool in_ssa_form_;
// Number of CHA guards in the graph. Used to short-circuit the
// CHA guard optimization pass when there is no CHA guard left.
uint32_t number_of_cha_guards_;
const InstructionSet instruction_set_;
// Cached constants.
HNullConstant* cached_null_constant_;
ArenaSafeMap<int32_t, HIntConstant*> cached_int_constants_;
ArenaSafeMap<int32_t, HFloatConstant*> cached_float_constants_;
ArenaSafeMap<int64_t, HLongConstant*> cached_long_constants_;
ArenaSafeMap<int64_t, HDoubleConstant*> cached_double_constants_;
HCurrentMethod* cached_current_method_;
// The ArtMethod this graph is for. Note that for AOT, it may be null,
// for example for methods whose declaring class could not be resolved
// (such as when the superclass could not be found).
ArtMethod* art_method_;
// The `ProfilingInfo` associated with the method being compiled.
ProfilingInfo* profiling_info_;
// How we are compiling the graph: either optimized, osr, or baseline.
// For osr, we will make all loops seen as irreducible and emit special
// stack maps to mark compiled code entries which the interpreter can
// directly jump to.
const CompilationKind compilation_kind_;
// Whether after compiling baseline it is still useful re-optimizing this
// method.
bool useful_optimizing_;
// List of methods that are assumed to have single implementation.
ArenaSet<ArtMethod*> cha_single_implementation_list_;
friend class SsaBuilder; // For caching constants.
friend class SsaLivenessAnalysis; // For the linear order.
friend class HInliner; // For the reverse post order.
ART_FRIEND_TEST(GraphTest, IfSuccessorSimpleJoinBlock1);
DISALLOW_COPY_AND_ASSIGN(HGraph);
};
} // namespace art
#endif // ART_COMPILER_OPTIMIZING_GRAPH_H_