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 */