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