Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jsanalyze.cpp
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 namespace js {
46 namespace analyze {
47
48 /////////////////////////////////////////////////////////////////////
49 // Script
50 /////////////////////////////////////////////////////////////////////
51
52 void
53 Script::destroy()
54 {
55     JS_FinishArenaPool(&pool);
56 }
57
58 template <typename T>
59 inline T *
60 ArenaArray(JSArenaPool &pool, unsigned count)
61 {
62     void *v;
63     JS_ARENA_ALLOCATE(v, &pool, count * sizeof(T));
64     return (T *) v;
65 }
66
67 template <typename T>
68 inline T *
69 ArenaNew(JSArenaPool &pool)
70 {
71     void *v;
72     JS_ARENA_ALLOCATE(v, &pool, sizeof(T));
73     return new (v) T();
74 }
75
76 /////////////////////////////////////////////////////////////////////
77 // Bytecode
78 /////////////////////////////////////////////////////////////////////
79
80 bool
81 Bytecode::mergeDefines(JSContext *cx, Script *script, bool initial, unsigned newDepth,
82                        uint32 *newArray, unsigned newCount)
83 {
84     if (initial) {
85         /*
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.
88          */
89         stackDepth = newDepth;
90         defineArray = newArray;
91         defineCount = newCount;
92         return true;
93     }
94
95     /*
96      * This bytecode has multiple incoming edges, intersect the new array with any
97      * variables known to be defined along other incoming edges.
98      */
99     if (analyzed) {
100 #ifdef DEBUG
101         /*
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.
110          */
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;
117             }
118             JS_ASSERT(found);
119         }
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;
129             }
130             if (!found) {
131                 /*
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.
135                  */
136                 if (!owned) {
137                     uint32 *reallocArray = ArenaArray<uint32>(script->pool, defineCount);
138                     if (!reallocArray) {
139                         script->setOOM(cx);
140                         return false;
141                     }
142                     memcpy(reallocArray, defineArray, defineCount * sizeof(uint32));
143                     defineArray = reallocArray;
144                     owned = true;
145                 }
146
147                 /* Swap with the last element and pop the array. */
148                 defineArray[i--] = defineArray[--defineCount];
149             }
150         }
151     }
152
153     return true;
154 }
155
156 /////////////////////////////////////////////////////////////////////
157 // Analysis
158 /////////////////////////////////////////////////////////////////////
159
160 inline bool
161 Script::addJump(JSContext *cx, unsigned offset,
162                 unsigned *currentOffset, unsigned *forwardJump,
163                 unsigned stackDepth, uint32 *defineArray, unsigned defineCount)
164 {
165     JS_ASSERT(offset < script->length);
166
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;
174         }
175     }
176
177     if (!bytecode->mergeDefines(cx, this, initial, stackDepth, defineArray, defineCount))
178         return false;
179     bytecode->jumpTarget = true;
180
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;
187         }
188     } else if (offset > *forwardJump) {
189         *forwardJump = offset;
190     }
191
192     return true;
193 }
194
195 inline void
196 Script::setLocal(uint32 local, uint32 offset)
197 {
198     JS_ASSERT(local < localCount());
199     JS_ASSERT(offset != LOCAL_CONDITIONALLY_DEFINED);
200
201     /*
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:
206      *
207      * while ((a = b) != 0) { x = a; }
208      *
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.
213      */
214     JS_ASSERT(locals[local] == LOCAL_CONDITIONALLY_DEFINED ||
215               locals[local] == offset || offset == LOCAL_USE_BEFORE_DEF);
216
217     locals[local] = offset;
218 }
219
220 static inline ptrdiff_t
221 GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
222 {
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);
227 }
228
229 static inline unsigned
230 GetBytecodeLength(jsbytecode *pc)
231 {
232     JSOp op = (JSOp)*pc;
233     JS_ASSERT(op < JSOP_LIMIT);
234     JS_ASSERT(op != JSOP_TRAP);
235
236     if (js_CodeSpec[op].length != -1)
237         return js_CodeSpec[op].length;
238     return js_GetVariableBytecodeLength(pc);
239 }
240
241 // return whether op bytecodes do not fallthrough (they may do a jump).
242 static inline bool
243 BytecodeNoFallThrough(JSOp op)
244 {
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;
266     }
267 }
268
269 /* Untrap a single PC, and retrap it at scope exit. */
270 struct UntrapOpcode
271 {
272     jsbytecode *pc;
273     bool trap;
274
275     UntrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
276         : pc(pc), trap(JSOp(*pc) == JSOP_TRAP)
277     {
278         if (trap)
279             *pc = JS_GetTrapOpcode(cx, script, pc);
280     }
281
282     ~UntrapOpcode()
283     {
284         if (trap)
285             *pc = JSOP_TRAP;
286     }
287 };
288
289 void
290 Script::analyze(JSContext *cx, JSScript *script)
291 {
292     JS_ASSERT(!code && !locals);
293     this->script = script;
294
295     JS_InitArenaPool(&pool, "script_analyze", 256, 8, NULL);
296
297     unsigned length = script->length;
298     unsigned nfixed = localCount();
299
300     code = ArenaArray<Bytecode*>(pool, length);
301     locals = ArenaArray<uint32>(pool, nfixed);
302
303     if (!code || !locals) {
304         setOOM(cx);
305         return;
306     }
307
308     PodZero(code, length);
309
310     for (unsigned i = 0; i < nfixed; i++)
311         locals[i] = LOCAL_CONDITIONALLY_DEFINED;
312
313     /*
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.
316      */
317
318     if (script->usesEval || cx->compartment->debugMode) {
319         for (uint32 i = 0; i < nfixed; i++)
320             setLocal(i, LOCAL_USE_BEFORE_DEF);
321     }
322
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);
327     }
328
329     /*
330      * If the script is in debug mode, JS_SetFrameReturnValue can be called at
331      * any safe point.
332      */
333     if (cx->compartment->debugMode)
334         usesRval = true;
335
336     /*
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.
340      */
341     unsigned forwardJump = 0;
342
343     /*
344      * If we are in the middle of a try block, the offset of the highest
345      * catch/finally/enditer.
346      */
347     unsigned forwardCatch = 0;
348
349     /* Fill in stack depth and definitions at initial bytecode. */
350     Bytecode *startcode = ArenaNew<Bytecode>(pool);
351     if (!startcode) {
352         setOOM(cx);
353         return;
354     }
355
356     startcode->stackDepth = 0;
357     code[0] = startcode;
358
359     unsigned offset, nextOffset = 0;
360     while (nextOffset < length) {
361         offset = nextOffset;
362
363         JS_ASSERT(forwardCatch <= forwardJump);
364
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;
370
371         Bytecode *&bytecode = code[offset];
372         jsbytecode *pc = script->code + offset;
373
374         UntrapOpcode untrap(cx, script, pc);
375
376         JSOp op = (JSOp)*pc;
377         JS_ASSERT(op < JSOP_LIMIT);
378
379         /* Immediate successor of this bytecode. */
380         unsigned successorOffset = offset + GetBytecodeLength(pc);
381
382         /*
383          * Next bytecode to analyze.  This is either the successor, or is an
384          * earlier bytecode if this bytecode has a loop backedge.
385          */
386         nextOffset = successorOffset;
387
388         if (!bytecode) {
389             /* Haven't found a path by which this bytecode is reachable. */
390             continue;
391         }
392
393         if (bytecode->analyzed) {
394             /* No need to reanalyze, see Bytecode::mergeDefines. */
395             continue;
396         }
397
398         bytecode->analyzed = true;
399
400         if (forwardCatch)
401             bytecode->inTryBlock = true;
402
403         unsigned stackDepth = bytecode->stackDepth;
404         uint32 *defineArray = bytecode->defineArray;
405         unsigned defineCount = bytecode->defineCount;
406
407         if (!forwardJump) {
408             /*
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.
416              */
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);
424             }
425             defineArray = NULL;
426             defineCount = 0;
427         }
428
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;
434
435         if (js_CodeSpec[op].ndefs == -1)
436             ndefs = js_GetEnterBlockStackDefs(cx, script, pc);
437         else
438             ndefs = js_CodeSpec[op].ndefs;
439
440         JS_ASSERT(stackDepth >= nuses);
441         stackDepth -= nuses;
442         stackDepth += ndefs;
443
444         switch (op) {
445
446           case JSOP_SETRVAL:
447           case JSOP_POPV:
448             usesRval = true;
449             break;
450
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;
463
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;
474
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;
483
484             if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
485                          stackDepth, defineArray, defineCount)) {
486                 return;
487             }
488
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;
495                     }
496                 }
497                 pc2 += JUMP_OFFSET_LEN;
498             }
499             break;
500           }
501
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;
508
509             if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
510                          stackDepth, defineArray, defineCount)) {
511                 return;
512             }
513
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;
520                 }
521                 pc2 += JUMP_OFFSET_LEN;
522                 npairs--;
523             }
524             break;
525           }
526
527           case JSOP_TRY: {
528             /*
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.
533              */
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;
540
541                     /* This will overestimate try block code, for multiple catch/finally. */
542                     if (catchOffset > forwardCatch)
543                         forwardCatch = catchOffset;
544
545                     if (tn->kind != JSTRY_ITER) {
546                         if (!addJump(cx, catchOffset, &nextOffset, &forwardJump,
547                                      stackDepth, defineArray, defineCount)) {
548                             return;
549                         }
550                         code[catchOffset]->exceptionEntry = true;
551                     }
552                 }
553             }
554             break;
555           }
556
557           case JSOP_GETLOCAL:
558             /*
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;'
562              */
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);
567             }
568             break;
569
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;
580           }
581
582           case JSOP_SETLOCAL:
583           case JSOP_FORLOCAL: {
584             uint32 local = GET_SLOTNO(pc);
585
586             /*
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).
592              */
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;
600                     }
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);
608                 }
609             }
610             break;
611           }
612
613           default:
614             break;
615         }
616
617         uint32 type = JOF_TYPE(js_CodeSpec[op].format);
618
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;
623
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;
632
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;
638
639               default:
640                 newStackDepth = stackDepth;
641                 break;
642             }
643
644             unsigned targetOffset = offset + GetJumpOffset(pc, pc);
645             if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
646                          newStackDepth, defineArray, defineCount)) {
647                 return;
648             }
649         }
650
651         /* Handle any fallthrough from this opcode. */
652         if (!BytecodeNoFallThrough(op)) {
653             JS_ASSERT(successorOffset < script->length);
654
655             Bytecode *&nextcode = code[successorOffset];
656             bool initial = (nextcode == NULL);
657
658             if (initial) {
659                 nextcode = ArenaNew<Bytecode>(pool);
660                 if (!nextcode) {
661                     setOOM(cx);
662                     return;
663                 }
664             }
665
666             if (!nextcode->mergeDefines(cx, this, initial, stackDepth, defineArray, defineCount))
667                 return;
668         }
669     }
670
671     JS_ASSERT(!failed());
672     JS_ASSERT(forwardJump == 0 && forwardCatch == 0);
673 }
674
675 } /* namespace analyze */
676 } /* namespace js */