File: | out/../deps/v8/src/compiler/js-inlining-heuristic.cc |
Warning: | line 739, column 25 2nd function call argument is an uninitialized value |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 | #include "src/compiler/js-inlining-heuristic.h" | |||
6 | ||||
7 | #include "src/codegen/optimized-compilation-info.h" | |||
8 | #include "src/compiler/common-operator.h" | |||
9 | #include "src/compiler/compiler-source-position-table.h" | |||
10 | #include "src/compiler/js-heap-broker.h" | |||
11 | #include "src/compiler/node-matchers.h" | |||
12 | #include "src/compiler/simplified-operator.h" | |||
13 | #include "src/objects/objects-inl.h" | |||
14 | ||||
15 | namespace v8 { | |||
16 | namespace internal { | |||
17 | namespace compiler { | |||
18 | ||||
19 | #define TRACE(...) \ | |||
20 | do { \ | |||
21 | if (FLAG_trace_turbo_inlining) StdoutStream{} << __VA_ARGS__ << std::endl; \ | |||
22 | } while (false) | |||
23 | ||||
24 | namespace { | |||
25 | bool IsSmall(int const size) { | |||
26 | return size <= FLAG_max_inlined_bytecode_size_small; | |||
27 | } | |||
28 | ||||
29 | bool CanConsiderForInlining(JSHeapBroker* broker, | |||
30 | FeedbackCellRef const& feedback_cell) { | |||
31 | base::Optional<FeedbackVectorRef> feedback_vector = | |||
32 | feedback_cell.feedback_vector(); | |||
33 | if (!feedback_vector.has_value()) { | |||
34 | TRACE("Cannot consider " << feedback_cell | |||
35 | << " for inlining (no feedback vector)"); | |||
36 | return false; | |||
37 | } | |||
38 | SharedFunctionInfoRef shared = feedback_vector->shared_function_info(); | |||
39 | ||||
40 | if (!shared.HasBytecodeArray()) { | |||
41 | TRACE("Cannot consider " << shared << " for inlining (no bytecode)"); | |||
42 | return false; | |||
43 | } | |||
44 | // Ensure we have a persistent handle to the bytecode in order to avoid | |||
45 | // flushing it during the remaining compilation. | |||
46 | shared.GetBytecodeArray(); | |||
47 | ||||
48 | // Read feedback vector again in case it got flushed before we were able to | |||
49 | // prevent flushing above. | |||
50 | base::Optional<FeedbackVectorRef> feedback_vector_again = | |||
51 | feedback_cell.feedback_vector(); | |||
52 | if (!feedback_vector_again.has_value()) { | |||
53 | TRACE("Cannot consider " << shared << " for inlining (no feedback vector)"); | |||
54 | return false; | |||
55 | } | |||
56 | if (!feedback_vector_again->equals(*feedback_vector)) { | |||
57 | // The new feedback vector likely contains lots of uninitialized slots, so | |||
58 | // it doesn't make much sense to inline this function now. | |||
59 | TRACE("Not considering " << shared | |||
60 | << " for inlining (feedback vector changed)"); | |||
61 | return false; | |||
62 | } | |||
63 | ||||
64 | SharedFunctionInfo::Inlineability inlineability = shared.GetInlineability(); | |||
65 | if (inlineability != SharedFunctionInfo::kIsInlineable) { | |||
66 | TRACE("Cannot consider " | |||
67 | << shared << " for inlining (reason: " << inlineability << ")"); | |||
68 | return false; | |||
69 | } | |||
70 | ||||
71 | TRACE("Considering " << shared << " for inlining with " << *feedback_vector); | |||
72 | return true; | |||
73 | } | |||
74 | ||||
75 | bool CanConsiderForInlining(JSHeapBroker* broker, | |||
76 | JSFunctionRef const& function) { | |||
77 | FeedbackCellRef feedback_cell = | |||
78 | function.raw_feedback_cell(broker->dependencies()); | |||
79 | bool const result = CanConsiderForInlining(broker, feedback_cell); | |||
80 | if (result) { | |||
81 | CHECK(do { if ((__builtin_expect(!!(!(function.shared().equals(feedback_cell .shared_function_info().value()))), 0))) { V8_Fatal("Check failed: %s." , "function.shared().equals(feedback_cell.shared_function_info().value())" ); } } while (false) | |||
82 | function.shared().equals(feedback_cell.shared_function_info().value()))do { if ((__builtin_expect(!!(!(function.shared().equals(feedback_cell .shared_function_info().value()))), 0))) { V8_Fatal("Check failed: %s." , "function.shared().equals(feedback_cell.shared_function_info().value())" ); } } while (false); | |||
83 | } | |||
84 | return result; | |||
85 | } | |||
86 | ||||
87 | } // namespace | |||
88 | ||||
89 | JSInliningHeuristic::Candidate JSInliningHeuristic::CollectFunctions( | |||
90 | Node* node, int functions_size) { | |||
91 | DCHECK_NE(0, functions_size)((void) 0); | |||
92 | Node* callee = node->InputAt(0); | |||
93 | Candidate out; | |||
94 | out.node = node; | |||
95 | ||||
96 | HeapObjectMatcher m(callee); | |||
97 | if (m.HasResolvedValue() && m.Ref(broker()).IsJSFunction()) { | |||
98 | JSFunctionRef function = m.Ref(broker()).AsJSFunction(); | |||
99 | out.functions[0] = function; | |||
100 | if (CanConsiderForInlining(broker(), function)) { | |||
101 | out.bytecode[0] = function.shared().GetBytecodeArray(); | |||
102 | out.num_functions = 1; | |||
103 | return out; | |||
104 | } | |||
105 | } | |||
106 | if (m.IsPhi()) { | |||
107 | int const value_input_count = m.node()->op()->ValueInputCount(); | |||
108 | if (value_input_count > functions_size) { | |||
109 | out.num_functions = 0; | |||
110 | return out; | |||
111 | } | |||
112 | for (int n = 0; n < value_input_count; ++n) { | |||
113 | HeapObjectMatcher m2(callee->InputAt(n)); | |||
114 | if (!m2.HasResolvedValue() || !m2.Ref(broker()).IsJSFunction()) { | |||
115 | out.num_functions = 0; | |||
116 | return out; | |||
117 | } | |||
118 | ||||
119 | out.functions[n] = m2.Ref(broker()).AsJSFunction(); | |||
120 | JSFunctionRef function = out.functions[n].value(); | |||
121 | if (CanConsiderForInlining(broker(), function)) { | |||
122 | out.bytecode[n] = function.shared().GetBytecodeArray(); | |||
123 | } | |||
124 | } | |||
125 | out.num_functions = value_input_count; | |||
126 | return out; | |||
127 | } | |||
128 | if (m.IsCheckClosure()) { | |||
129 | DCHECK(!out.functions[0].has_value())((void) 0); | |||
130 | FeedbackCellRef feedback_cell = MakeRef(broker(), FeedbackCellOf(m.op())); | |||
131 | if (CanConsiderForInlining(broker(), feedback_cell)) { | |||
132 | out.shared_info = feedback_cell.shared_function_info().value(); | |||
133 | out.bytecode[0] = out.shared_info->GetBytecodeArray(); | |||
134 | } | |||
135 | out.num_functions = 1; | |||
136 | return out; | |||
137 | } | |||
138 | if (m.IsJSCreateClosure()) { | |||
139 | DCHECK(!out.functions[0].has_value())((void) 0); | |||
140 | JSCreateClosureNode n(callee); | |||
141 | FeedbackCellRef feedback_cell = n.GetFeedbackCellRefChecked(broker()); | |||
142 | if (CanConsiderForInlining(broker(), feedback_cell)) { | |||
143 | out.shared_info = feedback_cell.shared_function_info().value(); | |||
144 | out.bytecode[0] = out.shared_info->GetBytecodeArray(); | |||
145 | CHECK(out.shared_info->equals(n.Parameters().shared_info(broker())))do { if ((__builtin_expect(!!(!(out.shared_info->equals(n. Parameters().shared_info(broker())))), 0))) { V8_Fatal("Check failed: %s." , "out.shared_info->equals(n.Parameters().shared_info(broker()))" ); } } while (false); | |||
146 | } | |||
147 | out.num_functions = 1; | |||
148 | return out; | |||
149 | } | |||
150 | out.num_functions = 0; | |||
151 | return out; | |||
152 | } | |||
153 | ||||
154 | Reduction JSInliningHeuristic::Reduce(Node* node) { | |||
155 | #if V8_ENABLE_WEBASSEMBLY1 | |||
156 | if (mode() == kWasmOnly) { | |||
157 | if (node->opcode() == IrOpcode::kJSWasmCall) { | |||
158 | return inliner_.ReduceJSWasmCall(node); | |||
159 | } | |||
160 | return NoChange(); | |||
161 | } | |||
162 | #endif // V8_ENABLE_WEBASSEMBLY | |||
163 | ||||
164 | DCHECK_EQ(mode(), kJSOnly)((void) 0); | |||
165 | if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange(); | |||
166 | ||||
167 | if (total_inlined_bytecode_size_ >= max_inlined_bytecode_size_absolute_) { | |||
168 | return NoChange(); | |||
169 | } | |||
170 | ||||
171 | // Check if we already saw that {node} before, and if so, just skip it. | |||
172 | if (seen_.find(node->id()) != seen_.end()) return NoChange(); | |||
173 | ||||
174 | // Check if the {node} is an appropriate candidate for inlining. | |||
175 | Candidate candidate = CollectFunctions(node, kMaxCallPolymorphism); | |||
176 | if (candidate.num_functions == 0) { | |||
177 | return NoChange(); | |||
178 | } else if (candidate.num_functions > 1 && !FLAG_polymorphic_inlining) { | |||
179 | TRACE("Not considering call site #" | |||
180 | << node->id() << ":" << node->op()->mnemonic() | |||
181 | << ", because polymorphic inlining is disabled"); | |||
182 | return NoChange(); | |||
183 | } | |||
184 | ||||
185 | bool can_inline_candidate = false, candidate_is_small = true; | |||
186 | candidate.total_size = 0; | |||
187 | FrameState frame_state{NodeProperties::GetFrameStateInput(node)}; | |||
188 | FrameStateInfo const& frame_info = frame_state.frame_state_info(); | |||
189 | Handle<SharedFunctionInfo> frame_shared_info; | |||
190 | for (int i = 0; i < candidate.num_functions; ++i) { | |||
191 | if (!candidate.bytecode[i].has_value()) { | |||
192 | candidate.can_inline_function[i] = false; | |||
193 | continue; | |||
194 | } | |||
195 | ||||
196 | SharedFunctionInfoRef shared = candidate.functions[i].has_value() | |||
197 | ? candidate.functions[i].value().shared() | |||
198 | : candidate.shared_info.value(); | |||
199 | candidate.can_inline_function[i] = candidate.bytecode[i].has_value(); | |||
200 | // Because of concurrent optimization, optimization of the inlining | |||
201 | // candidate could have been disabled meanwhile. | |||
202 | // JSInliner will check this again and not actually inline the function in | |||
203 | // this case. | |||
204 | CHECK_IMPLIES(candidate.can_inline_function[i],do { if ((__builtin_expect(!!(!(!(candidate.can_inline_function [i]) || (shared.IsInlineable() || shared.GetInlineability() == SharedFunctionInfo::kHasOptimizationDisabled))), 0))) { V8_Fatal ("Check failed: %s.", "candidate.can_inline_function[i]" " implies " "shared.IsInlineable() || shared.GetInlineability() == SharedFunctionInfo::kHasOptimizationDisabled" ); } } while (false) | |||
205 | shared.IsInlineable() ||do { if ((__builtin_expect(!!(!(!(candidate.can_inline_function [i]) || (shared.IsInlineable() || shared.GetInlineability() == SharedFunctionInfo::kHasOptimizationDisabled))), 0))) { V8_Fatal ("Check failed: %s.", "candidate.can_inline_function[i]" " implies " "shared.IsInlineable() || shared.GetInlineability() == SharedFunctionInfo::kHasOptimizationDisabled" ); } } while (false) | |||
206 | shared.GetInlineability() ==do { if ((__builtin_expect(!!(!(!(candidate.can_inline_function [i]) || (shared.IsInlineable() || shared.GetInlineability() == SharedFunctionInfo::kHasOptimizationDisabled))), 0))) { V8_Fatal ("Check failed: %s.", "candidate.can_inline_function[i]" " implies " "shared.IsInlineable() || shared.GetInlineability() == SharedFunctionInfo::kHasOptimizationDisabled" ); } } while (false) | |||
207 | SharedFunctionInfo::kHasOptimizationDisabled)do { if ((__builtin_expect(!!(!(!(candidate.can_inline_function [i]) || (shared.IsInlineable() || shared.GetInlineability() == SharedFunctionInfo::kHasOptimizationDisabled))), 0))) { V8_Fatal ("Check failed: %s.", "candidate.can_inline_function[i]" " implies " "shared.IsInlineable() || shared.GetInlineability() == SharedFunctionInfo::kHasOptimizationDisabled" ); } } while (false); | |||
208 | // Do not allow direct recursion i.e. f() -> f(). We still allow indirect | |||
209 | // recursion like f() -> g() -> f(). The indirect recursion is helpful in | |||
210 | // cases where f() is a small dispatch function that calls the appropriate | |||
211 | // function. In the case of direct recursion, we only have some static | |||
212 | // information for the first level of inlining and it may not be that useful | |||
213 | // to just inline one level in recursive calls. In some cases like tail | |||
214 | // recursion we may benefit from recursive inlining, if we have additional | |||
215 | // analysis that converts them to iterative implementations. Though it is | |||
216 | // not obvious if such an analysis is needed. | |||
217 | if (frame_info.shared_info().ToHandle(&frame_shared_info) && | |||
218 | frame_shared_info.equals(shared.object())) { | |||
219 | TRACE("Not considering call site #" << node->id() << ":" | |||
220 | << node->op()->mnemonic() | |||
221 | << ", because of recursive inlining"); | |||
222 | candidate.can_inline_function[i] = false; | |||
223 | } | |||
224 | if (candidate.can_inline_function[i]) { | |||
225 | can_inline_candidate = true; | |||
226 | BytecodeArrayRef bytecode = candidate.bytecode[i].value(); | |||
227 | candidate.total_size += bytecode.length(); | |||
228 | unsigned inlined_bytecode_size = 0; | |||
229 | if (candidate.functions[i].has_value()) { | |||
230 | JSFunctionRef function = candidate.functions[i].value(); | |||
231 | inlined_bytecode_size = function.code().GetInlinedBytecodeSize(); | |||
232 | candidate.total_size += inlined_bytecode_size; | |||
233 | } | |||
234 | candidate_is_small = candidate_is_small && | |||
235 | IsSmall(bytecode.length() + inlined_bytecode_size); | |||
236 | } | |||
237 | } | |||
238 | if (!can_inline_candidate) return NoChange(); | |||
239 | ||||
240 | // Gather feedback on how often this call site has been hit before. | |||
241 | if (node->opcode() == IrOpcode::kJSCall) { | |||
242 | CallParameters const p = CallParametersOf(node->op()); | |||
243 | candidate.frequency = p.frequency(); | |||
244 | } else { | |||
245 | ConstructParameters const p = ConstructParametersOf(node->op()); | |||
246 | candidate.frequency = p.frequency(); | |||
247 | } | |||
248 | ||||
249 | // Don't consider a {candidate} whose frequency is below the | |||
250 | // threshold, i.e. a call site that is only hit once every N | |||
251 | // invocations of the caller. | |||
252 | if (candidate.frequency.IsKnown() && | |||
253 | candidate.frequency.value() < FLAG_min_inlining_frequency) { | |||
254 | return NoChange(); | |||
255 | } | |||
256 | ||||
257 | // Found a candidate. Insert it into the set of seen nodes s.t. we don't | |||
258 | // revisit in the future. Note this insertion happens here and not earlier in | |||
259 | // order to make inlining decisions order-independent. A node may not be a | |||
260 | // candidate when first seen, but later reductions may turn it into a valid | |||
261 | // candidate. In that case, the node should be revisited by | |||
262 | // JSInliningHeuristic. | |||
263 | seen_.insert(node->id()); | |||
264 | ||||
265 | // Forcibly inline small functions here. In the case of polymorphic inlining | |||
266 | // candidate_is_small is set only when all functions are small. | |||
267 | if (candidate_is_small) { | |||
268 | TRACE("Inlining small function(s) at call site #" | |||
269 | << node->id() << ":" << node->op()->mnemonic()); | |||
270 | return InlineCandidate(candidate, true); | |||
271 | } | |||
272 | ||||
273 | // In the general case we remember the candidate for later. | |||
274 | candidates_.insert(candidate); | |||
275 | return NoChange(); | |||
276 | } | |||
277 | ||||
278 | void JSInliningHeuristic::Finalize() { | |||
279 | if (candidates_.empty()) return; // Nothing to do without candidates. | |||
| ||||
280 | if (FLAG_trace_turbo_inlining) PrintCandidates(); | |||
281 | ||||
282 | // We inline at most one candidate in every iteration of the fixpoint. | |||
283 | // This is to ensure that we don't consume the full inlining budget | |||
284 | // on things that aren't called very often. | |||
285 | // TODO(bmeurer): Use std::priority_queue instead of std::set here. | |||
286 | while (!candidates_.empty()) { | |||
287 | auto i = candidates_.begin(); | |||
288 | Candidate candidate = *i; | |||
289 | candidates_.erase(i); | |||
290 | ||||
291 | // Ignore this candidate if it's no longer valid. | |||
292 | if (!IrOpcode::IsInlineeOpcode(candidate.node->opcode())) continue; | |||
293 | if (candidate.node->IsDead()) continue; | |||
294 | ||||
295 | // Make sure we have some extra budget left, so that any small functions | |||
296 | // exposed by this function would be given a chance to inline. | |||
297 | double size_of_candidate = | |||
298 | candidate.total_size * FLAG_reserve_inline_budget_scale_factor; | |||
299 | int total_size = | |||
300 | total_inlined_bytecode_size_ + static_cast<int>(size_of_candidate); | |||
301 | if (total_size > max_inlined_bytecode_size_cumulative_) { | |||
302 | // Try if any smaller functions are available to inline. | |||
303 | continue; | |||
304 | } | |||
305 | ||||
306 | Reduction const reduction = InlineCandidate(candidate, false); | |||
307 | if (reduction.Changed()) return; | |||
308 | } | |||
309 | } | |||
310 | ||||
311 | namespace { | |||
312 | ||||
313 | struct NodeAndIndex { | |||
314 | Node* node; | |||
315 | int index; | |||
316 | }; | |||
317 | ||||
318 | bool CollectStateValuesOwnedUses(Node* node, Node* state_values, | |||
319 | NodeAndIndex* uses_buffer, size_t* use_count, | |||
320 | size_t max_uses) { | |||
321 | // Only accumulate states that are not shared with other users. | |||
322 | if (state_values->UseCount() > 1) return true; | |||
323 | for (int i = 0; i < state_values->InputCount(); i++) { | |||
324 | Node* input = state_values->InputAt(i); | |||
325 | if (input->opcode() == IrOpcode::kStateValues) { | |||
326 | if (!CollectStateValuesOwnedUses(node, input, uses_buffer, use_count, | |||
327 | max_uses)) { | |||
328 | return false; | |||
329 | } | |||
330 | } else if (input == node) { | |||
331 | if (*use_count >= max_uses) return false; | |||
332 | uses_buffer[*use_count] = {state_values, i}; | |||
333 | (*use_count)++; | |||
334 | } | |||
335 | } | |||
336 | return true; | |||
337 | } | |||
338 | ||||
339 | } // namespace | |||
340 | ||||
341 | Node* JSInliningHeuristic::DuplicateStateValuesAndRename(Node* state_values, | |||
342 | Node* from, Node* to, | |||
343 | StateCloneMode mode) { | |||
344 | // Only rename in states that are not shared with other users. This needs to | |||
345 | // be in sync with the condition in {CollectStateValuesOwnedUses}. | |||
346 | if (state_values->UseCount() > 1) return state_values; | |||
347 | Node* copy = mode == kChangeInPlace ? state_values : nullptr; | |||
348 | for (int i = 0; i < state_values->InputCount(); i++) { | |||
349 | Node* input = state_values->InputAt(i); | |||
350 | Node* processed; | |||
351 | if (input->opcode() == IrOpcode::kStateValues) { | |||
352 | processed = DuplicateStateValuesAndRename(input, from, to, mode); | |||
353 | } else if (input == from) { | |||
354 | processed = to; | |||
355 | } else { | |||
356 | processed = input; | |||
357 | } | |||
358 | if (processed != input) { | |||
359 | if (!copy) { | |||
360 | copy = graph()->CloneNode(state_values); | |||
361 | } | |||
362 | copy->ReplaceInput(i, processed); | |||
363 | } | |||
364 | } | |||
365 | return copy ? copy : state_values; | |||
366 | } | |||
367 | ||||
368 | namespace { | |||
369 | ||||
370 | bool CollectFrameStateUniqueUses(Node* node, FrameState frame_state, | |||
371 | NodeAndIndex* uses_buffer, size_t* use_count, | |||
372 | size_t max_uses) { | |||
373 | // Only accumulate states that are not shared with other users. | |||
374 | if (frame_state->UseCount() > 1) return true; | |||
375 | if (frame_state.stack() == node) { | |||
376 | if (*use_count >= max_uses) return false; | |||
377 | uses_buffer[*use_count] = {frame_state, FrameState::kFrameStateStackInput}; | |||
378 | (*use_count)++; | |||
379 | } | |||
380 | if (!CollectStateValuesOwnedUses(node, frame_state.locals(), uses_buffer, | |||
381 | use_count, max_uses)) { | |||
382 | return false; | |||
383 | } | |||
384 | return true; | |||
385 | } | |||
386 | ||||
387 | } // namespace | |||
388 | ||||
389 | FrameState JSInliningHeuristic::DuplicateFrameStateAndRename( | |||
390 | FrameState frame_state, Node* from, Node* to, StateCloneMode mode) { | |||
391 | // Only rename in states that are not shared with other users. This needs to | |||
392 | // be in sync with the condition in {DuplicateFrameStateAndRename}. | |||
393 | if (frame_state->UseCount() > 1) return frame_state; | |||
394 | Node* copy = | |||
395 | mode == kChangeInPlace ? static_cast<Node*>(frame_state) : nullptr; | |||
396 | if (frame_state.stack() == from) { | |||
397 | if (!copy) { | |||
398 | copy = graph()->CloneNode(frame_state); | |||
399 | } | |||
400 | copy->ReplaceInput(FrameState::kFrameStateStackInput, to); | |||
401 | } | |||
402 | Node* locals = frame_state.locals(); | |||
403 | Node* new_locals = DuplicateStateValuesAndRename(locals, from, to, mode); | |||
404 | if (new_locals != locals) { | |||
405 | if (!copy) { | |||
406 | copy = graph()->CloneNode(frame_state); | |||
407 | } | |||
408 | copy->ReplaceInput(FrameState::kFrameStateLocalsInput, new_locals); | |||
409 | } | |||
410 | return copy != nullptr ? FrameState{copy} : frame_state; | |||
411 | } | |||
412 | ||||
413 | bool JSInliningHeuristic::TryReuseDispatch(Node* node, Node* callee, | |||
414 | Node** if_successes, Node** calls, | |||
415 | Node** inputs, int input_count) { | |||
416 | // We will try to reuse the control flow branch created for computing | |||
417 | // the {callee} target of the call. We only reuse the branch if there | |||
418 | // is no side-effect between the call and the branch, and if the callee is | |||
419 | // only used as the target (and possibly also in the related frame states). | |||
420 | ||||
421 | // We are trying to match the following pattern: | |||
422 | // | |||
423 | // C1 C2 | |||
424 | // . . | |||
425 | // | | | |||
426 | // Merge(merge) <-----------------+ | |||
427 | // ^ ^ | | |||
428 | // V1 V2 | | E1 E2 | | |||
429 | // . . | +----+ . . | | |||
430 | // | | | | | | | | |||
431 | // Phi(callee) EffectPhi(effect_phi) | | |||
432 | // ^ ^ | | |||
433 | // | | | | |||
434 | // +----+ | | | |||
435 | // | | | | | |||
436 | // | StateValues | | | |||
437 | // | ^ | | | |||
438 | // +----+ | | | | |||
439 | // | | | | | | |||
440 | // | FrameState | | | |||
441 | // | ^ | | | |||
442 | // | | | +---+ | |||
443 | // | | | | | | |||
444 | // +----+ Checkpoint(checkpoint) | | |||
445 | // | | ^ | | |||
446 | // | StateValues | +-------------+ | |||
447 | // | | | | | |||
448 | // +-----+ | | | | |||
449 | // | | | | | | |||
450 | // | FrameState | | | |||
451 | // | ^ | | | |||
452 | // +-----------+ | | | | |||
453 | // Call(node) | |||
454 | // | | |||
455 | // | | |||
456 | // . | |||
457 | // | |||
458 | // The {callee} here is a phi that merges the possible call targets, {node} | |||
459 | // is the actual call that we will try to duplicate and connect to the | |||
460 | // control that comes into {merge}. There can be a {checkpoint} between | |||
461 | // the call and the calle phi. | |||
462 | // | |||
463 | // The idea is to get rid of the merge, effect phi and phi, then duplicate | |||
464 | // the call (with all the frame states and such), and connect the duplicated | |||
465 | // calls and states directly to the inputs of the ex-phi, ex-effect-phi and | |||
466 | // ex-merge. The tricky part is to make sure that there is no interference | |||
467 | // from the outside. In particular, there should not be any unaccounted uses | |||
468 | // of the phi, effect-phi and merge because we will remove them from | |||
469 | // the graph. | |||
470 | // | |||
471 | // V1 E1 C1 V2 E2 C2 | |||
472 | // . . . . . . | |||
473 | // | | | | | | | |||
474 | // +----+ | | +----+ | | |||
475 | // | | | | | | | | |||
476 | // | StateValues | | | StateValues | | |||
477 | // | ^ | | | ^ | | |||
478 | // +----+ | | | +----+ | | | |||
479 | // | | | | | | | | | | |||
480 | // | FrameState | | | FrameState | | |||
481 | // | ^ | | | ^ | | |||
482 | // | | | | | | | | |||
483 | // | | | | | | | | |||
484 | // +----+ Checkpoint | +----+ Checkpoint | | |||
485 | // | | ^ | | | ^ | | |||
486 | // | StateValues | | | StateValues | | | |||
487 | // | | | | | | | | | |||
488 | // +-----+ | | | +-----+ | | | | |||
489 | // | | | | | | | | | | | |||
490 | // | FrameState | | | FrameState | | | |||
491 | // | ^ | | | ^ | | | |||
492 | // +-------------+| | | +-------------+| | | | |||
493 | // Call----+ Call----+ | |||
494 | // | | | |||
495 | // +-------+ +------------+ | |||
496 | // | | | |||
497 | // Merge | |||
498 | // EffectPhi | |||
499 | // Phi | |||
500 | // | | |||
501 | // ... | |||
502 | ||||
503 | // Bailout if the call is not polymorphic anymore (other reducers might | |||
504 | // have replaced the callee phi with a constant). | |||
505 | if (callee->opcode() != IrOpcode::kPhi) return false; | |||
506 | int const num_calls = callee->op()->ValueInputCount(); | |||
507 | ||||
508 | // If there is a control node between the callee computation | |||
509 | // and the call, bail out. | |||
510 | Node* merge = NodeProperties::GetControlInput(callee); | |||
511 | if (NodeProperties::GetControlInput(node) != merge) return false; | |||
512 | ||||
513 | // If there is a non-checkpoint effect node between the callee computation | |||
514 | // and the call, bail out. We will drop any checkpoint between the call and | |||
515 | // the callee phi because the callee computation should have its own | |||
516 | // checkpoint that the call can fall back to. | |||
517 | Node* checkpoint = nullptr; | |||
518 | Node* effect = NodeProperties::GetEffectInput(node); | |||
519 | if (effect->opcode() == IrOpcode::kCheckpoint) { | |||
520 | checkpoint = effect; | |||
521 | if (NodeProperties::GetControlInput(checkpoint) != merge) return false; | |||
522 | effect = NodeProperties::GetEffectInput(effect); | |||
523 | } | |||
524 | if (effect->opcode() != IrOpcode::kEffectPhi) return false; | |||
525 | if (NodeProperties::GetControlInput(effect) != merge) return false; | |||
526 | Node* effect_phi = effect; | |||
527 | ||||
528 | // The effect phi, the callee, the call and the checkpoint must be the only | |||
529 | // users of the merge. | |||
530 | for (Node* merge_use : merge->uses()) { | |||
531 | if (merge_use != effect_phi && merge_use != callee && merge_use != node && | |||
532 | merge_use != checkpoint) { | |||
533 | return false; | |||
534 | } | |||
535 | } | |||
536 | ||||
537 | // The effect phi must be only used by the checkpoint or the call. | |||
538 | for (Node* effect_phi_use : effect_phi->uses()) { | |||
539 | if (effect_phi_use != node && effect_phi_use != checkpoint) return false; | |||
540 | } | |||
541 | ||||
542 | // We must replace the callee phi with the appropriate constant in | |||
543 | // the entire subgraph reachable by inputs from the call (terminating | |||
544 | // at phis and merges). Since we do not want to walk (and later duplicate) | |||
545 | // the subgraph here, we limit the possible uses to this set: | |||
546 | // | |||
547 | // 1. In the call (as a target). | |||
548 | // 2. The checkpoint between the call and the callee computation merge. | |||
549 | // 3. The lazy deoptimization frame state. | |||
550 | // | |||
551 | // This corresponds to the most common pattern, where the function is | |||
552 | // called with only local variables or constants as arguments. | |||
553 | // | |||
554 | // To check the uses, we first collect all the occurrences of callee in 1, 2 | |||
555 | // and 3, and then we check that all uses of callee are in the collected | |||
556 | // occurrences. If there is an unaccounted use, we do not try to rewire | |||
557 | // the control flow. | |||
558 | // | |||
559 | // Note: With CFG, this would be much easier and more robust - we would just | |||
560 | // duplicate all the nodes between the merge and the call, replacing all | |||
561 | // occurrences of the {callee} phi with the appropriate constant. | |||
562 | ||||
563 | // First compute the set of uses that are only reachable from 2 and 3. | |||
564 | const size_t kMaxUses = 8; | |||
565 | NodeAndIndex replaceable_uses[kMaxUses]; | |||
566 | size_t replaceable_uses_count = 0; | |||
567 | ||||
568 | // Collect the uses to check case 2. | |||
569 | Node* checkpoint_state = nullptr; | |||
570 | if (checkpoint
| |||
571 | checkpoint_state = checkpoint->InputAt(0); | |||
572 | if (!CollectFrameStateUniqueUses(callee, FrameState{checkpoint_state}, | |||
573 | replaceable_uses, &replaceable_uses_count, | |||
574 | kMaxUses)) { | |||
575 | return false; | |||
576 | } | |||
577 | } | |||
578 | ||||
579 | // Collect the uses to check case 3. | |||
580 | FrameState frame_state{NodeProperties::GetFrameStateInput(node)}; | |||
581 | if (!CollectFrameStateUniqueUses(callee, frame_state, replaceable_uses, | |||
582 | &replaceable_uses_count, kMaxUses)) { | |||
583 | return false; | |||
584 | } | |||
585 | ||||
586 | // Bail out if there is a use of {callee} that is not reachable from 1, 2 | |||
587 | // and 3. | |||
588 | for (Edge edge : callee->use_edges()) { | |||
589 | // Case 1 (use by the call as a target). | |||
590 | if (edge.from() == node && edge.index() == 0) continue; | |||
591 | // Case 2 and 3 - used in checkpoint and/or lazy deopt frame states. | |||
592 | bool found = false; | |||
593 | for (size_t i = 0; i < replaceable_uses_count; i++) { | |||
594 | if (replaceable_uses[i].node == edge.from() && | |||
595 | replaceable_uses[i].index == edge.index()) { | |||
596 | found = true; | |||
597 | break; | |||
598 | } | |||
599 | } | |||
600 | if (!found) return false; | |||
601 | } | |||
602 | ||||
603 | // Clone the call and the framestate, including the uniquely reachable | |||
604 | // state values, making sure that we replace the phi with the constant. | |||
605 | for (int i = 0; i < num_calls; ++i) { | |||
606 | // Clone the calls for each branch. | |||
607 | // We need to specialize the calls to the correct target, effect, and | |||
608 | // control. We also need to duplicate the checkpoint and the lazy | |||
609 | // frame state, and change all the uses of the callee to the constant | |||
610 | // callee. | |||
611 | Node* target = callee->InputAt(i); | |||
612 | Node* effect_phi_effect = effect_phi->InputAt(i); | |||
613 | Node* control = merge->InputAt(i); | |||
614 | ||||
615 | if (checkpoint) { | |||
616 | // Duplicate the checkpoint. | |||
617 | FrameState new_checkpoint_state = DuplicateFrameStateAndRename( | |||
618 | FrameState{checkpoint_state}, callee, target, | |||
619 | (i == num_calls - 1) ? kChangeInPlace : kCloneState); | |||
620 | effect_phi_effect = graph()->NewNode( | |||
621 | checkpoint->op(), new_checkpoint_state, effect_phi_effect, control); | |||
622 | } | |||
623 | ||||
624 | // Duplicate the call. | |||
625 | FrameState new_lazy_frame_state = DuplicateFrameStateAndRename( | |||
626 | frame_state, callee, target, | |||
627 | (i == num_calls - 1) ? kChangeInPlace : kCloneState); | |||
628 | inputs[0] = target; | |||
629 | inputs[input_count - 3] = new_lazy_frame_state; | |||
630 | inputs[input_count - 2] = effect_phi_effect; | |||
631 | inputs[input_count - 1] = control; | |||
632 | calls[i] = if_successes[i] = | |||
633 | graph()->NewNode(node->op(), input_count, inputs); | |||
634 | } | |||
635 | ||||
636 | // Mark the control inputs dead, so that we can kill the merge. | |||
637 | node->ReplaceInput(input_count - 1, jsgraph()->Dead()); | |||
638 | callee->ReplaceInput(num_calls, jsgraph()->Dead()); | |||
639 | effect_phi->ReplaceInput(num_calls, jsgraph()->Dead()); | |||
640 | if (checkpoint
| |||
641 | checkpoint->ReplaceInput(2, jsgraph()->Dead()); | |||
642 | } | |||
643 | ||||
644 | merge->Kill(); | |||
645 | return true; | |||
646 | } | |||
647 | ||||
648 | void JSInliningHeuristic::CreateOrReuseDispatch(Node* node, Node* callee, | |||
649 | Candidate const& candidate, | |||
650 | Node** if_successes, | |||
651 | Node** calls, Node** inputs, | |||
652 | int input_count) { | |||
653 | SourcePositionTable::Scope position( | |||
654 | source_positions_, source_positions_->GetSourcePosition(node)); | |||
655 | if (TryReuseDispatch(node, callee, if_successes, calls, inputs, | |||
656 | input_count)) { | |||
657 | return; | |||
658 | } | |||
659 | ||||
660 | STATIC_ASSERT(JSCallOrConstructNode::kHaveIdenticalLayouts)static_assert(JSCallOrConstructNode::kHaveIdenticalLayouts, "JSCallOrConstructNode::kHaveIdenticalLayouts" ); | |||
661 | ||||
662 | Node* fallthrough_control = NodeProperties::GetControlInput(node); | |||
663 | int const num_calls = candidate.num_functions; | |||
664 | ||||
665 | // Create the appropriate control flow to dispatch to the cloned calls. | |||
666 | for (int i = 0; i < num_calls; ++i) { | |||
667 | // TODO(2206): Make comparison be based on underlying SharedFunctionInfo | |||
668 | // instead of the target JSFunction reference directly. | |||
669 | Node* target = jsgraph()->Constant(candidate.functions[i].value()); | |||
670 | if (i != (num_calls - 1)) { | |||
671 | Node* check = | |||
672 | graph()->NewNode(simplified()->ReferenceEqual(), callee, target); | |||
673 | Node* branch = | |||
674 | graph()->NewNode(common()->Branch(), check, fallthrough_control); | |||
675 | fallthrough_control = graph()->NewNode(common()->IfFalse(), branch); | |||
676 | if_successes[i] = graph()->NewNode(common()->IfTrue(), branch); | |||
677 | } else { | |||
678 | if_successes[i] = fallthrough_control; | |||
679 | } | |||
680 | ||||
681 | // Clone the calls for each branch. | |||
682 | // The first input to the call is the actual target (which we specialize | |||
683 | // to the known {target}); the last input is the control dependency. | |||
684 | // We also specialize the new.target of JSConstruct {node}s if it refers | |||
685 | // to the same node as the {node}'s target input, so that we can later | |||
686 | // properly inline the JSCreate operations. | |||
687 | if (node->opcode() == IrOpcode::kJSConstruct) { | |||
688 | // TODO(jgruber, v8:10675): This branch seems unreachable. | |||
689 | JSConstructNode n(node); | |||
690 | if (inputs[n.TargetIndex()] == inputs[n.NewTargetIndex()]) { | |||
691 | inputs[n.NewTargetIndex()] = target; | |||
692 | } | |||
693 | } | |||
694 | inputs[JSCallOrConstructNode::TargetIndex()] = target; | |||
695 | inputs[input_count - 1] = if_successes[i]; | |||
696 | calls[i] = if_successes[i] = | |||
697 | graph()->NewNode(node->op(), input_count, inputs); | |||
698 | } | |||
699 | } | |||
700 | ||||
701 | Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate, | |||
702 | bool small_function) { | |||
703 | int const num_calls = candidate.num_functions; | |||
704 | Node* const node = candidate.node; | |||
705 | #if V8_ENABLE_WEBASSEMBLY1 | |||
706 | DCHECK_NE(node->opcode(), IrOpcode::kJSWasmCall)((void) 0); | |||
707 | #endif // V8_ENABLE_WEBASSEMBLY | |||
708 | if (num_calls == 1) { | |||
709 | Reduction const reduction = inliner_.ReduceJSCall(node); | |||
710 | if (reduction.Changed()) { | |||
711 | total_inlined_bytecode_size_ += candidate.bytecode[0].value().length(); | |||
712 | } | |||
713 | return reduction; | |||
714 | } | |||
715 | ||||
716 | // Expand the JSCall/JSConstruct node to a subgraph first if | |||
717 | // we have multiple known target functions. | |||
718 | DCHECK_LT(1, num_calls)((void) 0); | |||
719 | Node* calls[kMaxCallPolymorphism + 1]; | |||
720 | Node* if_successes[kMaxCallPolymorphism]; | |||
721 | Node* callee = NodeProperties::GetValueInput(node, 0); | |||
722 | ||||
723 | // Setup the inputs for the cloned call nodes. | |||
724 | int const input_count = node->InputCount(); | |||
725 | Node** inputs = graph()->zone()->NewArray<Node*>(input_count); | |||
726 | for (int i = 0; i < input_count; ++i) { | |||
727 | inputs[i] = node->InputAt(i); | |||
728 | } | |||
729 | ||||
730 | // Create the appropriate control flow to dispatch to the cloned calls. | |||
731 | CreateOrReuseDispatch(node, callee, candidate, if_successes, calls, inputs, | |||
732 | input_count); | |||
733 | ||||
734 | // Check if we have an exception projection for the call {node}. | |||
735 | Node* if_exception = nullptr; | |||
736 | if (NodeProperties::IsExceptionalCall(node, &if_exception)) { | |||
737 | Node* if_exceptions[kMaxCallPolymorphism + 1]; | |||
738 | for (int i = 0; i < num_calls; ++i) { | |||
739 | if_successes[i] = graph()->NewNode(common()->IfSuccess(), calls[i]); | |||
| ||||
740 | if_exceptions[i] = | |||
741 | graph()->NewNode(common()->IfException(), calls[i], calls[i]); | |||
742 | } | |||
743 | ||||
744 | // Morph the {if_exception} projection into a join. | |||
745 | Node* exception_control = | |||
746 | graph()->NewNode(common()->Merge(num_calls), num_calls, if_exceptions); | |||
747 | if_exceptions[num_calls] = exception_control; | |||
748 | Node* exception_effect = graph()->NewNode(common()->EffectPhi(num_calls), | |||
749 | num_calls + 1, if_exceptions); | |||
750 | Node* exception_value = graph()->NewNode( | |||
751 | common()->Phi(MachineRepresentation::kTagged, num_calls), num_calls + 1, | |||
752 | if_exceptions); | |||
753 | ReplaceWithValue(if_exception, exception_value, exception_effect, | |||
754 | exception_control); | |||
755 | } | |||
756 | ||||
757 | // Morph the original call site into a join of the dispatched call sites. | |||
758 | Node* control = | |||
759 | graph()->NewNode(common()->Merge(num_calls), num_calls, if_successes); | |||
760 | calls[num_calls] = control; | |||
761 | Node* effect = | |||
762 | graph()->NewNode(common()->EffectPhi(num_calls), num_calls + 1, calls); | |||
763 | Node* value = | |||
764 | graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, num_calls), | |||
765 | num_calls + 1, calls); | |||
766 | ReplaceWithValue(node, value, effect, control); | |||
767 | ||||
768 | // Inline the individual, cloned call sites. | |||
769 | for (int i = 0; i < num_calls && total_inlined_bytecode_size_ < | |||
770 | max_inlined_bytecode_size_absolute_; | |||
771 | ++i) { | |||
772 | if (candidate.can_inline_function[i] && | |||
773 | (small_function || total_inlined_bytecode_size_ < | |||
774 | max_inlined_bytecode_size_cumulative_)) { | |||
775 | Node* call = calls[i]; | |||
776 | Reduction const reduction = inliner_.ReduceJSCall(call); | |||
777 | if (reduction.Changed()) { | |||
778 | total_inlined_bytecode_size_ += candidate.bytecode[i]->length(); | |||
779 | // Killing the call node is not strictly necessary, but it is safer to | |||
780 | // make sure we do not resurrect the node. | |||
781 | call->Kill(); | |||
782 | } | |||
783 | } | |||
784 | } | |||
785 | ||||
786 | return Replace(value); | |||
787 | } | |||
788 | ||||
789 | bool JSInliningHeuristic::CandidateCompare::operator()( | |||
790 | const Candidate& left, const Candidate& right) const { | |||
791 | if (right.frequency.IsUnknown()) { | |||
792 | if (left.frequency.IsUnknown()) { | |||
793 | // If left and right are both unknown then the ordering is indeterminate, | |||
794 | // which breaks strict weak ordering requirements, so we fall back to the | |||
795 | // node id as a tie breaker. | |||
796 | return left.node->id() > right.node->id(); | |||
797 | } | |||
798 | return true; | |||
799 | } else if (left.frequency.IsUnknown()) { | |||
800 | return false; | |||
801 | } else if (left.frequency.value() > right.frequency.value()) { | |||
802 | return true; | |||
803 | } else if (left.frequency.value() < right.frequency.value()) { | |||
804 | return false; | |||
805 | } else { | |||
806 | return left.node->id() > right.node->id(); | |||
807 | } | |||
808 | } | |||
809 | ||||
810 | void JSInliningHeuristic::PrintCandidates() { | |||
811 | StdoutStream os; | |||
812 | os << candidates_.size() << " candidate(s) for inlining:" << std::endl; | |||
813 | for (const Candidate& candidate : candidates_) { | |||
814 | os << "- candidate: " << candidate.node->op()->mnemonic() << " node #" | |||
815 | << candidate.node->id() << " with frequency " << candidate.frequency | |||
816 | << ", " << candidate.num_functions << " target(s):" << std::endl; | |||
817 | for (int i = 0; i < candidate.num_functions; ++i) { | |||
818 | SharedFunctionInfoRef shared = candidate.functions[i].has_value() | |||
819 | ? candidate.functions[i]->shared() | |||
820 | : candidate.shared_info.value(); | |||
821 | os << " - target: " << shared; | |||
822 | if (candidate.bytecode[i].has_value()) { | |||
823 | os << ", bytecode size: " << candidate.bytecode[i]->length(); | |||
824 | if (candidate.functions[i].has_value()) { | |||
825 | JSFunctionRef function = candidate.functions[i].value(); | |||
826 | unsigned inlined_bytecode_size = | |||
827 | function.code().GetInlinedBytecodeSize(); | |||
828 | if (inlined_bytecode_size > 0) { | |||
829 | os << ", existing opt code's inlined bytecode size: " | |||
830 | << inlined_bytecode_size; | |||
831 | } | |||
832 | } | |||
833 | } else { | |||
834 | os << ", no bytecode"; | |||
835 | } | |||
836 | os << std::endl; | |||
837 | } | |||
838 | } | |||
839 | } | |||
840 | ||||
841 | Graph* JSInliningHeuristic::graph() const { return jsgraph()->graph(); } | |||
842 | ||||
843 | CompilationDependencies* JSInliningHeuristic::dependencies() const { | |||
844 | return broker()->dependencies(); | |||
845 | } | |||
846 | ||||
847 | CommonOperatorBuilder* JSInliningHeuristic::common() const { | |||
848 | return jsgraph()->common(); | |||
849 | } | |||
850 | ||||
851 | SimplifiedOperatorBuilder* JSInliningHeuristic::simplified() const { | |||
852 | return jsgraph()->simplified(); | |||
853 | } | |||
854 | ||||
855 | #undef TRACE | |||
856 | ||||
857 | } // namespace compiler | |||
858 | } // namespace internal | |||
859 | } // namespace v8 |