v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
instruction-selection-phase.cc
Go to the documentation of this file.
1// Copyright 2023 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
6
7#include <optional>
8
21
23
24namespace {
25
26void TraceSequence(OptimizedCompilationInfo* info,
27 InstructionSequence* sequence, JSHeapBroker* broker,
28 CodeTracer* code_tracer, const char* phase_name) {
29 if (info->trace_turbo_json()) {
30 UnparkedScopeIfNeeded scope(broker);
31 AllowHandleDereference allow_deref;
32 TurboJsonFile json_of(info, std::ios_base::app);
33 json_of << "{\"name\":\"" << phase_name << "\",\"type\":\"sequence\""
34 << ",\"blocks\":" << InstructionSequenceAsJSON{sequence}
35 << ",\"register_allocation\":{"
36 << "\"fixed_double_live_ranges\": {}"
37 << ",\"fixed_live_ranges\": {}"
38 << ",\"live_ranges\": {}"
39 << "}},\n";
40 }
41 if (info->trace_turbo_graph()) {
42 UnparkedScopeIfNeeded scope(broker);
43 AllowHandleDereference allow_deref;
44 CodeTracer::StreamScope tracing_scope(code_tracer);
45 tracing_scope.stream() << "----- Instruction sequence " << phase_name
46 << " -----\n"
47 << *sequence;
48 }
49}
50
51} // namespace
52
55 ZoneVector<Backedge> backedges(zone());
56 // Determined empirically on a large Wasm module. Since they are allocated
57 // only once per function compilation, the memory usage is not critical.
58 stack.reserve(64);
59 backedges.reserve(32);
60 size_t num_loops = 0;
61
62 auto Push = [&](const Block* block) {
63 auto succs = SuccessorBlocks(*block, *graph_);
64 stack.emplace_back(block, 0, std::move(succs));
66 };
67
68 const Block* entry = &graph_->StartBlock();
69
70 // Find correct insertion point within existing order.
71 const Block* order = nullptr;
72
73 Push(&graph_->StartBlock());
74
75 while (!stack.empty()) {
76 SpecialRPOStackFrame& frame = stack.back();
77
78 if (frame.index < frame.successors.size()) {
79 // Process the next successor.
80 const Block* succ = frame.successors[frame.index++];
81 if (rpo_number(succ) == kBlockVisited1) continue;
82 if (rpo_number(succ) == kBlockOnStack) {
83 // The successor is on the stack, so this is a backedge (cycle).
84 DCHECK_EQ(frame.index - 1, 0);
85 backedges.emplace_back(frame.block, frame.index - 1);
86 // Assign a new loop number to the header.
87 DCHECK(!has_loop_number(succ));
88 set_loop_number(succ, num_loops++);
89 } else {
90 // Push the successor onto the stack.
92 Push(succ);
93 }
94 } else {
95 // Finished with all successors; pop the stack and add the block.
96 order = PushFront(order, frame.block);
98 stack.pop_back();
99 }
100 }
101
102 // If no loops were encountered, then the order we computed was correct.
103 if (num_loops == 0) return ComputeBlockPermutation(entry);
104
105 // Otherwise, compute the loop information from the backedges in order
106 // to perform a traversal that groups loop bodies together.
107 ComputeLoopInfo(num_loops, backedges);
108
109 // Initialize the "loop stack". We assume that the entry cannot be a loop
110 // header.
111 CHECK(!has_loop_number(entry));
112 LoopInfo* loop = nullptr;
113 order = nullptr;
114
115 // Perform an iterative post-order traversal, visiting loop bodies before
116 // edges that lead out of loops. Visits each block once, but linking loop
117 // sections together is linear in the loop size, so overall is
118 // O(|B| + max(loop_depth) * max(|loop|))
119 DCHECK(stack.empty());
120 Push(&graph_->StartBlock());
121 while (!stack.empty()) {
122 SpecialRPOStackFrame& frame = stack.back();
123 const Block* block = frame.block;
124 const Block* succ = nullptr;
125
126 if (frame.index < frame.successors.size()) {
127 // Process the next normal successor.
128 succ = frame.successors[frame.index++];
129 } else if (has_loop_number(block)) {
130 // Process additional outgoing edges from the loop header.
131 if (rpo_number(block) == kBlockOnStack) {
132 // Finish the loop body the first time the header is left on the
133 // stack.
134 DCHECK_NOT_NULL(loop);
135 DCHECK_EQ(loop->header, block);
136 loop->start = PushFront(order, block);
137 order = loop->end;
139 // Pop the loop stack and continue visiting outgoing edges within
140 // the context of the outer loop, if any.
141 loop = loop->prev;
142 // We leave the loop header on the stack; the rest of this iteration
143 // and later iterations will go through its outgoing edges list.
144 }
145
146 // Use the next outgoing edge if there are any.
147 size_t outgoing_index = frame.index - frame.successors.size();
148 LoopInfo* info = &loops_[loop_number(block)];
149 DCHECK_NE(loop, info);
150 if (block != entry && outgoing_index < info->outgoing.size()) {
151 succ = info->outgoing[outgoing_index];
152 ++frame.index;
153 }
154 }
155
156 if (succ != nullptr) {
157 // Process the next successor.
158 if (rpo_number(succ) == kBlockOnStack) continue;
159 if (rpo_number(succ) == kBlockVisited2) continue;
161 if (loop != nullptr && !loop->members->Contains(succ->index().id())) {
162 // The successor is not in the current loop or any nested loop.
163 // Add it to the outgoing edges of this loop and visit it later.
164 loop->AddOutgoing(zone(), succ);
165 } else {
166 // Push the successor onto the stack.
167 Push(succ);
168 if (has_loop_number(succ)) {
169 // Push the inner loop onto the loop stack.
170 DCHECK_LT(loop_number(succ), num_loops);
171 LoopInfo* next = &loops_[loop_number(succ)];
172 next->end = order;
173 next->prev = loop;
174 loop = next;
175 }
176 }
177 } else {
178 // Finish with all successors of the current block.
179 if (has_loop_number(block)) {
180 // If we are going to pop a loop header, then add its entire body.
181 LoopInfo* info = &loops_[loop_number(block)];
182 for (const Block* b = info->start; true;
183 b = block_data_[b->index()].rpo_next) {
184 if (block_data_[b->index()].rpo_next == info->end) {
185 PushFront(order, b);
186 info->end = order;
187 break;
188 }
189 }
190 order = info->start;
191 } else {
192 // Pop a single node off the stack and add it to the order.
193 order = PushFront(order, block);
195 }
196 stack.pop_back();
197 }
198 }
199
200 return ComputeBlockPermutation(entry);
201}
202
203// Computes loop membership from the backedges of the control flow graph.
205 size_t num_loops, ZoneVector<Backedge>& backedges) {
207
208 // Extend loop information vector.
209 loops_.resize(num_loops, LoopInfo{});
210
211 // Compute loop membership starting from backedges.
212 // O(max(loop_depth) * |loop|)
213 for (auto [backedge, header_index] : backedges) {
214 const Block* header = SuccessorBlocks(*backedge, *graph_)[header_index];
215 DCHECK(header->IsLoop());
216 size_t loop_num = loop_number(header);
217 DCHECK_NULL(loops_[loop_num].header);
218 loops_[loop_num].header = header;
219 loops_[loop_num].members = zone()->New<SparseBitVector>(zone());
220
221 if (backedge != header) {
222 // As long as the header doesn't have a backedge to itself,
223 // Push the member onto the queue and process its predecessors.
224 DCHECK(!loops_[loop_num].members->Contains(backedge->index().id()));
225 loops_[loop_num].members->Add(backedge->index().id());
226 stack.push_back(backedge);
227 }
228
229 // Propagate loop membership backwards. All predecessors of M up to the
230 // loop header H are members of the loop too. O(|blocks between M and H|).
231 while (!stack.empty()) {
232 const Block* block = stack.back();
233 stack.pop_back();
234 for (const Block* pred : block->PredecessorsIterable()) {
235 if (pred != header) {
236 if (!loops_[loop_num].members->Contains(pred->index().id())) {
237 loops_[loop_num].members->Add(pred->index().id());
238 stack.push_back(pred);
239 }
240 }
241 }
242 }
243 }
244}
245
247 const Block* entry) {
248 ZoneVector<uint32_t> result(graph_->block_count(), zone());
249 size_t i = 0;
250 for (const Block* b = entry; b; b = block_data_[b->index()].rpo_next) {
251 result[i++] = b->index().id();
252 }
253 DCHECK_EQ(i, graph_->block_count());
254 return result;
255}
256
258 graph.StartBlock().set_custom_data(
260 for (Block& block : graph.blocks()) {
261 const Block* predecessor = block.LastPredecessor();
262 if (predecessor == nullptr) {
263 continue;
264 } else if (block.IsLoop()) {
265 // We only consider the forward edge for loop headers.
266 predecessor = predecessor->NeighboringPredecessor();
267 DCHECK_NOT_NULL(predecessor);
268 DCHECK_EQ(predecessor->NeighboringPredecessor(), nullptr);
269 block.set_custom_data(predecessor->get_custom_data(
272 } else if (predecessor->NeighboringPredecessor() == nullptr) {
273 // This block has only a single predecessor. Due to edge-split form, those
274 // are the only blocks that can be the target of a branch-like op which
275 // might potentially provide a BranchHint to defer this block.
276 const bool is_deferred =
277 predecessor->get_custom_data(
279 IsUnlikelySuccessor(predecessor, &block, graph);
280 block.set_custom_data(is_deferred,
282 } else {
283 block.set_custom_data(true, Block::CustomDataKind::kDeferredInSchedule);
284 for (; predecessor; predecessor = predecessor->NeighboringPredecessor()) {
285 // If there is a single predecessor that is not deferred, then block is
286 // also not deferred.
287 if (!predecessor->get_custom_data(
289 block.set_custom_data(false,
291 break;
292 }
293 }
294 }
295 }
296}
297
299 const ProfileDataFromFile* profile) {
300 Graph& graph = data->graph();
301 for (auto& op : graph.AllOperations()) {
302 if (BranchOp* branch = op.TryCast<BranchOp>()) {
303 uint32_t true_block_id = branch->if_true->index().id();
304 uint32_t false_block_id = branch->if_false->index().id();
305 BranchHint hint = profile->GetHint(true_block_id, false_block_id);
306 if (hint != BranchHint::kNone) {
307 // We update the hint in-place.
308 branch->hint = hint;
309 }
310 }
311 }
312}
313
315 Graph& graph = data->graph();
316
317 // Compute special RPO order....
318 TurboshaftSpecialRPONumberer numberer(graph, temp_zone);
319 if (!data->graph_has_special_rpo()) {
320 auto schedule = numberer.ComputeSpecialRPO();
321 graph.ReorderBlocks(base::VectorOf(schedule));
322 data->set_graph_has_special_rpo();
323 }
324
325 // Determine deferred blocks.
326 PropagateDeferred(graph);
327}
328
329std::optional<BailoutReason> InstructionSelectionPhase::Run(
330 PipelineData* data, Zone* temp_zone, const CallDescriptor* call_descriptor,
331 Linkage* linkage, CodeTracer* code_tracer) {
332 Graph& graph = data->graph();
333
334 // Initialize an instruction sequence.
335 data->InitializeInstructionComponent(call_descriptor);
336
337 // Run the actual instruction selection.
339 temp_zone, graph.op_id_count(), linkage, data->sequence(), &graph,
340 data->frame(),
341 data->info()->switch_jump_table()
344 &data->info()->tick_counter(), data->broker(),
345 &data->max_unoptimized_frame_height(), &data->max_pushed_argument_count(),
346 data->info()->source_positions()
350 v8_flags.turbo_instruction_scheduling
353 data->assembler_options().enable_root_relative_access
356 data->info()->trace_turbo_json()
359 if (std::optional<BailoutReason> bailout = selector.SelectInstructions()) {
360 return bailout;
361 }
362 TraceSequence(data->info(), data->sequence(), data->broker(), code_tracer,
363 "after instruction selection");
364 return std::nullopt;
365}
366
367} // namespace v8::internal::compiler::turboshaft
Schedule * schedule
V8_INLINE bool Contains(int i) const
void reserve(size_t new_cap)
T & emplace_back(Args &&... args)
T * New(Args &&... args)
Definition zone.h:114
std::optional< BailoutReason > SelectInstructions()
static InstructionSelector ForTurboshaft(Zone *zone, size_t node_count, Linkage *linkage, InstructionSequence *sequence, turboshaft::Graph *schedule, Frame *frame, EnableSwitchJumpTable enable_switch_jump_table, TickCounter *tick_counter, JSHeapBroker *broker, size_t *max_unoptimized_frame_height, size_t *max_pushed_argument_count, SourcePositionMode source_position_mode=kCallSourcePositions, Features features=SupportedFeatures(), EnableScheduling enable_scheduling=v8_flags.turbo_instruction_scheduling ? kEnableScheduling :kDisableScheduling, EnableRootsRelativeAddressing enable_roots_relative_addressing=kDisableRootsRelativeAddressing, EnableTraceTurboJson trace_turbo=kDisableTraceTurboJson)
NeighboringPredecessorIterable PredecessorsIterable() const
Definition graph.h:340
uint32_t get_custom_data(CustomDataKind kind_for_debug_check) const
Definition graph.h:516
const Block * PushFront(const Block *head, const Block *block)
void ComputeLoopInfo(size_t num_loops, ZoneVector< Backedge > &backedges)
JSHeapBroker * broker
Linkage * linkage
RpoNumber block
ZoneVector< RpoNumber > & result
ZoneStack< RpoNumber > & stack
constexpr Vector< T > VectorOf(T *start, size_t size)
Definition vector.h:360
base::SmallVector< Block *, 4 > SuccessorBlocks(const Block &block, const Graph &graph)
bool IsUnlikelySuccessor(const Block *block, const Block *successor, const Graph &graph)
PerThreadAssertScopeDebugOnly< true, HANDLE_DEREFERENCE_ASSERT > AllowHandleDereference
V8_EXPORT_PRIVATE FlagValues v8_flags
#define DCHECK_NULL(val)
Definition logging.h:491
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
std::optional< BailoutReason > Run(PipelineData *data, Zone *temp_zone, const CallDescriptor *call_descriptor, Linkage *linkage, CodeTracer *code_tracer)
const underlying_operation_t< Op > * TryCast() const
Definition operations.h:990
void Run(PipelineData *data, Zone *temp_zone, const ProfileDataFromFile *profile)