]>
Commit | Line | Data |
---|---|---|
93a37866 A |
1 | /* |
2 | * Copyright (C) 2012, 2013 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 "DFGArgumentsSimplificationPhase.h" | |
28 | ||
29 | #if ENABLE(DFG_JIT) | |
30 | ||
31 | #include "DFGAbstractState.h" | |
32 | #include "DFGBasicBlock.h" | |
33 | #include "DFGGraph.h" | |
34 | #include "DFGInsertionSet.h" | |
35 | #include "DFGPhase.h" | |
36 | #include "DFGValidate.h" | |
37 | #include "DFGVariableAccessDataDump.h" | |
38 | #include <wtf/HashSet.h> | |
39 | #include <wtf/HashMap.h> | |
40 | ||
41 | #if PLATFORM(IOS) | |
42 | #include "Operations.h" | |
43 | #endif | |
44 | ||
45 | namespace JSC { namespace DFG { | |
46 | ||
47 | namespace { | |
48 | ||
49 | struct ArgumentsAliasingData { | |
50 | InlineCallFrame* callContext; | |
51 | bool callContextSet; | |
52 | bool multipleCallContexts; | |
53 | ||
54 | bool assignedFromArguments; | |
55 | bool assignedFromManyThings; | |
56 | ||
57 | bool escapes; | |
58 | ||
59 | ArgumentsAliasingData() | |
60 | : callContext(0) | |
61 | , callContextSet(false) | |
62 | , multipleCallContexts(false) | |
63 | , assignedFromArguments(false) | |
64 | , assignedFromManyThings(false) | |
65 | , escapes(false) | |
66 | { | |
67 | } | |
68 | ||
69 | void mergeCallContext(InlineCallFrame* newCallContext) | |
70 | { | |
71 | if (multipleCallContexts) | |
72 | return; | |
73 | ||
74 | if (!callContextSet) { | |
75 | callContext = newCallContext; | |
76 | callContextSet = true; | |
77 | return; | |
78 | } | |
79 | ||
80 | if (callContext == newCallContext) | |
81 | return; | |
82 | ||
83 | multipleCallContexts = true; | |
84 | } | |
85 | ||
86 | bool callContextIsValid() | |
87 | { | |
88 | return callContextSet && !multipleCallContexts; | |
89 | } | |
90 | ||
91 | void mergeArgumentsAssignment() | |
92 | { | |
93 | assignedFromArguments = true; | |
94 | } | |
95 | ||
96 | void mergeNonArgumentsAssignment() | |
97 | { | |
98 | assignedFromManyThings = true; | |
99 | } | |
100 | ||
101 | bool argumentsAssignmentIsValid() | |
102 | { | |
103 | return assignedFromArguments && !assignedFromManyThings; | |
104 | } | |
105 | ||
106 | bool isValid() | |
107 | { | |
108 | return callContextIsValid() && argumentsAssignmentIsValid() && !escapes; | |
109 | } | |
110 | }; | |
111 | ||
112 | } // end anonymous namespace | |
113 | ||
114 | class ArgumentsSimplificationPhase : public Phase { | |
115 | public: | |
116 | ArgumentsSimplificationPhase(Graph& graph) | |
117 | : Phase(graph, "arguments simplification") | |
118 | { | |
119 | } | |
120 | ||
121 | bool run() | |
122 | { | |
123 | if (!m_graph.m_hasArguments) | |
124 | return false; | |
125 | ||
126 | bool changed = false; | |
127 | ||
128 | // Record which arguments are known to escape no matter what. | |
12899fa2 A |
129 | for (unsigned i = codeBlock()->inlineCallFrames().size(); i--;) |
130 | pruneObviousArgumentCreations(&codeBlock()->inlineCallFrames()[i]); | |
131 | pruneObviousArgumentCreations(0); // the machine call frame. | |
93a37866 A |
132 | |
133 | // Create data for variable access datas that we will want to analyze. | |
134 | for (unsigned i = m_graph.m_variableAccessData.size(); i--;) { | |
135 | VariableAccessData* variableAccessData = &m_graph.m_variableAccessData[i]; | |
136 | if (!variableAccessData->isRoot()) | |
137 | continue; | |
138 | if (variableAccessData->isCaptured()) | |
139 | continue; | |
140 | m_argumentsAliasing.add(variableAccessData, ArgumentsAliasingData()); | |
141 | } | |
142 | ||
143 | // Figure out which variables are live, using a conservative approximation of | |
144 | // liveness. | |
145 | for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { | |
146 | BasicBlock* block = m_graph.m_blocks[blockIndex].get(); | |
147 | if (!block) | |
148 | continue; | |
149 | for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { | |
150 | Node* node = block->at(indexInBlock); | |
151 | switch (node->op()) { | |
152 | case GetLocal: | |
153 | case Flush: | |
154 | case PhantomLocal: | |
155 | m_isLive.add(node->variableAccessData()); | |
156 | break; | |
157 | default: | |
158 | break; | |
159 | } | |
160 | } | |
161 | } | |
162 | ||
163 | // Figure out which variables alias the arguments and nothing else, and are | |
164 | // used only for GetByVal and GetArrayLength accesses. At the same time, | |
165 | // identify uses of CreateArguments that are not consistent with the arguments | |
166 | // being aliased only to variables that satisfy these constraints. | |
167 | for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { | |
168 | BasicBlock* block = m_graph.m_blocks[blockIndex].get(); | |
169 | if (!block) | |
170 | continue; | |
171 | for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { | |
172 | Node* node = block->at(indexInBlock); | |
173 | switch (node->op()) { | |
174 | case CreateArguments: { | |
175 | // Ignore this op. If we see a lone CreateArguments then we want to | |
176 | // completely ignore it because: | |
177 | // 1) The default would be to see that the child is a GetLocal on the | |
178 | // arguments register and conclude that we have an arguments escape. | |
179 | // 2) The fact that a CreateArguments exists does not mean that it | |
180 | // will continue to exist after we're done with this phase. As far | |
181 | // as this phase is concerned, a CreateArguments only "exists" if it | |
182 | // is used in a manner that necessitates its existance. | |
183 | break; | |
184 | } | |
185 | ||
186 | case TearOffArguments: { | |
187 | // Ignore arguments tear off, because it's only relevant if we actually | |
188 | // need to create the arguments. | |
189 | break; | |
190 | } | |
191 | ||
192 | case SetLocal: { | |
193 | Node* source = node->child1().node(); | |
194 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
195 | int argumentsRegister = | |
196 | m_graph.uncheckedArgumentsRegisterFor(node->codeOrigin); | |
197 | if (source->op() != CreateArguments && source->op() != PhantomArguments) { | |
198 | // Make sure that the source of the SetLocal knows that if it's | |
199 | // a variable that we think is aliased to the arguments, then it | |
200 | // may escape at this point. In future, we could track transitive | |
201 | // aliasing. But not yet. | |
202 | observeBadArgumentsUse(source); | |
203 | ||
204 | // If this is an assignment to the arguments register, then | |
205 | // pretend as if the arguments were created. We don't want to | |
206 | // optimize code that explicitly assigns to the arguments, | |
207 | // because that seems too ugly. | |
208 | ||
209 | // But, before getting rid of CreateArguments, we will have | |
210 | // an assignment to the arguments registers with JSValue(). | |
211 | // That's because CSE will refuse to get rid of the | |
212 | // init_lazy_reg since it treats CreateArguments as reading | |
213 | // local variables. That could be fixed, but it's easier to | |
214 | // work around this here. | |
215 | if (source->op() == JSConstant | |
216 | && !source->valueOfJSConstant(codeBlock())) | |
217 | break; | |
218 | ||
219 | // If the variable is totally dead, then ignore it. | |
220 | if (!m_isLive.contains(variableAccessData)) | |
221 | break; | |
222 | ||
223 | if (argumentsRegister != InvalidVirtualRegister | |
224 | && (variableAccessData->local() == argumentsRegister | |
225 | || variableAccessData->local() == unmodifiedArgumentsRegister(argumentsRegister))) { | |
226 | m_createsArguments.add(node->codeOrigin.inlineCallFrame); | |
227 | break; | |
228 | } | |
229 | ||
230 | if (variableAccessData->isCaptured()) | |
231 | break; | |
232 | ||
233 | // Make sure that if it's a variable that we think is aliased to | |
234 | // the arguments, that we know that it might actually not be. | |
235 | ArgumentsAliasingData& data = | |
236 | m_argumentsAliasing.find(variableAccessData)->value; | |
237 | data.mergeNonArgumentsAssignment(); | |
238 | data.mergeCallContext(node->codeOrigin.inlineCallFrame); | |
239 | break; | |
240 | } | |
241 | if (argumentsRegister != InvalidVirtualRegister | |
242 | && (variableAccessData->local() == argumentsRegister | |
243 | || variableAccessData->local() == unmodifiedArgumentsRegister(argumentsRegister))) { | |
244 | if (node->codeOrigin.inlineCallFrame == source->codeOrigin.inlineCallFrame) | |
245 | break; | |
246 | m_createsArguments.add(source->codeOrigin.inlineCallFrame); | |
247 | break; | |
248 | } | |
249 | if (variableAccessData->isCaptured()) { | |
250 | m_createsArguments.add(source->codeOrigin.inlineCallFrame); | |
251 | break; | |
252 | } | |
253 | ArgumentsAliasingData& data = | |
254 | m_argumentsAliasing.find(variableAccessData)->value; | |
255 | data.mergeArgumentsAssignment(); | |
256 | // This ensures that the variable's uses are in the same context as | |
257 | // the arguments it is aliasing. | |
258 | data.mergeCallContext(node->codeOrigin.inlineCallFrame); | |
259 | data.mergeCallContext(source->codeOrigin.inlineCallFrame); | |
260 | break; | |
261 | } | |
262 | ||
263 | case GetLocal: | |
264 | case Phi: /* FIXME: https://bugs.webkit.org/show_bug.cgi?id=108555 */ { | |
265 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
266 | if (variableAccessData->isCaptured()) | |
267 | break; | |
268 | ArgumentsAliasingData& data = | |
269 | m_argumentsAliasing.find(variableAccessData)->value; | |
270 | data.mergeCallContext(node->codeOrigin.inlineCallFrame); | |
271 | break; | |
272 | } | |
273 | ||
274 | case Flush: { | |
275 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
276 | if (variableAccessData->isCaptured()) | |
277 | break; | |
278 | ArgumentsAliasingData& data = | |
279 | m_argumentsAliasing.find(variableAccessData)->value; | |
280 | data.mergeCallContext(node->codeOrigin.inlineCallFrame); | |
281 | ||
282 | // If a variable is used in a flush then by definition it escapes. | |
283 | data.escapes = true; | |
284 | break; | |
285 | } | |
286 | ||
287 | case SetArgument: { | |
288 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
289 | if (variableAccessData->isCaptured()) | |
290 | break; | |
291 | ArgumentsAliasingData& data = | |
292 | m_argumentsAliasing.find(variableAccessData)->value; | |
293 | data.mergeNonArgumentsAssignment(); | |
294 | data.mergeCallContext(node->codeOrigin.inlineCallFrame); | |
295 | break; | |
296 | } | |
297 | ||
298 | case GetByVal: { | |
299 | if (node->arrayMode().type() != Array::Arguments) { | |
300 | observeBadArgumentsUses(node); | |
301 | break; | |
302 | } | |
303 | ||
304 | // That's so awful and pretty much impossible since it would | |
305 | // imply that the arguments were predicted integer, but it's | |
306 | // good to be defensive and thorough. | |
307 | observeBadArgumentsUse(node->child2().node()); | |
308 | observeProperArgumentsUse(node, node->child1()); | |
309 | break; | |
310 | } | |
311 | ||
312 | case GetArrayLength: { | |
313 | if (node->arrayMode().type() != Array::Arguments) { | |
314 | observeBadArgumentsUses(node); | |
315 | break; | |
316 | } | |
317 | ||
318 | observeProperArgumentsUse(node, node->child1()); | |
319 | break; | |
320 | } | |
321 | ||
322 | case Phantom: | |
323 | // We don't care about phantom uses, since phantom uses are all about | |
324 | // just keeping things alive for OSR exit. If something - like the | |
325 | // CreateArguments - is just being kept alive, then this transformation | |
326 | // will not break this, since the Phantom will now just keep alive a | |
327 | // PhantomArguments and OSR exit will still do the right things. | |
328 | break; | |
329 | ||
330 | case CheckStructure: | |
331 | case ForwardCheckStructure: | |
332 | case StructureTransitionWatchpoint: | |
333 | case ForwardStructureTransitionWatchpoint: | |
334 | case CheckArray: | |
335 | // We don't care about these because if we get uses of the relevant | |
336 | // variable then we can safely get rid of these, too. This of course | |
337 | // relies on there not being any information transferred by the CFA | |
338 | // from a CheckStructure on one variable to the information about the | |
339 | // structures of another variable. | |
340 | break; | |
341 | ||
342 | default: | |
343 | observeBadArgumentsUses(node); | |
344 | break; | |
345 | } | |
346 | } | |
347 | } | |
348 | ||
349 | // Now we know which variables are aliased to arguments. But if any of them are | |
350 | // found to have escaped, or were otherwise invalidated, then we need to mark | |
351 | // the arguments as requiring creation. This is a property of SetLocals to | |
352 | // variables that are neither the correct arguments register nor are marked as | |
353 | // being arguments-aliased. | |
354 | for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { | |
355 | BasicBlock* block = m_graph.m_blocks[blockIndex].get(); | |
356 | if (!block) | |
357 | continue; | |
358 | for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { | |
359 | Node* node = block->at(indexInBlock); | |
360 | if (node->op() != SetLocal) | |
361 | continue; | |
362 | Node* source = node->child1().node(); | |
363 | if (source->op() != CreateArguments) | |
364 | continue; | |
365 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
366 | if (variableAccessData->isCaptured()) { | |
367 | // The captured case would have already been taken care of in the | |
368 | // previous pass. | |
369 | continue; | |
370 | } | |
371 | ||
372 | ArgumentsAliasingData& data = | |
373 | m_argumentsAliasing.find(variableAccessData)->value; | |
374 | if (data.isValid()) | |
375 | continue; | |
376 | ||
377 | m_createsArguments.add(source->codeOrigin.inlineCallFrame); | |
378 | } | |
379 | } | |
380 | ||
381 | #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) | |
382 | dataLogF("Arguments aliasing states:\n"); | |
383 | for (unsigned i = 0; i < m_graph.m_variableAccessData.size(); ++i) { | |
384 | VariableAccessData* variableAccessData = &m_graph.m_variableAccessData[i]; | |
385 | if (!variableAccessData->isRoot()) | |
386 | continue; | |
387 | dataLog(" r", variableAccessData->local(), "(", VariableAccessDataDump(m_graph, variableAccessData), "): "); | |
388 | if (variableAccessData->isCaptured()) | |
389 | dataLogF("Captured"); | |
390 | else { | |
391 | ArgumentsAliasingData& data = | |
392 | m_argumentsAliasing.find(variableAccessData)->value; | |
393 | bool first = true; | |
394 | if (data.callContextIsValid()) { | |
395 | if (!first) | |
396 | dataLogF(", "); | |
397 | dataLogF("Have Call Context: %p", data.callContext); | |
398 | first = false; | |
399 | if (!m_createsArguments.contains(data.callContext)) | |
400 | dataLogF(" (Does Not Create Arguments)"); | |
401 | } | |
402 | if (data.argumentsAssignmentIsValid()) { | |
403 | if (!first) | |
404 | dataLogF(", "); | |
405 | dataLogF("Arguments Assignment Is Valid"); | |
406 | first = false; | |
407 | } | |
408 | if (!data.escapes) { | |
409 | if (!first) | |
410 | dataLogF(", "); | |
411 | dataLogF("Does Not Escape"); | |
412 | first = false; | |
413 | } | |
414 | if (!first) | |
415 | dataLogF(", "); | |
416 | if (data.isValid()) { | |
417 | if (m_createsArguments.contains(data.callContext)) | |
418 | dataLogF("VALID"); | |
419 | else | |
420 | dataLogF("INVALID (due to argument creation)"); | |
421 | } else | |
422 | dataLogF("INVALID (due to bad variable use)"); | |
423 | } | |
424 | dataLogF("\n"); | |
425 | } | |
426 | #endif | |
427 | ||
428 | InsertionSet insertionSet(m_graph); | |
429 | ||
430 | for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { | |
431 | BasicBlock* block = m_graph.m_blocks[blockIndex].get(); | |
432 | if (!block) | |
433 | continue; | |
434 | for (unsigned indexInBlock = 0; indexInBlock < block->size(); indexInBlock++) { | |
435 | Node* node = block->at(indexInBlock); | |
436 | switch (node->op()) { | |
437 | case SetLocal: { | |
438 | Node* source = node->child1().node(); | |
439 | if (source->op() != CreateArguments) | |
440 | break; | |
441 | ||
442 | if (m_createsArguments.contains(source->codeOrigin.inlineCallFrame)) | |
443 | break; | |
444 | ||
445 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
446 | ||
447 | if (m_graph.argumentsRegisterFor(node->codeOrigin) == variableAccessData->local() | |
448 | || unmodifiedArgumentsRegister(m_graph.argumentsRegisterFor(node->codeOrigin)) == variableAccessData->local()) | |
449 | break; | |
450 | ||
451 | ASSERT(!variableAccessData->isCaptured()); | |
452 | ||
453 | // If this is a store into a VariableAccessData* that is marked as | |
454 | // arguments aliasing for an InlineCallFrame* that does not create | |
455 | // arguments, then flag the VariableAccessData as being an | |
456 | // arguments-aliased. This'll let the OSR exit machinery do the right | |
457 | // things. Note also that the SetLocal should become dead as soon as | |
458 | // we replace all uses of this variable with GetMyArgumentsLength and | |
459 | // GetMyArgumentByVal. | |
460 | ASSERT(m_argumentsAliasing.find(variableAccessData)->value.isValid()); | |
461 | if (variableAccessData->mergeIsArgumentsAlias(true)) { | |
462 | changed = true; | |
463 | ||
464 | // Make sure that the variable knows, that it may now hold non-cell values. | |
465 | variableAccessData->predict(SpecEmpty); | |
466 | } | |
467 | ||
468 | // Make sure that the SetLocal doesn't check that the input is a Cell. | |
469 | if (node->child1().useKind() != UntypedUse) { | |
470 | node->child1().setUseKind(UntypedUse); | |
471 | changed = true; | |
472 | } | |
473 | break; | |
474 | } | |
475 | ||
476 | case PhantomLocal: { | |
477 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
478 | ||
479 | if (variableAccessData->isCaptured() | |
480 | || !m_argumentsAliasing.find(variableAccessData)->value.isValid() | |
481 | || m_createsArguments.contains(node->codeOrigin.inlineCallFrame)) | |
482 | break; | |
483 | ||
484 | // Turn PhantomLocals into just GetLocals. This will preserve the threading | |
485 | // of the local through to this point, but will allow it to die, causing | |
486 | // only OSR to know about it. | |
487 | ||
488 | node->setOpAndDefaultFlags(GetLocal); | |
489 | break; | |
490 | } | |
491 | ||
492 | case Flush: { | |
493 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
494 | ||
495 | if (variableAccessData->isCaptured() | |
496 | || !m_argumentsAliasing.find(variableAccessData)->value.isValid() | |
497 | || m_createsArguments.contains(node->codeOrigin.inlineCallFrame)) | |
498 | break; | |
499 | ||
500 | RELEASE_ASSERT_NOT_REACHED(); | |
501 | break; | |
502 | } | |
503 | ||
504 | case Phantom: { | |
505 | // It's highly likely that we will have a Phantom referencing either | |
506 | // CreateArguments, or a local op for the arguments register, or a | |
507 | // local op for an arguments-aliased variable. In any of those cases, | |
508 | // we should remove the phantom reference, since: | |
509 | // 1) Phantoms only exist to aid OSR exit. But arguments simplification | |
510 | // has its own OSR exit story, which is to inform OSR exit to reify | |
511 | // the arguments as necessary. | |
512 | // 2) The Phantom may keep the CreateArguments node alive, which is | |
513 | // precisely what we don't want. | |
514 | for (unsigned i = 0; i < AdjacencyList::Size; ++i) | |
515 | removeArgumentsReferencingPhantomChild(node, i); | |
516 | break; | |
517 | } | |
518 | ||
519 | case CheckStructure: | |
520 | case ForwardCheckStructure: | |
521 | case StructureTransitionWatchpoint: | |
522 | case ForwardStructureTransitionWatchpoint: | |
523 | case CheckArray: { | |
524 | // We can just get rid of this node, if it references a phantom argument. | |
525 | if (!isOKToOptimize(node->child1().node())) | |
526 | break; | |
527 | node->convertToPhantom(); | |
528 | node->children.setChild1(Edge()); | |
529 | break; | |
530 | } | |
531 | ||
532 | case GetByVal: { | |
533 | if (node->arrayMode().type() != Array::Arguments) | |
534 | break; | |
535 | ||
536 | // This can be simplified to GetMyArgumentByVal if we know that | |
537 | // it satisfies either condition (1) or (2): | |
538 | // 1) Its first child is a valid ArgumentsAliasingData and the | |
539 | // InlineCallFrame* is not marked as creating arguments. | |
540 | // 2) Its first child is CreateArguments and its InlineCallFrame* | |
541 | // is not marked as creating arguments. | |
542 | ||
543 | if (!isOKToOptimize(node->child1().node())) | |
544 | break; | |
545 | ||
546 | node->children.child1() = node->children.child2(); | |
547 | node->children.child2() = Edge(); | |
548 | node->setOpAndDefaultFlags(GetMyArgumentByVal); | |
549 | changed = true; | |
550 | --indexInBlock; // Force reconsideration of this op now that it's a GetMyArgumentByVal. | |
551 | break; | |
552 | } | |
553 | ||
554 | case GetArrayLength: { | |
555 | if (node->arrayMode().type() != Array::Arguments) | |
556 | break; | |
557 | ||
558 | if (!isOKToOptimize(node->child1().node())) | |
559 | break; | |
560 | ||
561 | node->children.child1() = Edge(); | |
562 | node->setOpAndDefaultFlags(GetMyArgumentsLength); | |
563 | changed = true; | |
564 | --indexInBlock; // Force reconsideration of this op noew that it's a GetMyArgumentsLength. | |
565 | break; | |
566 | } | |
567 | ||
568 | case GetMyArgumentsLength: | |
569 | case GetMyArgumentsLengthSafe: { | |
570 | if (m_createsArguments.contains(node->codeOrigin.inlineCallFrame)) { | |
571 | ASSERT(node->op() == GetMyArgumentsLengthSafe); | |
572 | break; | |
573 | } | |
574 | if (node->op() == GetMyArgumentsLengthSafe) { | |
575 | node->setOp(GetMyArgumentsLength); | |
576 | changed = true; | |
577 | } | |
578 | ||
579 | CodeOrigin codeOrigin = node->codeOrigin; | |
580 | if (!codeOrigin.inlineCallFrame) | |
581 | break; | |
582 | ||
583 | // We know exactly what this will return. But only after we have checked | |
584 | // that nobody has escaped our arguments. | |
585 | insertionSet.insertNode( | |
586 | indexInBlock, SpecNone, CheckArgumentsNotCreated, codeOrigin); | |
587 | ||
588 | m_graph.convertToConstant( | |
589 | node, jsNumber(codeOrigin.inlineCallFrame->arguments.size() - 1)); | |
590 | changed = true; | |
591 | break; | |
592 | } | |
593 | ||
594 | case GetMyArgumentByVal: | |
595 | case GetMyArgumentByValSafe: { | |
596 | if (m_createsArguments.contains(node->codeOrigin.inlineCallFrame)) { | |
597 | ASSERT(node->op() == GetMyArgumentByValSafe); | |
598 | break; | |
599 | } | |
600 | if (node->op() == GetMyArgumentByValSafe) { | |
601 | node->setOp(GetMyArgumentByVal); | |
602 | changed = true; | |
603 | } | |
604 | if (!node->codeOrigin.inlineCallFrame) | |
605 | break; | |
606 | if (!node->child1()->hasConstant()) | |
607 | break; | |
608 | JSValue value = node->child1()->valueOfJSConstant(codeBlock()); | |
609 | if (!value.isInt32()) | |
610 | break; | |
611 | int32_t index = value.asInt32(); | |
612 | if (index < 0 | |
613 | || static_cast<size_t>(index + 1) >= | |
614 | node->codeOrigin.inlineCallFrame->arguments.size()) | |
615 | break; | |
616 | ||
617 | // We know which argument this is accessing. But only after we have checked | |
618 | // that nobody has escaped our arguments. We also need to ensure that the | |
619 | // index is kept alive. That's somewhat pointless since it's a constant, but | |
620 | // it's important because this is one of those invariants that we like to | |
621 | // have in the DFG. Note finally that we use the GetLocalUnlinked opcode | |
622 | // here, since this is being done _after_ the prediction propagation phase | |
623 | // has run - therefore it makes little sense to link the GetLocal operation | |
624 | // into the VariableAccessData and Phi graphs. | |
625 | ||
626 | CodeOrigin codeOrigin = node->codeOrigin; | |
627 | AdjacencyList children = node->children; | |
628 | ||
629 | node->convertToGetLocalUnlinked( | |
630 | static_cast<VirtualRegister>( | |
631 | node->codeOrigin.inlineCallFrame->stackOffset + | |
632 | m_graph.baselineCodeBlockFor(node->codeOrigin)->argumentIndexAfterCapture(index))); | |
633 | ||
634 | insertionSet.insertNode( | |
635 | indexInBlock, SpecNone, CheckArgumentsNotCreated, | |
636 | codeOrigin); | |
637 | insertionSet.insertNode( | |
638 | indexInBlock, SpecNone, Phantom, codeOrigin, | |
639 | children); | |
640 | ||
641 | changed = true; | |
642 | break; | |
643 | } | |
644 | ||
645 | case TearOffArguments: { | |
646 | if (m_createsArguments.contains(node->codeOrigin.inlineCallFrame)) | |
647 | continue; | |
648 | ||
649 | node->setOpAndDefaultFlags(Nop); | |
650 | m_graph.clearAndDerefChild1(node); | |
651 | m_graph.clearAndDerefChild2(node); | |
652 | break; | |
653 | } | |
654 | ||
655 | default: | |
656 | break; | |
657 | } | |
658 | } | |
659 | insertionSet.execute(block); | |
660 | } | |
661 | ||
662 | for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { | |
663 | BasicBlock* block = m_graph.m_blocks[blockIndex].get(); | |
664 | if (!block) | |
665 | continue; | |
666 | for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { | |
667 | Node* node = block->at(indexInBlock); | |
668 | if (node->op() != CreateArguments) | |
669 | continue; | |
670 | // If this is a CreateArguments for an InlineCallFrame* that does | |
671 | // not create arguments, then replace it with a PhantomArguments. | |
672 | // PhantomArguments is a non-executing node that just indicates | |
673 | // that the node should be reified as an arguments object on OSR | |
674 | // exit. | |
675 | if (m_createsArguments.contains(node->codeOrigin.inlineCallFrame)) | |
676 | continue; | |
677 | insertionSet.insertNode( | |
678 | indexInBlock, SpecNone, Phantom, node->codeOrigin, node->children); | |
679 | node->setOpAndDefaultFlags(PhantomArguments); | |
680 | node->children.reset(); | |
681 | changed = true; | |
682 | } | |
683 | insertionSet.execute(block); | |
684 | } | |
685 | ||
686 | if (changed) { | |
687 | m_graph.dethread(); | |
688 | m_graph.m_form = LoadStore; | |
689 | } | |
690 | ||
691 | return changed; | |
692 | } | |
693 | ||
694 | private: | |
695 | HashSet<InlineCallFrame*, | |
696 | DefaultHash<InlineCallFrame*>::Hash, | |
697 | NullableHashTraits<InlineCallFrame*> > m_createsArguments; | |
698 | HashMap<VariableAccessData*, ArgumentsAliasingData, | |
699 | DefaultHash<VariableAccessData*>::Hash, | |
700 | NullableHashTraits<VariableAccessData*> > m_argumentsAliasing; | |
701 | HashSet<VariableAccessData*> m_isLive; | |
702 | ||
12899fa2 A |
703 | void pruneObviousArgumentCreations(InlineCallFrame* inlineCallFrame) |
704 | { | |
705 | ScriptExecutable* executable = jsCast<ScriptExecutable*>(m_graph.executableFor(inlineCallFrame)); | |
706 | if (m_graph.m_executablesWhoseArgumentsEscaped.contains(executable) | |
707 | || executable->isStrictMode()) | |
708 | m_createsArguments.add(inlineCallFrame); | |
709 | } | |
710 | ||
93a37866 A |
711 | void observeBadArgumentsUse(Node* node) |
712 | { | |
713 | if (!node) | |
714 | return; | |
715 | ||
716 | switch (node->op()) { | |
717 | case CreateArguments: { | |
718 | m_createsArguments.add(node->codeOrigin.inlineCallFrame); | |
719 | break; | |
720 | } | |
721 | ||
722 | case GetLocal: { | |
723 | int argumentsRegister = m_graph.uncheckedArgumentsRegisterFor(node->codeOrigin); | |
724 | if (argumentsRegister != InvalidVirtualRegister | |
725 | && (node->local() == argumentsRegister | |
726 | || node->local() == unmodifiedArgumentsRegister(argumentsRegister))) { | |
727 | m_createsArguments.add(node->codeOrigin.inlineCallFrame); | |
728 | break; | |
729 | } | |
730 | ||
731 | VariableAccessData* variableAccessData = node->variableAccessData(); | |
732 | if (variableAccessData->isCaptured()) | |
733 | break; | |
734 | ||
735 | ArgumentsAliasingData& data = m_argumentsAliasing.find(variableAccessData)->value; | |
736 | data.escapes = true; | |
737 | break; | |
738 | } | |
739 | ||
740 | default: | |
741 | break; | |
742 | } | |
743 | } | |
744 | ||
745 | void observeBadArgumentsUses(Node* node) | |
746 | { | |
747 | for (unsigned i = m_graph.numChildren(node); i--;) | |
748 | observeBadArgumentsUse(m_graph.child(node, i).node()); | |
749 | } | |
750 | ||
751 | void observeProperArgumentsUse(Node* node, Edge edge) | |
752 | { | |
753 | if (edge->op() != GetLocal) { | |
754 | // When can this happen? At least two cases that I can think | |
755 | // of: | |
756 | // | |
757 | // 1) Aliased use of arguments in the same basic block, | |
758 | // like: | |
759 | // | |
760 | // var a = arguments; | |
761 | // var x = arguments[i]; | |
762 | // | |
763 | // 2) If we're accessing arguments we got from the heap! | |
764 | ||
765 | if (edge->op() == CreateArguments | |
766 | && node->codeOrigin.inlineCallFrame | |
767 | != edge->codeOrigin.inlineCallFrame) | |
768 | m_createsArguments.add(edge->codeOrigin.inlineCallFrame); | |
769 | ||
770 | return; | |
771 | } | |
772 | ||
773 | VariableAccessData* variableAccessData = edge->variableAccessData(); | |
774 | if (edge->local() == m_graph.uncheckedArgumentsRegisterFor(edge->codeOrigin) | |
775 | && node->codeOrigin.inlineCallFrame != edge->codeOrigin.inlineCallFrame) { | |
776 | m_createsArguments.add(edge->codeOrigin.inlineCallFrame); | |
777 | return; | |
778 | } | |
779 | ||
780 | if (variableAccessData->isCaptured()) | |
781 | return; | |
782 | ||
783 | ArgumentsAliasingData& data = m_argumentsAliasing.find(variableAccessData)->value; | |
784 | data.mergeCallContext(node->codeOrigin.inlineCallFrame); | |
785 | } | |
786 | ||
787 | bool isOKToOptimize(Node* source) | |
788 | { | |
789 | if (m_createsArguments.contains(source->codeOrigin.inlineCallFrame)) | |
790 | return false; | |
791 | ||
792 | switch (source->op()) { | |
793 | case GetLocal: { | |
794 | VariableAccessData* variableAccessData = source->variableAccessData(); | |
795 | int argumentsRegister = m_graph.uncheckedArgumentsRegisterFor(source->codeOrigin); | |
796 | if (argumentsRegister == InvalidVirtualRegister) | |
797 | break; | |
798 | if (argumentsRegister == variableAccessData->local()) | |
799 | return true; | |
800 | if (unmodifiedArgumentsRegister(argumentsRegister) == variableAccessData->local()) | |
801 | return true; | |
802 | if (variableAccessData->isCaptured()) | |
803 | break; | |
804 | ArgumentsAliasingData& data = | |
805 | m_argumentsAliasing.find(variableAccessData)->value; | |
806 | if (!data.isValid()) | |
807 | break; | |
808 | ||
809 | return true; | |
810 | } | |
811 | ||
812 | case CreateArguments: { | |
813 | return true; | |
814 | } | |
815 | ||
816 | default: | |
817 | break; | |
818 | } | |
819 | ||
820 | return false; | |
821 | } | |
822 | ||
823 | void removeArgumentsReferencingPhantomChild(Node* node, unsigned edgeIndex) | |
824 | { | |
825 | Edge edge = node->children.child(edgeIndex); | |
826 | if (!edge) | |
827 | return; | |
828 | ||
829 | switch (edge->op()) { | |
830 | case Phi: // Arises if we had CSE on a GetLocal of the arguments register. | |
831 | case GetLocal: // Arises if we had CSE on an arguments access to a variable aliased to the arguments. | |
832 | case SetLocal: { // Arises if we had CSE on a GetLocal of the arguments register. | |
833 | VariableAccessData* variableAccessData = edge->variableAccessData(); | |
834 | bool isDeadArgumentsRegister = | |
835 | variableAccessData->local() == | |
836 | m_graph.uncheckedArgumentsRegisterFor(edge->codeOrigin) | |
837 | && !m_createsArguments.contains(edge->codeOrigin.inlineCallFrame); | |
838 | bool isAliasedArgumentsRegister = | |
839 | !variableAccessData->isCaptured() | |
840 | && m_argumentsAliasing.find(variableAccessData)->value.isValid() | |
841 | && !m_createsArguments.contains(edge->codeOrigin.inlineCallFrame); | |
842 | if (!isDeadArgumentsRegister && !isAliasedArgumentsRegister) | |
843 | break; | |
844 | node->children.removeEdge(edgeIndex); | |
845 | break; | |
846 | } | |
847 | ||
848 | case CreateArguments: { // Arises if we CSE two GetLocals to the arguments register and then CSE the second use of the GetLocal to the first. | |
849 | if (m_createsArguments.contains(edge->codeOrigin.inlineCallFrame)) | |
850 | break; | |
851 | node->children.removeEdge(edgeIndex); | |
852 | break; | |
853 | } | |
854 | ||
855 | default: | |
856 | break; | |
857 | } | |
858 | } | |
859 | }; | |
860 | ||
861 | bool performArgumentsSimplification(Graph& graph) | |
862 | { | |
863 | SamplingRegion samplingRegion("DFG Arguments Simplification Phase"); | |
864 | return runPhase<ArgumentsSimplificationPhase>(graph); | |
865 | } | |
866 | ||
867 | } } // namespace JSC::DFG | |
868 | ||
869 | #endif // ENABLE(DFG_JIT) | |
870 | ||
871 |