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
16 * The Original Code is the Mozilla SpiderMonkey bytecode analysis
18 * The Initial Developer of the Original Code is
20 * Portions created by the Initial Developer are Copyright (C) 2010
21 * the Initial Developer. All Rights Reserved.
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"
48 /////////////////////////////////////////////////////////////////////
50 /////////////////////////////////////////////////////////////////////
55 JS_FinishArenaPool(&pool
);
60 ArenaArray(JSArenaPool
&pool
, unsigned count
)
63 JS_ARENA_ALLOCATE(v
, &pool
, count
* sizeof(T
));
69 ArenaNew(JSArenaPool
&pool
)
72 JS_ARENA_ALLOCATE(v
, &pool
, sizeof(T
));
76 /////////////////////////////////////////////////////////////////////
78 /////////////////////////////////////////////////////////////////////
81 Bytecode::mergeDefines(JSContext
*cx
, Script
*script
, bool initial
, unsigned newDepth
,
82 uint32
*newArray
, unsigned newCount
)
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
;
96 * This bytecode has multiple incoming edges, intersect the new array with any
97 * variables known to be defined along other incoming edges.
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
++) {
114 for (unsigned j
= 0; j
< newCount
; j
++) {
115 if (newArray
[j
] == defineArray
[i
])
122 JS_ASSERT(stackDepth
== newDepth
);
124 for (unsigned i
= 0; i
< defineCount
; i
++) {
126 for (unsigned j
= 0; j
< newCount
; j
++) {
127 if (newArray
[j
] == defineArray
[i
])
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.
137 uint32
*reallocArray
= ArenaArray
<uint32
>(script
->pool
, defineCount
);
142 memcpy(reallocArray
, defineArray
, defineCount
* sizeof(uint32
));
143 defineArray
= reallocArray
;
147 /* Swap with the last element and pop the array. */
148 defineArray
[i
--] = defineArray
[--defineCount
];
156 /////////////////////////////////////////////////////////////////////
158 /////////////////////////////////////////////////////////////////////
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
);
170 bytecode
= ArenaNew
<Bytecode
>(pool
);
177 if (!bytecode
->mergeDefines(cx
, this, initial
, stackDepth
, defineArray
, defineCount
))
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
;
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
)
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).
243 BytecodeNoFallThrough(JSOp op
)
254 case JSOP_TABLESWITCH
:
255 case JSOP_TABLESWITCHX
:
256 case JSOP_LOOKUPSWITCH
:
257 case JSOP_LOOKUPSWITCHX
:
262 // these fall through indirectly, after executing a 'finally'.
269 /* Untrap a single PC, and retrap it at scope exit. */
275 UntrapOpcode(JSContext
*cx
, JSScript
*script
, jsbytecode
*pc
)
276 : pc(pc
), trap(JSOp(*pc
) == JSOP_TRAP
)
279 *pc
= JS_GetTrapOpcode(cx
, script
, pc
);
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
) {
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
);
326 setLocal(slot
, LOCAL_USE_BEFORE_DEF
);
330 * If the script is in debug mode, JS_SetFrameReturnValue can be called at
333 if (cx
->compartment
->debugMode
)
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
);
356 startcode
->stackDepth
= 0;
359 unsigned offset
, nextOffset
= 0;
360 while (nextOffset
< length
) {
363 JS_ASSERT(forwardCatch
<= forwardJump
);
365 /* Check if the current forward jump/try-block has finished. */
366 if (forwardJump
&& forwardJump
== offset
)
368 if (forwardCatch
&& forwardCatch
== offset
)
371 Bytecode
*&bytecode
= code
[offset
];
372 jsbytecode
*pc
= script
->code
+ offset
;
374 UntrapOpcode
untrap(cx
, script
, 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
;
389 /* Haven't found a path by which this bytecode is reachable. */
393 if (bytecode
->analyzed
) {
394 /* No need to reanalyze, see Bytecode::mergeDefines. */
398 bytecode
->analyzed
= true;
401 bytecode
->inTryBlock
= true;
403 unsigned stackDepth
= bytecode
->stackDepth
;
404 uint32
*defineArray
= bytecode
->defineArray
;
405 unsigned defineCount
= bytecode
->defineCount
;
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
);
429 unsigned nuses
, ndefs
;
430 if (js_CodeSpec
[op
].nuses
== -1)
431 nuses
= js_GetVariableStackUses(op
, pc
);
433 nuses
= js_CodeSpec
[op
].nuses
;
435 if (js_CodeSpec
[op
].ndefs
== -1)
436 ndefs
= js_GetEnterBlockStackDefs(cx
, script
, pc
);
438 ndefs
= js_CodeSpec
[op
].ndefs
;
440 JS_ASSERT(stackDepth
>= nuses
);
464 /* Watch for opcodes the method JIT doesn't compile. */
470 case JSOP_TABLESWITCHX
:
471 case JSOP_LOOKUPSWITCHX
:
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
)) {
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
)) {
497 pc2
+= JUMP_OFFSET_LEN
;
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
);
509 if (!addJump(cx
, defaultOffset
, &nextOffset
, &forwardJump
,
510 stackDepth
, defineArray
, defineCount
)) {
516 unsigned targetOffset
= offset
+ GetJumpOffset(pc
, pc2
);
517 if (!addJump(cx
, targetOffset
, &nextOffset
, &forwardJump
,
518 stackDepth
, defineArray
, defineCount
)) {
521 pc2
+= JUMP_OFFSET_LEN
;
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
)) {
550 code
[catchOffset
]->exceptionEntry
= true;
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
);
571 case JSOP_GETLOCALPROP
:
575 case JSOP_LOCALDEC
: {
576 uint32 local
= GET_SLOTNO(pc
);
577 if (local
< nfixed
&& !localDefined(local
, offset
))
578 setLocal(local
, LOCAL_USE_BEFORE_DEF
);
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
593 if (local
< nfixed
&& locals
[local
] == LOCAL_CONDITIONALLY_DEFINED
) {
595 /* Add this local to the variables defined after this bytecode. */
596 uint32
*newArray
= ArenaArray
<uint32
>(pool
, defineCount
+ 1);
602 memcpy(newArray
, defineArray
, defineCount
* sizeof(uint32
));
603 defineArray
= newArray
;
604 defineArray
[defineCount
++] = local
;
606 /* This local is unconditionally defined by this bytecode. */
607 setLocal(local
, offset
);
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
;
629 /* OR/AND instructions push the operation result when short circuiting. */
630 newStackDepth
= stackDepth
+ 1;
635 /* Case instructions do not push the lvalue back when branching. */
636 newStackDepth
= stackDepth
- 1;
640 newStackDepth
= stackDepth
;
644 unsigned targetOffset
= offset
+ GetJumpOffset(pc
, pc
);
645 if (!addJump(cx
, targetOffset
, &nextOffset
, &forwardJump
,
646 newStackDepth
, defineArray
, defineCount
)) {
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
);
659 nextcode
= ArenaNew
<Bytecode
>(pool
);
666 if (!nextcode
->mergeDefines(cx
, this, initial
, stackDepth
, defineArray
, defineCount
))
671 JS_ASSERT(!failed());
672 JS_ASSERT(forwardJump
== 0 && forwardCatch
== 0);
675 } /* namespace analyze */