1 | /* |
2 | * Copyright (C) 2014-2019 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "DFGPutStackSinkingPhase.h" |
28 | |
29 | #if ENABLE(DFG_JIT) |
30 | |
31 | #include "DFGBlockMapInlines.h" |
32 | #include "DFGGraph.h" |
33 | #include "DFGInsertionSet.h" |
34 | #include "DFGPhase.h" |
35 | #include "DFGPreciseLocalClobberize.h" |
36 | #include "DFGSSACalculator.h" |
37 | #include "DFGValidate.h" |
38 | #include "JSCInlines.h" |
39 | #include "OperandsInlines.h" |
40 | |
41 | namespace JSC { namespace DFG { |
42 | |
43 | namespace { |
44 | |
45 | namespace DFGPutStackSinkingPhaseInternal { |
46 | static constexpr bool verbose = false; |
47 | } |
48 | |
49 | class PutStackSinkingPhase : public Phase { |
50 | public: |
51 | PutStackSinkingPhase(Graph& graph) |
52 | : Phase(graph, "PutStack sinking" ) |
53 | { |
54 | } |
55 | |
56 | bool run() |
57 | { |
58 | // FIXME: One of the problems of this approach is that it will create a duplicate Phi graph |
59 | // for sunken PutStacks in the presence of interesting control flow merges, and where the |
60 | // value being PutStack'd is also otherwise live in the DFG code. We could work around this |
61 | // by doing the sinking over CPS, or maybe just by doing really smart hoisting. It's also |
62 | // possible that the duplicate Phi graph can be deduplicated by B3. It would be best if we |
63 | // could observe that there is already a Phi graph in place that does what we want. In |
64 | // principle if we have a request to place a Phi at a particular place, we could just check |
65 | // if there is already a Phi that does what we want. Because PutStackSinkingPhase runs just |
66 | // after SSA conversion, we have almost a guarantee that the Phi graph we produce here would |
67 | // be trivially redundant to the one we already have. |
68 | |
69 | // FIXME: This phase doesn't adequately use KillStacks. KillStack can be viewed as a def. |
70 | // This is mostly inconsequential; it would be a bug to have a local live at a KillStack. |
71 | // More important is that KillStack should swallow any deferral. After a KillStack, the |
72 | // local should behave like a TOP deferral because it would be invalid for anyone to trust |
73 | // the stack. It's not clear to me if this is important or not. |
74 | // https://bugs.webkit.org/show_bug.cgi?id=145296 |
75 | |
76 | if (DFGPutStackSinkingPhaseInternal::verbose) { |
77 | dataLog("Graph before PutStack sinking:\n" ); |
78 | m_graph.dump(); |
79 | } |
80 | |
81 | m_graph.ensureSSADominators(); |
82 | |
83 | SSACalculator ssaCalculator(m_graph); |
84 | InsertionSet insertionSet(m_graph); |
85 | |
86 | // First figure out where various locals are live. |
87 | BlockMap<Operands<bool>> liveAtHead(m_graph); |
88 | BlockMap<Operands<bool>> liveAtTail(m_graph); |
89 | |
90 | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
91 | liveAtHead[block] = Operands<bool>(OperandsLike, block->variablesAtHead); |
92 | liveAtTail[block] = Operands<bool>(OperandsLike, block->variablesAtHead); |
93 | |
94 | liveAtHead[block].fill(false); |
95 | liveAtTail[block].fill(false); |
96 | } |
97 | |
98 | bool changed; |
99 | do { |
100 | changed = false; |
101 | |
102 | for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
103 | BasicBlock* block = m_graph.block(blockIndex); |
104 | if (!block) |
105 | continue; |
106 | |
107 | Operands<bool> live = liveAtTail[block]; |
108 | for (unsigned nodeIndex = block->size(); nodeIndex--;) { |
109 | Node* node = block->at(nodeIndex); |
110 | if (DFGPutStackSinkingPhaseInternal::verbose) |
111 | dataLog("Live at " , node, ": " , live, "\n" ); |
112 | |
113 | Vector<VirtualRegister, 4> reads; |
114 | Vector<VirtualRegister, 4> writes; |
115 | auto escapeHandler = [&] (VirtualRegister operand) { |
116 | if (operand.isHeader()) |
117 | return; |
118 | if (DFGPutStackSinkingPhaseInternal::verbose) |
119 | dataLog(" " , operand, " is live at " , node, "\n" ); |
120 | reads.append(operand); |
121 | }; |
122 | |
123 | auto writeHandler = [&] (VirtualRegister operand) { |
124 | if (operand.isHeader()) |
125 | return; |
126 | RELEASE_ASSERT(node->op() == PutStack || node->op() == LoadVarargs || node->op() == ForwardVarargs || node->op() == KillStack); |
127 | writes.append(operand); |
128 | }; |
129 | |
130 | preciseLocalClobberize( |
131 | m_graph, node, escapeHandler, writeHandler, |
132 | [&] (VirtualRegister, LazyNode) { }); |
133 | |
134 | for (VirtualRegister operand : writes) |
135 | live.operand(operand) = false; |
136 | for (VirtualRegister operand : reads) |
137 | live.operand(operand) = true; |
138 | } |
139 | |
140 | if (live == liveAtHead[block]) |
141 | continue; |
142 | |
143 | liveAtHead[block] = live; |
144 | changed = true; |
145 | |
146 | for (BasicBlock* predecessor : block->predecessors) { |
147 | for (size_t i = live.size(); i--;) |
148 | liveAtTail[predecessor][i] |= live[i]; |
149 | } |
150 | } |
151 | |
152 | } while (changed); |
153 | |
154 | // All of the arguments should be live at head of root. Note that we may find that some |
155 | // locals are live at head of root. This seems wrong but isn't. This will happen for example |
156 | // if the function accesses closure variable #42 for some other function and we either don't |
157 | // have variable #42 at all or we haven't set it at root, for whatever reason. Basically this |
158 | // arises since our aliasing for closure variables is conservatively based on variable number |
159 | // and ignores the owning symbol table. We should probably fix this eventually and make our |
160 | // aliasing more precise. |
161 | // |
162 | // For our purposes here, the imprecision in the aliasing is harmless. It just means that we |
163 | // may not do as much Phi pruning as we wanted. |
164 | for (size_t i = liveAtHead.atIndex(0).numberOfArguments(); i--;) |
165 | DFG_ASSERT(m_graph, nullptr, liveAtHead.atIndex(0).argument(i)); |
166 | |
167 | // Next identify where we would want to sink PutStacks to. We say that there is a deferred |
168 | // flush if we had a PutStack with a given FlushFormat but it hasn't been materialized yet. |
169 | // Deferrals have the following lattice; but it's worth noting that the TOP part of the |
170 | // lattice serves an entirely different purpose than the rest of the lattice: it just means |
171 | // that we're in a region of code where nobody should have been relying on the value. The |
172 | // rest of the lattice means that we either have a PutStack that is deferred (i.e. still |
173 | // needs to be executed) or there isn't one (because we've alraedy executed it). |
174 | // |
175 | // Bottom: |
176 | // Represented as DeadFlush. |
177 | // Means that all previous PutStacks have been executed so there is nothing deferred. |
178 | // During merging this is subordinate to the other kinds of deferrals, because it |
179 | // represents the fact that we've already executed all necessary PutStacks. This implies |
180 | // that there *had* been some PutStacks that we should have executed. |
181 | // |
182 | // Top: |
183 | // Represented as ConflictingFlush. |
184 | // Represents the fact that we know, via forward flow, that there isn't any value in the |
185 | // given local that anyone should have been relying on. This comes into play at the |
186 | // prologue (because in SSA form at the prologue no local has any value) or when we merge |
187 | // deferrals for different formats's. A lexical scope in which a local had some semantic |
188 | // meaning will by this point share the same format; if we had stores from different |
189 | // lexical scopes that got merged together then we may have a conflicting format. Hence |
190 | // a conflicting format proves that we're no longer in an area in which the variable was |
191 | // in scope. Note that this is all approximate and only precise enough to later answer |
192 | // questions pertinent to sinking. For example, this doesn't always detect when a local |
193 | // is no longer semantically relevant - we may well have a deferral from inside some |
194 | // inlined call survive outside of that inlined code, and this is generally OK. In the |
195 | // worst case it means that we might think that a deferral that is actually dead must |
196 | // still be executed. But we usually catch that with liveness. Liveness usually catches |
197 | // such cases, but that's not guaranteed since liveness is conservative. |
198 | // |
199 | // What Top does give us is detects situations where we both don't need to care about a |
200 | // deferral and there is no way that we could reason about it anyway. If we merged |
201 | // deferrals for different formats then we wouldn't know the format to use. So, we use |
202 | // Top in that case because that's also a case where we know that we can ignore the |
203 | // deferral. |
204 | // |
205 | // Deferral with a concrete format: |
206 | // Represented by format values other than DeadFlush or ConflictingFlush. |
207 | // Represents the fact that the original code would have done a PutStack but we haven't |
208 | // identified an operation that would have observed that PutStack. |
209 | // |
210 | // We need to be precise about liveness in this phase because not doing so |
211 | // could cause us to insert a PutStack before a node we thought may escape a |
212 | // value that it doesn't really escape. Sinking this PutStack above such a node may |
213 | // cause us to insert a GetStack that we forward to the Phi we're feeding into the |
214 | // sunken PutStack. Inserting such a GetStack could cause us to load garbage and |
215 | // can confuse the AI to claim untrue things (like that the program will exit when |
216 | // it really won't). |
217 | BlockMap<Operands<FlushFormat>> deferredAtHead(m_graph); |
218 | BlockMap<Operands<FlushFormat>> deferredAtTail(m_graph); |
219 | |
220 | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
221 | deferredAtHead[block] = |
222 | Operands<FlushFormat>(OperandsLike, block->variablesAtHead); |
223 | deferredAtTail[block] = |
224 | Operands<FlushFormat>(OperandsLike, block->variablesAtHead); |
225 | } |
226 | |
227 | for (unsigned local = deferredAtHead.atIndex(0).numberOfLocals(); local--;) |
228 | deferredAtHead.atIndex(0).local(local) = ConflictingFlush; |
229 | |
230 | do { |
231 | changed = false; |
232 | |
233 | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
234 | Operands<FlushFormat> deferred = deferredAtHead[block]; |
235 | |
236 | for (Node* node : *block) { |
237 | if (DFGPutStackSinkingPhaseInternal::verbose) |
238 | dataLog("Deferred at " , node, ":" , deferred, "\n" ); |
239 | |
240 | if (node->op() == GetStack) { |
241 | // Handle the case that the input doesn't match our requirements. This is |
242 | // really a bug, but it's a benign one if we simply don't run this phase. |
243 | // It usually arises because of patterns like: |
244 | // |
245 | // if (thing) |
246 | // PutStack() |
247 | // ... |
248 | // if (thing) |
249 | // GetStack() |
250 | // |
251 | // Or: |
252 | // |
253 | // if (never happens) |
254 | // GetStack() |
255 | // |
256 | // Because this phase runs early in SSA, it should be sensible to enforce |
257 | // that no such code pattern has arisen yet. So, when validation is |
258 | // enabled, we assert that we aren't seeing this. But with validation |
259 | // disabled we silently let this fly and we just abort this phase. |
260 | // FIXME: Get rid of all remaining cases of conflicting GetStacks. |
261 | // https://bugs.webkit.org/show_bug.cgi?id=150398 |
262 | |
263 | bool isConflicting = |
264 | deferred.operand(node->stackAccessData()->local) == ConflictingFlush; |
265 | |
266 | if (validationEnabled()) |
267 | DFG_ASSERT(m_graph, node, !isConflicting); |
268 | |
269 | if (isConflicting) { |
270 | // Oh noes! Abort!! |
271 | return false; |
272 | } |
273 | |
274 | // A GetStack doesn't affect anything, since we know which local we are reading |
275 | // from. |
276 | continue; |
277 | } else if (node->op() == PutStack) { |
278 | VirtualRegister operand = node->stackAccessData()->local; |
279 | deferred.operand(operand) = node->stackAccessData()->format; |
280 | continue; |
281 | } else if (node->op() == KillStack) { |
282 | // We don't want to sink a PutStack past a KillStack. |
283 | deferred.operand(node->unlinkedLocal()) = ConflictingFlush; |
284 | continue; |
285 | } |
286 | |
287 | auto escapeHandler = [&] (VirtualRegister operand) { |
288 | if (DFGPutStackSinkingPhaseInternal::verbose) |
289 | dataLog("For " , node, " escaping " , operand, "\n" ); |
290 | if (operand.isHeader()) |
291 | return; |
292 | // We will materialize just before any reads. |
293 | deferred.operand(operand) = DeadFlush; |
294 | }; |
295 | |
296 | auto writeHandler = [&] (VirtualRegister operand) { |
297 | if (operand.isHeader()) |
298 | return; |
299 | RELEASE_ASSERT(node->op() == LoadVarargs || node->op() == ForwardVarargs); |
300 | deferred.operand(operand) = DeadFlush; |
301 | }; |
302 | |
303 | preciseLocalClobberize( |
304 | m_graph, node, escapeHandler, writeHandler, |
305 | [&] (VirtualRegister, LazyNode) { }); |
306 | } |
307 | |
308 | if (deferred == deferredAtTail[block]) |
309 | continue; |
310 | |
311 | deferredAtTail[block] = deferred; |
312 | changed = true; |
313 | |
314 | for (BasicBlock* successor : block->successors()) { |
315 | for (size_t i = deferred.size(); i--;) { |
316 | if (DFGPutStackSinkingPhaseInternal::verbose) |
317 | dataLog("Considering " , VirtualRegister(deferred.operandForIndex(i)), " at " , pointerDump(block), "->" , pointerDump(successor), ": " , deferred[i], " and " , deferredAtHead[successor][i], " merges to " ); |
318 | |
319 | deferredAtHead[successor][i] = |
320 | merge(deferredAtHead[successor][i], deferred[i]); |
321 | |
322 | if (DFGPutStackSinkingPhaseInternal::verbose) |
323 | dataLog(deferredAtHead[successor][i], "\n" ); |
324 | } |
325 | } |
326 | } |
327 | |
328 | } while (changed); |
329 | |
330 | // We wish to insert PutStacks at all of the materialization points, which are defined |
331 | // implicitly as the places where we set deferred to Dead while it was previously not Dead. |
332 | // To do this, we may need to build some Phi functions to handle stuff like this: |
333 | // |
334 | // Before: |
335 | // |
336 | // if (p) |
337 | // PutStack(r42, @x) |
338 | // else |
339 | // PutStack(r42, @y) |
340 | // |
341 | // After: |
342 | // |
343 | // if (p) |
344 | // Upsilon(@x, ^z) |
345 | // else |
346 | // Upsilon(@y, ^z) |
347 | // z: Phi() |
348 | // PutStack(r42, @z) |
349 | // |
350 | // This means that we have an SSACalculator::Variable for each local, and a Def is any |
351 | // PutStack in the original program. The original PutStacks will simply vanish. |
352 | |
353 | Operands<SSACalculator::Variable*> operandToVariable( |
354 | OperandsLike, m_graph.block(0)->variablesAtHead); |
355 | Vector<VirtualRegister> indexToOperand; |
356 | for (size_t i = m_graph.block(0)->variablesAtHead.size(); i--;) { |
357 | VirtualRegister operand(m_graph.block(0)->variablesAtHead.operandForIndex(i)); |
358 | |
359 | SSACalculator::Variable* variable = ssaCalculator.newVariable(); |
360 | operandToVariable.operand(operand) = variable; |
361 | ASSERT(indexToOperand.size() == variable->index()); |
362 | indexToOperand.append(operand); |
363 | } |
364 | |
365 | HashSet<Node*> putStacksToSink; |
366 | |
367 | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
368 | for (Node* node : *block) { |
369 | switch (node->op()) { |
370 | case PutStack: |
371 | putStacksToSink.add(node); |
372 | ssaCalculator.newDef( |
373 | operandToVariable.operand(node->stackAccessData()->local), |
374 | block, node->child1().node()); |
375 | break; |
376 | case GetStack: |
377 | ssaCalculator.newDef( |
378 | operandToVariable.operand(node->stackAccessData()->local), |
379 | block, node); |
380 | break; |
381 | default: |
382 | break; |
383 | } |
384 | } |
385 | } |
386 | |
387 | ssaCalculator.computePhis( |
388 | [&] (SSACalculator::Variable* variable, BasicBlock* block) -> Node* { |
389 | VirtualRegister operand = indexToOperand[variable->index()]; |
390 | |
391 | if (!liveAtHead[block].operand(operand)) |
392 | return nullptr; |
393 | |
394 | FlushFormat format = deferredAtHead[block].operand(operand); |
395 | |
396 | // We could have an invalid deferral because liveness is imprecise. |
397 | if (!isConcrete(format)) |
398 | return nullptr; |
399 | |
400 | if (DFGPutStackSinkingPhaseInternal::verbose) |
401 | dataLog("Adding Phi for " , operand, " at " , pointerDump(block), "\n" ); |
402 | |
403 | Node* phiNode = m_graph.addNode(SpecHeapTop, Phi, block->at(0)->origin.withInvalidExit()); |
404 | phiNode->mergeFlags(resultFor(format)); |
405 | return phiNode; |
406 | }); |
407 | |
408 | Operands<Node*> mapping(OperandsLike, m_graph.block(0)->variablesAtHead); |
409 | Operands<FlushFormat> deferred; |
410 | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
411 | mapping.fill(nullptr); |
412 | |
413 | for (size_t i = mapping.size(); i--;) { |
414 | VirtualRegister operand(mapping.operandForIndex(i)); |
415 | |
416 | SSACalculator::Variable* variable = operandToVariable.operand(operand); |
417 | SSACalculator::Def* def = ssaCalculator.reachingDefAtHead(block, variable); |
418 | if (!def) |
419 | continue; |
420 | |
421 | mapping.operand(operand) = def->value(); |
422 | } |
423 | |
424 | if (DFGPutStackSinkingPhaseInternal::verbose) |
425 | dataLog("Mapping at top of " , pointerDump(block), ": " , mapping, "\n" ); |
426 | |
427 | for (SSACalculator::Def* phiDef : ssaCalculator.phisForBlock(block)) { |
428 | VirtualRegister operand = indexToOperand[phiDef->variable()->index()]; |
429 | |
430 | insertionSet.insert(0, phiDef->value()); |
431 | |
432 | if (DFGPutStackSinkingPhaseInternal::verbose) |
433 | dataLog(" Mapping " , operand, " to " , phiDef->value(), "\n" ); |
434 | mapping.operand(operand) = phiDef->value(); |
435 | } |
436 | |
437 | deferred = deferredAtHead[block]; |
438 | for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { |
439 | Node* node = block->at(nodeIndex); |
440 | if (DFGPutStackSinkingPhaseInternal::verbose) |
441 | dataLog("Deferred at " , node, ":" , deferred, "\n" ); |
442 | |
443 | switch (node->op()) { |
444 | case PutStack: { |
445 | StackAccessData* data = node->stackAccessData(); |
446 | VirtualRegister operand = data->local; |
447 | deferred.operand(operand) = data->format; |
448 | if (DFGPutStackSinkingPhaseInternal::verbose) |
449 | dataLog(" Mapping " , operand, " to " , node->child1().node(), " at " , node, "\n" ); |
450 | mapping.operand(operand) = node->child1().node(); |
451 | break; |
452 | } |
453 | |
454 | case GetStack: { |
455 | StackAccessData* data = node->stackAccessData(); |
456 | FlushFormat format = deferred.operand(data->local); |
457 | if (!isConcrete(format)) { |
458 | DFG_ASSERT( |
459 | m_graph, node, |
460 | deferred.operand(data->local) != ConflictingFlush, deferred.operand(data->local)); |
461 | |
462 | // This means there is no deferral. No deferral means that the most |
463 | // authoritative value for this stack slot is what is stored in the stack. So, |
464 | // keep the GetStack. |
465 | mapping.operand(data->local) = node; |
466 | break; |
467 | } |
468 | |
469 | // We have a concrete deferral, which means a PutStack that hasn't executed yet. It |
470 | // would have stored a value with a certain format. That format must match our |
471 | // format. But more importantly, we can simply use the value that the PutStack would |
472 | // have stored and get rid of the GetStack. |
473 | DFG_ASSERT(m_graph, node, format == data->format, format, data->format); |
474 | |
475 | Node* incoming = mapping.operand(data->local); |
476 | node->child1() = incoming->defaultEdge(); |
477 | node->convertToIdentity(); |
478 | break; |
479 | } |
480 | |
481 | case KillStack: { |
482 | deferred.operand(node->unlinkedLocal()) = ConflictingFlush; |
483 | break; |
484 | } |
485 | |
486 | default: { |
487 | auto escapeHandler = [&] (VirtualRegister operand) { |
488 | if (DFGPutStackSinkingPhaseInternal::verbose) |
489 | dataLog("For " , node, " escaping " , operand, "\n" ); |
490 | |
491 | if (operand.isHeader()) |
492 | return; |
493 | |
494 | FlushFormat format = deferred.operand(operand); |
495 | if (!isConcrete(format)) { |
496 | // It's dead now, rather than conflicting. |
497 | deferred.operand(operand) = DeadFlush; |
498 | return; |
499 | } |
500 | |
501 | // Gotta insert a PutStack. |
502 | if (DFGPutStackSinkingPhaseInternal::verbose) |
503 | dataLog("Inserting a PutStack for " , operand, " at " , node, "\n" ); |
504 | |
505 | Node* incoming = mapping.operand(operand); |
506 | DFG_ASSERT(m_graph, node, incoming); |
507 | |
508 | insertionSet.insertNode( |
509 | nodeIndex, SpecNone, PutStack, node->origin, |
510 | OpInfo(m_graph.m_stackAccessData.add(operand, format)), |
511 | Edge(incoming, uncheckedUseKindFor(format))); |
512 | |
513 | deferred.operand(operand) = DeadFlush; |
514 | }; |
515 | |
516 | auto writeHandler = [&] (VirtualRegister operand) { |
517 | if (operand.isHeader()) |
518 | return; |
519 | // LoadVarargs and ForwardVarargs are unconditional writes to the stack |
520 | // locations they claim to write to. They do not read from the stack |
521 | // locations they write to. This makes those stack locations dead right |
522 | // before a LoadVarargs/ForwardVarargs. This means we should never sink |
523 | // PutStacks right to this point. |
524 | RELEASE_ASSERT(node->op() == LoadVarargs || node->op() == ForwardVarargs); |
525 | deferred.operand(operand) = DeadFlush; |
526 | }; |
527 | |
528 | preciseLocalClobberize( |
529 | m_graph, node, escapeHandler, writeHandler, |
530 | [&] (VirtualRegister, LazyNode) { }); |
531 | break; |
532 | } } |
533 | } |
534 | |
535 | NodeAndIndex terminal = block->findTerminal(); |
536 | size_t upsilonInsertionPoint = terminal.index; |
537 | NodeOrigin upsilonOrigin = terminal.node->origin; |
538 | for (BasicBlock* successorBlock : block->successors()) { |
539 | for (SSACalculator::Def* phiDef : ssaCalculator.phisForBlock(successorBlock)) { |
540 | Node* phiNode = phiDef->value(); |
541 | SSACalculator::Variable* variable = phiDef->variable(); |
542 | VirtualRegister operand = indexToOperand[variable->index()]; |
543 | if (DFGPutStackSinkingPhaseInternal::verbose) |
544 | dataLog("Creating Upsilon for " , operand, " at " , pointerDump(block), "->" , pointerDump(successorBlock), "\n" ); |
545 | FlushFormat format = deferredAtHead[successorBlock].operand(operand); |
546 | DFG_ASSERT(m_graph, nullptr, isConcrete(format), format); |
547 | UseKind useKind = uncheckedUseKindFor(format); |
548 | |
549 | // We need to get a value for the stack slot. This phase doesn't really have a |
550 | // good way of determining if a stack location got clobbered. It just knows if |
551 | // there is a deferral. The lack of a deferral might mean that a PutStack or |
552 | // GetStack had never happened, or it might mean that the value was read, or |
553 | // that it was written. It's OK for us to make some bad decisions here, since |
554 | // GCSE will clean it up anyway. |
555 | Node* incoming; |
556 | if (isConcrete(deferred.operand(operand))) { |
557 | incoming = mapping.operand(operand); |
558 | DFG_ASSERT(m_graph, phiNode, incoming); |
559 | } else { |
560 | // Issue a GetStack to get the value. This might introduce some redundancy |
561 | // into the code, but if it's bad enough, GCSE will clean it up. |
562 | incoming = insertionSet.insertNode( |
563 | upsilonInsertionPoint, SpecNone, GetStack, upsilonOrigin, |
564 | OpInfo(m_graph.m_stackAccessData.add(operand, format))); |
565 | incoming->setResult(resultFor(format)); |
566 | } |
567 | |
568 | insertionSet.insertNode( |
569 | upsilonInsertionPoint, SpecNone, Upsilon, upsilonOrigin, |
570 | OpInfo(phiNode), Edge(incoming, useKind)); |
571 | } |
572 | } |
573 | |
574 | insertionSet.execute(block); |
575 | } |
576 | |
577 | // Finally eliminate the sunken PutStacks by turning them into Checks. This keeps whatever |
578 | // type check they were doing. |
579 | for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
580 | for (auto* node : *block) { |
581 | if (!putStacksToSink.contains(node)) |
582 | continue; |
583 | |
584 | node->remove(m_graph); |
585 | } |
586 | } |
587 | |
588 | if (DFGPutStackSinkingPhaseInternal::verbose) { |
589 | dataLog("Graph after PutStack sinking:\n" ); |
590 | m_graph.dump(); |
591 | } |
592 | |
593 | return true; |
594 | } |
595 | }; |
596 | |
597 | } // anonymous namespace |
598 | |
599 | bool performPutStackSinking(Graph& graph) |
600 | { |
601 | return runPhase<PutStackSinkingPhase>(graph); |
602 | } |
603 | |
604 | } } // namespace JSC::DFG |
605 | |
606 | #endif // ENABLE(DFG_JIT) |
607 | |
608 | |