Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / js / src / jsanalyze.cpp
blobc16a83cd8c6505813bf8b00adf91cce866a48a37
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
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/
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.
16 * The Original Code is the Mozilla SpiderMonkey bytecode analysis
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.
23 * Contributor(s):
24 * Brian Hackett <bhackett@mozilla.com>
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.
38 * ***** END LICENSE BLOCK ***** */
40 #include "jsanalyze.h"
41 #include "jsautooplen.h"
42 #include "jscompartment.h"
43 #include "jscntxt.h"
45 namespace js {
46 namespace analyze {
48 /////////////////////////////////////////////////////////////////////
49 // Script
50 /////////////////////////////////////////////////////////////////////
52 void
53 Script::destroy()
55 JS_FinishArenaPool(&pool);
58 template <typename T>
59 inline T *
60 ArenaArray(JSArenaPool &pool, unsigned count)
62 void *v;
63 JS_ARENA_ALLOCATE(v, &pool, count * sizeof(T));
64 return (T *) v;
67 template <typename T>
68 inline T *
69 ArenaNew(JSArenaPool &pool)
71 void *v;
72 JS_ARENA_ALLOCATE(v, &pool, sizeof(T));
73 return new (v) T();
76 /////////////////////////////////////////////////////////////////////
77 // Bytecode
78 /////////////////////////////////////////////////////////////////////
80 bool
81 Bytecode::mergeDefines(JSContext *cx, Script *script, bool initial, unsigned newDepth,
82 uint32 *newArray, unsigned newCount)
84 if (initial) {
86 * Haven't handled any incoming edges to this bytecode before.
87 * Define arrays are copy on write, so just reuse the array for this bytecode.
89 stackDepth = newDepth;
90 defineArray = newArray;
91 defineCount = newCount;
92 return true;
96 * This bytecode has multiple incoming edges, intersect the new array with any
97 * variables known to be defined along other incoming edges.
99 if (analyzed) {
100 #ifdef DEBUG
102 * Once analyzed, a bytecode has its full set of definitions. There are two
103 * properties we depend on to ensure this. First, bytecode for a function
104 * is emitted in topological order, and since we analyze bytecodes in the
105 * order they were emitted we will have seen all incoming jumps except
106 * for any loop back edges. Second, javascript has structured control flow,
107 * so loop heads dominate their bodies; the set of variables defined
108 * on a back edge will be at least as large as at the head of the loop,
109 * so no intersection or further analysis needs to be done.
111 JS_ASSERT(stackDepth == newDepth);
112 for (unsigned i = 0; i < defineCount; i++) {
113 bool found = false;
114 for (unsigned j = 0; j < newCount; j++) {
115 if (newArray[j] == defineArray[i])
116 found = true;
118 JS_ASSERT(found);
120 #endif
121 } else {
122 JS_ASSERT(stackDepth == newDepth);
123 bool owned = false;
124 for (unsigned i = 0; i < defineCount; i++) {
125 bool found = false;
126 for (unsigned j = 0; j < newCount; j++) {
127 if (newArray[j] == defineArray[i])
128 found = true;
130 if (!found) {
132 * Get a mutable copy of the defines. This can end up making
133 * several copies for a bytecode if it has many incoming edges
134 * with progressively smaller sets of defined variables.
136 if (!owned) {
137 uint32 *reallocArray = ArenaArray<uint32>(script->pool, defineCount);
138 if (!reallocArray) {
139 script->setOOM(cx);
140 return false;
142 memcpy(reallocArray, defineArray, defineCount * sizeof(uint32));
143 defineArray = reallocArray;
144 owned = true;
147 /* Swap with the last element and pop the array. */
148 defineArray[i--] = defineArray[--defineCount];
153 return true;
156 /////////////////////////////////////////////////////////////////////
157 // Analysis
158 /////////////////////////////////////////////////////////////////////
160 inline bool
161 Script::addJump(JSContext *cx, unsigned offset,
162 unsigned *currentOffset, unsigned *forwardJump,
163 unsigned stackDepth, uint32 *defineArray, unsigned defineCount)
165 JS_ASSERT(offset < script->length);
167 Bytecode *&bytecode = code[offset];
168 bool initial = (bytecode == NULL);
169 if (initial) {
170 bytecode = ArenaNew<Bytecode>(pool);
171 if (!bytecode) {
172 setOOM(cx);
173 return false;
177 if (!bytecode->mergeDefines(cx, this, initial, stackDepth, defineArray, defineCount))
178 return false;
179 bytecode->jumpTarget = true;
181 if (offset < *currentOffset) {
182 /* Don't follow back edges to bytecode which has already been analyzed. */
183 if (!bytecode->analyzed) {
184 if (*forwardJump == 0)
185 *forwardJump = *currentOffset;
186 *currentOffset = offset;
188 } else if (offset > *forwardJump) {
189 *forwardJump = offset;
192 return true;
195 inline void
196 Script::setLocal(uint32 local, uint32 offset)
198 JS_ASSERT(local < localCount());
199 JS_ASSERT(offset != LOCAL_CONDITIONALLY_DEFINED);
202 * It isn't possible to change the point when a variable becomes unconditionally
203 * defined, or to mark it as unconditionally defined after it has already been
204 * marked as having a use before def. It *is* possible to mark it as having
205 * a use before def after marking it as unconditionally defined. In a loop such as:
207 * while ((a = b) != 0) { x = a; }
209 * When walking through the body of this loop, we will first analyze the test
210 * (which comes after the body in the bytecode stream) as unconditional code,
211 * and mark a as definitely defined. a is not in the define array when taking
212 * the loop's back edge, so it is treated as possibly undefined when written to x.
214 JS_ASSERT(locals[local] == LOCAL_CONDITIONALLY_DEFINED ||
215 locals[local] == offset || offset == LOCAL_USE_BEFORE_DEF);
217 locals[local] = offset;
220 static inline ptrdiff_t
221 GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
223 uint32 type = JOF_OPTYPE(*pc);
224 if (JOF_TYPE_IS_EXTENDED_JUMP(type))
225 return GET_JUMPX_OFFSET(pc2);
226 return GET_JUMP_OFFSET(pc2);
229 static inline unsigned
230 GetBytecodeLength(jsbytecode *pc)
232 JSOp op = (JSOp)*pc;
233 JS_ASSERT(op < JSOP_LIMIT);
234 JS_ASSERT(op != JSOP_TRAP);
236 if (js_CodeSpec[op].length != -1)
237 return js_CodeSpec[op].length;
238 return js_GetVariableBytecodeLength(pc);
241 // return whether op bytecodes do not fallthrough (they may do a jump).
242 static inline bool
243 BytecodeNoFallThrough(JSOp op)
245 switch (op) {
246 case JSOP_GOTO:
247 case JSOP_GOTOX:
248 case JSOP_DEFAULT:
249 case JSOP_DEFAULTX:
250 case JSOP_RETURN:
251 case JSOP_STOP:
252 case JSOP_RETRVAL:
253 case JSOP_THROW:
254 case JSOP_TABLESWITCH:
255 case JSOP_TABLESWITCHX:
256 case JSOP_LOOKUPSWITCH:
257 case JSOP_LOOKUPSWITCHX:
258 case JSOP_FILTER:
259 return true;
260 case JSOP_GOSUB:
261 case JSOP_GOSUBX:
262 // these fall through indirectly, after executing a 'finally'.
263 return false;
264 default:
265 return false;
269 /* Untrap a single PC, and retrap it at scope exit. */
270 struct UntrapOpcode
272 jsbytecode *pc;
273 bool trap;
275 UntrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
276 : pc(pc), trap(JSOp(*pc) == JSOP_TRAP)
278 if (trap)
279 *pc = JS_GetTrapOpcode(cx, script, pc);
282 ~UntrapOpcode()
284 if (trap)
285 *pc = JSOP_TRAP;
289 void
290 Script::analyze(JSContext *cx, JSScript *script)
292 JS_ASSERT(!code && !locals);
293 this->script = script;
295 JS_InitArenaPool(&pool, "script_analyze", 256, 8, NULL);
297 unsigned length = script->length;
298 unsigned nfixed = localCount();
300 code = ArenaArray<Bytecode*>(pool, length);
301 locals = ArenaArray<uint32>(pool, nfixed);
303 if (!code || !locals) {
304 setOOM(cx);
305 return;
308 PodZero(code, length);
310 for (unsigned i = 0; i < nfixed; i++)
311 locals[i] = LOCAL_CONDITIONALLY_DEFINED;
314 * Treat locals as having a possible use-before-def if they could be accessed
315 * by debug code or by eval, or if they could be accessed by an inner script.
318 if (script->usesEval || cx->compartment->debugMode) {
319 for (uint32 i = 0; i < nfixed; i++)
320 setLocal(i, LOCAL_USE_BEFORE_DEF);
323 for (uint32 i = 0; i < script->nClosedVars; i++) {
324 uint32 slot = script->getClosedVar(i);
325 if (slot < nfixed)
326 setLocal(slot, LOCAL_USE_BEFORE_DEF);
330 * If the script is in debug mode, JS_SetFrameReturnValue can be called at
331 * any safe point.
333 if (cx->compartment->debugMode)
334 usesRval = true;
337 * If we are in the middle of one or more jumps, the offset of the highest
338 * target jumping over this bytecode. Includes implicit jumps from
339 * try/catch/finally blocks.
341 unsigned forwardJump = 0;
344 * If we are in the middle of a try block, the offset of the highest
345 * catch/finally/enditer.
347 unsigned forwardCatch = 0;
349 /* Fill in stack depth and definitions at initial bytecode. */
350 Bytecode *startcode = ArenaNew<Bytecode>(pool);
351 if (!startcode) {
352 setOOM(cx);
353 return;
356 startcode->stackDepth = 0;
357 code[0] = startcode;
359 unsigned offset, nextOffset = 0;
360 while (nextOffset < length) {
361 offset = nextOffset;
363 JS_ASSERT(forwardCatch <= forwardJump);
365 /* Check if the current forward jump/try-block has finished. */
366 if (forwardJump && forwardJump == offset)
367 forwardJump = 0;
368 if (forwardCatch && forwardCatch == offset)
369 forwardCatch = 0;
371 Bytecode *&bytecode = code[offset];
372 jsbytecode *pc = script->code + offset;
374 UntrapOpcode untrap(cx, script, pc);
376 JSOp op = (JSOp)*pc;
377 JS_ASSERT(op < JSOP_LIMIT);
379 /* Immediate successor of this bytecode. */
380 unsigned successorOffset = offset + GetBytecodeLength(pc);
383 * Next bytecode to analyze. This is either the successor, or is an
384 * earlier bytecode if this bytecode has a loop backedge.
386 nextOffset = successorOffset;
388 if (!bytecode) {
389 /* Haven't found a path by which this bytecode is reachable. */
390 continue;
393 if (bytecode->analyzed) {
394 /* No need to reanalyze, see Bytecode::mergeDefines. */
395 continue;
398 bytecode->analyzed = true;
400 if (forwardCatch)
401 bytecode->inTryBlock = true;
403 unsigned stackDepth = bytecode->stackDepth;
404 uint32 *defineArray = bytecode->defineArray;
405 unsigned defineCount = bytecode->defineCount;
407 if (!forwardJump) {
409 * There is no jump over this bytecode, nor a containing try block.
410 * Either this bytecode will definitely be executed, or an exception
411 * will be thrown which the script does not catch. Either way,
412 * any variables definitely defined at this bytecode will stay
413 * defined throughout the rest of the script. We just need to
414 * remember the offset where the variable became unconditionally
415 * defined, rather than continue to maintain it in define arrays.
417 for (unsigned i = 0; i < defineCount; i++) {
418 uint32 local = defineArray[i];
419 JS_ASSERT_IF(locals[local] != LOCAL_CONDITIONALLY_DEFINED &&
420 locals[local] != LOCAL_USE_BEFORE_DEF,
421 locals[local] <= offset);
422 if (locals[local] == LOCAL_CONDITIONALLY_DEFINED)
423 setLocal(local, offset);
425 defineArray = NULL;
426 defineCount = 0;
429 unsigned nuses, ndefs;
430 if (js_CodeSpec[op].nuses == -1)
431 nuses = js_GetVariableStackUses(op, pc);
432 else
433 nuses = js_CodeSpec[op].nuses;
435 if (js_CodeSpec[op].ndefs == -1)
436 ndefs = js_GetEnterBlockStackDefs(cx, script, pc);
437 else
438 ndefs = js_CodeSpec[op].ndefs;
440 JS_ASSERT(stackDepth >= nuses);
441 stackDepth -= nuses;
442 stackDepth += ndefs;
444 switch (op) {
446 case JSOP_SETRVAL:
447 case JSOP_POPV:
448 usesRval = true;
449 break;
451 case JSOP_NAME:
452 case JSOP_CALLNAME:
453 case JSOP_BINDNAME:
454 case JSOP_SETNAME:
455 case JSOP_DELNAME:
456 case JSOP_INCNAME:
457 case JSOP_DECNAME:
458 case JSOP_NAMEINC:
459 case JSOP_NAMEDEC:
460 case JSOP_FORNAME:
461 usesScope = true;
462 break;
464 /* Watch for opcodes the method JIT doesn't compile. */
465 case JSOP_GOSUB:
466 case JSOP_GOSUBX:
467 case JSOP_IFPRIMTOP:
468 case JSOP_FILTER:
469 case JSOP_ENDFILTER:
470 case JSOP_TABLESWITCHX:
471 case JSOP_LOOKUPSWITCHX:
472 hadFailure = true;
473 return;
475 case JSOP_TABLESWITCH: {
476 jsbytecode *pc2 = pc;
477 unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
478 pc2 += JUMP_OFFSET_LEN;
479 jsint low = GET_JUMP_OFFSET(pc2);
480 pc2 += JUMP_OFFSET_LEN;
481 jsint high = GET_JUMP_OFFSET(pc2);
482 pc2 += JUMP_OFFSET_LEN;
484 if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
485 stackDepth, defineArray, defineCount)) {
486 return;
489 for (jsint i = low; i <= high; i++) {
490 unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
491 if (targetOffset != offset) {
492 if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
493 stackDepth, defineArray, defineCount)) {
494 return;
497 pc2 += JUMP_OFFSET_LEN;
499 break;
502 case JSOP_LOOKUPSWITCH: {
503 jsbytecode *pc2 = pc;
504 unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
505 pc2 += JUMP_OFFSET_LEN;
506 unsigned npairs = GET_UINT16(pc2);
507 pc2 += UINT16_LEN;
509 if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
510 stackDepth, defineArray, defineCount)) {
511 return;
514 while (npairs) {
515 pc2 += INDEX_LEN;
516 unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
517 if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
518 stackDepth, defineArray, defineCount)) {
519 return;
521 pc2 += JUMP_OFFSET_LEN;
522 npairs--;
524 break;
527 case JSOP_TRY: {
529 * Everything between a try and corresponding catch or finally is conditional.
530 * Note that there is no problem with code which is skipped by a thrown
531 * exception but is not caught by a later handler in the same function:
532 * no more code will execute, and it does not matter what is defined.
534 JSTryNote *tn = script->trynotes()->vector;
535 JSTryNote *tnlimit = tn + script->trynotes()->length;
536 for (; tn < tnlimit; tn++) {
537 unsigned startOffset = script->main - script->code + tn->start;
538 if (startOffset == offset + 1) {
539 unsigned catchOffset = startOffset + tn->length;
541 /* This will overestimate try block code, for multiple catch/finally. */
542 if (catchOffset > forwardCatch)
543 forwardCatch = catchOffset;
545 if (tn->kind != JSTRY_ITER) {
546 if (!addJump(cx, catchOffset, &nextOffset, &forwardJump,
547 stackDepth, defineArray, defineCount)) {
548 return;
550 code[catchOffset]->exceptionEntry = true;
554 break;
557 case JSOP_GETLOCAL:
559 * Watch for uses of variables not known to be defined, and mark
560 * them as having possible uses before definitions. Ignore GETLOCAL
561 * followed by a POP, these are generated for, e.g. 'var x;'
563 if (pc[JSOP_GETLOCAL_LENGTH] != JSOP_POP) {
564 uint32 local = GET_SLOTNO(pc);
565 if (local < nfixed && !localDefined(local, offset))
566 setLocal(local, LOCAL_USE_BEFORE_DEF);
568 break;
570 case JSOP_CALLLOCAL:
571 case JSOP_GETLOCALPROP:
572 case JSOP_INCLOCAL:
573 case JSOP_DECLOCAL:
574 case JSOP_LOCALINC:
575 case JSOP_LOCALDEC: {
576 uint32 local = GET_SLOTNO(pc);
577 if (local < nfixed && !localDefined(local, offset))
578 setLocal(local, LOCAL_USE_BEFORE_DEF);
579 break;
582 case JSOP_SETLOCAL:
583 case JSOP_FORLOCAL: {
584 uint32 local = GET_SLOTNO(pc);
587 * The local variable may already have been marked as unconditionally
588 * defined at a later point in the script, if that definition was in the
589 * condition for a loop which then jumped back here. In such cases we
590 * will not treat the variable as ever being defined in the loop body
591 * (see setLocal).
593 if (local < nfixed && locals[local] == LOCAL_CONDITIONALLY_DEFINED) {
594 if (forwardJump) {
595 /* Add this local to the variables defined after this bytecode. */
596 uint32 *newArray = ArenaArray<uint32>(pool, defineCount + 1);
597 if (!newArray) {
598 setOOM(cx);
599 return;
601 if (defineCount)
602 memcpy(newArray, defineArray, defineCount * sizeof(uint32));
603 defineArray = newArray;
604 defineArray[defineCount++] = local;
605 } else {
606 /* This local is unconditionally defined by this bytecode. */
607 setLocal(local, offset);
610 break;
613 default:
614 break;
617 uint32 type = JOF_TYPE(js_CodeSpec[op].format);
619 /* Check basic jump opcodes, which may or may not have a fallthrough. */
620 if (type == JOF_JUMP || type == JOF_JUMPX) {
621 /* Some opcodes behave differently on their branching path. */
622 unsigned newStackDepth;
624 switch (op) {
625 case JSOP_OR:
626 case JSOP_AND:
627 case JSOP_ORX:
628 case JSOP_ANDX:
629 /* OR/AND instructions push the operation result when short circuiting. */
630 newStackDepth = stackDepth + 1;
631 break;
633 case JSOP_CASE:
634 case JSOP_CASEX:
635 /* Case instructions do not push the lvalue back when branching. */
636 newStackDepth = stackDepth - 1;
637 break;
639 default:
640 newStackDepth = stackDepth;
641 break;
644 unsigned targetOffset = offset + GetJumpOffset(pc, pc);
645 if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
646 newStackDepth, defineArray, defineCount)) {
647 return;
651 /* Handle any fallthrough from this opcode. */
652 if (!BytecodeNoFallThrough(op)) {
653 JS_ASSERT(successorOffset < script->length);
655 Bytecode *&nextcode = code[successorOffset];
656 bool initial = (nextcode == NULL);
658 if (initial) {
659 nextcode = ArenaNew<Bytecode>(pool);
660 if (!nextcode) {
661 setOOM(cx);
662 return;
666 if (!nextcode->mergeDefines(cx, this, initial, stackDepth, defineArray, defineCount))
667 return;
671 JS_ASSERT(!failed());
672 JS_ASSERT(forwardJump == 0 && forwardCatch == 0);
675 } /* namespace analyze */
676 } /* namespace js */