/* * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. *
*/
class FpuStackAllocator; class IRScopeDebugInfo; class Interval; class IntervalWalker; class LIRGenerator; class LinearScan; class MoveResolver; class Range;
enum IntervalUseKind { // priority of use kinds must be ascending
noUse = 0,
loopEndMarker = 1,
shouldHaveRegister = 2,
mustHaveRegister = 3,
firstValidKind = 1,
lastValidKind = 3
};
enum IntervalKind {
fixedKind = 0, // interval pre-colored by LIR_Generator
anyKind = 1, // no register/memory allocated by LIR_Generator
nofKinds,
firstKind = fixedKind
};
// during linear scan an interval is in one of four states in enum IntervalState {
unhandledState = 0, // unhandled state (not processed yet)
activeState = 1, // life and is in a physical register
inactiveState = 2, // in a life time hole and is in a physical register
handledState = 3, // spilled or not life again
invalidState = -1
};
enum IntervalSpillState {
noDefinitionFound, // starting state of calculation: no definition found yet
oneDefinitionFound, // one definition has already been found. // Note: two consecutive definitions are treated as one (e.g. consecutive move and add because of two-operand LIR form) // the position of this definition is stored in _definition_pos
oneMoveInserted, // one spill move has already been inserted.
storeAtDefinition, // the interval should be stored immediately after its definition because otherwise // there would be multiple redundant stores
startInMemory, // the interval starts in memory (e.g. method parameter), so a store is never necessary
noOptimization // the interval has more then one definition (e.g. resulting from phi moves), so stores to memory are not optimized
};
class LinearScan : public CompilationResourceObj { // declare classes used by LinearScan as friends because they // need a wide variety of functions declared here // // Only the small interface to the rest of the compiler is public friendclass Interval; friendclass IntervalWalker; friendclass LinearScanWalker; friendclass FpuStackAllocator; friendclass MoveResolver; friendclass LinearScanStatistic; friendclass LinearScanTimers; friendclass RegisterVerifier;
BlockList _cached_blocks; // cached list with all blocks in linear-scan order (only correct if original list keeps unchanged) int _num_virtual_regs; // number of virtual registers (without new registers introduced because of splitting intervals) bool _has_fpu_registers; // true if this method uses any floating point registers (and so fpu stack allocation is necessary) int _num_calls; // total number of calls in this method int _max_spills; // number of stack slots used for intervals allocated to memory int _unused_spill_slot; // unused spill slot for a single-word value because of alignment of a double-word value
IntervalList _intervals; // mapping from register number to interval
IntervalList* _new_intervals_from_allocation; // list with all intervals created during allocation when an existing interval is split
IntervalArray* _sorted_intervals; // intervals sorted by Interval::from() bool _needs_full_resort; // set to true if an Interval::from() is changed and _sorted_intervals must be resorted
LIR_OpArray _lir_ops; // mapping from LIR_Op id to LIR_Op node
BlockBeginArray _block_of_op; // mapping from LIR_Op id to the BlockBegin containing this instruction
ResourceBitMap _has_info; // bit set for each LIR_Op id that has a CodeEmitInfo
ResourceBitMap _has_call; // bit set for each LIR_Op id that destroys all caller save registers
BitMap2D _interval_in_loop; // bit set for each virtual register that is contained in each loop
// cached debug info to prevent multiple creation of same object // TODO: cached scope values for registers could be static
ScopeValueArray _scope_value_cache;
// spill move optimization: eliminate moves from register to stack if // stack slot is known to be correct void change_spill_definition_pos(Interval* interval, int def_pos); void change_spill_state(Interval* interval, int spill_pos); staticbool must_store_at_definition(const Interval* i); void eliminate_spill_moves();
// Phase 1: number all instructions in all blocks void number_instructions();
// Phase 2: compute local live sets separately for each block // (sets live_gen and live_kill for each block) // // helper methods used by compute_local_live_sets() void set_live_gen_kill(Value value, LIR_Op* op, BitMap& live_gen, BitMap& live_kill);
void compute_local_live_sets();
// Phase 3: perform a backward dataflow analysis to compute global live sets // (sets live_in and live_out for each block) void compute_global_live_sets();
// Phase 4: build intervals // (fills the list _intervals) // // helper methods used by build_intervals() void add_use (Value value, int from, int to, IntervalUseKind use_kind);
void add_def (LIR_Opr opr, int def_pos, IntervalUseKind use_kind); void add_use (LIR_Opr opr, int from, int to, IntervalUseKind use_kind); void add_temp(LIR_Opr opr, int temp_pos, IntervalUseKind use_kind);
void add_def (int reg_num, int def_pos, IntervalUseKind use_kind, BasicType type); void add_use (int reg_num, int from, int to, IntervalUseKind use_kind, BasicType type); void add_temp(int reg_num, int temp_pos, IntervalUseKind use_kind, BasicType type);
// Add platform dependent kills for particular LIR ops. Can be used // to add platform dependent behaviour for some operations. void pd_add_temps(LIR_Op* op);
// Phase 6: resolve data flow // (insert moves at edges between blocks if intervals have been split) // // helper functions for resolve_data_flow()
Interval* split_child_at_op_id(Interval* interval, int op_id, LIR_OpVisitState::OprMode mode);
Interval* interval_at_block_begin(BlockBegin* block, int reg_num);
Interval* interval_at_block_end(BlockBegin* block, int reg_num);
Interval* interval_at_op_id(int reg_num, int op_id); void resolve_collect_mappings(BlockBegin* from_block, BlockBegin* to_block, MoveResolver &move_resolver); void resolve_find_insert_pos(BlockBegin* from_block, BlockBegin* to_block, MoveResolver &move_resolver); void resolve_data_flow();
void resolve_exception_entry(BlockBegin* block, int reg_num, MoveResolver &move_resolver); void resolve_exception_entry(BlockBegin* block, MoveResolver &move_resolver); void resolve_exception_edge(XHandler* handler, int throwing_op_id, int reg_num, Phi* phi, MoveResolver &move_resolver); void resolve_exception_edge(XHandler* handler, int throwing_op_id, MoveResolver &move_resolver); void resolve_exception_handlers();
// Phase 7: assign register numbers back to LIR // (includes computation of debug information and oop maps) // // helper functions for assign_reg_num()
VMReg vm_reg_for_interval(Interval* interval);
VMReg vm_reg_for_operand(LIR_Opr opr);
int append_scope_value_for_constant(LIR_Opr opr, GrowableArray<ScopeValue*>* scope_values); int append_scope_value_for_operand(LIR_Opr opr, GrowableArray<ScopeValue*>* scope_values); int append_scope_value(int op_id, Value value, GrowableArray<ScopeValue*>* scope_values);
#ifdef ASSERT // verification functions for allocation // (check that all intervals have a correct register and that no registers are overwritten) void verify(); void verify_intervals(); void verify_no_oops_in_fixed_intervals(); void verify_constants(); void verify_registers(); #endif
public: // creation
LinearScan(IR* ir, LIRGenerator* gen, FrameMap* frame_map);
// main entry function: perform linear scan register allocation void do_linear_scan();
// accessors used by Compilation int max_spills() const { return _max_spills; } int num_calls() const { assert(_num_calls >= 0, "not set"); return _num_calls; }
// Used for debugging
Interval* find_interval_at(int reg_num) const; #endif
};
// Helper class for ordering moves that are inserted at the same position in the LIR // When moves between registers are inserted, it is important that the moves are // ordered such that no register is overwritten. So moves from register to stack // are processed prior to moves from stack to register. When moves have circular // dependencies, a temporary stack slot is used to break the circle. // The same logic is used in the LinearScanWalker and in LinearScan during resolve_data_flow // and therefore factored out in a separate class class MoveResolver: public StackObj { private:
LinearScan* _allocator;
LIR_List* _insert_list; int _insert_idx;
LIR_InsertionBuffer _insertion_buffer; // buffer where moves are inserted
IntervalList _mapping_from;
LIR_OprList _mapping_from_opr;
IntervalList _mapping_to; bool _multiple_reads_allowed; int _register_blocked[LinearScan::nof_regs];
int register_blocked(int reg) { assert(reg >= 0 && reg < LinearScan::nof_regs, "out of bounds"); return _register_blocked[reg]; } void set_register_blocked(int reg, int direction) { assert(reg >= 0 && reg < LinearScan::nof_regs, "out of bounds"); assert(direction == 1 || direction == -1, "out of bounds"); _register_blocked[reg] += direction; }
// for testing void print(outputStream* out = tty) const PRODUCT_RETURN;
};
// Interval is an ordered list of disjoint ranges.
// For pre-colored double word LIR_Oprs, one interval is created for // the low word register and one is created for the hi word register. // On Intel for FPU double registers only one interval is created. At // all times assigned_reg contains the reg. number of the physical // register.
// For LIR_Opr in virtual registers a single interval can represent // single and double word values. When a physical register is // assigned to the interval, assigned_reg contains the // phys. reg. number and for double word values assigned_regHi the // phys. reg. number of the hi word if there is any. For spilled // intervals assigned_reg contains the stack index. assigned_regHi is // always -1.
class Interval : public CompilationResourceObj { private: static Interval* _end; // sentinel (interval with only range Range::end())
int _reg_num;
BasicType _type; // valid only for virtual registers
Range* _first; // sorted list of Ranges
intStack _use_pos_and_kinds; // sorted list of use-positions and their according use-kinds
Range* _current; // interval iteration: the current Range
Interval* _next; // interval iteration: sorted list of Intervals (ends with sentinel)
IntervalState _state; // interval iteration: to which set belongs this interval
int _assigned_reg; int _assigned_regHi;
int _cached_to; // cached value: to of last range (-1: not cached)
LIR_Opr _cached_opr;
VMReg _cached_vm_reg;
Interval* _split_parent; // the original interval where this interval is derived from
IntervalList* _split_children; // list of all intervals that are split off from this interval (only available for split parents)
Interval* _current_split_child; // the current split child that has been active or inactive last (always stored in split parents)
int _canonical_spill_slot; // the stack slot where all split parts of this interval are spilled to (always stored in split parents) bool _insert_move_when_activated; // true if move is inserted between _current_split_child and this interval when interval gets active the first time
IntervalSpillState _spill_state; // for spill move optimization int _spill_definition_pos; // position where the interval is defined (if defined only once)
Interval* _register_hint; // this interval should be in the same register as the hint interval
int calc_to();
Interval* new_split_child(); public:
Interval(int reg_num);
// for spill optimization
IntervalSpillState spill_state() const { return split_parent()->_spill_state; } int spill_definition_pos() const { return split_parent()->_spill_definition_pos; } void set_spill_state(IntervalSpillState state) { assert(state >= spill_state(), "state cannot decrease"); split_parent()->_spill_state = state; } void set_spill_definition_pos(int pos) { assert(spill_definition_pos() == -1, "cannot set the position twice"); split_parent()->_spill_definition_pos = pos; } // returns true if this interval has a shadow copy on the stack that is always correct bool always_in_memory() const { return split_parent()->_spill_state == storeAtDefinition || split_parent()->_spill_state == startInMemory; }
// caching of values that take time to compute and are used multiple times
LIR_Opr cached_opr() const { return _cached_opr; }
VMReg cached_vm_reg() const { return _cached_vm_reg; } void set_cached_opr(LIR_Opr opr) { _cached_opr = opr; } void set_cached_vm_reg(VMReg reg) { _cached_vm_reg = reg; }
// access to use positions int first_usage(IntervalUseKind min_use_kind) const; // id of the first operation requiring this interval in a register int next_usage(IntervalUseKind min_use_kind, int from) const; // id of next usage seen from the given position int next_usage_exact(IntervalUseKind exact_use_kind, int from) const; int previous_usage(IntervalUseKind min_use_kind, int from) const;
// printing #ifndef PRODUCT void print() const { print_on(tty); } void print_on(outputStream* out) const {
print_on(out, false);
} // Special version for compatibility with C1 Visualizer. void print_on(outputStream* out, bool is_cfg_printer) const;
// Used for debugging void print_parent() const; void print_children() const; #endif
};
class IntervalWalker : public CompilationResourceObj { protected:
Compilation* _compilation;
LinearScan* _allocator;
Interval* _unhandled_first[nofKinds]; // sorted list of intervals, not life before the current position
Interval* _active_first [nofKinds]; // sorted list of intervals, life at the current position
Interval* _inactive_first [nofKinds]; // sorted list of intervals, intervals in a life time hole at the current position
Interval* _current; // the current interval coming from unhandled list int _current_position; // the current position (intercept point through the intervals)
IntervalKind _current_kind; // and whether it is fixed_kind or any_kind.
// activate_current() is called when an unhandled interval becomes active (in current(), current_kind()). // Return false if current() should not be moved the active interval list. // It is safe to append current to any interval list but the unhandled list. virtualbool activate_current() { returntrue; }
// This method is called whenever an interval moves from one interval list to another to print some // information about it and its state change if TraceLinearScanLevel is set appropriately.
DEBUG_ONLY(void interval_moved(Interval* interval, IntervalKind kind, IntervalState from, IntervalState to);)
// active contains the intervals that are live after the lir_op void walk_to(int lir_op_id); // active contains the intervals that are live before the lir_op void walk_before(int lir_op_id) { walk_to(lir_op_id-1); } // walk through all intervals void walk() { walk_to(max_jint); }
int current_position() { return _current_position; }
};
// The actual linear scan register allocator class LinearScanWalker : public IntervalWalker { enum {
any_reg = LinearScan::any_reg
};
private: int _first_reg; // the reg. number of the first phys. register int _last_reg; // the reg. nmber of the last phys. register int _num_phys_regs; // required by current interval bool _adjacent_regs; // have lo/hi words of phys. regs be adjacent
int _use_pos[LinearScan::nof_regs]; int _block_pos[LinearScan::nof_regs];
IntervalList* _spill_intervals[LinearScan::nof_regs];
MoveResolver _move_resolver; // for ordering spill moves
// accessors mapped to same functions in class LinearScan int block_count() const { return allocator()->block_count(); }
BlockBegin* block_at(int idx) const { return allocator()->block_at(idx); }
BlockBegin* block_of_op_with_id(int op_id) const { return allocator()->block_of_op_with_id(op_id); }
void init_use_lists(bool only_process_use_pos); void exclude_from_use(int reg); void exclude_from_use(Interval* i); void set_use_pos(int reg, Interval* i, int use_pos, bool only_process_use_pos); void set_use_pos(Interval* i, int use_pos, bool only_process_use_pos); void set_block_pos(int reg, Interval* i, int block_pos); void set_block_pos(Interval* i, int block_pos);
void insert_move(int op_id, Interval* src_it, Interval* dst_it); int find_optimal_split_pos(BlockBegin* min_block, BlockBegin* max_block, int max_split_pos); int find_optimal_split_pos(Interval* it, int min_split_pos, int max_split_pos, bool do_loop_optimization); void split_before_usage(Interval* it, int min_split_pos, int max_split_pos); void split_for_spilling(Interval* it); void split_stack_interval(Interval* it); void split_when_partial_register_available(Interval* it, int register_available_until); void split_and_spill_interval(Interval* it);
int find_free_reg(int reg_needed_until, int interval_to, int hint_reg, int ignore_reg, bool* need_split); int find_free_double_reg(int reg_needed_until, int interval_to, int hint_reg, bool* need_split); bool alloc_free_reg(Interval* cur);
int find_locked_reg(int reg_needed_until, int interval_to, int ignore_reg, bool* need_split); int find_locked_double_reg(int reg_needed_until, int interval_to, bool* need_split); void split_and_spill_intersecting_intervals(int reg, int regHi); void alloc_locked_reg(Interval* cur);
// must be called when all intervals are allocated void finish_allocation() { _move_resolver.resolve_and_append_moves(); }
};
/* When a block has more than one predecessor, and all predecessors end with the same sequence of move-instructions, than this moves can be placed once at the beginning of the block instead of multiple times in the predecessors.
Similarly, when a block has more than one successor, then equal sequences of moves at the beginning of the successors can be placed once at the end of the block. But because the moves must be inserted before all branch instructions, this works only when there is exactly one conditional branch at the end of the block (because the moves must be inserted before all branches, but after all compares).
This optimization affects all kind of moves (reg->reg, reg->stack and stack->reg). Because this optimization works best when a block contains only few moves, it has a huge impact on the number of blocks that are totally empty.
*/ class EdgeMoveOptimizer : public StackObj { private: // the class maintains a list with all lir-instruction-list of the // successors (predecessors) and the current index into the lir-lists
LIR_OpListStack _edge_instructions;
intStack _edge_instructions_idx;
// counter for different types of moves
counter_move_total,
counter_move_reg_reg,
counter_move_reg_stack,
counter_move_stack_reg,
counter_move_stack_stack,
counter_move_reg_mem,
counter_move_mem_reg,
counter_move_const_any,
number_of_counters,
invalid_counter = -1
};
private: int _counters_sum[number_of_counters]; int _counters_max[number_of_counters];
void inc_counter(Counter idx, int value = 1) { _counters_sum[idx] += value; }
void begin_method(); // called for each method when register allocation starts void end_method(LinearScan* allocator); // called for each method when register allocation completed void print(double total_time); // called before termination of VM to print global summary
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 ist noch experimentell.