1 // Copyright 2015 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef V8_COMPILER_BACKEND_INSTRUCTION_SCHEDULER_H_ 6 #define V8_COMPILER_BACKEND_INSTRUCTION_SCHEDULER_H_ 7 8 #include "src/base/optional.h" 9 #include "src/compiler/backend/instruction.h" 10 #include "src/zone/zone-containers.h" 11 12 namespace v8 { 13 14 namespace base { 15 class RandomNumberGenerator; 16 } // namespace base 17 18 namespace internal { 19 namespace compiler { 20 21 // A set of flags describing properties of the instructions so that the 22 // scheduler is aware of dependencies between instructions. 23 enum ArchOpcodeFlags { 24 kNoOpcodeFlags = 0, 25 kHasSideEffect = 1, // The instruction has some side effects (memory 26 // store, function call...) 27 kIsLoadOperation = 2, // The instruction is a memory load. 28 kMayNeedDeoptOrTrapCheck = 4, // The instruction may be associated with a 29 // deopt or trap check which must be run before 30 // instruction e.g. div on Intel platform which 31 // will raise an exception when the divisor is 32 // zero. 33 kIsBarrier = 8, // The instruction can cause GC or it reads/writes registers 34 // that are not explicitly given. Nothing can be reordered 35 // across such an instruction. 36 }; 37 38 class InstructionScheduler final : public ZoneObject { 39 public: 40 V8_EXPORT_PRIVATE InstructionScheduler(Zone* zone, 41 InstructionSequence* sequence); 42 43 V8_EXPORT_PRIVATE void StartBlock(RpoNumber rpo); 44 V8_EXPORT_PRIVATE void EndBlock(RpoNumber rpo); 45 46 V8_EXPORT_PRIVATE void AddInstruction(Instruction* instr); 47 V8_EXPORT_PRIVATE void AddTerminator(Instruction* instr); 48 49 static bool SchedulerSupported(); 50 51 private: 52 // A scheduling graph node. 53 // Represent an instruction and their dependencies. 54 class ScheduleGraphNode : public ZoneObject { 55 public: 56 ScheduleGraphNode(Zone* zone, Instruction* instr); 57 58 // Mark the instruction represented by 'node' as a dependency of this one. 59 // The current instruction will be registered as an unscheduled predecessor 60 // of 'node' (i.e. it must be scheduled before 'node'). 61 void AddSuccessor(ScheduleGraphNode* node); 62 63 // Check if all the predecessors of this instruction have been scheduled. HasUnscheduledPredecessor()64 bool HasUnscheduledPredecessor() { 65 return unscheduled_predecessors_count_ != 0; 66 } 67 68 // Record that we have scheduled one of the predecessors of this node. DropUnscheduledPredecessor()69 void DropUnscheduledPredecessor() { 70 DCHECK_LT(0, unscheduled_predecessors_count_); 71 unscheduled_predecessors_count_--; 72 } 73 instruction()74 Instruction* instruction() { return instr_; } successors()75 ZoneDeque<ScheduleGraphNode*>& successors() { return successors_; } latency()76 int latency() const { return latency_; } 77 total_latency()78 int total_latency() const { return total_latency_; } set_total_latency(int latency)79 void set_total_latency(int latency) { total_latency_ = latency; } 80 start_cycle()81 int start_cycle() const { return start_cycle_; } set_start_cycle(int start_cycle)82 void set_start_cycle(int start_cycle) { start_cycle_ = start_cycle; } 83 84 private: 85 Instruction* instr_; 86 ZoneDeque<ScheduleGraphNode*> successors_; 87 88 // Number of unscheduled predecessors for this node. 89 int unscheduled_predecessors_count_; 90 91 // Estimate of the instruction latency (the number of cycles it takes for 92 // instruction to complete). 93 int latency_; 94 95 // The sum of all the latencies on the path from this node to the end of 96 // the graph (i.e. a node with no successor). 97 int total_latency_; 98 99 // The scheduler keeps a nominal cycle count to keep track of when the 100 // result of an instruction is available. This field is updated by the 101 // scheduler to indicate when the value of all the operands of this 102 // instruction will be available. 103 int start_cycle_; 104 }; 105 106 // Keep track of all nodes ready to be scheduled (i.e. all their dependencies 107 // have been scheduled. Note that this class is inteded to be extended by 108 // concrete implementation of the scheduling queue which define the policy 109 // to pop node from the queue. 110 class SchedulingQueueBase { 111 public: SchedulingQueueBase(InstructionScheduler * scheduler)112 explicit SchedulingQueueBase(InstructionScheduler* scheduler) 113 : scheduler_(scheduler), nodes_(scheduler->zone()) {} 114 115 void AddNode(ScheduleGraphNode* node); 116 IsEmpty()117 bool IsEmpty() const { return nodes_.empty(); } 118 119 protected: 120 InstructionScheduler* scheduler_; 121 ZoneLinkedList<ScheduleGraphNode*> nodes_; 122 }; 123 124 // A scheduling queue which prioritize nodes on the critical path (we look 125 // for the instruction with the highest latency on the path to reach the end 126 // of the graph). 127 class CriticalPathFirstQueue : public SchedulingQueueBase { 128 public: CriticalPathFirstQueue(InstructionScheduler * scheduler)129 explicit CriticalPathFirstQueue(InstructionScheduler* scheduler) 130 : SchedulingQueueBase(scheduler) {} 131 132 // Look for the best candidate to schedule, remove it from the queue and 133 // return it. 134 ScheduleGraphNode* PopBestCandidate(int cycle); 135 }; 136 137 // A queue which pop a random node from the queue to perform stress tests on 138 // the scheduler. 139 class StressSchedulerQueue : public SchedulingQueueBase { 140 public: StressSchedulerQueue(InstructionScheduler * scheduler)141 explicit StressSchedulerQueue(InstructionScheduler* scheduler) 142 : SchedulingQueueBase(scheduler) {} 143 144 ScheduleGraphNode* PopBestCandidate(int cycle); 145 146 private: random_number_generator()147 base::RandomNumberGenerator* random_number_generator() { 148 return scheduler_->random_number_generator(); 149 } 150 }; 151 152 // Perform scheduling for the current block specifying the queue type to 153 // use to determine the next best candidate. 154 template <typename QueueType> 155 void Schedule(); 156 157 // Return the scheduling properties of the given instruction. 158 V8_EXPORT_PRIVATE int GetInstructionFlags(const Instruction* instr) const; 159 int GetTargetInstructionFlags(const Instruction* instr) const; 160 IsBarrier(const Instruction * instr)161 bool IsBarrier(const Instruction* instr) const { 162 return (GetInstructionFlags(instr) & kIsBarrier) != 0; 163 } 164 165 // Check whether the given instruction has side effects (e.g. function call, 166 // memory store). HasSideEffect(const Instruction * instr)167 bool HasSideEffect(const Instruction* instr) const { 168 return (GetInstructionFlags(instr) & kHasSideEffect) != 0; 169 } 170 171 // Return true if the instruction is a memory load. IsLoadOperation(const Instruction * instr)172 bool IsLoadOperation(const Instruction* instr) const { 173 return (GetInstructionFlags(instr) & kIsLoadOperation) != 0; 174 } 175 176 // The scheduler will not move the following instructions before the last 177 // deopt/trap check: 178 // * loads (this is conservative) 179 // * instructions with side effect 180 // * other deopts/traps 181 // Any other instruction can be moved, apart from those that raise exceptions 182 // on specific inputs - these are filtered out by the deopt/trap check. MayNeedDeoptOrTrapCheck(const Instruction * instr)183 bool MayNeedDeoptOrTrapCheck(const Instruction* instr) const { 184 return (GetInstructionFlags(instr) & kMayNeedDeoptOrTrapCheck) != 0; 185 } 186 187 // Return true if the instruction cannot be moved before the last deopt or 188 // trap point we encountered. DependsOnDeoptOrTrap(const Instruction * instr)189 bool DependsOnDeoptOrTrap(const Instruction* instr) const { 190 return MayNeedDeoptOrTrapCheck(instr) || instr->IsDeoptimizeCall() || 191 instr->IsTrap() || HasSideEffect(instr) || IsLoadOperation(instr); 192 } 193 194 // Identify nops used as a definition point for live-in registers at 195 // function entry. IsFixedRegisterParameter(const Instruction * instr)196 bool IsFixedRegisterParameter(const Instruction* instr) const { 197 return (instr->arch_opcode() == kArchNop) && (instr->OutputCount() == 1) && 198 (instr->OutputAt(0)->IsUnallocated()) && 199 (UnallocatedOperand::cast(instr->OutputAt(0)) 200 ->HasFixedRegisterPolicy() || 201 UnallocatedOperand::cast(instr->OutputAt(0)) 202 ->HasFixedFPRegisterPolicy()); 203 } 204 205 void ComputeTotalLatencies(); 206 207 static int GetInstructionLatency(const Instruction* instr); 208 zone()209 Zone* zone() { return zone_; } sequence()210 InstructionSequence* sequence() { return sequence_; } random_number_generator()211 base::RandomNumberGenerator* random_number_generator() { 212 return &random_number_generator_.value(); 213 } 214 215 Zone* zone_; 216 InstructionSequence* sequence_; 217 ZoneVector<ScheduleGraphNode*> graph_; 218 219 friend class InstructionSchedulerTester; 220 221 // Last side effect instruction encountered while building the graph. 222 ScheduleGraphNode* last_side_effect_instr_; 223 224 // Set of load instructions encountered since the last side effect instruction 225 // which will be added as predecessors of the next instruction with side 226 // effects. 227 ZoneVector<ScheduleGraphNode*> pending_loads_; 228 229 // Live-in register markers are nop instructions which are emitted at the 230 // beginning of a basic block so that the register allocator will find a 231 // defining instruction for live-in values. They must not be moved. 232 // All these nops are chained together and added as a predecessor of every 233 // other instructions in the basic block. 234 ScheduleGraphNode* last_live_in_reg_marker_; 235 236 // Last deoptimization or trap instruction encountered while building the 237 // graph. 238 ScheduleGraphNode* last_deopt_or_trap_; 239 240 // Keep track of definition points for virtual registers. This is used to 241 // record operand dependencies in the scheduling graph. 242 ZoneMap<int32_t, ScheduleGraphNode*> operands_map_; 243 244 base::Optional<base::RandomNumberGenerator> random_number_generator_; 245 }; 246 247 } // namespace compiler 248 } // namespace internal 249 } // namespace v8 250 251 #endif // V8_COMPILER_BACKEND_INSTRUCTION_SCHEDULER_H_ 252