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