mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 05:45:37 +00:00
Analysis for defined variables, bug 604426. r=dvander
This commit is contained in:
parent
ab1de59e65
commit
034d5c35db
@ -126,6 +126,7 @@ DIST_INSTALL = 1
|
||||
VPATH = $(srcdir)
|
||||
|
||||
CPPSRCS = \
|
||||
jsanalyze.cpp \
|
||||
jsapi.cpp \
|
||||
jsarena.cpp \
|
||||
jsarray.cpp \
|
||||
@ -181,6 +182,7 @@ INSTALLED_HEADERS = \
|
||||
jsautocfg.h \
|
||||
$(CURDIR)/jsautokw.h \
|
||||
js.msg \
|
||||
jsanalyze.h \
|
||||
jsapi.h \
|
||||
jsarray.h \
|
||||
jsarena.h \
|
||||
@ -304,7 +306,6 @@ ifdef ENABLE_METHODJIT
|
||||
VPATH += $(srcdir)/methodjit
|
||||
|
||||
CPPSRCS += MethodJIT.cpp \
|
||||
BytecodeAnalyzer.cpp \
|
||||
StubCalls.cpp \
|
||||
Compiler.cpp \
|
||||
FrameState.cpp \
|
||||
|
664
js/src/jsanalyze.cpp
Normal file
664
js/src/jsanalyze.cpp
Normal file
@ -0,0 +1,664 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
|
||||
/* vim: set ts=40 sw=4 et tw=99: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is the Mozilla SpiderMonkey bytecode analysis
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Brian Hackett <bhackett@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "jsanalyze.h"
|
||||
#include "jsautooplen.h"
|
||||
#include "jscompartment.h"
|
||||
#include "jscntxt.h"
|
||||
|
||||
namespace js {
|
||||
namespace analyze {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Script
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
Script::destroy()
|
||||
{
|
||||
JS_FinishArenaPool(&pool);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T *
|
||||
ArenaArray(JSArenaPool &pool, unsigned count)
|
||||
{
|
||||
void *v;
|
||||
JS_ARENA_ALLOCATE(v, &pool, count * sizeof(T));
|
||||
return (T *) v;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T *
|
||||
ArenaNew(JSArenaPool &pool)
|
||||
{
|
||||
void *v;
|
||||
JS_ARENA_ALLOCATE(v, &pool, sizeof(T));
|
||||
return new (v) T();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Bytecode
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool
|
||||
Bytecode::mergeDefines(JSContext *cx, Script *script, bool initial, unsigned newDepth,
|
||||
uint32 *newArray, unsigned newCount)
|
||||
{
|
||||
if (initial) {
|
||||
/*
|
||||
* Haven't handled any incoming edges to this bytecode before.
|
||||
* Define arrays are copy on write, so just reuse the array for this bytecode.
|
||||
*/
|
||||
stackDepth = newDepth;
|
||||
defineArray = newArray;
|
||||
defineCount = newCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This bytecode has multiple incoming edges, intersect the new array with any
|
||||
* variables known to be defined along other incoming edges.
|
||||
*/
|
||||
if (analyzed) {
|
||||
#ifdef DEBUG
|
||||
/*
|
||||
* Once analyzed, a bytecode has its full set of definitions. There are two
|
||||
* properties we depend on to ensure this. First, bytecode for a function
|
||||
* is emitted in topological order, and since we analyze bytecodes in the
|
||||
* order they were emitted we will have seen all incoming jumps except
|
||||
* for any loop back edges. Second, javascript has structured control flow,
|
||||
* so loop heads dominate their bodies; the set of variables defined
|
||||
* on a back edge will be at least as large as at the head of the loop,
|
||||
* so no intersection or further analysis needs to be done.
|
||||
*/
|
||||
JS_ASSERT(stackDepth == newDepth);
|
||||
for (unsigned i = 0; i < defineCount; i++) {
|
||||
bool found = false;
|
||||
for (unsigned j = 0; j < newCount; j++) {
|
||||
if (newArray[j] == defineArray[i])
|
||||
found = true;
|
||||
}
|
||||
JS_ASSERT(found);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
JS_ASSERT(stackDepth == newDepth);
|
||||
bool owned = false;
|
||||
for (unsigned i = 0; i < defineCount; i++) {
|
||||
bool found = false;
|
||||
for (unsigned j = 0; j < newCount; j++) {
|
||||
if (newArray[j] == defineArray[i])
|
||||
found = true;
|
||||
}
|
||||
if (!found) {
|
||||
/*
|
||||
* Get a mutable copy of the defines. This can end up making
|
||||
* several copies for a bytecode if it has many incoming edges
|
||||
* with progressively smaller sets of defined variables.
|
||||
*/
|
||||
if (!owned) {
|
||||
uint32 *reallocArray = ArenaArray<uint32>(script->pool, defineCount);
|
||||
if (!reallocArray) {
|
||||
script->setOOM(cx);
|
||||
return false;
|
||||
}
|
||||
memcpy(reallocArray, defineArray, defineCount * sizeof(uint32));
|
||||
defineArray = reallocArray;
|
||||
owned = true;
|
||||
}
|
||||
|
||||
/* Swap with the last element and pop the array. */
|
||||
defineArray[i--] = defineArray[--defineCount];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Analysis
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline bool
|
||||
Script::addJump(JSContext *cx, unsigned offset,
|
||||
unsigned *currentOffset, unsigned *forwardJump,
|
||||
unsigned stackDepth, uint32 *defineArray, unsigned defineCount)
|
||||
{
|
||||
JS_ASSERT(offset < script->length);
|
||||
|
||||
Bytecode *&bytecode = code[offset];
|
||||
bool initial = (bytecode == NULL);
|
||||
if (initial) {
|
||||
bytecode = ArenaNew<Bytecode>(pool);
|
||||
if (!bytecode) {
|
||||
setOOM(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bytecode->mergeDefines(cx, this, initial, stackDepth, defineArray, defineCount))
|
||||
return false;
|
||||
bytecode->jumpTarget = true;
|
||||
|
||||
if (offset < *currentOffset) {
|
||||
/* Don't follow back edges to bytecode which has already been analyzed. */
|
||||
if (!bytecode->analyzed) {
|
||||
if (*forwardJump == 0)
|
||||
*forwardJump = *currentOffset;
|
||||
*currentOffset = offset;
|
||||
}
|
||||
} else if (offset > *forwardJump) {
|
||||
*forwardJump = offset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void
|
||||
Script::setLocal(uint32 local, uint32 offset)
|
||||
{
|
||||
JS_ASSERT(local < localCount());
|
||||
JS_ASSERT(offset != LOCAL_CONDITIONALLY_DEFINED);
|
||||
|
||||
/*
|
||||
* It isn't possible to change the point when a variable becomes unconditionally
|
||||
* defined, or to mark it as unconditionally defined after it has already been
|
||||
* marked as having a use before def. It *is* possible to mark it as having
|
||||
* a use before def after marking it as unconditionally defined. In a loop such as:
|
||||
*
|
||||
* while ((a = b) != 0) { x = a; }
|
||||
*
|
||||
* When walking through the body of this loop, we will first analyze the test
|
||||
* (which comes after the body in the bytecode stream) as unconditional code,
|
||||
* and mark a as definitely defined. a is not in the define array when taking
|
||||
* the loop's back edge, so it is treated as possibly undefined when written to x.
|
||||
*/
|
||||
JS_ASSERT(locals[local] == LOCAL_CONDITIONALLY_DEFINED ||
|
||||
locals[local] == offset || offset == LOCAL_USE_BEFORE_DEF);
|
||||
|
||||
locals[local] = offset;
|
||||
}
|
||||
|
||||
static inline ptrdiff_t
|
||||
GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
|
||||
{
|
||||
uint32 type = JOF_OPTYPE(*pc);
|
||||
if (JOF_TYPE_IS_EXTENDED_JUMP(type))
|
||||
return GET_JUMPX_OFFSET(pc2);
|
||||
return GET_JUMP_OFFSET(pc2);
|
||||
}
|
||||
|
||||
static inline unsigned
|
||||
GetBytecodeLength(jsbytecode *pc)
|
||||
{
|
||||
JSOp op = (JSOp)*pc;
|
||||
JS_ASSERT(op < JSOP_LIMIT);
|
||||
JS_ASSERT(op != JSOP_TRAP);
|
||||
|
||||
if (js_CodeSpec[op].length != -1)
|
||||
return js_CodeSpec[op].length;
|
||||
return js_GetVariableBytecodeLength(pc);
|
||||
}
|
||||
|
||||
// return whether op bytecodes do not fallthrough (they may do a jump).
|
||||
static inline bool
|
||||
BytecodeNoFallThrough(JSOp op)
|
||||
{
|
||||
switch (op) {
|
||||
case JSOP_GOTO:
|
||||
case JSOP_GOTOX:
|
||||
case JSOP_DEFAULT:
|
||||
case JSOP_DEFAULTX:
|
||||
case JSOP_RETURN:
|
||||
case JSOP_STOP:
|
||||
case JSOP_RETRVAL:
|
||||
case JSOP_THROW:
|
||||
case JSOP_TABLESWITCH:
|
||||
case JSOP_TABLESWITCHX:
|
||||
case JSOP_LOOKUPSWITCH:
|
||||
case JSOP_LOOKUPSWITCHX:
|
||||
case JSOP_FILTER:
|
||||
return true;
|
||||
case JSOP_GOSUB:
|
||||
case JSOP_GOSUBX:
|
||||
// these fall through indirectly, after executing a 'finally'.
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Untrap a single PC, and retrap it at scope exit. */
|
||||
struct UntrapOpcode
|
||||
{
|
||||
jsbytecode *pc;
|
||||
bool trap;
|
||||
|
||||
UntrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
|
||||
: pc(pc), trap(JSOp(*pc) == JSOP_TRAP)
|
||||
{
|
||||
if (trap)
|
||||
*pc = JS_GetTrapOpcode(cx, script, pc);
|
||||
}
|
||||
|
||||
~UntrapOpcode()
|
||||
{
|
||||
if (trap)
|
||||
*pc = JSOP_TRAP;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
Script::analyze(JSContext *cx, JSScript *script)
|
||||
{
|
||||
JS_ASSERT(!code && !locals);
|
||||
this->script = script;
|
||||
|
||||
JS_InitArenaPool(&pool, "script_analyze", 256, 8, NULL);
|
||||
|
||||
unsigned length = script->length;
|
||||
unsigned nfixed = localCount();
|
||||
|
||||
code = ArenaArray<Bytecode*>(pool, length);
|
||||
locals = ArenaArray<uint32>(pool, nfixed);
|
||||
|
||||
if (!code || !locals) {
|
||||
setOOM(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
PodZero(code, length);
|
||||
|
||||
for (unsigned i = 0; i < nfixed; i++)
|
||||
locals[i] = LOCAL_CONDITIONALLY_DEFINED;
|
||||
|
||||
/*
|
||||
* Treat locals as having a possible use-before-def if they could be accessed
|
||||
* by debug code or by eval, or if they could be accessed by an inner script.
|
||||
*/
|
||||
|
||||
if (script->usesEval || cx->compartment->debugMode) {
|
||||
for (uint32 i = 0; i < nfixed; i++)
|
||||
setLocal(i, LOCAL_USE_BEFORE_DEF);
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < script->nClosedVars; i++) {
|
||||
uint32 slot = script->getClosedVar(i);
|
||||
if (slot < nfixed)
|
||||
setLocal(slot, LOCAL_USE_BEFORE_DEF);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are in the middle of one or more jumps, the offset of the highest
|
||||
* target jumping over this bytecode. Includes implicit jumps from
|
||||
* try/catch/finally blocks.
|
||||
*/
|
||||
unsigned forwardJump = 0;
|
||||
|
||||
/*
|
||||
* If we are in the middle of a try block, the offset of the highest
|
||||
* catch/finally/enditer.
|
||||
*/
|
||||
unsigned forwardCatch = 0;
|
||||
|
||||
/* Fill in stack depth and definitions at initial bytecode. */
|
||||
Bytecode *startcode = ArenaNew<Bytecode>(pool);
|
||||
if (!startcode) {
|
||||
setOOM(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
startcode->stackDepth = 0;
|
||||
code[0] = startcode;
|
||||
|
||||
unsigned offset, nextOffset = 0;
|
||||
while (nextOffset < length) {
|
||||
offset = nextOffset;
|
||||
|
||||
JS_ASSERT(forwardCatch <= forwardJump);
|
||||
|
||||
/* Check if the current forward jump/try-block has finished. */
|
||||
if (forwardJump && forwardJump == offset)
|
||||
forwardJump = 0;
|
||||
if (forwardCatch && forwardCatch == offset)
|
||||
forwardCatch = 0;
|
||||
|
||||
Bytecode *&bytecode = code[offset];
|
||||
jsbytecode *pc = script->code + offset;
|
||||
|
||||
UntrapOpcode untrap(cx, script, pc);
|
||||
|
||||
JSOp op = (JSOp)*pc;
|
||||
JS_ASSERT(op < JSOP_LIMIT);
|
||||
|
||||
/* Immediate successor of this bytecode. */
|
||||
unsigned successorOffset = offset + GetBytecodeLength(pc);
|
||||
|
||||
/*
|
||||
* Next bytecode to analyze. This is either the successor, or is an
|
||||
* earlier bytecode if this bytecode has a loop backedge.
|
||||
*/
|
||||
nextOffset = successorOffset;
|
||||
|
||||
if (!bytecode) {
|
||||
/* Haven't found a path by which this bytecode is reachable. */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bytecode->analyzed) {
|
||||
/* No need to reanalyze, see Bytecode::mergeDefines. */
|
||||
continue;
|
||||
}
|
||||
|
||||
bytecode->analyzed = true;
|
||||
|
||||
if (forwardCatch)
|
||||
bytecode->inTryBlock = true;
|
||||
|
||||
unsigned stackDepth = bytecode->stackDepth;
|
||||
uint32 *defineArray = bytecode->defineArray;
|
||||
unsigned defineCount = bytecode->defineCount;
|
||||
|
||||
if (!forwardJump) {
|
||||
/*
|
||||
* There is no jump over this bytecode, nor a containing try block.
|
||||
* Either this bytecode will definitely be executed, or an exception
|
||||
* will be thrown which the script does not catch. Either way,
|
||||
* any variables definitely defined at this bytecode will stay
|
||||
* defined throughout the rest of the script. We just need to
|
||||
* remember the offset where the variable became unconditionally
|
||||
* defined, rather than continue to maintain it in define arrays.
|
||||
*/
|
||||
for (unsigned i = 0; i < defineCount; i++) {
|
||||
uint32 local = defineArray[i];
|
||||
JS_ASSERT_IF(locals[local] != LOCAL_CONDITIONALLY_DEFINED &&
|
||||
locals[local] != LOCAL_USE_BEFORE_DEF,
|
||||
locals[local] <= offset);
|
||||
if (locals[local] == LOCAL_CONDITIONALLY_DEFINED)
|
||||
setLocal(local, offset);
|
||||
}
|
||||
defineArray = NULL;
|
||||
defineCount = 0;
|
||||
}
|
||||
|
||||
unsigned nuses, ndefs;
|
||||
if (js_CodeSpec[op].nuses == -1)
|
||||
nuses = js_GetVariableStackUses(op, pc);
|
||||
else
|
||||
nuses = js_CodeSpec[op].nuses;
|
||||
|
||||
if (js_CodeSpec[op].ndefs == -1)
|
||||
ndefs = js_GetEnterBlockStackDefs(cx, script, pc);
|
||||
else
|
||||
ndefs = js_CodeSpec[op].ndefs;
|
||||
|
||||
JS_ASSERT(stackDepth >= nuses);
|
||||
stackDepth -= nuses;
|
||||
stackDepth += ndefs;
|
||||
|
||||
switch (op) {
|
||||
|
||||
case JSOP_SETRVAL:
|
||||
case JSOP_POPV:
|
||||
usesRval = true;
|
||||
break;
|
||||
|
||||
case JSOP_NAME:
|
||||
case JSOP_CALLNAME:
|
||||
case JSOP_BINDNAME:
|
||||
case JSOP_SETNAME:
|
||||
case JSOP_DELNAME:
|
||||
case JSOP_INCNAME:
|
||||
case JSOP_DECNAME:
|
||||
case JSOP_NAMEINC:
|
||||
case JSOP_NAMEDEC:
|
||||
case JSOP_FORNAME:
|
||||
usesScope = true;
|
||||
break;
|
||||
|
||||
/* Watch for opcodes the method JIT doesn't compile. */
|
||||
case JSOP_GOSUB:
|
||||
case JSOP_GOSUBX:
|
||||
case JSOP_IFPRIMTOP:
|
||||
case JSOP_FILTER:
|
||||
case JSOP_ENDFILTER:
|
||||
case JSOP_TABLESWITCHX:
|
||||
case JSOP_LOOKUPSWITCHX:
|
||||
hadFailure = true;
|
||||
return;
|
||||
|
||||
case JSOP_TABLESWITCH: {
|
||||
jsbytecode *pc2 = pc;
|
||||
unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
|
||||
pc2 += JUMP_OFFSET_LEN;
|
||||
jsint low = GET_JUMP_OFFSET(pc2);
|
||||
pc2 += JUMP_OFFSET_LEN;
|
||||
jsint high = GET_JUMP_OFFSET(pc2);
|
||||
pc2 += JUMP_OFFSET_LEN;
|
||||
|
||||
if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
|
||||
stackDepth, defineArray, defineCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (jsint i = low; i <= high; i++) {
|
||||
unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
|
||||
if (targetOffset != offset) {
|
||||
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
||||
stackDepth, defineArray, defineCount)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
pc2 += JUMP_OFFSET_LEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_LOOKUPSWITCH: {
|
||||
jsbytecode *pc2 = pc;
|
||||
unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
|
||||
pc2 += JUMP_OFFSET_LEN;
|
||||
unsigned npairs = GET_UINT16(pc2);
|
||||
pc2 += UINT16_LEN;
|
||||
|
||||
if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
|
||||
stackDepth, defineArray, defineCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (npairs) {
|
||||
pc2 += INDEX_LEN;
|
||||
unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
|
||||
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
||||
stackDepth, defineArray, defineCount)) {
|
||||
return;
|
||||
}
|
||||
pc2 += JUMP_OFFSET_LEN;
|
||||
npairs--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_TRY: {
|
||||
/*
|
||||
* Everything between a try and corresponding catch or finally is conditional.
|
||||
* Note that there is no problem with code which is skipped by a thrown
|
||||
* exception but is not caught by a later handler in the same function:
|
||||
* no more code will execute, and it does not matter what is defined.
|
||||
*/
|
||||
JSTryNote *tn = script->trynotes()->vector;
|
||||
JSTryNote *tnlimit = tn + script->trynotes()->length;
|
||||
for (; tn < tnlimit; tn++) {
|
||||
unsigned startOffset = script->main - script->code + tn->start;
|
||||
if (startOffset == offset + 1) {
|
||||
unsigned catchOffset = startOffset + tn->length;
|
||||
|
||||
/* This will overestimate try block code, for multiple catch/finally. */
|
||||
if (catchOffset > forwardCatch)
|
||||
forwardCatch = catchOffset;
|
||||
|
||||
if (tn->kind != JSTRY_ITER) {
|
||||
if (!addJump(cx, catchOffset, &nextOffset, &forwardJump,
|
||||
stackDepth, defineArray, defineCount)) {
|
||||
return;
|
||||
}
|
||||
code[catchOffset]->exceptionEntry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_GETLOCAL:
|
||||
/*
|
||||
* Watch for uses of variables not known to be defined, and mark
|
||||
* them as having possible uses before definitions. Ignore GETLOCAL
|
||||
* followed by a POP, these are generated for, e.g. 'var x;'
|
||||
*/
|
||||
if (pc[JSOP_GETLOCAL_LENGTH] != JSOP_POP) {
|
||||
uint32 local = GET_SLOTNO(pc);
|
||||
if (local < nfixed && !localDefined(local, offset))
|
||||
setLocal(local, LOCAL_USE_BEFORE_DEF);
|
||||
}
|
||||
break;
|
||||
|
||||
case JSOP_CALLLOCAL:
|
||||
case JSOP_INCLOCAL:
|
||||
case JSOP_DECLOCAL:
|
||||
case JSOP_LOCALINC:
|
||||
case JSOP_LOCALDEC: {
|
||||
uint32 local = GET_SLOTNO(pc);
|
||||
if (local < nfixed && !localDefined(local, offset))
|
||||
setLocal(local, LOCAL_USE_BEFORE_DEF);
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_SETLOCAL:
|
||||
case JSOP_FORLOCAL: {
|
||||
uint32 local = GET_SLOTNO(pc);
|
||||
JS_ASSERT_IF(local < nfixed &&
|
||||
locals[local] != LOCAL_CONDITIONALLY_DEFINED &&
|
||||
locals[local] != LOCAL_USE_BEFORE_DEF,
|
||||
locals[local] <= offset);
|
||||
if (local < nfixed && locals[local] == LOCAL_CONDITIONALLY_DEFINED) {
|
||||
if (forwardJump) {
|
||||
/* Add this local to the variables defined after this bytecode. */
|
||||
uint32 *newArray = ArenaArray<uint32>(pool, defineCount + 1);
|
||||
if (!newArray) {
|
||||
setOOM(cx);
|
||||
return;
|
||||
}
|
||||
if (defineCount)
|
||||
memcpy(newArray, defineArray, defineCount * sizeof(uint32));
|
||||
defineArray = newArray;
|
||||
defineArray[defineCount++] = local;
|
||||
} else {
|
||||
/* This local is unconditionally defined by this bytecode. */
|
||||
setLocal(local, offset);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
uint32 type = JOF_TYPE(js_CodeSpec[op].format);
|
||||
|
||||
/* Check basic jump opcodes, which may or may not have a fallthrough. */
|
||||
if (type == JOF_JUMP || type == JOF_JUMPX) {
|
||||
/* Some opcodes behave differently on their branching path. */
|
||||
unsigned newStackDepth;
|
||||
|
||||
switch (op) {
|
||||
case JSOP_OR:
|
||||
case JSOP_AND:
|
||||
case JSOP_ORX:
|
||||
case JSOP_ANDX:
|
||||
/* OR/AND instructions push the operation result when short circuiting. */
|
||||
newStackDepth = stackDepth + 1;
|
||||
break;
|
||||
|
||||
case JSOP_CASE:
|
||||
case JSOP_CASEX:
|
||||
/* Case instructions do not push the lvalue back when branching. */
|
||||
newStackDepth = stackDepth - 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
newStackDepth = stackDepth;
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned targetOffset = offset + GetJumpOffset(pc, pc);
|
||||
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
||||
newStackDepth, defineArray, defineCount)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle any fallthrough from this opcode. */
|
||||
if (!BytecodeNoFallThrough(op)) {
|
||||
JS_ASSERT(successorOffset < script->length);
|
||||
|
||||
Bytecode *&nextcode = code[successorOffset];
|
||||
bool initial = (nextcode == NULL);
|
||||
|
||||
if (initial) {
|
||||
nextcode = ArenaNew<Bytecode>(pool);
|
||||
if (!nextcode) {
|
||||
setOOM(cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextcode->mergeDefines(cx, this, initial, stackDepth, defineArray, defineCount))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
JS_ASSERT(!failed());
|
||||
JS_ASSERT(forwardJump == 0 && forwardCatch == 0);
|
||||
}
|
||||
|
||||
} /* namespace analyze */
|
||||
} /* namespace js */
|
213
js/src/jsanalyze.h
Normal file
213
js/src/jsanalyze.h
Normal file
@ -0,0 +1,213 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
|
||||
/* vim: set ts=40 sw=4 et tw=99: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is the Mozilla SpiderMonkey bytecode analysis
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/* Definitions for javascript analysis. */
|
||||
|
||||
#ifndef jsanalyze_h___
|
||||
#define jsanalyze_h___
|
||||
|
||||
#include "jsarena.h"
|
||||
#include "jscntxt.h"
|
||||
#include "jsscript.h"
|
||||
|
||||
struct JSScript;
|
||||
|
||||
namespace js {
|
||||
namespace analyze {
|
||||
|
||||
class Script;
|
||||
|
||||
/* Information about a bytecode instruction. */
|
||||
struct Bytecode
|
||||
{
|
||||
friend class Script;
|
||||
|
||||
/* Whether there are any incoming jumps to this instruction. */
|
||||
bool jumpTarget : 1;
|
||||
|
||||
/* Whether this instruction has been analyzed to get its output defines and stack. */
|
||||
bool analyzed : 1;
|
||||
|
||||
/* Whether this is a catch/finally entry point. */
|
||||
bool exceptionEntry : 1;
|
||||
|
||||
/* Whether this is in a try block. */
|
||||
bool inTryBlock : 1;
|
||||
|
||||
/* Whether this is a method JIT safe point. */
|
||||
bool safePoint : 1;
|
||||
|
||||
/* Stack depth before this opcode. */
|
||||
uint32 stackDepth;
|
||||
|
||||
/*
|
||||
* The set of locals defined at this point. This does not include locals which
|
||||
* were unconditionally defined at an earlier point in the script.
|
||||
*/
|
||||
uint32 defineCount;
|
||||
uint32 *defineArray;
|
||||
|
||||
Bytecode()
|
||||
{
|
||||
PodZero(this);
|
||||
}
|
||||
|
||||
private:
|
||||
bool mergeDefines(JSContext *cx,
|
||||
Script *script, bool initial, uint32 newDepth,
|
||||
uint32 *newArray, uint32 newCount);
|
||||
|
||||
/* Whether a local variable is in the define set at this bytecode. */
|
||||
bool isDefined(uint32 slot)
|
||||
{
|
||||
JS_ASSERT(analyzed);
|
||||
for (size_t ind = 0; ind < defineCount; ind++) {
|
||||
if (defineArray[ind] == slot)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/* Information about a script. */
|
||||
class Script
|
||||
{
|
||||
friend struct Bytecode;
|
||||
|
||||
JSScript *script;
|
||||
Bytecode **code;
|
||||
|
||||
/* Maximum number of locals to consider for a script. */
|
||||
static const unsigned LOCAL_LIMIT = 50;
|
||||
|
||||
/* Offsets at which each local becomes unconditionally defined, or a value below. */
|
||||
uint32 *locals;
|
||||
|
||||
static const uint32 LOCAL_USE_BEFORE_DEF = uint32(-1);
|
||||
static const uint32 LOCAL_CONDITIONALLY_DEFINED = uint32(-2);
|
||||
|
||||
bool outOfMemory;
|
||||
bool hadFailure;
|
||||
bool usesRval;
|
||||
bool usesScope;
|
||||
|
||||
public:
|
||||
/* Pool for allocating analysis structures which will not outlive this script. */
|
||||
JSArenaPool pool;
|
||||
|
||||
void analyze(JSContext *cx, JSScript *script);
|
||||
void destroy();
|
||||
|
||||
/*
|
||||
* For analysis scripts allocated on the stack. Scripts don't have constructors,
|
||||
* and must be zeroed out before being used.
|
||||
*/
|
||||
~Script() { destroy(); }
|
||||
|
||||
/* Whether we ran out of memory during analysis. */
|
||||
bool OOM() { return outOfMemory; }
|
||||
|
||||
/* Whether the script was analyzed successfully. */
|
||||
bool failed() { return hadFailure; }
|
||||
|
||||
/* Whether there are POPV/SETRVAL bytecodes which can write to the frame's rval. */
|
||||
bool usesReturnValue() const { return usesRval; }
|
||||
|
||||
/* Whether there are NAME bytecodes which can access the frame's scope chain. */
|
||||
bool usesScopeChain() const { return usesScope; }
|
||||
|
||||
/* Accessors for bytecode information. */
|
||||
|
||||
Bytecode& getCode(uint32 offset) {
|
||||
JS_ASSERT(offset < script->length);
|
||||
JS_ASSERT(code[offset]);
|
||||
return *code[offset];
|
||||
}
|
||||
Bytecode& getCode(jsbytecode *pc) { return getCode(pc - script->code); }
|
||||
|
||||
Bytecode* maybeCode(uint32 offset) {
|
||||
JS_ASSERT(offset < script->length);
|
||||
return code[offset];
|
||||
}
|
||||
Bytecode* maybeCode(jsbytecode *pc) { return maybeCode(pc - script->code); }
|
||||
|
||||
bool jumpTarget(uint32 offset) {
|
||||
JS_ASSERT(offset < script->length);
|
||||
return code[offset] && code[offset]->jumpTarget;
|
||||
}
|
||||
bool jumpTarget(jsbytecode *pc) { return jumpTarget(pc - script->code); }
|
||||
|
||||
/* Accessors for local variable information. */
|
||||
|
||||
unsigned localCount() {
|
||||
return (script->nfixed >= LOCAL_LIMIT) ? LOCAL_LIMIT : script->nfixed;
|
||||
}
|
||||
|
||||
bool localHasUseBeforeDef(uint32 local) {
|
||||
JS_ASSERT(local < script->nfixed && !failed());
|
||||
return local >= localCount() || locals[local] == LOCAL_USE_BEFORE_DEF;
|
||||
}
|
||||
|
||||
/* These return true for variables that may have a use before def. */
|
||||
bool localDefined(uint32 local, uint32 offset) {
|
||||
return localHasUseBeforeDef(local) || (locals[local] <= offset) ||
|
||||
getCode(offset).isDefined(local);
|
||||
}
|
||||
bool localDefined(uint32 local, jsbytecode *pc) {
|
||||
return localDefined(local, pc - script->code);
|
||||
}
|
||||
|
||||
private:
|
||||
void setOOM(JSContext *cx) {
|
||||
if (!outOfMemory)
|
||||
js_ReportOutOfMemory(cx);
|
||||
outOfMemory = true;
|
||||
hadFailure = true;
|
||||
}
|
||||
|
||||
inline bool addJump(JSContext *cx, unsigned offset,
|
||||
unsigned *currentOffset, unsigned *forwardJump,
|
||||
unsigned stackDepth, uint32 *defineArray, unsigned defineCount);
|
||||
|
||||
inline void setLocal(uint32 local, uint32 offset);
|
||||
};
|
||||
|
||||
} /* namespace analyze */
|
||||
} /* namespace js */
|
||||
|
||||
#endif // jsanalyze_h___
|
@ -1,326 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=4 sw=4 et tw=99:
|
||||
*
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released
|
||||
* May 28, 2008.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Brendan Eich <brendan@mozilla.org>
|
||||
*
|
||||
* Contributor(s):
|
||||
* David Anderson <danderson@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "BytecodeAnalyzer.h"
|
||||
#include "jsautooplen.h"
|
||||
#include "jsemit.h"
|
||||
#include "Retcon.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
BytecodeAnalyzer::~BytecodeAnalyzer()
|
||||
{
|
||||
cx->free(ops);
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeAnalyzer::addEdge(jsbytecode *pc, int32 offset, uint32 stackDepth)
|
||||
{
|
||||
uint32 idx = (uint32)((pc + offset) - script->code);
|
||||
|
||||
JS_ASSERT_IF(ops[idx].visited || ops[idx].nincoming,
|
||||
ops[idx].stackDepth == stackDepth);
|
||||
|
||||
if (!ops[idx].visited && !doList.append(pc + offset))
|
||||
return false;
|
||||
|
||||
ops[idx].stackDepth = stackDepth;
|
||||
ops[idx].nincoming++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeAnalyzer::analyze(uint32 index)
|
||||
{
|
||||
mjit::AutoScriptRetrapper trapper(cx, script);
|
||||
jsbytecode *pc = doList[index];
|
||||
uint32 stackDepth = ops[pc - script->code].stackDepth;
|
||||
|
||||
for (;;) {
|
||||
JSOp op = JSOp(pc[0]);
|
||||
OpcodeStatus &status = ops[pc - script->code];
|
||||
|
||||
if (status.visited)
|
||||
return true;
|
||||
|
||||
status.visited = true;
|
||||
status.stackDepth = stackDepth;
|
||||
|
||||
if (op == JSOP_TRAP) {
|
||||
status.trap = true;
|
||||
if (!trapper.untrap(pc))
|
||||
return false;
|
||||
op = JSOp(pc[0]);
|
||||
}
|
||||
|
||||
uint32 nuses, ndefs;
|
||||
if (js_CodeSpec[op].nuses == -1)
|
||||
nuses = js_GetVariableStackUses(op, pc);
|
||||
else
|
||||
nuses = js_CodeSpec[op].nuses;
|
||||
|
||||
if (js_CodeSpec[op].ndefs == -1)
|
||||
ndefs = js_GetEnterBlockStackDefs(cx, script, pc);
|
||||
else
|
||||
ndefs = js_CodeSpec[op].ndefs;
|
||||
|
||||
JS_ASSERT(nuses <= stackDepth);
|
||||
stackDepth -= nuses;
|
||||
stackDepth += ndefs;
|
||||
|
||||
uint32 offs;
|
||||
jsbytecode *newpc;
|
||||
switch (op) {
|
||||
case JSOP_TRAP:
|
||||
return false;
|
||||
|
||||
case JSOP_SETRVAL:
|
||||
case JSOP_POPV:
|
||||
usesRval = true;
|
||||
break;
|
||||
|
||||
case JSOP_NAME:
|
||||
case JSOP_CALLNAME:
|
||||
case JSOP_BINDNAME:
|
||||
case JSOP_SETNAME:
|
||||
case JSOP_DELNAME:
|
||||
case JSOP_INCNAME:
|
||||
case JSOP_DECNAME:
|
||||
case JSOP_NAMEINC:
|
||||
case JSOP_NAMEDEC:
|
||||
case JSOP_FORNAME:
|
||||
usesScope = true;
|
||||
break;
|
||||
|
||||
case JSOP_DEFAULT:
|
||||
case JSOP_GOTO:
|
||||
offs = (pc + JSOP_GOTO_LENGTH) - script->code;
|
||||
if (!ops[offs].visited && ops[offs].nincoming && !doList.append(pc + JSOP_GOTO_LENGTH))
|
||||
return false;
|
||||
pc += GET_JUMP_OFFSET(pc);
|
||||
ops[pc - script->code].nincoming++;
|
||||
continue;
|
||||
|
||||
case JSOP_DEFAULTX:
|
||||
case JSOP_GOTOX:
|
||||
offs = (pc + JSOP_GOTOX_LENGTH) - script->code;
|
||||
if (!ops[offs].visited && ops[offs].nincoming && !doList.append(pc + JSOP_GOTOX_LENGTH))
|
||||
return false;
|
||||
pc += GET_JUMPX_OFFSET(pc);
|
||||
ops[pc - script->code].nincoming++;
|
||||
continue;
|
||||
|
||||
case JSOP_IFEQ:
|
||||
case JSOP_IFNE:
|
||||
if (!addEdge(pc, GET_JUMP_OFFSET(pc), stackDepth))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_OR:
|
||||
case JSOP_AND:
|
||||
/* If the jump is taken, the condition is pushed. */
|
||||
if (!addEdge(pc, GET_JUMP_OFFSET(pc), stackDepth + 1))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_IFEQX:
|
||||
case JSOP_IFNEX:
|
||||
if (!addEdge(pc, GET_JUMPX_OFFSET(pc), stackDepth))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_ORX:
|
||||
case JSOP_ANDX:
|
||||
if (!addEdge(pc, GET_JUMPX_OFFSET(pc), stackDepth + 1))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_CASE:
|
||||
/* If the jump is taken, the extra value is not pushed. */
|
||||
if (!addEdge(pc, GET_JUMP_OFFSET(pc), stackDepth - 1))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_CASEX:
|
||||
/* If the jump is taken, the extra value is not pushed. */
|
||||
if (!addEdge(pc, GET_JUMPX_OFFSET(pc), stackDepth - 1))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_GOSUB:
|
||||
case JSOP_GOSUBX:
|
||||
case JSOP_IFPRIMTOP:
|
||||
case JSOP_FILTER:
|
||||
case JSOP_ENDFILTER:
|
||||
case JSOP_TABLESWITCHX:
|
||||
case JSOP_LOOKUPSWITCHX:
|
||||
return false;
|
||||
|
||||
case JSOP_TABLESWITCH:
|
||||
{
|
||||
jsint def = GET_JUMP_OFFSET(pc);
|
||||
if (!addEdge(pc, def, stackDepth))
|
||||
return false;
|
||||
|
||||
newpc = pc + JUMP_OFFSET_LEN;
|
||||
jsint low = GET_JUMP_OFFSET(newpc);
|
||||
newpc += JUMP_OFFSET_LEN;
|
||||
jsint high = GET_JUMP_OFFSET(newpc);
|
||||
newpc += JUMP_OFFSET_LEN;
|
||||
uint32 ncases = (uint32)(high - low + 1);
|
||||
|
||||
for (uint32 i = 0; i < ncases; i++) {
|
||||
jsint offs = GET_JUMP_OFFSET(newpc);
|
||||
newpc += JUMP_OFFSET_LEN;
|
||||
if (!offs)
|
||||
offs = def;
|
||||
if (!addEdge(pc, offs, stackDepth))
|
||||
return false;
|
||||
}
|
||||
pc = newpc + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_LOOKUPSWITCH:
|
||||
{
|
||||
if (!addEdge(pc, GET_JUMP_OFFSET(pc), stackDepth))
|
||||
return false;
|
||||
|
||||
newpc = pc + JUMP_OFFSET_LEN;
|
||||
uint32 npairs = GET_UINT16(newpc);
|
||||
newpc += UINT16_LEN;
|
||||
|
||||
JS_ASSERT(npairs);
|
||||
for (uint32 i = 0; i < npairs; i++) {
|
||||
newpc += INDEX_LEN ;
|
||||
if (!addEdge(pc, GET_JUMP_OFFSET(newpc), stackDepth))
|
||||
return false;
|
||||
newpc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
pc = newpc + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_RETRVAL:
|
||||
case JSOP_RETURN:
|
||||
{
|
||||
/*
|
||||
* If there is nothing incoming, just leave.
|
||||
* This is to defeat the emitter doing things like:
|
||||
* leaveblock 1
|
||||
* retrval
|
||||
* leaveblock 1
|
||||
* (see testNullCallee in jit-tests)
|
||||
*/
|
||||
JS_ASSERT(js_CodeSpec[op].length == 1);
|
||||
uint32 offs = (pc + 1) - script->code;
|
||||
if (ops[offs].visited || !ops[offs].nincoming)
|
||||
return true;
|
||||
|
||||
/* Otherwise, restore the stack depth and continue. */
|
||||
stackDepth = ops[offs].stackDepth;
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_THROW:
|
||||
/* Control flow stops here. */
|
||||
return true;
|
||||
|
||||
case JSOP_STOP:
|
||||
JS_ASSERT(uint32(pc - script->code) + 1 == script->length);
|
||||
return true;
|
||||
|
||||
default:
|
||||
#ifdef DEBUG
|
||||
uint32 type = JOF_TYPE(js_CodeSpec[op].format);
|
||||
JS_ASSERT(type != JOF_JUMP && type != JOF_JUMPX);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
if (js_CodeSpec[op].length != -1)
|
||||
pc += js_CodeSpec[op].length;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeAnalyzer::analyze()
|
||||
{
|
||||
ops = (OpcodeStatus *)cx->malloc(sizeof(OpcodeStatus) * script->length);
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(OpcodeStatus) * script->length);
|
||||
|
||||
if (!doList.append(script->code))
|
||||
return false;
|
||||
|
||||
if (script->trynotesOffset) {
|
||||
JSTryNoteArray *tnarray = script->trynotes();
|
||||
for (unsigned i = 0; i < tnarray->length; ++i) {
|
||||
JSTryNote &tn = tnarray->vector[i];
|
||||
unsigned pcstart = script->main + tn.start - script->code;
|
||||
unsigned pcoff = pcstart + tn.length;
|
||||
|
||||
for (unsigned j = pcstart; j < pcoff; j++)
|
||||
ops[j].inTryBlock = true;
|
||||
|
||||
if (tn.kind == JSTRY_ITER)
|
||||
continue;
|
||||
|
||||
ops[pcoff].exceptionEntry = true;
|
||||
ops[pcoff].nincoming = 1;
|
||||
ops[pcoff].stackDepth = tn.stackDepth;
|
||||
if (!doList.append(script->code + pcoff))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < doList.length(); i++) {
|
||||
if (ops[doList[i] - script->code].visited)
|
||||
continue;
|
||||
if (!analyze(i))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,118 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=4 sw=4 et tw=99:
|
||||
*
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released
|
||||
* May 28, 2008.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Brendan Eich <brendan@mozilla.org>
|
||||
*
|
||||
* Contributor(s):
|
||||
* David Anderson <danderson@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#if !defined jsjaeger_bytecodeAnalyzer_h__ && defined JS_METHODJIT
|
||||
#define jsjaeger_bytecodeAnalyzer_h__
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jscntxt.h"
|
||||
#include "jsscript.h"
|
||||
#include "jsopcode.h"
|
||||
|
||||
namespace js
|
||||
{
|
||||
struct OpcodeStatus
|
||||
{
|
||||
bool visited; /* flag for CFG traversal */
|
||||
bool exceptionEntry; /* true iff this is a catch/finally entry point */
|
||||
bool safePoint; /* false by default */
|
||||
bool trap; /* It's a trap! */
|
||||
bool inTryBlock; /* true if in try block */
|
||||
uint32 nincoming; /* number of CFG inedges here */
|
||||
uint32 stackDepth; /* stack depth before this opcode */
|
||||
};
|
||||
|
||||
class BytecodeAnalyzer
|
||||
{
|
||||
JSContext *cx;
|
||||
JSScript *script;
|
||||
OpcodeStatus *ops;
|
||||
Vector<jsbytecode *, 16, ContextAllocPolicy> doList;
|
||||
|
||||
/* Whether there are POPV/SETRVAL bytecodes which can write to the frame's rval. */
|
||||
bool usesRval;
|
||||
|
||||
/* Whether there are NAME bytecodes which can access the frame's scope chain. */
|
||||
bool usesScope;
|
||||
|
||||
public:
|
||||
BytecodeAnalyzer(JSContext *cx, JSScript *script)
|
||||
: cx(cx), script(script), ops(NULL),
|
||||
doList(ContextAllocPolicy(cx)),
|
||||
usesRval(false), usesScope(false)
|
||||
{
|
||||
}
|
||||
~BytecodeAnalyzer();
|
||||
|
||||
bool analyze(uint32 offs);
|
||||
bool addEdge(jsbytecode *pc, int32 offset, uint32 stackDepth);
|
||||
|
||||
public:
|
||||
|
||||
bool usesReturnValue() const { return usesRval; }
|
||||
bool usesScopeChain() const { return usesScope; }
|
||||
|
||||
inline const OpcodeStatus & operator [](uint32 offs) const {
|
||||
JS_ASSERT(offs < script->length);
|
||||
return ops[offs];
|
||||
}
|
||||
|
||||
inline OpcodeStatus & operator [](uint32 offs) {
|
||||
JS_ASSERT(offs < script->length);
|
||||
return ops[offs];
|
||||
}
|
||||
|
||||
inline const OpcodeStatus & operator [](jsbytecode *pc) const {
|
||||
JS_ASSERT(pc < script->code + script->length);
|
||||
return ops[pc - script->code];
|
||||
}
|
||||
|
||||
inline OpcodeStatus & operator [](jsbytecode *pc) {
|
||||
JS_ASSERT(pc < script->code + script->length);
|
||||
return ops[pc - script->code];
|
||||
}
|
||||
|
||||
inline bool OOM() { return !ops; }
|
||||
|
||||
bool analyze();
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* jsjaeger_bytecodeAnalyzer_h__ */
|
||||
|
@ -93,7 +93,7 @@ mjit::Compiler::Compiler(JSContext *cx, JSStackFrame *fp)
|
||||
? fp->fun()
|
||||
: NULL),
|
||||
isConstructing(fp->isConstructing()),
|
||||
analysis(cx, script), jumpMap(NULL), frame(cx, script, masm),
|
||||
analysis(NULL), jumpMap(NULL), frame(cx, script, masm),
|
||||
branchPatches(ContextAllocPolicy(cx)),
|
||||
#if defined JS_MONOIC
|
||||
mics(ContextAllocPolicy(cx)),
|
||||
@ -156,14 +156,20 @@ mjit::Compiler::performCompilation(JITScript **jitp)
|
||||
JaegerSpew(JSpew_Scripts, "compiling script (file \"%s\") (line \"%d\") (length \"%d\")\n",
|
||||
script->filename, script->lineno, script->length);
|
||||
|
||||
/* Perform bytecode analysis. */
|
||||
if (!analysis.analyze()) {
|
||||
if (analysis.OOM())
|
||||
return Compile_Error;
|
||||
analyze::Script analysis;
|
||||
PodZero(&analysis);
|
||||
|
||||
analysis.analyze(cx, script);
|
||||
|
||||
if (analysis.OOM())
|
||||
return Compile_Error;
|
||||
if (analysis.failed()) {
|
||||
JaegerSpew(JSpew_Abort, "couldn't analyze bytecode; probably switchX or OOM\n");
|
||||
return Compile_Abort;
|
||||
}
|
||||
|
||||
this->analysis = &analysis;
|
||||
|
||||
uint32 nargs = fun ? fun->nargs : 0;
|
||||
if (!frame.init(nargs) || !stubcc.init(nargs))
|
||||
return Compile_Abort;
|
||||
@ -291,10 +297,16 @@ mjit::Compiler::generatePrologue()
|
||||
stubcc.crossJump(stubcc.masm.jump(), masm.label());
|
||||
}
|
||||
|
||||
/* Set locals to undefined, as in initCallFrameLatePrologue */
|
||||
/*
|
||||
* Set locals to undefined, as in initCallFrameLatePrologue.
|
||||
* Skip locals which aren't closed and are known to be defined before used,
|
||||
* :FIXME: bug 604541: write undefined if we might be using the tracer, so it works.
|
||||
*/
|
||||
for (uint32 i = 0; i < script->nfixed; i++) {
|
||||
Address local(JSFrameReg, sizeof(JSStackFrame) + i * sizeof(Value));
|
||||
masm.storeValue(UndefinedValue(), local);
|
||||
if (analysis->localHasUseBeforeDef(i) || addTraceHints) {
|
||||
Address local(JSFrameReg, sizeof(JSStackFrame) + i * sizeof(Value));
|
||||
masm.storeValue(UndefinedValue(), local);
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the call object. */
|
||||
@ -305,7 +317,7 @@ mjit::Compiler::generatePrologue()
|
||||
|
||||
j.linkTo(masm.label(), &masm);
|
||||
|
||||
if (analysis.usesScopeChain() && !fun->isHeavyweight()) {
|
||||
if (analysis->usesScopeChain() && !fun->isHeavyweight()) {
|
||||
/*
|
||||
* Load the scope chain into the frame if necessary. The scope chain
|
||||
* is always set for global and eval frames, and will have been set by
|
||||
@ -400,7 +412,8 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
|
||||
|
||||
for (size_t i = 0; i < script->length; i++) {
|
||||
Label L = jumpMap[i];
|
||||
if (analysis[i].safePoint) {
|
||||
analyze::Bytecode *opinfo = analysis->maybeCode(i);
|
||||
if (opinfo && opinfo->safePoint) {
|
||||
JS_ASSERT(L.isValid());
|
||||
nmap[i] = (uint8 *)(result + masm.distanceOf(L));
|
||||
}
|
||||
@ -693,22 +706,17 @@ mjit::Compiler::generateMethod()
|
||||
|
||||
for (;;) {
|
||||
JSOp op = JSOp(*PC);
|
||||
bool trap = (op == JSOP_TRAP);
|
||||
|
||||
OpcodeStatus &opinfo = analysis[PC];
|
||||
frame.setInTryBlock(opinfo.inTryBlock);
|
||||
if (opinfo.nincoming || opinfo.trap) {
|
||||
frame.syncAndForgetEverything(opinfo.stackDepth);
|
||||
opinfo.safePoint = true;
|
||||
}
|
||||
jumpMap[uint32(PC - script->code)] = masm.label();
|
||||
|
||||
if (opinfo.trap) {
|
||||
if (trap) {
|
||||
if (!trapper.untrap(PC))
|
||||
return Compile_Error;
|
||||
op = JSOp(*PC);
|
||||
}
|
||||
|
||||
if (!opinfo.visited) {
|
||||
analyze::Bytecode *opinfo = analysis->maybeCode(PC);
|
||||
|
||||
if (!opinfo) {
|
||||
if (op == JSOP_STOP)
|
||||
break;
|
||||
if (js_CodeSpec[op].length != -1)
|
||||
@ -718,10 +726,17 @@ mjit::Compiler::generateMethod()
|
||||
continue;
|
||||
}
|
||||
|
||||
SPEW_OPCODE();
|
||||
JS_ASSERT(frame.stackDepth() == opinfo.stackDepth);
|
||||
frame.setInTryBlock(opinfo->inTryBlock);
|
||||
if (opinfo->jumpTarget || trap) {
|
||||
frame.syncAndForgetEverything(opinfo->stackDepth);
|
||||
opinfo->safePoint = true;
|
||||
}
|
||||
jumpMap[uint32(PC - script->code)] = masm.label();
|
||||
|
||||
if (opinfo.trap) {
|
||||
SPEW_OPCODE();
|
||||
JS_ASSERT(frame.stackDepth() == opinfo->stackDepth);
|
||||
|
||||
if (trap) {
|
||||
prepareStubCall(Uses(0));
|
||||
masm.move(ImmPtr(PC), Registers::ArgReg1);
|
||||
stubCall(stubs::Trap);
|
||||
@ -832,7 +847,7 @@ mjit::Compiler::generateMethod()
|
||||
/* Detect fusions. */
|
||||
jsbytecode *next = &PC[JSOP_GE_LENGTH];
|
||||
JSOp fused = JSOp(*next);
|
||||
if ((fused != JSOP_IFEQ && fused != JSOP_IFNE) || analysis[next].nincoming)
|
||||
if ((fused != JSOP_IFEQ && fused != JSOP_IFNE) || analysis->jumpTarget(next))
|
||||
fused = JSOP_NOP;
|
||||
|
||||
/* Get jump target, if any. */
|
||||
@ -1323,7 +1338,7 @@ mjit::Compiler::generateMethod()
|
||||
BEGIN_CASE(JSOP_SETLOCAL)
|
||||
{
|
||||
jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH];
|
||||
bool pop = JSOp(*next) == JSOP_POP && !analysis[next].nincoming;
|
||||
bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next);
|
||||
frame.storeLocal(GET_SLOTNO(PC), pop);
|
||||
if (pop) {
|
||||
frame.pop();
|
||||
@ -1390,7 +1405,7 @@ mjit::Compiler::generateMethod()
|
||||
{
|
||||
jsbytecode *next = &PC[JSOP_ARGINC_LENGTH];
|
||||
bool popped = false;
|
||||
if (JSOp(*next) == JSOP_POP && !analysis[next].nincoming)
|
||||
if (JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next))
|
||||
popped = true;
|
||||
jsop_arginc(op, GET_SLOTNO(PC), popped);
|
||||
PC += JSOP_ARGINC_LENGTH;
|
||||
@ -1407,7 +1422,7 @@ mjit::Compiler::generateMethod()
|
||||
{
|
||||
jsbytecode *next = &PC[JSOP_LOCALINC_LENGTH];
|
||||
bool popped = false;
|
||||
if (JSOp(*next) == JSOP_POP && !analysis[next].nincoming)
|
||||
if (JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next))
|
||||
popped = true;
|
||||
/* These manually advance the PC. */
|
||||
jsop_localinc(op, GET_SLOTNO(PC), popped);
|
||||
@ -1790,7 +1805,7 @@ mjit::Compiler::generateMethod()
|
||||
BEGIN_CASE(JSOP_TRACE)
|
||||
BEGIN_CASE(JSOP_NOTRACE)
|
||||
{
|
||||
if (analysis[PC].nincoming > 0)
|
||||
if (analysis->jumpTarget(PC))
|
||||
interruptCheckHelper();
|
||||
}
|
||||
END_CASE(JSOP_TRACE)
|
||||
@ -2008,7 +2023,7 @@ mjit::Compiler::loadReturnValue(Assembler *masm, FrameEntry *fe)
|
||||
// Load a return value from POPV or SETRVAL into the return registers,
|
||||
// otherwise return undefined.
|
||||
masm->loadValueAsComponents(UndefinedValue(), typeReg, dataReg);
|
||||
if (analysis.usesReturnValue()) {
|
||||
if (analysis->usesReturnValue()) {
|
||||
Jump rvalClear = masm->branchTest32(Assembler::Zero,
|
||||
FrameFlagsAddress(),
|
||||
Imm32(JSFRAME_HAS_RVAL));
|
||||
@ -3343,7 +3358,7 @@ mjit::Compiler::jsop_bindname(uint32 index, bool usePropCache)
|
||||
// set. Rather, it relies on the up-front analysis statically determining
|
||||
// whether BINDNAME can be used, which reifies the scope chain at the
|
||||
// prologue.
|
||||
JS_ASSERT(analysis.usesScopeChain());
|
||||
JS_ASSERT(analysis->usesScopeChain());
|
||||
|
||||
pic.shapeReg = frame.allocReg();
|
||||
pic.objReg = frame.allocReg();
|
||||
@ -3489,7 +3504,7 @@ mjit::Compiler::jsop_gnameinc(JSOp op, VoidStubAtom stub, uint32 index)
|
||||
{
|
||||
#if defined JS_MONOIC
|
||||
jsbytecode *next = &PC[JSOP_GNAMEINC_LENGTH];
|
||||
bool pop = (JSOp(*next) == JSOP_POP) && !analysis[next].nincoming;
|
||||
bool pop = (JSOp(*next) == JSOP_POP) && !analysis->jumpTarget(next);
|
||||
int amt = (op == JSOP_GNAMEINC || op == JSOP_INCGNAME) ? -1 : 1;
|
||||
|
||||
if (pop || (op == JSOP_INCGNAME || op == JSOP_DECGNAME)) {
|
||||
@ -3578,7 +3593,7 @@ mjit::Compiler::jsop_nameinc(JSOp op, VoidStubAtom stub, uint32 index)
|
||||
JSAtom *atom = script->getAtom(index);
|
||||
#if defined JS_POLYIC
|
||||
jsbytecode *next = &PC[JSOP_NAMEINC_LENGTH];
|
||||
bool pop = (JSOp(*next) == JSOP_POP) && !analysis[next].nincoming;
|
||||
bool pop = (JSOp(*next) == JSOP_POP) && !analysis->jumpTarget(next);
|
||||
int amt = (op == JSOP_NAMEINC || op == JSOP_INCNAME) ? -1 : 1;
|
||||
|
||||
if (pop || (op == JSOP_INCNAME || op == JSOP_DECNAME)) {
|
||||
@ -3671,7 +3686,7 @@ mjit::Compiler::jsop_propinc(JSOp op, VoidStubAtom stub, uint32 index)
|
||||
FrameEntry *objFe = frame.peek(-1);
|
||||
if (!objFe->isTypeKnown() || objFe->getKnownType() == JSVAL_TYPE_OBJECT) {
|
||||
jsbytecode *next = &PC[JSOP_PROPINC_LENGTH];
|
||||
bool pop = (JSOp(*next) == JSOP_POP) && !analysis[next].nincoming;
|
||||
bool pop = (JSOp(*next) == JSOP_POP) && !analysis->jumpTarget(next);
|
||||
int amt = (op == JSOP_PROPINC || op == JSOP_INCPROP) ? -1 : 1;
|
||||
|
||||
if (pop || (op == JSOP_INCPROP || op == JSOP_DECPROP)) {
|
||||
@ -4474,7 +4489,7 @@ mjit::Compiler::enterBlock(JSObject *obj)
|
||||
// VMFrame::fp to the correct fp for the entry point. We need to copy
|
||||
// that value here to FpReg so that FpReg also has the correct sp.
|
||||
// Otherwise, we would simply be using a stale FpReg value.
|
||||
if (analysis[PC].exceptionEntry)
|
||||
if (analysis->getCode(PC).exceptionEntry)
|
||||
restoreFrameRegs(masm);
|
||||
|
||||
uint32 oldFrameDepth = frame.frameDepth();
|
||||
|
@ -40,9 +40,9 @@
|
||||
#if !defined jsjaeger_compiler_h__ && defined JS_METHODJIT
|
||||
#define jsjaeger_compiler_h__
|
||||
|
||||
#include "jsanalyze.h"
|
||||
#include "jscntxt.h"
|
||||
#include "jstl.h"
|
||||
#include "BytecodeAnalyzer.h"
|
||||
#include "MethodJIT.h"
|
||||
#include "CodeGenIncludes.h"
|
||||
#include "BaseCompiler.h"
|
||||
@ -236,7 +236,7 @@ class Compiler : public BaseCompiler
|
||||
JSObject *globalObj;
|
||||
JSFunction *fun;
|
||||
bool isConstructing;
|
||||
BytecodeAnalyzer analysis;
|
||||
analyze::Script *analysis;
|
||||
Label *jumpMap;
|
||||
jsbytecode *PC;
|
||||
Assembler masm;
|
||||
|
@ -582,7 +582,7 @@ mjit::Compiler::jsop_globalinc(JSOp op, uint32 index)
|
||||
|
||||
bool popped = false;
|
||||
PC += JSOP_GLOBALINC_LENGTH;
|
||||
if (JSOp(*PC) == JSOP_POP && !analysis[PC].nincoming) {
|
||||
if (JSOp(*PC) == JSOP_POP && !analysis->jumpTarget(PC)) {
|
||||
popped = true;
|
||||
PC += JSOP_POP_LENGTH;
|
||||
}
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include "jstracer.h"
|
||||
#include "jspropertycache.h"
|
||||
#include "methodjit/MonoIC.h"
|
||||
#include "jsanalyze.h"
|
||||
|
||||
#include "jsinterpinlines.h"
|
||||
#include "jspropertycacheinlines.h"
|
||||
|
Loading…
Reference in New Issue
Block a user