1 : /* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
2 : /* vim: set ts=40 sw=4 et tw=99: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is the Mozilla SpiderMonkey bytecode analysis
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Mozilla Foundation
20 : * Portions created by the Initial Developer are Copyright (C) 2010
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Brian Hackett <bhackett@mozilla.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "jsanalyze.h"
41 : #include "jsautooplen.h"
42 : #include "jscompartment.h"
43 : #include "jscntxt.h"
44 :
45 : #include "jsinferinlines.h"
46 : #include "jsobjinlines.h"
47 :
48 : namespace js {
49 : namespace analyze {
50 :
51 : /////////////////////////////////////////////////////////////////////
52 : // Bytecode
53 : /////////////////////////////////////////////////////////////////////
54 :
55 : #ifdef DEBUG
56 : void
57 0 : PrintBytecode(JSContext *cx, JSScript *script, jsbytecode *pc)
58 : {
59 0 : printf("#%u:", script->id());
60 0 : Sprinter sprinter(cx);
61 0 : if (!sprinter.init())
62 : return;
63 0 : js_Disassemble1(cx, script, pc, pc - script->code, true, &sprinter);
64 0 : fprintf(stdout, "%s", sprinter.string());
65 : }
66 : #endif
67 :
68 : /////////////////////////////////////////////////////////////////////
69 : // Bytecode Analysis
70 : /////////////////////////////////////////////////////////////////////
71 :
72 : inline bool
73 2603846 : ScriptAnalysis::addJump(JSContext *cx, unsigned offset,
74 : unsigned *currentOffset, unsigned *forwardJump,
75 : unsigned stackDepth)
76 : {
77 2603846 : JS_ASSERT(offset < script->length);
78 :
79 2603846 : Bytecode *&code = codeArray[offset];
80 2603846 : if (!code) {
81 2211030 : code = cx->typeLifoAlloc().new_<Bytecode>();
82 2211030 : if (!code) {
83 0 : setOOM(cx);
84 0 : return false;
85 : }
86 2211030 : code->stackDepth = stackDepth;
87 : }
88 2603846 : JS_ASSERT(code->stackDepth == stackDepth);
89 :
90 2603846 : code->jumpTarget = true;
91 :
92 2603846 : if (offset < *currentOffset) {
93 : /* Scripts containing loops are never inlined. */
94 178111 : isInlineable = false;
95 :
96 : /* Don't follow back edges to bytecode which has already been analyzed. */
97 178111 : if (!code->analyzed) {
98 156278 : if (*forwardJump == 0)
99 119570 : *forwardJump = *currentOffset;
100 156278 : *currentOffset = offset;
101 : }
102 2425735 : } else if (offset > *forwardJump) {
103 1198736 : *forwardJump = offset;
104 : }
105 :
106 2603846 : return true;
107 : }
108 :
109 : void
110 8919598 : ScriptAnalysis::checkAliasedName(JSContext *cx, jsbytecode *pc)
111 : {
112 : /*
113 : * Check to see if an accessed name aliases a local or argument in the
114 : * current script, and mark that local/arg as escaping. We don't need to
115 : * worry about marking locals/arguments in scripts this is nested in, as
116 : * the escaping name will be caught by the parser and the nested local/arg
117 : * will be marked as closed.
118 : */
119 :
120 : JSAtom *atom;
121 8919598 : if (JSOp(*pc) == JSOP_DEFFUN) {
122 187443 : JSFunction *fun = script->getFunction(GET_UINT32_INDEX(pc));
123 187443 : atom = fun->atom;
124 : } else {
125 8732155 : JS_ASSERT(JOF_TYPE(js_CodeSpec[*pc].format) == JOF_ATOM);
126 8732155 : atom = script->getAtom(GET_UINT32_INDEX(pc));
127 : }
128 :
129 : unsigned index;
130 8919598 : BindingKind kind = script->bindings.lookup(cx, atom, &index);
131 :
132 8919598 : if (kind == ARGUMENT)
133 0 : escapedSlots[ArgSlot(index)] = true;
134 8919598 : else if (kind == VARIABLE)
135 180 : escapedSlots[LocalSlot(script, index)] = true;
136 8919598 : }
137 :
138 : void
139 900984 : ScriptAnalysis::analyzeBytecode(JSContext *cx)
140 : {
141 900984 : JS_ASSERT(cx->compartment->activeAnalysis);
142 900984 : JS_ASSERT(!ranBytecode());
143 900984 : LifoAlloc &tla = cx->typeLifoAlloc();
144 :
145 900984 : unsigned length = script->length;
146 900984 : unsigned nargs = script->function() ? script->function()->nargs : 0;
147 :
148 900984 : numSlots = TotalSlots(script);
149 :
150 900984 : codeArray = tla.newArray<Bytecode*>(length);
151 900984 : escapedSlots = tla.newArray<bool>(numSlots);
152 :
153 900984 : if (!codeArray || !escapedSlots) {
154 0 : setOOM(cx);
155 0 : return;
156 : }
157 :
158 900984 : PodZero(codeArray, length);
159 :
160 : /*
161 : * Populate arg and local slots which can escape and be accessed in ways
162 : * other than through ARG* and LOCAL* opcodes (though arguments can still
163 : * be indirectly read but not written through 'arguments' properties).
164 : * All escaping locals are treated as having possible use-before-defs.
165 : */
166 :
167 900984 : PodZero(escapedSlots, numSlots);
168 :
169 900984 : if (script->usesEval || script->usesArguments || script->compartment()->debugMode()) {
170 897713 : for (unsigned i = 0; i < nargs; i++)
171 499641 : escapedSlots[ArgSlot(i)] = true;
172 : } else {
173 559245 : for (unsigned i = 0; i < script->nClosedArgs; i++) {
174 56333 : unsigned arg = script->getClosedArg(i);
175 56333 : JS_ASSERT(arg < nargs);
176 56333 : escapedSlots[ArgSlot(arg)] = true;
177 : }
178 : }
179 :
180 900984 : if (script->usesEval || script->compartment()->debugMode()) {
181 793141 : for (unsigned i = 0; i < script->nfixed; i++)
182 400106 : escapedSlots[LocalSlot(script, i)] = true;
183 : } else {
184 565191 : for (uint32_t i = 0; i < script->nClosedVars; i++) {
185 57242 : unsigned local = script->getClosedVar(i);
186 57242 : JS_ASSERT(local < script->nfixed);
187 57242 : escapedSlots[LocalSlot(script, local)] = true;
188 : }
189 : }
190 :
191 : /*
192 : * If the script is in debug mode, JS_SetFrameReturnValue can be called at
193 : * any safe point.
194 : */
195 900984 : if (cx->compartment->debugMode())
196 388733 : usesReturnValue_ = true;
197 :
198 900984 : bool heavyweight = script->function() && script->function()->isHeavyweight();
199 :
200 900984 : isCompileable = true;
201 :
202 900984 : isInlineable = true;
203 1695981 : if (script->nClosedArgs || script->nClosedVars || heavyweight ||
204 794997 : script->usesEval || script->usesArguments || cx->compartment->debugMode()) {
205 458253 : isInlineable = false;
206 : }
207 :
208 900984 : modifiesArguments_ = false;
209 900984 : if (script->nClosedArgs || heavyweight)
210 77684 : modifiesArguments_ = true;
211 :
212 900984 : canTrackVars = true;
213 :
214 : /*
215 : * If we are in the middle of one or more jumps, the offset of the highest
216 : * target jumping over this bytecode. Includes implicit jumps from
217 : * try/catch/finally blocks.
218 : */
219 900984 : unsigned forwardJump = 0;
220 :
221 : /*
222 : * If we are in the middle of a try block, the offset of the highest
223 : * catch/finally/enditer.
224 : */
225 900984 : unsigned forwardCatch = 0;
226 :
227 : /* Fill in stack depth and definitions at initial bytecode. */
228 900984 : Bytecode *startcode = tla.new_<Bytecode>();
229 900984 : if (!startcode) {
230 0 : setOOM(cx);
231 0 : return;
232 : }
233 :
234 900984 : startcode->stackDepth = 0;
235 900984 : codeArray[0] = startcode;
236 :
237 : /* Number of JOF_TYPESET opcodes we have encountered. */
238 900984 : unsigned nTypeSets = 0;
239 900984 : types::TypeSet *typeArray = script->types->typeArray();
240 :
241 900984 : unsigned offset, nextOffset = 0;
242 148120476 : while (nextOffset < length) {
243 146318508 : offset = nextOffset;
244 :
245 146318508 : JS_ASSERT(forwardCatch <= forwardJump);
246 :
247 : /* Check if the current forward jump/try-block has finished. */
248 146318508 : if (forwardJump && forwardJump == offset)
249 1085897 : forwardJump = 0;
250 146318508 : if (forwardCatch && forwardCatch == offset)
251 241550 : forwardCatch = 0;
252 :
253 146318508 : Bytecode *code = maybeCode(offset);
254 146318508 : jsbytecode *pc = script->code + offset;
255 :
256 146318508 : JSOp op = (JSOp)*pc;
257 146318508 : JS_ASSERT(op < JSOP_LIMIT);
258 :
259 : /* Immediate successor of this bytecode. */
260 146318508 : unsigned successorOffset = offset + GetBytecodeLength(pc);
261 :
262 : /*
263 : * Next bytecode to analyze. This is either the successor, or is an
264 : * earlier bytecode if this bytecode has a loop backedge.
265 : */
266 146318508 : nextOffset = successorOffset;
267 :
268 146318508 : if (!code) {
269 : /* Haven't found a path by which this bytecode is reachable. */
270 10735700 : continue;
271 : }
272 :
273 135582808 : if (code->analyzed) {
274 : /* No need to reanalyze, see Bytecode::mergeDefines. */
275 798769 : continue;
276 : }
277 :
278 134784039 : code->analyzed = true;
279 :
280 134784039 : if (forwardCatch)
281 13604386 : code->inTryBlock = true;
282 :
283 134784039 : if (script->hasBreakpointsAt(pc)) {
284 540 : code->safePoint = true;
285 540 : isInlineable = canTrackVars = false;
286 : }
287 :
288 134784039 : unsigned stackDepth = code->stackDepth;
289 :
290 134784039 : if (!forwardJump)
291 70332892 : code->unconditional = true;
292 :
293 : /*
294 : * Treat decompose ops as no-ops which do not adjust the stack. We will
295 : * pick up the stack depths as we go through the decomposed version.
296 : */
297 134784039 : if (!(js_CodeSpec[op].format & JOF_DECOMPOSE)) {
298 131412563 : unsigned nuses = GetUseCount(script, offset);
299 131412563 : unsigned ndefs = GetDefCount(script, offset);
300 :
301 131412563 : JS_ASSERT(stackDepth >= nuses);
302 131412563 : stackDepth -= nuses;
303 131412563 : stackDepth += ndefs;
304 : }
305 :
306 : /*
307 : * Assign an observed type set to each reachable JOF_TYPESET opcode.
308 : * This may be less than the number of type sets in the script if some
309 : * are unreachable, and may be greater in case the number of type sets
310 : * overflows a uint16. In the latter case a single type set will be
311 : * used for the observed types of all ops after the overflow.
312 : */
313 134784039 : if ((js_CodeSpec[op].format & JOF_TYPESET) && cx->typeInferenceEnabled()) {
314 1270588 : if (nTypeSets < script->nTypeSets) {
315 1270588 : code->observedTypes = &typeArray[nTypeSets++];
316 : } else {
317 0 : JS_ASSERT(nTypeSets == UINT16_MAX);
318 0 : code->observedTypes = &typeArray[nTypeSets - 1];
319 : }
320 : }
321 :
322 134784039 : switch (op) {
323 :
324 : case JSOP_RETURN:
325 : case JSOP_STOP:
326 1083969 : numReturnSites_++;
327 1083969 : break;
328 :
329 : case JSOP_SETRVAL:
330 : case JSOP_POPV:
331 812177 : usesReturnValue_ = true;
332 812177 : isInlineable = false;
333 812177 : break;
334 :
335 : case JSOP_QNAMEPART:
336 : case JSOP_QNAMECONST:
337 1393 : isCompileable = false;
338 : case JSOP_NAME:
339 : case JSOP_CALLNAME:
340 : case JSOP_BINDNAME:
341 : case JSOP_SETNAME:
342 : case JSOP_DELNAME:
343 8244363 : checkAliasedName(cx, pc);
344 8244363 : usesScopeChain_ = true;
345 8244363 : isInlineable = false;
346 8244363 : break;
347 :
348 : case JSOP_DEFFUN:
349 : case JSOP_DEFVAR:
350 : case JSOP_DEFCONST:
351 : case JSOP_SETCONST:
352 675235 : checkAliasedName(cx, pc);
353 675235 : extendsScope_ = true;
354 675235 : isInlineable = canTrackVars = false;
355 675235 : break;
356 :
357 : case JSOP_EVAL:
358 4422 : extendsScope_ = true;
359 4422 : isInlineable = canTrackVars = false;
360 4422 : break;
361 :
362 : case JSOP_ENTERWITH:
363 684 : addsScopeObjects_ = true;
364 684 : isCompileable = isInlineable = canTrackVars = false;
365 684 : break;
366 :
367 : case JSOP_ENTERLET0:
368 : case JSOP_ENTERLET1:
369 : case JSOP_ENTERBLOCK:
370 : case JSOP_LEAVEBLOCK:
371 776180 : addsScopeObjects_ = true;
372 776180 : isInlineable = false;
373 776180 : break;
374 :
375 : case JSOP_THIS:
376 2064005 : usesThisValue_ = true;
377 2064005 : break;
378 :
379 : case JSOP_CALL:
380 : case JSOP_NEW:
381 : /* Only consider potentially inlineable calls here. */
382 3263475 : hasFunctionCalls_ = true;
383 3263475 : break;
384 :
385 : case JSOP_TABLESWITCH: {
386 571 : isInlineable = false;
387 571 : unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc);
388 571 : jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
389 571 : int32_t low = GET_JUMP_OFFSET(pc2);
390 571 : pc2 += JUMP_OFFSET_LEN;
391 571 : int32_t high = GET_JUMP_OFFSET(pc2);
392 571 : pc2 += JUMP_OFFSET_LEN;
393 :
394 571 : if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump, stackDepth))
395 0 : return;
396 571 : getCode(defaultOffset).switchTarget = true;
397 571 : getCode(defaultOffset).safePoint = true;
398 :
399 2128 : for (int32_t i = low; i <= high; i++) {
400 1557 : unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2);
401 1557 : if (targetOffset != offset) {
402 1519 : if (!addJump(cx, targetOffset, &nextOffset, &forwardJump, stackDepth))
403 0 : return;
404 : }
405 1557 : getCode(targetOffset).switchTarget = true;
406 1557 : getCode(targetOffset).safePoint = true;
407 1557 : pc2 += JUMP_OFFSET_LEN;
408 : }
409 571 : break;
410 : }
411 :
412 : case JSOP_LOOKUPSWITCH: {
413 6775 : isInlineable = false;
414 6775 : unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc);
415 6775 : jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
416 6775 : unsigned npairs = GET_UINT16(pc2);
417 6775 : pc2 += UINT16_LEN;
418 :
419 6775 : if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump, stackDepth))
420 0 : return;
421 6775 : getCode(defaultOffset).switchTarget = true;
422 6775 : getCode(defaultOffset).safePoint = true;
423 :
424 50696 : while (npairs) {
425 37146 : pc2 += UINT32_INDEX_LEN;
426 37146 : unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2);
427 37146 : if (!addJump(cx, targetOffset, &nextOffset, &forwardJump, stackDepth))
428 0 : return;
429 37146 : getCode(targetOffset).switchTarget = true;
430 37146 : getCode(targetOffset).safePoint = true;
431 37146 : pc2 += JUMP_OFFSET_LEN;
432 37146 : npairs--;
433 : }
434 6775 : break;
435 : }
436 :
437 : case JSOP_TRY: {
438 : /*
439 : * Everything between a try and corresponding catch or finally is conditional.
440 : * Note that there is no problem with code which is skipped by a thrown
441 : * exception but is not caught by a later handler in the same function:
442 : * no more code will execute, and it does not matter what is defined.
443 : */
444 254512 : isInlineable = false;
445 254512 : JSTryNote *tn = script->trynotes()->vector;
446 254512 : JSTryNote *tnlimit = tn + script->trynotes()->length;
447 287037078 : for (; tn < tnlimit; tn++) {
448 286782566 : unsigned startOffset = script->mainOffset + tn->start;
449 286782566 : if (startOffset == offset + 1) {
450 257932 : unsigned catchOffset = startOffset + tn->length;
451 :
452 : /* This will overestimate try block code, for multiple catch/finally. */
453 257932 : if (catchOffset > forwardCatch)
454 244908 : forwardCatch = catchOffset;
455 :
456 257932 : if (tn->kind != JSTRY_ITER) {
457 257932 : if (!addJump(cx, catchOffset, &nextOffset, &forwardJump, stackDepth))
458 0 : return;
459 257932 : getCode(catchOffset).exceptionEntry = true;
460 257932 : getCode(catchOffset).safePoint = true;
461 : }
462 : }
463 : }
464 254512 : break;
465 : }
466 :
467 : case JSOP_GETLOCAL: {
468 : /*
469 : * Watch for uses of variables not known to be defined, and mark
470 : * them as having possible uses before definitions. Ignore GETLOCAL
471 : * followed by a POP, these are generated for, e.g. 'var x;'
472 : */
473 3014020 : jsbytecode *next = pc + JSOP_GETLOCAL_LENGTH;
474 3014020 : if (JSOp(*next) != JSOP_POP || jumpTarget(next)) {
475 2813568 : uint32_t local = GET_SLOTNO(pc);
476 2813568 : if (local >= script->nfixed) {
477 683191 : localsAliasStack_ = true;
478 683191 : break;
479 : }
480 : }
481 2330829 : break;
482 : }
483 :
484 : case JSOP_CALLLOCAL:
485 : case JSOP_INCLOCAL:
486 : case JSOP_DECLOCAL:
487 : case JSOP_LOCALINC:
488 : case JSOP_LOCALDEC:
489 : case JSOP_SETLOCAL:
490 : case JSOP_SETLOCALPOP: {
491 1724772 : uint32_t local = GET_SLOTNO(pc);
492 1724772 : if (local >= script->nfixed) {
493 462893 : localsAliasStack_ = true;
494 462893 : break;
495 : }
496 1261879 : break;
497 : }
498 :
499 : case JSOP_SETARG:
500 : case JSOP_INCARG:
501 : case JSOP_DECARG:
502 : case JSOP_ARGINC:
503 : case JSOP_ARGDEC:
504 41496 : modifiesArguments_ = true;
505 41496 : isInlineable = false;
506 41496 : break;
507 :
508 : /* Additional opcodes which can be compiled but which can't be inlined. */
509 : case JSOP_ARGUMENTS:
510 : case JSOP_THROW:
511 : case JSOP_EXCEPTION:
512 : case JSOP_DEFLOCALFUN:
513 : case JSOP_DEFLOCALFUN_FC:
514 : case JSOP_LAMBDA:
515 : case JSOP_LAMBDA_FC:
516 : case JSOP_GETFCSLOT:
517 : case JSOP_CALLFCSLOT:
518 : case JSOP_DEBUGGER:
519 : case JSOP_FUNCALL:
520 : case JSOP_FUNAPPLY:
521 1403601 : isInlineable = false;
522 1403601 : break;
523 :
524 : /* Additional opcodes which can be both compiled both normally and inline. */
525 : case JSOP_NOP:
526 : case JSOP_UNDEFINED:
527 : case JSOP_GOTO:
528 : case JSOP_DEFAULT:
529 : case JSOP_IFEQ:
530 : case JSOP_IFNE:
531 : case JSOP_ITERNEXT:
532 : case JSOP_DUP:
533 : case JSOP_DUP2:
534 : case JSOP_SWAP:
535 : case JSOP_PICK:
536 : case JSOP_BITOR:
537 : case JSOP_BITXOR:
538 : case JSOP_BITAND:
539 : case JSOP_LT:
540 : case JSOP_LE:
541 : case JSOP_GT:
542 : case JSOP_GE:
543 : case JSOP_EQ:
544 : case JSOP_NE:
545 : case JSOP_LSH:
546 : case JSOP_RSH:
547 : case JSOP_URSH:
548 : case JSOP_ADD:
549 : case JSOP_SUB:
550 : case JSOP_MUL:
551 : case JSOP_DIV:
552 : case JSOP_MOD:
553 : case JSOP_NOT:
554 : case JSOP_BITNOT:
555 : case JSOP_NEG:
556 : case JSOP_POS:
557 : case JSOP_DELPROP:
558 : case JSOP_DELELEM:
559 : case JSOP_TYPEOF:
560 : case JSOP_TYPEOFEXPR:
561 : case JSOP_VOID:
562 : case JSOP_GETPROP:
563 : case JSOP_CALLPROP:
564 : case JSOP_LENGTH:
565 : case JSOP_GETELEM:
566 : case JSOP_CALLELEM:
567 : case JSOP_TOID:
568 : case JSOP_SETELEM:
569 : case JSOP_IMPLICITTHIS:
570 : case JSOP_DOUBLE:
571 : case JSOP_STRING:
572 : case JSOP_ZERO:
573 : case JSOP_ONE:
574 : case JSOP_NULL:
575 : case JSOP_FALSE:
576 : case JSOP_TRUE:
577 : case JSOP_OR:
578 : case JSOP_AND:
579 : case JSOP_CASE:
580 : case JSOP_STRICTEQ:
581 : case JSOP_STRICTNE:
582 : case JSOP_ITER:
583 : case JSOP_MOREITER:
584 : case JSOP_ENDITER:
585 : case JSOP_POP:
586 : case JSOP_GETARG:
587 : case JSOP_CALLARG:
588 : case JSOP_BINDGNAME:
589 : case JSOP_UINT16:
590 : case JSOP_NEWINIT:
591 : case JSOP_NEWARRAY:
592 : case JSOP_NEWOBJECT:
593 : case JSOP_ENDINIT:
594 : case JSOP_INITMETHOD:
595 : case JSOP_INITPROP:
596 : case JSOP_INITELEM:
597 : case JSOP_SETPROP:
598 : case JSOP_SETMETHOD:
599 : case JSOP_IN:
600 : case JSOP_INSTANCEOF:
601 : case JSOP_LINENO:
602 : case JSOP_ENUMELEM:
603 : case JSOP_CONDSWITCH:
604 : case JSOP_LABEL:
605 : case JSOP_RETRVAL:
606 : case JSOP_GETGNAME:
607 : case JSOP_CALLGNAME:
608 : case JSOP_SETGNAME:
609 : case JSOP_REGEXP:
610 : case JSOP_OBJECT:
611 : case JSOP_UINT24:
612 : case JSOP_GETXPROP:
613 : case JSOP_INT8:
614 : case JSOP_INT32:
615 : case JSOP_HOLE:
616 : case JSOP_LOOPHEAD:
617 : case JSOP_LOOPENTRY:
618 107831917 : break;
619 :
620 : default:
621 3581865 : if (!(js_CodeSpec[op].format & JOF_DECOMPOSE))
622 210389 : isCompileable = isInlineable = false;
623 3581865 : break;
624 : }
625 :
626 134784039 : uint32_t type = JOF_TYPE(js_CodeSpec[op].format);
627 :
628 : /* Check basic jump opcodes, which may or may not have a fallthrough. */
629 134784039 : if (type == JOF_JUMP) {
630 : /* Some opcodes behave differently on their branching path. */
631 2299903 : unsigned newStackDepth = stackDepth;
632 :
633 2299903 : switch (op) {
634 : case JSOP_CASE:
635 : /* Case instructions do not push the lvalue back when branching. */
636 22060 : newStackDepth--;
637 22060 : break;
638 :
639 : default:;
640 : }
641 :
642 2299903 : unsigned targetOffset = offset + GET_JUMP_OFFSET(pc);
643 2299903 : if (!addJump(cx, targetOffset, &nextOffset, &forwardJump, newStackDepth))
644 0 : return;
645 : }
646 :
647 : /* Handle any fallthrough from this opcode. */
648 134784039 : if (!BytecodeNoFallThrough(op)) {
649 132560747 : JS_ASSERT(successorOffset < script->length);
650 :
651 132560747 : Bytecode *&nextcode = codeArray[successorOffset];
652 :
653 132560747 : if (!nextcode) {
654 131672025 : nextcode = tla.new_<Bytecode>();
655 131672025 : if (!nextcode) {
656 0 : setOOM(cx);
657 0 : return;
658 : }
659 131672025 : nextcode->stackDepth = stackDepth;
660 : }
661 132560747 : JS_ASSERT(nextcode->stackDepth == stackDepth);
662 :
663 132560747 : if (type == JOF_JUMP)
664 1453962 : nextcode->jumpFallthrough = true;
665 :
666 : /* Treat the fallthrough of a branch instruction as a jump target. */
667 132560747 : if (type == JOF_JUMP)
668 1453962 : nextcode->jumpTarget = true;
669 : else
670 131106785 : nextcode->fallthrough = true;
671 : }
672 : }
673 :
674 900984 : JS_ASSERT(!failed());
675 900984 : JS_ASSERT(forwardJump == 0 && forwardCatch == 0);
676 :
677 900984 : ranBytecode_ = true;
678 : }
679 :
680 : /////////////////////////////////////////////////////////////////////
681 : // Lifetime Analysis
682 : /////////////////////////////////////////////////////////////////////
683 :
684 : void
685 38782 : ScriptAnalysis::analyzeLifetimes(JSContext *cx)
686 : {
687 38782 : JS_ASSERT(cx->compartment->activeAnalysis && !ranLifetimes() && !failed());
688 :
689 38782 : if (!ranBytecode()) {
690 0 : analyzeBytecode(cx);
691 0 : if (failed())
692 0 : return;
693 : }
694 :
695 38782 : LifoAlloc &tla = cx->typeLifoAlloc();
696 :
697 38782 : lifetimes = tla.newArray<LifetimeVariable>(numSlots);
698 38782 : if (!lifetimes) {
699 0 : setOOM(cx);
700 0 : return;
701 : }
702 38782 : PodZero(lifetimes, numSlots);
703 :
704 : /*
705 : * Variables which are currently dead. On forward branches to locations
706 : * where these are live, they need to be marked as live.
707 : */
708 : LifetimeVariable **saved = (LifetimeVariable **)
709 38782 : cx->calloc_(numSlots * sizeof(LifetimeVariable*));
710 38782 : if (!saved) {
711 0 : setOOM(cx);
712 0 : return;
713 : }
714 38782 : unsigned savedCount = 0;
715 :
716 38782 : LoopAnalysis *loop = NULL;
717 :
718 38782 : uint32_t offset = script->length - 1;
719 5614898 : while (offset < script->length) {
720 5537334 : Bytecode *code = maybeCode(offset);
721 5537334 : if (!code) {
722 3529630 : offset--;
723 3529630 : continue;
724 : }
725 :
726 2007704 : if (loop && code->safePoint)
727 3946 : loop->hasSafePoints = true;
728 :
729 2007704 : jsbytecode *pc = script->code + offset;
730 :
731 2007704 : JSOp op = (JSOp) *pc;
732 :
733 2007704 : if (op == JSOP_LOOPHEAD && code->loop) {
734 : /*
735 : * This is the head of a loop, we need to go and make sure that any
736 : * variables live at the head are live at the backedge and points prior.
737 : * For each such variable, look for the last lifetime segment in the body
738 : * and extend it to the end of the loop.
739 : */
740 10792 : JS_ASSERT(loop == code->loop);
741 10792 : unsigned backedge = code->loop->backedge;
742 64974 : for (unsigned i = 0; i < numSlots; i++) {
743 54182 : if (lifetimes[i].lifetime)
744 9998 : extendVariable(cx, lifetimes[i], offset, backedge);
745 : }
746 :
747 10792 : loop = loop->parent;
748 10792 : JS_ASSERT_IF(loop, loop->head < offset);
749 : }
750 :
751 : /* Find the last jump target in the loop, other than the initial entry point. */
752 2007704 : if (loop && code->jumpTarget && offset != loop->entry && offset > loop->lastBlock)
753 3401 : loop->lastBlock = offset;
754 :
755 2007704 : if (code->exceptionEntry) {
756 30506 : DebugOnly<bool> found = false;
757 15253 : JSTryNote *tn = script->trynotes()->vector;
758 15253 : JSTryNote *tnlimit = tn + script->trynotes()->length;
759 31815039 : for (; tn < tnlimit; tn++) {
760 31815039 : unsigned startOffset = script->mainOffset + tn->start;
761 31815039 : if (startOffset + tn->length == offset) {
762 : /*
763 : * Extend all live variables at exception entry to the start of
764 : * the try block.
765 : */
766 47071 : for (unsigned i = 0; i < numSlots; i++) {
767 31818 : if (lifetimes[i].lifetime)
768 183 : ensureVariable(lifetimes[i], startOffset - 1);
769 : }
770 :
771 15253 : found = true;
772 15253 : break;
773 : }
774 : }
775 15253 : JS_ASSERT(found);
776 : }
777 :
778 2007704 : switch (op) {
779 : case JSOP_GETARG:
780 : case JSOP_CALLARG:
781 : case JSOP_GETLOCAL:
782 : case JSOP_CALLLOCAL:
783 : case JSOP_THIS: {
784 132521 : uint32_t slot = GetBytecodeSlot(script, pc);
785 132521 : if (!slotEscapes(slot))
786 52646 : addVariable(cx, lifetimes[slot], offset, saved, savedCount);
787 132521 : break;
788 : }
789 :
790 : case JSOP_SETARG:
791 : case JSOP_SETLOCAL:
792 : case JSOP_SETLOCALPOP:
793 : case JSOP_DEFLOCALFUN:
794 : case JSOP_DEFLOCALFUN_FC: {
795 98004 : uint32_t slot = GetBytecodeSlot(script, pc);
796 98004 : if (!slotEscapes(slot))
797 39795 : killVariable(cx, lifetimes[slot], offset, saved, savedCount);
798 98004 : break;
799 : }
800 :
801 : case JSOP_INCARG:
802 : case JSOP_DECARG:
803 : case JSOP_ARGINC:
804 : case JSOP_ARGDEC:
805 : case JSOP_INCLOCAL:
806 : case JSOP_DECLOCAL:
807 : case JSOP_LOCALINC:
808 : case JSOP_LOCALDEC: {
809 6402 : uint32_t slot = GetBytecodeSlot(script, pc);
810 6402 : if (!slotEscapes(slot)) {
811 2556 : killVariable(cx, lifetimes[slot], offset, saved, savedCount);
812 2556 : addVariable(cx, lifetimes[slot], offset, saved, savedCount);
813 : }
814 6402 : break;
815 : }
816 :
817 : case JSOP_LOOKUPSWITCH:
818 : case JSOP_TABLESWITCH:
819 : /* Restore all saved variables. :FIXME: maybe do this precisely. */
820 151 : for (unsigned i = 0; i < savedCount; i++) {
821 21 : LifetimeVariable &var = *saved[i];
822 21 : var.lifetime = tla.new_<Lifetime>(offset, var.savedEnd, var.saved);
823 21 : if (!var.lifetime) {
824 0 : cx->free_(saved);
825 0 : setOOM(cx);
826 0 : return;
827 : }
828 21 : var.saved = NULL;
829 21 : saved[i--] = saved[--savedCount];
830 : }
831 130 : savedCount = 0;
832 130 : break;
833 :
834 : case JSOP_TRY:
835 47039 : for (unsigned i = 0; i < numSlots; i++) {
836 31796 : LifetimeVariable &var = lifetimes[i];
837 31796 : if (var.ensured) {
838 182 : JS_ASSERT(var.lifetime);
839 182 : if (var.lifetime->start == offset)
840 181 : var.ensured = false;
841 : }
842 : }
843 15243 : break;
844 :
845 : case JSOP_NEW:
846 : case JSOP_CALL:
847 : case JSOP_EVAL:
848 : case JSOP_FUNAPPLY:
849 : case JSOP_FUNCALL:
850 56863 : if (loop)
851 14788 : loop->hasCallsLoops = true;
852 56863 : break;
853 :
854 : default:;
855 : }
856 :
857 2007704 : uint32_t type = JOF_TYPE(js_CodeSpec[op].format);
858 2007704 : if (type == JOF_JUMP) {
859 : /*
860 : * Forward jumps need to pull in all variables which are live at
861 : * their target offset --- the variables live before the jump are
862 : * the union of those live at the fallthrough and at the target.
863 : */
864 69843 : uint32_t targetOffset = FollowBranch(cx, script, offset);
865 :
866 : /*
867 : * Watch for 'continue' statements in the loop body, which are
868 : * jumps to the entry offset separate from the initial jump.
869 : */
870 69843 : if (loop && loop->entry == targetOffset && loop->entry > loop->lastBlock)
871 213 : loop->lastBlock = loop->entry;
872 :
873 69843 : if (targetOffset < offset) {
874 : /* This is a loop back edge, no lifetime to pull in yet. */
875 :
876 : #ifdef DEBUG
877 10792 : JSOp nop = JSOp(script->code[targetOffset]);
878 10792 : JS_ASSERT(nop == JSOP_LOOPHEAD);
879 : #endif
880 :
881 : /*
882 : * If we already have a loop, it is an outer loop and we
883 : * need to prune the last block in the loop --- we do not
884 : * track 'continue' statements for outer loops.
885 : */
886 10792 : if (loop && loop->entry > loop->lastBlock)
887 1441 : loop->lastBlock = loop->entry;
888 :
889 10792 : LoopAnalysis *nloop = tla.new_<LoopAnalysis>();
890 10792 : if (!nloop) {
891 0 : cx->free_(saved);
892 0 : setOOM(cx);
893 0 : return;
894 : }
895 10792 : PodZero(nloop);
896 :
897 10792 : if (loop)
898 1631 : loop->hasCallsLoops = true;
899 :
900 10792 : nloop->parent = loop;
901 10792 : loop = nloop;
902 :
903 10792 : getCode(targetOffset).loop = loop;
904 10792 : loop->head = targetOffset;
905 10792 : loop->backedge = offset;
906 10792 : loop->lastBlock = loop->head;
907 :
908 : /*
909 : * Find the entry jump, which will be a GOTO for 'for' or
910 : * 'while' loops or a fallthrough for 'do while' loops.
911 : */
912 10792 : uint32_t entry = targetOffset;
913 10792 : if (entry) {
914 53684 : do {
915 53684 : entry--;
916 53684 : } while (!maybeCode(entry));
917 :
918 10792 : jsbytecode *entrypc = script->code + entry;
919 :
920 10792 : if (JSOp(*entrypc) == JSOP_GOTO || JSOp(*entrypc) == JSOP_FILTER)
921 10723 : loop->entry = entry + GET_JUMP_OFFSET(entrypc);
922 : else
923 69 : loop->entry = targetOffset;
924 : } else {
925 : /* Do-while loop at the start of the script. */
926 0 : loop->entry = targetOffset;
927 : }
928 21515 : JS_ASSERT(script->code[loop->entry] == JSOP_LOOPHEAD ||
929 21515 : script->code[loop->entry] == JSOP_LOOPENTRY);
930 : } else {
931 65261 : for (unsigned i = 0; i < savedCount; i++) {
932 6210 : LifetimeVariable &var = *saved[i];
933 6210 : JS_ASSERT(!var.lifetime && var.saved);
934 6210 : if (var.live(targetOffset)) {
935 : /*
936 : * Jumping to a place where this variable is live. Make a new
937 : * lifetime segment for the variable.
938 : */
939 500 : var.lifetime = tla.new_<Lifetime>(offset, var.savedEnd, var.saved);
940 500 : if (!var.lifetime) {
941 0 : cx->free_(saved);
942 0 : setOOM(cx);
943 0 : return;
944 : }
945 500 : var.saved = NULL;
946 500 : saved[i--] = saved[--savedCount];
947 5710 : } else if (loop && !var.savedEnd) {
948 : /*
949 : * This jump precedes the basic block which killed the variable,
950 : * remember it and use it for the end of the next lifetime
951 : * segment should the variable become live again. This is needed
952 : * for loops, as if we wrap liveness around the loop the isLive
953 : * test below may have given the wrong answer.
954 : */
955 670 : var.savedEnd = offset;
956 : }
957 : }
958 : }
959 : }
960 :
961 2007704 : offset--;
962 : }
963 :
964 38782 : cx->free_(saved);
965 :
966 38782 : ranLifetimes_ = true;
967 : }
968 :
969 : #ifdef DEBUG
970 : void
971 0 : LifetimeVariable::print() const
972 : {
973 0 : Lifetime *segment = lifetime ? lifetime : saved;
974 0 : while (segment) {
975 0 : printf(" (%u,%u%s)", segment->start, segment->end, segment->loopTail ? ",tail" : "");
976 0 : segment = segment->next;
977 : }
978 0 : printf("\n");
979 0 : }
980 : #endif /* DEBUG */
981 :
982 : inline void
983 55202 : ScriptAnalysis::addVariable(JSContext *cx, LifetimeVariable &var, unsigned offset,
984 : LifetimeVariable **&saved, unsigned &savedCount)
985 : {
986 55202 : if (var.lifetime) {
987 33335 : if (var.ensured)
988 62 : return;
989 :
990 33273 : JS_ASSERT(offset < var.lifetime->start);
991 33273 : var.lifetime->start = offset;
992 : } else {
993 21867 : if (var.saved) {
994 : /* Remove from the list of saved entries. */
995 8351 : for (unsigned i = 0; i < savedCount; i++) {
996 8351 : if (saved[i] == &var) {
997 4497 : JS_ASSERT(savedCount);
998 4497 : saved[i--] = saved[--savedCount];
999 4497 : break;
1000 : }
1001 : }
1002 : }
1003 21867 : var.lifetime = cx->typeLifoAlloc().new_<Lifetime>(offset, var.savedEnd, var.saved);
1004 21867 : if (!var.lifetime) {
1005 0 : setOOM(cx);
1006 0 : return;
1007 : }
1008 21867 : var.saved = NULL;
1009 : }
1010 : }
1011 :
1012 : inline void
1013 42351 : ScriptAnalysis::killVariable(JSContext *cx, LifetimeVariable &var, unsigned offset,
1014 : LifetimeVariable **&saved, unsigned &savedCount)
1015 : {
1016 42351 : if (!var.lifetime) {
1017 : /* Make a point lifetime indicating the write. */
1018 32269 : if (!var.saved)
1019 692 : saved[savedCount++] = &var;
1020 32269 : var.saved = cx->typeLifoAlloc().new_<Lifetime>(offset, var.savedEnd, var.saved);
1021 32269 : if (!var.saved) {
1022 0 : setOOM(cx);
1023 0 : return;
1024 : }
1025 32269 : var.saved->write = true;
1026 32269 : var.savedEnd = 0;
1027 32269 : return;
1028 : }
1029 :
1030 10082 : JS_ASSERT_IF(!var.ensured, offset < var.lifetime->start);
1031 10082 : unsigned start = var.lifetime->start;
1032 :
1033 : /*
1034 : * The variable is considered to be live at the bytecode which kills it
1035 : * (just not at earlier bytecodes). This behavior is needed by downstream
1036 : * register allocation (see FrameState::bestEvictReg).
1037 : */
1038 10082 : var.lifetime->start = offset;
1039 10082 : var.lifetime->write = true;
1040 :
1041 10082 : if (var.ensured) {
1042 : /*
1043 : * The variable is live even before the write, due to an enclosing try
1044 : * block. We need to split the lifetime to indicate there was a write.
1045 : * We set the new interval's savedEnd to 0, since it will always be
1046 : * adjacent to the old interval, so it never needs to be extended.
1047 : */
1048 48 : var.lifetime = cx->typeLifoAlloc().new_<Lifetime>(start, 0, var.lifetime);
1049 48 : if (!var.lifetime) {
1050 0 : setOOM(cx);
1051 0 : return;
1052 : }
1053 48 : var.lifetime->end = offset;
1054 : } else {
1055 10034 : var.saved = var.lifetime;
1056 10034 : var.savedEnd = 0;
1057 10034 : var.lifetime = NULL;
1058 :
1059 10034 : saved[savedCount++] = &var;
1060 : }
1061 : }
1062 :
1063 : inline void
1064 9998 : ScriptAnalysis::extendVariable(JSContext *cx, LifetimeVariable &var,
1065 : unsigned start, unsigned end)
1066 : {
1067 9998 : JS_ASSERT(var.lifetime);
1068 9998 : if (var.ensured) {
1069 : /*
1070 : * If we are still ensured to be live, the try block must scope over
1071 : * the loop, in which case the variable is already guaranteed to be
1072 : * live for the entire loop.
1073 : */
1074 38 : JS_ASSERT(var.lifetime->start < start);
1075 38 : return;
1076 : }
1077 :
1078 9960 : var.lifetime->start = start;
1079 :
1080 : /*
1081 : * Consider this code:
1082 : *
1083 : * while (...) { (#1)
1084 : * use x; (#2)
1085 : * ...
1086 : * x = ...; (#3)
1087 : * ...
1088 : * } (#4)
1089 : *
1090 : * Just before analyzing the while statement, there would be a live range
1091 : * from #1..#2 and a "point range" at #3. The job of extendVariable is to
1092 : * create a new live range from #3..#4.
1093 : *
1094 : * However, more extensions may be required if the definition of x is
1095 : * conditional. Consider the following.
1096 : *
1097 : * while (...) { (#1)
1098 : * use x; (#2)
1099 : * ...
1100 : * if (...) (#5)
1101 : * x = ...; (#3)
1102 : * ...
1103 : * } (#4)
1104 : *
1105 : * Assume that x is not used after the loop. Then, before extendVariable is
1106 : * run, the live ranges would be the same as before (#1..#2 and #3..#3). We
1107 : * still need to create a range from #3..#4. But, since the assignment at #3
1108 : * may never run, we also need to create a range from #2..#3. This is done
1109 : * as follows.
1110 : *
1111 : * Each time we create a Lifetime, we store the start of the most recently
1112 : * seen sequence of conditional code in the Lifetime's savedEnd field. So,
1113 : * when creating the Lifetime at #2, we set the Lifetime's savedEnd to
1114 : * #5. (The start of the most recent conditional is cached in each
1115 : * variable's savedEnd field.) Consequently, extendVariable is able to
1116 : * create a new interval from #2..#5 using the savedEnd field of the
1117 : * existing #1..#2 interval.
1118 : */
1119 :
1120 9960 : Lifetime *segment = var.lifetime;
1121 30188 : while (segment && segment->start < end) {
1122 13717 : uint32_t savedEnd = segment->savedEnd;
1123 13717 : if (!segment->next || segment->next->start >= end) {
1124 : /*
1125 : * savedEnd is only set for variables killed in the middle of the
1126 : * loop. Make a tail segment connecting the last use with the
1127 : * back edge.
1128 : */
1129 9960 : if (segment->end >= end) {
1130 : /* Variable known to be live after the loop finishes. */
1131 3449 : break;
1132 : }
1133 6511 : savedEnd = end;
1134 : }
1135 10268 : JS_ASSERT(savedEnd <= end);
1136 10268 : if (savedEnd > segment->end) {
1137 6556 : Lifetime *tail = cx->typeLifoAlloc().new_<Lifetime>(savedEnd, 0, segment->next);
1138 6556 : if (!tail) {
1139 0 : setOOM(cx);
1140 0 : return;
1141 : }
1142 6556 : tail->start = segment->end;
1143 6556 : tail->loopTail = true;
1144 :
1145 : /*
1146 : * Clear the segment's saved end, but preserve in the tail if this
1147 : * is the last segment in the loop and the variable is killed in an
1148 : * outer loop before the backedge.
1149 : */
1150 6556 : if (segment->savedEnd > end) {
1151 25 : JS_ASSERT(savedEnd == end);
1152 25 : tail->savedEnd = segment->savedEnd;
1153 : }
1154 6556 : segment->savedEnd = 0;
1155 :
1156 6556 : segment->next = tail;
1157 6556 : segment = tail->next;
1158 : } else {
1159 3712 : JS_ASSERT(segment->savedEnd == 0);
1160 3712 : segment = segment->next;
1161 : }
1162 : }
1163 : }
1164 :
1165 : inline void
1166 183 : ScriptAnalysis::ensureVariable(LifetimeVariable &var, unsigned until)
1167 : {
1168 183 : JS_ASSERT(var.lifetime);
1169 :
1170 : /*
1171 : * If we are already ensured, the current range we are trying to ensure
1172 : * should already be included.
1173 : */
1174 183 : if (var.ensured) {
1175 2 : JS_ASSERT(var.lifetime->start <= until);
1176 2 : return;
1177 : }
1178 :
1179 181 : JS_ASSERT(until < var.lifetime->start);
1180 181 : var.lifetime->start = until;
1181 181 : var.ensured = true;
1182 : }
1183 :
1184 : void
1185 131876 : ScriptAnalysis::clearAllocations()
1186 : {
1187 : /*
1188 : * Clear out storage used for register allocations in a compilation once
1189 : * that compilation has finished. Register allocations are only used for
1190 : * a single compilation.
1191 : */
1192 33567394 : for (unsigned i = 0; i < script->length; i++) {
1193 33435518 : Bytecode *code = maybeCode(i);
1194 33435518 : if (code)
1195 13142778 : code->allocation = NULL;
1196 : }
1197 131876 : }
1198 :
1199 : /////////////////////////////////////////////////////////////////////
1200 : // SSA Analysis
1201 : /////////////////////////////////////////////////////////////////////
1202 :
1203 : void
1204 38782 : ScriptAnalysis::analyzeSSA(JSContext *cx)
1205 : {
1206 38782 : JS_ASSERT(cx->compartment->activeAnalysis && !ranSSA() && !failed());
1207 :
1208 38782 : if (!ranLifetimes()) {
1209 38782 : analyzeLifetimes(cx);
1210 38782 : if (failed())
1211 0 : return;
1212 : }
1213 :
1214 38782 : LifoAlloc &tla = cx->typeLifoAlloc();
1215 38782 : unsigned maxDepth = script->nslots - script->nfixed;
1216 :
1217 : /*
1218 : * Current value of each variable and stack value. Empty for missing or
1219 : * untracked entries, i.e. escaping locals and arguments.
1220 : */
1221 : SSAValueInfo *values = (SSAValueInfo *)
1222 38782 : cx->calloc_((numSlots + maxDepth) * sizeof(SSAValueInfo));
1223 38782 : if (!values) {
1224 0 : setOOM(cx);
1225 0 : return;
1226 : }
1227 : struct FreeSSAValues {
1228 : JSContext *cx;
1229 : SSAValueInfo *values;
1230 38782 : FreeSSAValues(JSContext *cx, SSAValueInfo *values) : cx(cx), values(values) {}
1231 38782 : ~FreeSSAValues() { cx->free_(values); }
1232 77564 : } free(cx, values);
1233 :
1234 38782 : SSAValueInfo *stack = values + numSlots;
1235 38782 : uint32_t stackDepth = 0;
1236 :
1237 117398 : for (uint32_t slot = ArgSlot(0); slot < numSlots; slot++) {
1238 78616 : if (trackSlot(slot))
1239 12559 : values[slot].v.initInitial(slot);
1240 : }
1241 :
1242 : /*
1243 : * All target offsets for forward jumps we have seen (including ones whose
1244 : * target we have advanced past). We lazily add pending entries at these
1245 : * targets for the original value of variables modified before the branch
1246 : * rejoins.
1247 : */
1248 77564 : Vector<uint32_t> branchTargets(cx);
1249 :
1250 : /*
1251 : * Subset of branchTargets which are exception handlers at future offsets.
1252 : * Any new value of a variable modified before the target is reached is a
1253 : * potential value at that target, along with the lazy original value.
1254 : */
1255 77564 : Vector<uint32_t> exceptionTargets(cx);
1256 :
1257 38782 : uint32_t offset = 0;
1258 2107993 : while (offset < script->length) {
1259 2030429 : jsbytecode *pc = script->code + offset;
1260 2030429 : JSOp op = (JSOp)*pc;
1261 :
1262 2030429 : uint32_t successorOffset = offset + GetBytecodeLength(pc);
1263 :
1264 2030429 : Bytecode *code = maybeCode(pc);
1265 2030429 : if (!code) {
1266 22725 : offset = successorOffset;
1267 22725 : continue;
1268 : }
1269 :
1270 2007704 : if (code->exceptionEntry) {
1271 : /* Remove from exception targets list, which reflects only future targets. */
1272 15261 : for (size_t i = 0; i < exceptionTargets.length(); i++) {
1273 15261 : if (exceptionTargets[i] == offset) {
1274 15253 : exceptionTargets[i] = exceptionTargets.back();
1275 15253 : exceptionTargets.popBack();
1276 15253 : break;
1277 : }
1278 : }
1279 : }
1280 :
1281 2007704 : if (code->stackDepth > stackDepth)
1282 272 : PodZero(stack + stackDepth, code->stackDepth - stackDepth);
1283 2007704 : stackDepth = code->stackDepth;
1284 :
1285 2007704 : if (op == JSOP_LOOPHEAD && code->loop) {
1286 : /*
1287 : * Make sure there is a pending value array for phi nodes at the
1288 : * loop head. We won't be able to clear these until we reach the
1289 : * loop's back edge.
1290 : *
1291 : * We need phi nodes for all variables which might be modified
1292 : * during the loop. This ensures that in the loop body we have
1293 : * already updated state to reflect possible changes that happen
1294 : * before the back edge, and don't need to go back and fix things
1295 : * up when we *do* get to the back edge. This could be made lazier.
1296 : *
1297 : * We don't make phi nodes for values on the stack at the head of
1298 : * the loop. These may be popped during the loop (i.e. for ITER
1299 : * loops), but in such cases the original value is pushed back.
1300 : */
1301 10792 : Vector<SlotValue> *&pending = code->pendingValues;
1302 10792 : if (!pending) {
1303 10792 : pending = cx->new_< Vector<SlotValue> >(cx);
1304 10792 : if (!pending) {
1305 0 : setOOM(cx);
1306 : return;
1307 : }
1308 : }
1309 :
1310 : /*
1311 : * Make phi nodes and update state for slots which are already in
1312 : * pending from previous branches to the loop head, and which are
1313 : * modified in the body of the loop.
1314 : */
1315 10792 : for (unsigned i = 0; i < pending->length(); i++) {
1316 0 : SlotValue &v = (*pending)[i];
1317 0 : if (v.slot < numSlots && liveness(v.slot).firstWrite(code->loop) != UINT32_MAX) {
1318 0 : if (v.value.kind() != SSAValue::PHI || v.value.phiOffset() != offset) {
1319 0 : JS_ASSERT(v.value.phiOffset() < offset);
1320 0 : SSAValue ov = v.value;
1321 0 : if (!makePhi(cx, v.slot, offset, &ov))
1322 : return;
1323 0 : insertPhi(cx, ov, v.value);
1324 0 : v.value = ov;
1325 : }
1326 : }
1327 0 : if (code->fallthrough || code->jumpFallthrough)
1328 0 : mergeValue(cx, offset, values[v.slot].v, &v);
1329 0 : mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets, offset - 1);
1330 0 : values[v.slot].v = v.value;
1331 : }
1332 :
1333 : /*
1334 : * Make phi nodes for all other slots which might be modified
1335 : * during the loop. This ensures that in the loop body we have
1336 : * already updated state to reflect possible changes that happen
1337 : * before the back edge, and don't need to go back and fix things
1338 : * up when we *do* get to the back edge. This could be made lazier.
1339 : */
1340 63589 : for (uint32_t slot = ArgSlot(0); slot < numSlots + stackDepth; slot++) {
1341 52797 : if (slot >= numSlots || !trackSlot(slot))
1342 38014 : continue;
1343 14783 : if (liveness(slot).firstWrite(code->loop) == UINT32_MAX)
1344 9256 : continue;
1345 5527 : if (values[slot].v.kind() == SSAValue::PHI && values[slot].v.phiOffset() == offset) {
1346 : /* There is already a pending entry for this slot. */
1347 0 : continue;
1348 : }
1349 : SSAValue ov;
1350 5527 : if (!makePhi(cx, slot, offset, &ov))
1351 : return;
1352 5527 : if (code->fallthrough || code->jumpFallthrough)
1353 16 : insertPhi(cx, ov, values[slot].v);
1354 5527 : mergeBranchTarget(cx, values[slot], slot, branchTargets, offset - 1);
1355 5527 : values[slot].v = ov;
1356 5527 : if (!pending->append(SlotValue(slot, ov))) {
1357 0 : setOOM(cx);
1358 : return;
1359 : }
1360 10792 : }
1361 1996912 : } else if (code->pendingValues) {
1362 : /*
1363 : * New values at this point from a previous jump to this bytecode.
1364 : * If there is fallthrough from the previous instruction, merge
1365 : * with the current state and create phi nodes where necessary,
1366 : * otherwise replace current values with the new values.
1367 : *
1368 : * Catch blocks are artifically treated as having fallthrough, so
1369 : * that values written inside the block but not subsequently
1370 : * overwritten are picked up.
1371 : */
1372 58773 : bool exception = getCode(offset).exceptionEntry;
1373 58773 : Vector<SlotValue> *pending = code->pendingValues;
1374 96568 : for (unsigned i = 0; i < pending->length(); i++) {
1375 37795 : SlotValue &v = (*pending)[i];
1376 38005 : if (code->fallthrough || code->jumpFallthrough ||
1377 210 : (exception && values[v.slot].v.kind() != SSAValue::EMPTY)) {
1378 32918 : mergeValue(cx, offset, values[v.slot].v, &v);
1379 : }
1380 37795 : mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets, offset);
1381 37795 : values[v.slot].v = v.value;
1382 : }
1383 58773 : freezeNewValues(cx, offset);
1384 : }
1385 :
1386 2007704 : if (js_CodeSpec[op].format & JOF_DECOMPOSE) {
1387 3660 : offset = successorOffset;
1388 3660 : continue;
1389 : }
1390 :
1391 2004044 : unsigned nuses = GetUseCount(script, offset);
1392 2004044 : unsigned ndefs = GetDefCount(script, offset);
1393 2004044 : JS_ASSERT(stackDepth >= nuses);
1394 :
1395 2004044 : unsigned xuses = ExtendedUse(pc) ? nuses + 1 : nuses;
1396 :
1397 2004044 : if (xuses) {
1398 1097141 : code->poppedValues = tla.newArray<SSAValue>(xuses);
1399 1097141 : if (!code->poppedValues) {
1400 0 : setOOM(cx);
1401 : return;
1402 : }
1403 2770226 : for (unsigned i = 0; i < nuses; i++) {
1404 1673085 : SSAValue &v = stack[stackDepth - 1 - i].v;
1405 1673085 : code->poppedValues[i] = v;
1406 1673085 : v.clear();
1407 : }
1408 1097141 : if (xuses > nuses) {
1409 : /*
1410 : * For SETLOCAL, INCLOCAL, etc. opcodes, add an extra popped
1411 : * value holding the value of the local before the op.
1412 : */
1413 223858 : uint32_t slot = GetBytecodeSlot(script, pc);
1414 223858 : if (trackSlot(slot))
1415 81888 : code->poppedValues[nuses] = values[slot].v;
1416 : else
1417 141970 : code->poppedValues[nuses].clear();
1418 : }
1419 :
1420 1097141 : if (xuses) {
1421 1097141 : SSAUseChain *useChains = tla.newArray<SSAUseChain>(xuses);
1422 1097141 : if (!useChains) {
1423 0 : setOOM(cx);
1424 : return;
1425 : }
1426 1097141 : PodZero(useChains, xuses);
1427 2994084 : for (unsigned i = 0; i < xuses; i++) {
1428 1896943 : const SSAValue &v = code->poppedValues[i];
1429 1896943 : if (trackUseChain(v)) {
1430 1723912 : SSAUseChain *&uses = useChain(v);
1431 1723912 : useChains[i].popped = true;
1432 1723912 : useChains[i].offset = offset;
1433 1723912 : useChains[i].u.which = i;
1434 1723912 : useChains[i].next = uses;
1435 1723912 : uses = &useChains[i];
1436 : }
1437 : }
1438 : }
1439 : }
1440 :
1441 2004044 : stackDepth -= nuses;
1442 :
1443 3678408 : for (unsigned i = 0; i < ndefs; i++)
1444 1674364 : stack[stackDepth + i].v.initPushed(offset, i);
1445 :
1446 2004044 : unsigned xdefs = ExtendedDef(pc) ? ndefs + 1 : ndefs;
1447 2004044 : if (xdefs) {
1448 1592364 : code->pushedUses = tla.newArray<SSAUseChain *>(xdefs);
1449 1592364 : if (!code->pushedUses) {
1450 0 : setOOM(cx);
1451 : return;
1452 : }
1453 1592364 : PodZero(code->pushedUses, xdefs);
1454 : }
1455 :
1456 2004044 : stackDepth += ndefs;
1457 :
1458 2004044 : if (BytecodeUpdatesSlot(op)) {
1459 104406 : uint32_t slot = GetBytecodeSlot(script, pc);
1460 104406 : if (trackSlot(slot)) {
1461 42338 : mergeBranchTarget(cx, values[slot], slot, branchTargets, offset);
1462 42338 : mergeExceptionTarget(cx, values[slot].v, slot, exceptionTargets);
1463 42338 : values[slot].v.initWritten(slot, offset);
1464 : }
1465 : }
1466 :
1467 2004044 : switch (op) {
1468 : case JSOP_GETARG:
1469 : case JSOP_GETLOCAL: {
1470 117762 : uint32_t slot = GetBytecodeSlot(script, pc);
1471 117762 : if (trackSlot(slot)) {
1472 : /*
1473 : * Propagate the current value of the local to the pushed value,
1474 : * and remember it with an extended use on the opcode.
1475 : */
1476 39046 : stack[stackDepth - 1].v = code->poppedValues[0] = values[slot].v;
1477 : }
1478 117762 : break;
1479 : }
1480 :
1481 : /* Short circuit ops which push back one of their operands. */
1482 :
1483 : case JSOP_MOREITER:
1484 1643 : stack[stackDepth - 2].v = code->poppedValues[0];
1485 1643 : break;
1486 :
1487 : case JSOP_INITPROP:
1488 : case JSOP_INITMETHOD:
1489 4938 : stack[stackDepth - 1].v = code->poppedValues[1];
1490 4938 : break;
1491 :
1492 : case JSOP_INITELEM:
1493 11160 : stack[stackDepth - 1].v = code->poppedValues[2];
1494 11160 : break;
1495 :
1496 : case JSOP_DUP:
1497 20712 : stack[stackDepth - 1].v = stack[stackDepth - 2].v = code->poppedValues[0];
1498 20712 : break;
1499 :
1500 : case JSOP_DUP2:
1501 296 : stack[stackDepth - 1].v = stack[stackDepth - 3].v = code->poppedValues[0];
1502 296 : stack[stackDepth - 2].v = stack[stackDepth - 4].v = code->poppedValues[1];
1503 296 : break;
1504 :
1505 : case JSOP_SWAP:
1506 : /* Swap is like pick 1. */
1507 : case JSOP_PICK: {
1508 21662 : unsigned pickedDepth = (op == JSOP_SWAP ? 1 : pc[1]);
1509 21662 : stack[stackDepth - 1].v = code->poppedValues[pickedDepth];
1510 46452 : for (unsigned i = 0; i < pickedDepth; i++)
1511 24790 : stack[stackDepth - 2 - i].v = code->poppedValues[i];
1512 21662 : break;
1513 : }
1514 :
1515 : /*
1516 : * Switch and try blocks preserve the stack between the original op
1517 : * and all case statements or exception/finally handlers.
1518 : */
1519 :
1520 : case JSOP_TABLESWITCH: {
1521 104 : unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc);
1522 104 : jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
1523 104 : int32_t low = GET_JUMP_OFFSET(pc2);
1524 104 : pc2 += JUMP_OFFSET_LEN;
1525 104 : int32_t high = GET_JUMP_OFFSET(pc2);
1526 104 : pc2 += JUMP_OFFSET_LEN;
1527 :
1528 402 : for (int32_t i = low; i <= high; i++) {
1529 298 : unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2);
1530 298 : if (targetOffset != offset)
1531 290 : checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth);
1532 298 : pc2 += JUMP_OFFSET_LEN;
1533 : }
1534 :
1535 104 : checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth);
1536 104 : break;
1537 : }
1538 :
1539 : case JSOP_LOOKUPSWITCH: {
1540 26 : unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc);
1541 26 : jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
1542 26 : unsigned npairs = GET_UINT16(pc2);
1543 26 : pc2 += UINT16_LEN;
1544 :
1545 164 : while (npairs) {
1546 112 : pc2 += UINT32_INDEX_LEN;
1547 112 : unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2);
1548 112 : checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth);
1549 112 : pc2 += JUMP_OFFSET_LEN;
1550 112 : npairs--;
1551 : }
1552 :
1553 26 : checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth);
1554 26 : break;
1555 : }
1556 :
1557 : case JSOP_TRY: {
1558 15243 : JSTryNote *tn = script->trynotes()->vector;
1559 15243 : JSTryNote *tnlimit = tn + script->trynotes()->length;
1560 63629948 : for (; tn < tnlimit; tn++) {
1561 63614705 : unsigned startOffset = script->mainOffset + tn->start;
1562 63614705 : if (startOffset == offset + 1) {
1563 15253 : unsigned catchOffset = startOffset + tn->length;
1564 :
1565 15253 : if (tn->kind != JSTRY_ITER) {
1566 15253 : checkBranchTarget(cx, catchOffset, branchTargets, values, stackDepth);
1567 15253 : checkExceptionTarget(cx, catchOffset, exceptionTargets);
1568 : }
1569 : }
1570 : }
1571 15243 : break;
1572 : }
1573 :
1574 : case JSOP_THROW:
1575 : case JSOP_RETURN:
1576 : case JSOP_STOP:
1577 : case JSOP_RETRVAL:
1578 40509 : mergeAllExceptionTargets(cx, values, exceptionTargets);
1579 40509 : break;
1580 :
1581 : default:;
1582 : }
1583 :
1584 2004044 : uint32_t type = JOF_TYPE(js_CodeSpec[op].format);
1585 2004044 : if (type == JOF_JUMP) {
1586 69843 : unsigned targetOffset = FollowBranch(cx, script, offset);
1587 69843 : checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth);
1588 :
1589 : /*
1590 : * If this is a back edge, we're done with the loop and can freeze
1591 : * the phi values at the head now.
1592 : */
1593 69843 : if (targetOffset < offset)
1594 10792 : freezeNewValues(cx, targetOffset);
1595 : }
1596 :
1597 2004044 : offset = successorOffset;
1598 : }
1599 :
1600 77564 : ranSSA_ = true;
1601 : }
1602 :
1603 : /* Get a phi node's capacity for a given length. */
1604 : static inline unsigned
1605 39671 : PhiNodeCapacity(unsigned length)
1606 : {
1607 39671 : if (length <= 4)
1608 39652 : return 4;
1609 :
1610 : unsigned log2;
1611 19 : JS_FLOOR_LOG2(log2, length - 1);
1612 19 : return 1 << (log2 + 1);
1613 : }
1614 :
1615 : bool
1616 14910 : ScriptAnalysis::makePhi(JSContext *cx, uint32_t slot, uint32_t offset, SSAValue *pv)
1617 : {
1618 14910 : SSAPhiNode *node = cx->typeLifoAlloc().new_<SSAPhiNode>();
1619 14910 : SSAValue *options = cx->typeLifoAlloc().newArray<SSAValue>(PhiNodeCapacity(0));
1620 14910 : if (!node || !options) {
1621 0 : setOOM(cx);
1622 0 : return false;
1623 : }
1624 14910 : node->slot = slot;
1625 14910 : node->options = options;
1626 14910 : pv->initPhi(offset, node);
1627 14910 : return true;
1628 : }
1629 :
1630 : void
1631 24915 : ScriptAnalysis::insertPhi(JSContext *cx, SSAValue &phi, const SSAValue &v)
1632 : {
1633 24915 : JS_ASSERT(phi.kind() == SSAValue::PHI);
1634 24915 : SSAPhiNode *node = phi.phiNode();
1635 :
1636 : /*
1637 : * Filter dupes inserted into small nodes to keep things clean and avoid
1638 : * extra type constraints, but don't bother on large phi nodes to avoid
1639 : * quadratic behavior.
1640 : */
1641 24915 : if (node->length <= 8) {
1642 35290 : for (unsigned i = 0; i < node->length; i++) {
1643 10546 : if (v == node->options[i])
1644 171 : return;
1645 : }
1646 : }
1647 :
1648 24744 : if (trackUseChain(v)) {
1649 22666 : SSAUseChain *&uses = useChain(v);
1650 :
1651 22666 : SSAUseChain *use = cx->typeLifoAlloc().new_<SSAUseChain>();
1652 22666 : if (!use) {
1653 0 : setOOM(cx);
1654 0 : return;
1655 : }
1656 :
1657 22666 : use->popped = false;
1658 22666 : use->offset = phi.phiOffset();
1659 22666 : use->u.phi = node;
1660 22666 : use->next = uses;
1661 22666 : uses = use;
1662 : }
1663 :
1664 24744 : if (node->length < PhiNodeCapacity(node->length)) {
1665 24727 : node->options[node->length++] = v;
1666 24727 : return;
1667 : }
1668 :
1669 : SSAValue *newOptions =
1670 17 : cx->typeLifoAlloc().newArray<SSAValue>(PhiNodeCapacity(node->length + 1));
1671 17 : if (!newOptions) {
1672 0 : setOOM(cx);
1673 0 : return;
1674 : }
1675 :
1676 17 : PodCopy(newOptions, node->options, node->length);
1677 17 : node->options = newOptions;
1678 17 : node->options[node->length++] = v;
1679 : }
1680 :
1681 : inline void
1682 39761 : ScriptAnalysis::mergeValue(JSContext *cx, uint32_t offset, const SSAValue &v, SlotValue *pv)
1683 : {
1684 : /* Make sure that v is accounted for in the pending value or phi value at pv. */
1685 39761 : JS_ASSERT(v.kind() != SSAValue::EMPTY && pv->value.kind() != SSAValue::EMPTY);
1686 :
1687 39761 : if (v == pv->value)
1688 24245 : return;
1689 :
1690 15516 : if (pv->value.kind() != SSAValue::PHI || pv->value.phiOffset() < offset) {
1691 9383 : SSAValue ov = pv->value;
1692 9383 : if (makePhi(cx, pv->slot, offset, &pv->value)) {
1693 9383 : insertPhi(cx, pv->value, v);
1694 9383 : insertPhi(cx, pv->value, ov);
1695 : }
1696 9383 : return;
1697 : }
1698 :
1699 6133 : JS_ASSERT(pv->value.phiOffset() == offset);
1700 6133 : insertPhi(cx, pv->value, v);
1701 : }
1702 :
1703 : void
1704 58408 : ScriptAnalysis::checkPendingValue(JSContext *cx, const SSAValue &v, uint32_t slot,
1705 : Vector<SlotValue> *pending)
1706 : {
1707 58408 : JS_ASSERT(v.kind() != SSAValue::EMPTY);
1708 :
1709 67206180 : for (unsigned i = 0; i < pending->length(); i++) {
1710 67148186 : if ((*pending)[i].slot == slot)
1711 414 : return;
1712 : }
1713 :
1714 57994 : if (!pending->append(SlotValue(slot, v)))
1715 0 : setOOM(cx);
1716 : }
1717 :
1718 : void
1719 85628 : ScriptAnalysis::checkBranchTarget(JSContext *cx, uint32_t targetOffset,
1720 : Vector<uint32_t> &branchTargets,
1721 : SSAValueInfo *values, uint32_t stackDepth)
1722 : {
1723 85628 : unsigned targetDepth = getCode(targetOffset).stackDepth;
1724 85628 : JS_ASSERT(targetDepth <= stackDepth);
1725 :
1726 : /*
1727 : * If there is already an active branch to target, make sure its pending
1728 : * values reflect any changes made since the first branch. Otherwise, add a
1729 : * new pending branch and determine its pending values lazily.
1730 : */
1731 85628 : Vector<SlotValue> *&pending = getCode(targetOffset).pendingValues;
1732 85628 : if (pending) {
1733 33489 : for (unsigned i = 0; i < pending->length(); i++) {
1734 6634 : SlotValue &v = (*pending)[i];
1735 6634 : mergeValue(cx, targetOffset, values[v.slot].v, &v);
1736 : }
1737 : } else {
1738 58773 : pending = cx->new_< Vector<SlotValue> >(cx);
1739 58773 : if (!pending || !branchTargets.append(targetOffset)) {
1740 0 : setOOM(cx);
1741 0 : return;
1742 : }
1743 : }
1744 :
1745 : /*
1746 : * Make sure there is a pending entry for each value on the stack.
1747 : * The number of stack entries at join points is usually zero, and
1748 : * we don't want to look at the active branches while popping and
1749 : * pushing values in each opcode.
1750 : */
1751 136984 : for (unsigned i = 0; i < targetDepth; i++) {
1752 51356 : uint32_t slot = StackSlot(script, i);
1753 51356 : checkPendingValue(cx, values[slot].v, slot, pending);
1754 : }
1755 : }
1756 :
1757 : void
1758 15253 : ScriptAnalysis::checkExceptionTarget(JSContext *cx, uint32_t catchOffset,
1759 : Vector<uint32_t> &exceptionTargets)
1760 : {
1761 15253 : JS_ASSERT(getCode(catchOffset).exceptionEntry);
1762 :
1763 : /*
1764 : * The catch offset will already be in the branch targets, just check
1765 : * whether this is already a known exception target.
1766 : */
1767 15271 : for (unsigned i = 0; i < exceptionTargets.length(); i++) {
1768 18 : if (exceptionTargets[i] == catchOffset)
1769 0 : return;
1770 : }
1771 15253 : if (!exceptionTargets.append(catchOffset))
1772 0 : setOOM(cx);
1773 : }
1774 :
1775 : void
1776 85660 : ScriptAnalysis::mergeBranchTarget(JSContext *cx, SSAValueInfo &value, uint32_t slot,
1777 : const Vector<uint32_t> &branchTargets, uint32_t currentOffset)
1778 : {
1779 85660 : if (slot >= numSlots) {
1780 : /*
1781 : * There is no need to lazily check that there are pending values at
1782 : * branch targets for slots on the stack, these are added to pending
1783 : * eagerly.
1784 : */
1785 30743 : return;
1786 : }
1787 :
1788 54917 : JS_ASSERT(trackSlot(slot));
1789 :
1790 : /*
1791 : * Before changing the value of a variable, make sure the old value is
1792 : * marked at the target of any branches jumping over the current opcode.
1793 : * Only look at new branch targets which have appeared since the last time
1794 : * the variable was written.
1795 : */
1796 69293 : for (int i = branchTargets.length() - 1; i >= value.branchSize; i--) {
1797 14376 : if (branchTargets[i] <= currentOffset)
1798 7324 : continue;
1799 :
1800 7052 : const Bytecode &code = getCode(branchTargets[i]);
1801 :
1802 7052 : Vector<SlotValue> *pending = code.pendingValues;
1803 7052 : checkPendingValue(cx, value.v, slot, pending);
1804 : }
1805 :
1806 54917 : value.branchSize = branchTargets.length();
1807 : }
1808 :
1809 : void
1810 42348 : ScriptAnalysis::mergeExceptionTarget(JSContext *cx, const SSAValue &value, uint32_t slot,
1811 : const Vector<uint32_t> &exceptionTargets)
1812 : {
1813 42348 : JS_ASSERT(trackSlot(slot));
1814 :
1815 : /*
1816 : * Update the value at exception targets with the value of a variable
1817 : * before it is overwritten. Unlike mergeBranchTarget, this is done whether
1818 : * or not the overwritten value is the value of the variable at the
1819 : * original branch. Values for a variable which are written after the
1820 : * try block starts and overwritten before it is finished can still be
1821 : * seen at exception handlers via exception paths.
1822 : */
1823 42557 : for (unsigned i = 0; i < exceptionTargets.length(); i++) {
1824 209 : unsigned offset = exceptionTargets[i];
1825 209 : Vector<SlotValue> *pending = getCode(offset).pendingValues;
1826 :
1827 209 : bool duplicate = false;
1828 251 : for (unsigned i = 0; i < pending->length(); i++) {
1829 251 : if ((*pending)[i].slot == slot) {
1830 209 : duplicate = true;
1831 209 : SlotValue &v = (*pending)[i];
1832 209 : mergeValue(cx, offset, value, &v);
1833 209 : break;
1834 : }
1835 : }
1836 :
1837 209 : if (!duplicate && !pending->append(SlotValue(slot, value)))
1838 0 : setOOM(cx);
1839 : }
1840 42348 : }
1841 :
1842 : void
1843 40509 : ScriptAnalysis::mergeAllExceptionTargets(JSContext *cx, SSAValueInfo *values,
1844 : const Vector<uint32_t> &exceptionTargets)
1845 : {
1846 40629 : for (unsigned i = 0; i < exceptionTargets.length(); i++) {
1847 120 : Vector<SlotValue> *pending = getCode(exceptionTargets[i]).pendingValues;
1848 142 : for (unsigned i = 0; i < pending->length(); i++) {
1849 22 : const SlotValue &v = (*pending)[i];
1850 22 : if (trackSlot(v.slot))
1851 10 : mergeExceptionTarget(cx, values[v.slot].v, v.slot, exceptionTargets);
1852 : }
1853 : }
1854 40509 : }
1855 :
1856 : void
1857 69565 : ScriptAnalysis::freezeNewValues(JSContext *cx, uint32_t offset)
1858 : {
1859 69565 : Bytecode &code = getCode(offset);
1860 :
1861 69565 : Vector<SlotValue> *pending = code.pendingValues;
1862 69565 : code.pendingValues = NULL;
1863 :
1864 69565 : unsigned count = pending->length();
1865 69565 : if (count == 0) {
1866 53349 : cx->delete_(pending);
1867 53349 : return;
1868 : }
1869 :
1870 16216 : code.newValues = cx->typeLifoAlloc().newArray<SlotValue>(count + 1);
1871 16216 : if (!code.newValues) {
1872 0 : setOOM(cx);
1873 0 : return;
1874 : }
1875 :
1876 79737 : for (unsigned i = 0; i < count; i++)
1877 63521 : code.newValues[i] = (*pending)[i];
1878 16216 : code.newValues[count].slot = 0;
1879 16216 : code.newValues[count].value.clear();
1880 :
1881 16216 : cx->delete_(pending);
1882 : }
1883 :
1884 : CrossSSAValue
1885 25464 : CrossScriptSSA::foldValue(const CrossSSAValue &cv)
1886 : {
1887 25464 : const Frame &frame = getFrame(cv.frame);
1888 25464 : const SSAValue &v = cv.v;
1889 :
1890 25464 : JSScript *parentScript = NULL;
1891 25464 : ScriptAnalysis *parentAnalysis = NULL;
1892 25464 : if (frame.parent != INVALID_FRAME) {
1893 730 : parentScript = getFrame(frame.parent).script;
1894 730 : parentAnalysis = parentScript->analysis();
1895 : }
1896 :
1897 25464 : if (v.kind() == SSAValue::VAR && v.varInitial() && parentScript) {
1898 98 : uint32_t slot = v.varSlot();
1899 98 : if (slot >= ArgSlot(0) && slot < LocalSlot(frame.script, 0)) {
1900 98 : uint32_t argc = GET_ARGC(frame.parentpc);
1901 98 : SSAValue argv = parentAnalysis->poppedValue(frame.parentpc, argc - 1 - (slot - ArgSlot(0)));
1902 98 : return foldValue(CrossSSAValue(frame.parent, argv));
1903 : }
1904 : }
1905 :
1906 25366 : if (v.kind() == SSAValue::PUSHED) {
1907 12803 : jsbytecode *pc = frame.script->code + v.pushedOffset();
1908 :
1909 12803 : switch (JSOp(*pc)) {
1910 : case JSOP_THIS:
1911 609 : if (parentScript) {
1912 349 : uint32_t argc = GET_ARGC(frame.parentpc);
1913 349 : SSAValue thisv = parentAnalysis->poppedValue(frame.parentpc, argc);
1914 349 : return foldValue(CrossSSAValue(frame.parent, thisv));
1915 : }
1916 260 : break;
1917 :
1918 : case JSOP_CALL: {
1919 : /*
1920 : * If there is a single inline callee with a single return site,
1921 : * propagate back to that.
1922 : */
1923 73 : JSScript *callee = NULL;
1924 73 : uint32_t calleeFrame = INVALID_FRAME;
1925 607 : for (unsigned i = 0; i < numFrames(); i++) {
1926 534 : if (iterFrame(i).parent == cv.frame && iterFrame(i).parentpc == pc) {
1927 65 : if (callee)
1928 0 : return cv; /* Multiple callees */
1929 65 : callee = iterFrame(i).script;
1930 65 : calleeFrame = iterFrame(i).index;
1931 : }
1932 : }
1933 73 : if (callee && callee->analysis()->numReturnSites() == 1) {
1934 63 : ScriptAnalysis *analysis = callee->analysis();
1935 63 : uint32_t offset = 0;
1936 354 : while (offset < callee->length) {
1937 291 : jsbytecode *pc = callee->code + offset;
1938 291 : if (analysis->maybeCode(pc) && JSOp(*pc) == JSOP_RETURN)
1939 63 : return foldValue(CrossSSAValue(calleeFrame, analysis->poppedValue(pc, 0)));
1940 228 : offset += GetBytecodeLength(pc);
1941 : }
1942 : }
1943 10 : break;
1944 : }
1945 :
1946 : case JSOP_TOID: {
1947 : /*
1948 : * TOID acts as identity for integers, so to get better precision
1949 : * we should propagate its popped values forward if it acted as
1950 : * identity.
1951 : */
1952 344 : ScriptAnalysis *analysis = frame.script->analysis();
1953 344 : SSAValue toidv = analysis->poppedValue(pc, 0);
1954 344 : if (analysis->getValueTypes(toidv)->getKnownTypeTag(cx) == JSVAL_TYPE_INT32)
1955 338 : return foldValue(CrossSSAValue(cv.frame, toidv));
1956 6 : break;
1957 : }
1958 :
1959 : default:;
1960 : }
1961 : }
1962 :
1963 24616 : return cv;
1964 : }
1965 :
1966 : #ifdef DEBUG
1967 :
1968 : void
1969 0 : ScriptAnalysis::printSSA(JSContext *cx)
1970 : {
1971 0 : AutoEnterAnalysis enter(cx);
1972 :
1973 0 : printf("\n");
1974 :
1975 0 : for (unsigned offset = 0; offset < script->length; offset++) {
1976 0 : Bytecode *code = maybeCode(offset);
1977 0 : if (!code)
1978 0 : continue;
1979 :
1980 0 : jsbytecode *pc = script->code + offset;
1981 :
1982 0 : PrintBytecode(cx, script, pc);
1983 :
1984 0 : SlotValue *newv = code->newValues;
1985 0 : if (newv) {
1986 0 : while (newv->slot) {
1987 0 : if (newv->value.kind() != SSAValue::PHI || newv->value.phiOffset() != offset) {
1988 0 : newv++;
1989 0 : continue;
1990 : }
1991 0 : printf(" phi ");
1992 0 : newv->value.print();
1993 0 : printf(" [");
1994 0 : for (unsigned i = 0; i < newv->value.phiLength(); i++) {
1995 0 : if (i)
1996 0 : printf(",");
1997 0 : newv->value.phiValue(i).print();
1998 : }
1999 0 : printf("]\n");
2000 0 : newv++;
2001 : }
2002 : }
2003 :
2004 0 : unsigned nuses = GetUseCount(script, offset);
2005 0 : unsigned xuses = ExtendedUse(pc) ? nuses + 1 : nuses;
2006 :
2007 0 : for (unsigned i = 0; i < xuses; i++) {
2008 0 : printf(" popped%d: ", i);
2009 0 : code->poppedValues[i].print();
2010 0 : printf("\n");
2011 : }
2012 : }
2013 :
2014 0 : printf("\n");
2015 0 : }
2016 :
2017 : void
2018 0 : SSAValue::print() const
2019 : {
2020 0 : switch (kind()) {
2021 :
2022 : case EMPTY:
2023 0 : printf("empty");
2024 0 : break;
2025 :
2026 : case PUSHED:
2027 0 : printf("pushed:%05u#%u", pushedOffset(), pushedIndex());
2028 0 : break;
2029 :
2030 : case VAR:
2031 0 : if (varInitial())
2032 0 : printf("initial:%u", varSlot());
2033 : else
2034 0 : printf("write:%05u", varOffset());
2035 0 : break;
2036 :
2037 : case PHI:
2038 0 : printf("phi:%05u#%u", phiOffset(), phiSlot());
2039 0 : break;
2040 :
2041 : default:
2042 0 : JS_NOT_REACHED("Bad kind");
2043 : }
2044 0 : }
2045 :
2046 : void
2047 136548 : ScriptAnalysis::assertMatchingDebugMode()
2048 : {
2049 136548 : JS_ASSERT(!!script->compartment()->debugMode() == !!originalDebugMode_);
2050 136548 : }
2051 :
2052 : #endif /* DEBUG */
2053 :
2054 : } /* namespace analyze */
2055 : } /* namespace js */
|