1 // Copyright 2014 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 #include "src/compiler/osr.h"
6 #include "src/ast/scopes.h"
7 #include "src/compilation-info.h"
8 #include "src/compiler/all-nodes.h"
9 #include "src/compiler/common-operator-reducer.h"
10 #include "src/compiler/common-operator.h"
11 #include "src/compiler/dead-code-elimination.h"
12 #include "src/compiler/frame.h"
13 #include "src/compiler/graph-reducer.h"
14 #include "src/compiler/graph-trimmer.h"
15 #include "src/compiler/graph-visualizer.h"
16 #include "src/compiler/graph.h"
17 #include "src/compiler/js-graph.h"
18 #include "src/compiler/loop-analysis.h"
19 #include "src/compiler/node-marker.h"
20 #include "src/compiler/node.h"
21 #include "src/objects-inl.h"
22
23 namespace v8 {
24 namespace internal {
25 namespace compiler {
26
OsrHelper(CompilationInfo * info)27 OsrHelper::OsrHelper(CompilationInfo* info)
28 : parameter_count_(
29 info->is_optimizing_from_bytecode()
30 ? info->shared_info()->bytecode_array()->parameter_count()
31 : info->scope()->num_parameters()),
32 stack_slot_count_(
33 info->is_optimizing_from_bytecode()
34 ? info->shared_info()->bytecode_array()->register_count() +
35 InterpreterFrameConstants::kExtraSlotCount
36 : info->scope()->num_stack_slots() +
37 info->osr_expr_stack_height()) {}
38
39 #ifdef DEBUG
40 #define TRACE_COND (FLAG_trace_turbo_graph && FLAG_trace_osr)
41 #else
42 #define TRACE_COND false
43 #endif
44
45 #define TRACE(...) \
46 do { \
47 if (TRACE_COND) PrintF(__VA_ARGS__); \
48 } while (false)
49
50 namespace {
51
52 // Peel outer loops and rewire the graph so that control reduction can
53 // produce a properly formed graph.
PeelOuterLoopsForOsr(Graph * graph,CommonOperatorBuilder * common,Zone * tmp_zone,Node * dead,LoopTree * loop_tree,LoopTree::Loop * osr_loop,Node * osr_normal_entry,Node * osr_loop_entry)54 void PeelOuterLoopsForOsr(Graph* graph, CommonOperatorBuilder* common,
55 Zone* tmp_zone, Node* dead, LoopTree* loop_tree,
56 LoopTree::Loop* osr_loop, Node* osr_normal_entry,
57 Node* osr_loop_entry) {
58 const size_t original_count = graph->NodeCount();
59 AllNodes all(tmp_zone, graph);
60 NodeVector tmp_inputs(tmp_zone);
61 Node* sentinel = graph->NewNode(dead->op());
62
63 // Make a copy of the graph for each outer loop.
64 ZoneVector<NodeVector*> copies(tmp_zone);
65 for (LoopTree::Loop* loop = osr_loop->parent(); loop; loop = loop->parent()) {
66 void* stuff = tmp_zone->New(sizeof(NodeVector));
67 NodeVector* mapping =
68 new (stuff) NodeVector(original_count, sentinel, tmp_zone);
69 copies.push_back(mapping);
70 TRACE("OsrDuplication #%zu, depth %zu, header #%d:%s\n", copies.size(),
71 loop->depth(), loop_tree->HeaderNode(loop)->id(),
72 loop_tree->HeaderNode(loop)->op()->mnemonic());
73
74 // Prepare the mapping for OSR values and the OSR loop entry.
75 mapping->at(osr_normal_entry->id()) = dead;
76 mapping->at(osr_loop_entry->id()) = dead;
77
78 // The outer loops are dead in this copy.
79 for (LoopTree::Loop* outer = loop->parent(); outer;
80 outer = outer->parent()) {
81 for (Node* node : loop_tree->HeaderNodes(outer)) {
82 mapping->at(node->id()) = dead;
83 TRACE(" ---- #%d:%s -> dead (header)\n", node->id(),
84 node->op()->mnemonic());
85 }
86 }
87
88 // Copy all nodes.
89 for (size_t i = 0; i < all.reachable.size(); i++) {
90 Node* orig = all.reachable[i];
91 Node* copy = mapping->at(orig->id());
92 if (copy != sentinel) {
93 // Mapping already exists.
94 continue;
95 }
96 if (orig->InputCount() == 0 || orig->opcode() == IrOpcode::kParameter ||
97 orig->opcode() == IrOpcode::kOsrValue ||
98 orig->opcode() == IrOpcode::kOsrGuard) {
99 // No need to copy leaf nodes or parameters.
100 mapping->at(orig->id()) = orig;
101 continue;
102 }
103
104 // Copy the node.
105 tmp_inputs.clear();
106 for (Node* input : orig->inputs()) {
107 tmp_inputs.push_back(mapping->at(input->id()));
108 }
109 copy = graph->NewNode(orig->op(), orig->InputCount(), &tmp_inputs[0]);
110 if (NodeProperties::IsTyped(orig)) {
111 NodeProperties::SetType(copy, NodeProperties::GetType(orig));
112 }
113 mapping->at(orig->id()) = copy;
114 TRACE(" copy #%d:%s -> #%d\n", orig->id(), orig->op()->mnemonic(),
115 copy->id());
116 }
117
118 // Fix missing inputs.
119 for (Node* orig : all.reachable) {
120 Node* copy = mapping->at(orig->id());
121 for (int j = 0; j < copy->InputCount(); j++) {
122 if (copy->InputAt(j) == sentinel) {
123 copy->ReplaceInput(j, mapping->at(orig->InputAt(j)->id()));
124 }
125 }
126 }
127
128 // Construct the entry into this loop from previous copies.
129
130 // Gather the live loop header nodes, {loop_header} first.
131 Node* loop_header = loop_tree->HeaderNode(loop);
132 NodeVector header_nodes(tmp_zone);
133 header_nodes.reserve(loop->HeaderSize());
134 header_nodes.push_back(loop_header); // put the loop header first.
135 for (Node* node : loop_tree->HeaderNodes(loop)) {
136 if (node != loop_header && all.IsLive(node)) {
137 header_nodes.push_back(node);
138 }
139 }
140
141 // Gather backedges from the previous copies of the inner loops of {loop}.
142 NodeVectorVector backedges(tmp_zone);
143 TRACE("Gathering backedges...\n");
144 for (int i = 1; i < loop_header->InputCount(); i++) {
145 if (TRACE_COND) {
146 Node* control = loop_header->InputAt(i);
147 size_t incoming_depth = 0;
148 for (int j = 0; j < control->op()->ControlInputCount(); j++) {
149 Node* k = NodeProperties::GetControlInput(control, j);
150 incoming_depth =
151 std::max(incoming_depth, loop_tree->ContainingLoop(k)->depth());
152 }
153
154 TRACE(" edge @%d #%d:%s, incoming depth %zu\n", i, control->id(),
155 control->op()->mnemonic(), incoming_depth);
156 }
157
158 for (int pos = static_cast<int>(copies.size()) - 1; pos >= 0; pos--) {
159 backedges.push_back(NodeVector(tmp_zone));
160 backedges.back().reserve(header_nodes.size());
161
162 NodeVector* previous_map = pos > 0 ? copies[pos - 1] : nullptr;
163
164 for (Node* node : header_nodes) {
165 Node* input = node->InputAt(i);
166 if (previous_map) input = previous_map->at(input->id());
167 backedges.back().push_back(input);
168 TRACE(" node #%d:%s(@%d) = #%d:%s\n", node->id(),
169 node->op()->mnemonic(), i, input->id(),
170 input->op()->mnemonic());
171 }
172 }
173 }
174
175 int backedge_count = static_cast<int>(backedges.size());
176 if (backedge_count == 1) {
177 // Simple case of single backedge, therefore a single entry.
178 int index = 0;
179 for (Node* node : header_nodes) {
180 Node* copy = mapping->at(node->id());
181 Node* input = backedges[0][index];
182 copy->ReplaceInput(0, input);
183 TRACE(" header #%d:%s(0) => #%d:%s\n", copy->id(),
184 copy->op()->mnemonic(), input->id(), input->op()->mnemonic());
185 index++;
186 }
187 } else {
188 // Complex case of multiple backedges from previous copies requires
189 // merging the backedges to create the entry into the loop header.
190 Node* merge = nullptr;
191 int index = 0;
192 for (Node* node : header_nodes) {
193 // Gather edge inputs into {tmp_inputs}.
194 tmp_inputs.clear();
195 for (int edge = 0; edge < backedge_count; edge++) {
196 tmp_inputs.push_back(backedges[edge][index]);
197 }
198 Node* copy = mapping->at(node->id());
199 Node* input;
200 if (node == loop_header) {
201 // Create the merge for the entry into the loop header.
202 input = merge = graph->NewNode(common->Merge(backedge_count),
203 backedge_count, &tmp_inputs[0]);
204 copy->ReplaceInput(0, merge);
205 } else {
206 // Create a phi that merges values at entry into the loop header.
207 DCHECK_NOT_NULL(merge);
208 DCHECK(IrOpcode::IsPhiOpcode(node->opcode()));
209 tmp_inputs.push_back(merge);
210 Node* phi = input = graph->NewNode(
211 common->ResizeMergeOrPhi(node->op(), backedge_count),
212 backedge_count + 1, &tmp_inputs[0]);
213 copy->ReplaceInput(0, phi);
214 }
215
216 // Print the merge.
217 if (TRACE_COND) {
218 TRACE(" header #%d:%s(0) => #%d:%s(", copy->id(),
219 copy->op()->mnemonic(), input->id(), input->op()->mnemonic());
220 for (size_t i = 0; i < tmp_inputs.size(); i++) {
221 if (i > 0) TRACE(", ");
222 Node* input = tmp_inputs[i];
223 TRACE("#%d:%s", input->id(), input->op()->mnemonic());
224 }
225 TRACE(")\n");
226 }
227
228 index++;
229 }
230 }
231 }
232
233 // Kill the outer loops in the original graph.
234 TRACE("Killing outer loop headers...\n");
235 for (LoopTree::Loop* outer = osr_loop->parent(); outer;
236 outer = outer->parent()) {
237 Node* loop_header = loop_tree->HeaderNode(outer);
238 loop_header->ReplaceUses(dead);
239 TRACE(" ---- #%d:%s\n", loop_header->id(), loop_header->op()->mnemonic());
240 }
241
242 // Merge the ends of the graph copies.
243 Node* const end = graph->end();
244 int const input_count = end->InputCount();
245 for (int i = 0; i < input_count; ++i) {
246 NodeId const id = end->InputAt(i)->id();
247 for (NodeVector* const copy : copies) {
248 end->AppendInput(graph->zone(), copy->at(id));
249 NodeProperties::ChangeOp(end, common->End(end->InputCount()));
250 }
251 }
252
253 if (FLAG_trace_turbo_graph) { // Simple textual RPO.
254 OFStream os(stdout);
255 os << "-- Graph after OSR duplication -- " << std::endl;
256 os << AsRPO(*graph);
257 }
258 }
259
SetTypeForOsrValue(Node * osr_value,Node * loop,CommonOperatorBuilder * common)260 void SetTypeForOsrValue(Node* osr_value, Node* loop,
261 CommonOperatorBuilder* common) {
262 Node* osr_guard = nullptr;
263 for (Node* use : osr_value->uses()) {
264 if (use->opcode() == IrOpcode::kOsrGuard) {
265 DCHECK_EQ(use->InputAt(0), osr_value);
266 osr_guard = use;
267 break;
268 }
269 }
270
271 NodeProperties::ChangeOp(osr_guard, common->OsrGuard(OsrGuardType::kAny));
272 }
273
274 } // namespace
275
Deconstruct(JSGraph * jsgraph,CommonOperatorBuilder * common,Zone * tmp_zone)276 void OsrHelper::Deconstruct(JSGraph* jsgraph, CommonOperatorBuilder* common,
277 Zone* tmp_zone) {
278 Graph* graph = jsgraph->graph();
279 Node* osr_normal_entry = nullptr;
280 Node* osr_loop_entry = nullptr;
281 Node* osr_loop = nullptr;
282
283 for (Node* node : graph->start()->uses()) {
284 if (node->opcode() == IrOpcode::kOsrLoopEntry) {
285 osr_loop_entry = node; // found the OSR loop entry
286 } else if (node->opcode() == IrOpcode::kOsrNormalEntry) {
287 osr_normal_entry = node;
288 }
289 }
290
291 CHECK_NOT_NULL(osr_normal_entry); // Should have found the OSR normal entry.
292 CHECK_NOT_NULL(osr_loop_entry); // Should have found the OSR loop entry.
293
294 for (Node* use : osr_loop_entry->uses()) {
295 if (use->opcode() == IrOpcode::kLoop) {
296 CHECK(!osr_loop); // should be only one OSR loop.
297 osr_loop = use; // found the OSR loop.
298 }
299 }
300
301 CHECK(osr_loop); // Should have found the OSR loop.
302
303 for (Node* use : osr_loop_entry->uses()) {
304 if (use->opcode() == IrOpcode::kOsrValue) {
305 SetTypeForOsrValue(use, osr_loop, common);
306 }
307 }
308
309 // Analyze the graph to determine how deeply nested the OSR loop is.
310 LoopTree* loop_tree = LoopFinder::BuildLoopTree(graph, tmp_zone);
311
312 Node* dead = jsgraph->Dead();
313 LoopTree::Loop* loop = loop_tree->ContainingLoop(osr_loop);
314 if (loop->depth() > 0) {
315 PeelOuterLoopsForOsr(graph, common, tmp_zone, dead, loop_tree, loop,
316 osr_normal_entry, osr_loop_entry);
317 }
318
319 // Replace the normal entry with {Dead} and the loop entry with {Start}
320 // and run the control reducer to clean up the graph.
321 osr_normal_entry->ReplaceUses(dead);
322 osr_normal_entry->Kill();
323 osr_loop_entry->ReplaceUses(graph->start());
324 osr_loop_entry->Kill();
325
326 // Remove the first input to the {osr_loop}.
327 int const live_input_count = osr_loop->InputCount() - 1;
328 CHECK_NE(0, live_input_count);
329 for (Node* const use : osr_loop->uses()) {
330 if (NodeProperties::IsPhi(use)) {
331 use->RemoveInput(0);
332 NodeProperties::ChangeOp(
333 use, common->ResizeMergeOrPhi(use->op(), live_input_count));
334 }
335 }
336 osr_loop->RemoveInput(0);
337 NodeProperties::ChangeOp(
338 osr_loop, common->ResizeMergeOrPhi(osr_loop->op(), live_input_count));
339
340 // Run control reduction and graph trimming.
341 // TODO(bmeurer): The OSR deconstruction could be a regular reducer and play
342 // nice together with the rest, instead of having this custom stuff here.
343 GraphReducer graph_reducer(tmp_zone, graph);
344 DeadCodeElimination dce(&graph_reducer, graph, common);
345 CommonOperatorReducer cor(&graph_reducer, graph, common, jsgraph->machine());
346 graph_reducer.AddReducer(&dce);
347 graph_reducer.AddReducer(&cor);
348 graph_reducer.ReduceGraph();
349 GraphTrimmer trimmer(tmp_zone, graph);
350 NodeVector roots(tmp_zone);
351 jsgraph->GetCachedNodes(&roots);
352 trimmer.TrimGraph(roots.begin(), roots.end());
353 }
354
355
SetupFrame(Frame * frame)356 void OsrHelper::SetupFrame(Frame* frame) {
357 // The optimized frame will subsume the unoptimized frame. Do so by reserving
358 // the first spill slots.
359 frame->ReserveSpillSlots(UnoptimizedFrameSlots());
360 }
361
362 } // namespace compiler
363 } // namespace internal
364 } // namespace v8
365