2010-10-26 12:00:54 -07:00
|
|
|
/* -*- 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"
|
|
|
|
|
2011-04-05 18:12:03 -07:00
|
|
|
#include "jsinferinlines.h"
|
|
|
|
#include "jsobjinlines.h"
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
void
|
|
|
|
JSScript::makeAnalysis(JSContext *cx)
|
2010-12-28 11:53:50 -08:00
|
|
|
{
|
2011-04-22 07:59:45 -07:00
|
|
|
JS_ASSERT(!analysis_);
|
|
|
|
analysis_ = cx->new_<js::analyze::ScriptAnalysis>(this);
|
2010-12-28 11:53:50 -08:00
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
namespace js {
|
|
|
|
namespace analyze {
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
// Bytecode
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool
|
2011-04-22 07:59:45 -07:00
|
|
|
Bytecode::mergeDefines(JSContext *cx, ScriptAnalysis *script, bool initial,
|
2010-12-28 11:53:50 -08:00
|
|
|
unsigned newDepth, uint32 *newArray, unsigned newCount)
|
2010-10-26 12:00:54 -07:00
|
|
|
{
|
|
|
|
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) {
|
2011-04-22 07:59:45 -07:00
|
|
|
uint32 *reallocArray =
|
|
|
|
ArenaArray<uint32>(cx->compartment->pool, defineCount);
|
2010-10-26 12:00:54 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
#ifdef DEBUG
|
|
|
|
void
|
|
|
|
PrintBytecode(JSContext *cx, JSScript *script, jsbytecode *pc)
|
|
|
|
{
|
|
|
|
printf("#%u:", script->id());
|
|
|
|
void *mark = JS_ARENA_MARK(&cx->tempPool);
|
|
|
|
Sprinter sprinter;
|
|
|
|
INIT_SPRINTER(cx, &sprinter, &cx->tempPool, 0);
|
|
|
|
js_Disassemble1(cx, script, pc, pc - script->code, true, &sprinter);
|
|
|
|
fprintf(stdout, "%s", sprinter.base);
|
|
|
|
JS_ARENA_RELEASE(&cx->tempPool, mark);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
/////////////////////////////////////////////////////////////////////
|
2011-04-22 07:59:45 -07:00
|
|
|
// Bytecode Analysis
|
2010-10-26 12:00:54 -07:00
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
inline bool
|
2011-04-22 07:59:45 -07:00
|
|
|
ScriptAnalysis::addJump(JSContext *cx, unsigned offset,
|
|
|
|
unsigned *currentOffset, unsigned *forwardJump,
|
|
|
|
unsigned stackDepth, uint32 *defineArray, unsigned defineCount)
|
2010-10-26 12:00:54 -07:00
|
|
|
{
|
|
|
|
JS_ASSERT(offset < script->length);
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
Bytecode *&code = codeArray[offset];
|
|
|
|
bool initial = (code == NULL);
|
2010-10-26 12:00:54 -07:00
|
|
|
if (initial) {
|
2011-04-22 07:59:45 -07:00
|
|
|
code = ArenaNew<Bytecode>(cx->compartment->pool);
|
2010-10-29 08:05:55 -07:00
|
|
|
if (!code) {
|
2010-10-26 12:00:54 -07:00
|
|
|
setOOM(cx);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-28 11:53:50 -08:00
|
|
|
if (!code->mergeDefines(cx, this, initial, stackDepth, defineArray, defineCount))
|
2010-10-26 12:00:54 -07:00
|
|
|
return false;
|
2010-10-29 08:05:55 -07:00
|
|
|
code->jumpTarget = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
if (offset < *currentOffset) {
|
2011-04-22 07:59:45 -07:00
|
|
|
JSOp op = JSOp(script->code[offset]);
|
|
|
|
if (op == JSOP_TRACE || op == JSOP_NOTRACE)
|
|
|
|
code->loopHead = true;
|
|
|
|
|
2011-03-26 19:07:13 -07:00
|
|
|
/* Scripts containing loops are never inlined. */
|
|
|
|
isInlineable = false;
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
/* Don't follow back edges to bytecode which has already been analyzed. */
|
2010-10-29 08:05:55 -07:00
|
|
|
if (!code->analyzed) {
|
2010-10-26 12:00:54 -07:00
|
|
|
if (*forwardJump == 0)
|
|
|
|
*forwardJump = *currentOffset;
|
|
|
|
*currentOffset = offset;
|
|
|
|
}
|
|
|
|
} else if (offset > *forwardJump) {
|
|
|
|
*forwardJump = offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void
|
2011-04-22 07:59:45 -07:00
|
|
|
ScriptAnalysis::setLocal(uint32 local, uint32 offset)
|
2010-10-26 12:00:54 -07:00
|
|
|
{
|
2011-04-22 07:59:45 -07:00
|
|
|
JS_ASSERT(local < script->nfixed);
|
2010-10-26 12:00:54 -07:00
|
|
|
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.
|
|
|
|
*/
|
2011-04-22 07:59:45 -07:00
|
|
|
JS_ASSERT(definedLocals[local] == LOCAL_CONDITIONALLY_DEFINED ||
|
|
|
|
definedLocals[local] == offset || offset == LOCAL_USE_BEFORE_DEF);
|
2010-10-26 12:00:54 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
definedLocals[local] = offset;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
void
|
2011-04-22 07:59:45 -07:00
|
|
|
ScriptAnalysis::analyzeBytecode(JSContext *cx)
|
2010-10-29 08:05:55 -07:00
|
|
|
{
|
2011-04-22 07:59:45 -07:00
|
|
|
JS_ASSERT(cx->compartment->activeAnalysis);
|
|
|
|
JS_ASSERT(!ranBytecode());
|
|
|
|
JSArenaPool &pool = cx->compartment->pool;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
unsigned length = script->length;
|
2011-03-20 08:23:27 -07:00
|
|
|
unsigned nargs = script->fun ? script->fun->nargs : 0;
|
2011-04-22 07:59:45 -07:00
|
|
|
|
|
|
|
numSlots = TotalSlots(script);
|
2010-10-26 12:00:54 -07:00
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
codeArray = ArenaArray<Bytecode*>(pool, length);
|
2011-04-22 07:59:45 -07:00
|
|
|
definedLocals = ArenaArray<uint32>(pool, script->nfixed);
|
|
|
|
escapedSlots = ArenaArray<JSPackedBool>(pool, numSlots);
|
2010-10-26 12:00:54 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
if (!codeArray || !definedLocals || !escapedSlots) {
|
2010-10-26 12:00:54 -07:00
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
PodZero(codeArray, length);
|
2010-10-26 12:00:54 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
for (unsigned i = 0; i < script->nfixed; i++)
|
|
|
|
definedLocals[i] = LOCAL_CONDITIONALLY_DEFINED;
|
2011-03-20 08:23:27 -07:00
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
/*
|
2011-04-22 07:59:45 -07:00
|
|
|
* Populate arg and local slots which can escape and be accessed in ways
|
|
|
|
* other than through ARG* and LOCAL* opcodes (though arguments can still
|
|
|
|
* be indirectly read but not written through 'arguments' properties).
|
|
|
|
* All escaping locals are treated as having possible use-before-defs.
|
2010-10-26 12:00:54 -07:00
|
|
|
*/
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
PodZero(escapedSlots, numSlots);
|
|
|
|
|
|
|
|
if (script->usesEval || script->usesArguments || script->compartment->debugMode) {
|
|
|
|
for (unsigned i = 0; i < nargs; i++)
|
|
|
|
escapedSlots[ArgSlot(i)] = true;
|
|
|
|
} else {
|
|
|
|
for (unsigned i = 0; i < script->nClosedArgs; i++) {
|
|
|
|
unsigned arg = script->getClosedArg(i);
|
|
|
|
JS_ASSERT(arg < nargs);
|
|
|
|
escapedSlots[ArgSlot(arg)] = true;
|
|
|
|
}
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
if (script->usesEval || script->compartment->debugMode) {
|
|
|
|
for (unsigned i = 0; i < script->nfixed; i++) {
|
|
|
|
escapedSlots[LocalSlot(script, i)] = true;
|
|
|
|
setLocal(i, LOCAL_USE_BEFORE_DEF);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (uint32 i = 0; i < script->nClosedVars; i++) {
|
|
|
|
unsigned local = script->getClosedVar(i);
|
|
|
|
escapedSlots[LocalSlot(script, local)] = true;
|
|
|
|
setLocal(local, LOCAL_USE_BEFORE_DEF);
|
|
|
|
}
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
/* Maximum number of locals we will keep track of in defined variables analysis. */
|
|
|
|
static const uint32 LOCAL_LIMIT = 50;
|
|
|
|
for (unsigned i = LOCAL_LIMIT; i < script->nfixed; i++)
|
|
|
|
setLocal(i, LOCAL_USE_BEFORE_DEF);
|
|
|
|
|
2010-11-08 09:28:12 -08:00
|
|
|
/*
|
|
|
|
* If the script is in debug mode, JS_SetFrameReturnValue can be called at
|
|
|
|
* any safe point.
|
|
|
|
*/
|
|
|
|
if (cx->compartment->debugMode)
|
|
|
|
usesRval = true;
|
|
|
|
|
2011-03-26 19:07:13 -07:00
|
|
|
isInlineable = true;
|
2011-04-08 17:57:58 -07:00
|
|
|
if (script->nClosedArgs || script->nClosedVars || script->nfixed >= LOCAL_LIMIT ||
|
2011-03-29 06:39:09 -07:00
|
|
|
(script->fun && script->fun->isHeavyweight()) ||
|
2011-03-26 19:07:13 -07:00
|
|
|
script->usesEval || script->usesArguments || cx->compartment->debugMode) {
|
|
|
|
isInlineable = false;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
canTrackVars = true;
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
/*
|
|
|
|
* 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. */
|
2011-03-03 14:07:48 -08:00
|
|
|
Bytecode *startcode = ArenaNew<Bytecode>(pool);
|
2010-10-26 12:00:54 -07:00
|
|
|
if (!startcode) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
startcode->stackDepth = 0;
|
2010-10-29 08:05:55 -07:00
|
|
|
codeArray[0] = startcode;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
Bytecode *code = maybeCode(offset);
|
2010-10-26 12:00:54 -07:00
|
|
|
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;
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
if (!code) {
|
2010-10-26 12:00:54 -07:00
|
|
|
/* Haven't found a path by which this bytecode is reachable. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
if (code->analyzed) {
|
2010-10-26 12:00:54 -07:00
|
|
|
/* No need to reanalyze, see Bytecode::mergeDefines. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
code->analyzed = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
if (forwardCatch)
|
2010-10-29 08:05:55 -07:00
|
|
|
code->inTryBlock = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
2011-03-26 19:07:13 -07:00
|
|
|
if (untrap.trap) {
|
2010-12-20 09:06:43 -08:00
|
|
|
code->safePoint = true;
|
2011-04-22 07:59:45 -07:00
|
|
|
isInlineable = canTrackVars = false;
|
2011-03-26 19:07:13 -07:00
|
|
|
}
|
2010-12-20 09:06:43 -08:00
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
unsigned stackDepth = code->stackDepth;
|
|
|
|
uint32 *defineArray = code->defineArray;
|
|
|
|
unsigned defineCount = code->defineCount;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
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];
|
2011-04-22 07:59:45 -07:00
|
|
|
JS_ASSERT_IF(definedLocals[local] != LOCAL_CONDITIONALLY_DEFINED &&
|
|
|
|
definedLocals[local] != LOCAL_USE_BEFORE_DEF,
|
|
|
|
definedLocals[local] <= offset);
|
|
|
|
if (definedLocals[local] == LOCAL_CONDITIONALLY_DEFINED)
|
2010-10-26 12:00:54 -07:00
|
|
|
setLocal(local, offset);
|
|
|
|
}
|
2011-03-09 09:58:49 -08:00
|
|
|
defineArray = code->defineArray = NULL;
|
|
|
|
defineCount = code->defineCount = 0;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
unsigned nuses = GetUseCount(script, offset);
|
|
|
|
unsigned ndefs = GetDefCount(script, offset);
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
JS_ASSERT(stackDepth >= nuses);
|
|
|
|
stackDepth -= nuses;
|
|
|
|
stackDepth += ndefs;
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
|
2011-05-05 13:59:29 -07:00
|
|
|
case JSOP_RETURN:
|
|
|
|
case JSOP_STOP:
|
|
|
|
numReturnSites_++;
|
|
|
|
break;
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
case JSOP_SETRVAL:
|
|
|
|
case JSOP_POPV:
|
|
|
|
usesRval = true;
|
2011-03-26 19:07:13 -07:00
|
|
|
isInlineable = false;
|
2010-10-26 12:00:54 -07:00
|
|
|
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;
|
2011-03-26 19:07:13 -07:00
|
|
|
isInlineable = false;
|
|
|
|
break;
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
case JSOP_DEFFUN:
|
|
|
|
case JSOP_DEFVAR:
|
|
|
|
case JSOP_DEFCONST:
|
|
|
|
case JSOP_SETCONST:
|
2011-04-24 09:26:37 -07:00
|
|
|
case JSOP_ENTERWITH:
|
2011-04-22 07:59:45 -07:00
|
|
|
isInlineable = canTrackVars = false;
|
|
|
|
break;
|
|
|
|
|
2011-03-26 19:07:13 -07:00
|
|
|
case JSOP_THIS:
|
|
|
|
usesThis = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
break;
|
|
|
|
|
2011-03-27 07:48:03 -07:00
|
|
|
case JSOP_CALL:
|
|
|
|
case JSOP_NEW:
|
|
|
|
/* Only consider potentially inlineable calls here. */
|
|
|
|
hasCalls = true;
|
|
|
|
break;
|
|
|
|
|
2010-11-24 17:41:52 -08:00
|
|
|
case JSOP_TABLESWITCH:
|
|
|
|
case JSOP_TABLESWITCHX: {
|
2011-04-22 07:59:45 -07:00
|
|
|
isInlineable = canTrackVars = false;
|
2010-10-26 12:00:54 -07:00
|
|
|
jsbytecode *pc2 = pc;
|
2010-11-24 17:41:52 -08:00
|
|
|
unsigned jmplen = (op == JSOP_TABLESWITCH) ? JUMP_OFFSET_LEN : JUMPX_OFFSET_LEN;
|
2010-10-26 12:00:54 -07:00
|
|
|
unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
|
2010-11-24 17:41:52 -08:00
|
|
|
pc2 += jmplen;
|
2010-10-26 12:00:54 -07:00
|
|
|
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,
|
2010-12-28 11:53:50 -08:00
|
|
|
stackDepth, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
2010-12-06 11:53:53 -08:00
|
|
|
getCode(defaultOffset).switchTarget = true;
|
2010-12-20 09:06:43 -08:00
|
|
|
getCode(defaultOffset).safePoint = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
for (jsint i = low; i <= high; i++) {
|
|
|
|
unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
if (targetOffset != offset) {
|
|
|
|
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
2010-12-28 11:53:50 -08:00
|
|
|
stackDepth, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2010-12-06 11:53:53 -08:00
|
|
|
getCode(targetOffset).switchTarget = true;
|
2010-12-20 09:06:43 -08:00
|
|
|
getCode(targetOffset).safePoint = true;
|
2010-11-24 17:41:52 -08:00
|
|
|
pc2 += jmplen;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-11-24 17:41:52 -08:00
|
|
|
case JSOP_LOOKUPSWITCH:
|
|
|
|
case JSOP_LOOKUPSWITCHX: {
|
2011-04-22 07:59:45 -07:00
|
|
|
isInlineable = canTrackVars = false;
|
2010-10-26 12:00:54 -07:00
|
|
|
jsbytecode *pc2 = pc;
|
2010-11-24 17:41:52 -08:00
|
|
|
unsigned jmplen = (op == JSOP_LOOKUPSWITCH) ? JUMP_OFFSET_LEN : JUMPX_OFFSET_LEN;
|
2010-10-26 12:00:54 -07:00
|
|
|
unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
|
2010-11-24 17:41:52 -08:00
|
|
|
pc2 += jmplen;
|
2010-10-26 12:00:54 -07:00
|
|
|
unsigned npairs = GET_UINT16(pc2);
|
|
|
|
pc2 += UINT16_LEN;
|
|
|
|
|
|
|
|
if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump,
|
2010-12-28 11:53:50 -08:00
|
|
|
stackDepth, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
2010-12-06 11:53:53 -08:00
|
|
|
getCode(defaultOffset).switchTarget = true;
|
2010-12-20 09:06:43 -08:00
|
|
|
getCode(defaultOffset).safePoint = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
while (npairs) {
|
|
|
|
pc2 += INDEX_LEN;
|
|
|
|
unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
2010-12-28 11:53:50 -08:00
|
|
|
stackDepth, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
2010-12-06 11:53:53 -08:00
|
|
|
getCode(targetOffset).switchTarget = true;
|
2010-12-20 09:06:43 -08:00
|
|
|
getCode(targetOffset).safePoint = true;
|
2010-11-24 17:41:52 -08:00
|
|
|
pc2 += jmplen;
|
2010-10-26 12:00:54 -07:00
|
|
|
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.
|
|
|
|
*/
|
2011-04-22 07:59:45 -07:00
|
|
|
isInlineable = canTrackVars = false;
|
2010-10-26 12:00:54 -07:00
|
|
|
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,
|
2010-12-28 11:53:50 -08:00
|
|
|
stackDepth, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
2010-10-29 08:05:55 -07:00
|
|
|
getCode(catchOffset).exceptionEntry = true;
|
2010-12-20 09:06:43 -08:00
|
|
|
getCode(catchOffset).safePoint = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2011-04-22 07:59:45 -07:00
|
|
|
if (local < script->nfixed && !localDefined(local, offset)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
setLocal(local, LOCAL_USE_BEFORE_DEF);
|
2011-03-26 19:07:13 -07:00
|
|
|
isInlineable = false;
|
|
|
|
}
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_CALLLOCAL:
|
|
|
|
case JSOP_INCLOCAL:
|
|
|
|
case JSOP_DECLOCAL:
|
|
|
|
case JSOP_LOCALINC:
|
2011-03-03 14:07:48 -08:00
|
|
|
case JSOP_LOCALDEC: {
|
2010-10-26 12:00:54 -07:00
|
|
|
uint32 local = GET_SLOTNO(pc);
|
2011-04-22 07:59:45 -07:00
|
|
|
if (local < script->nfixed && !localDefined(local, offset)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
setLocal(local, LOCAL_USE_BEFORE_DEF);
|
2011-03-26 19:07:13 -07:00
|
|
|
isInlineable = false;
|
|
|
|
}
|
2010-10-26 12:00:54 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case JSOP_SETLOCAL:
|
|
|
|
case JSOP_FORLOCAL: {
|
|
|
|
uint32 local = GET_SLOTNO(pc);
|
2010-11-11 17:21:14 -08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The local variable may already have been marked as unconditionally
|
|
|
|
* defined at a later point in the script, if that definition was in the
|
|
|
|
* condition for a loop which then jumped back here. In such cases we
|
|
|
|
* will not treat the variable as ever being defined in the loop body
|
|
|
|
* (see setLocal).
|
|
|
|
*/
|
2011-04-22 07:59:45 -07:00
|
|
|
if (local < script->nfixed && definedLocals[local] == LOCAL_CONDITIONALLY_DEFINED) {
|
2010-10-26 12:00:54 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-03-26 19:07:13 -07:00
|
|
|
/* Additional opcodes which can be compiled but which can't be inlined. */
|
|
|
|
case JSOP_ARGUMENTS:
|
|
|
|
case JSOP_EVAL:
|
|
|
|
case JSOP_FORARG:
|
|
|
|
case JSOP_SETARG:
|
|
|
|
case JSOP_INCARG:
|
|
|
|
case JSOP_DECARG:
|
|
|
|
case JSOP_ARGINC:
|
|
|
|
case JSOP_ARGDEC:
|
|
|
|
case JSOP_THROW:
|
|
|
|
case JSOP_EXCEPTION:
|
|
|
|
case JSOP_DEFLOCALFUN:
|
|
|
|
case JSOP_DEFLOCALFUN_FC:
|
|
|
|
case JSOP_LAMBDA:
|
|
|
|
case JSOP_LAMBDA_FC:
|
|
|
|
case JSOP_GETFCSLOT:
|
|
|
|
case JSOP_CALLFCSLOT:
|
|
|
|
case JSOP_ARGSUB:
|
|
|
|
case JSOP_ARGCNT:
|
|
|
|
case JSOP_DEBUGGER:
|
|
|
|
case JSOP_ENTERBLOCK:
|
|
|
|
case JSOP_LEAVEBLOCK:
|
|
|
|
case JSOP_FUNCALL:
|
|
|
|
case JSOP_FUNAPPLY:
|
|
|
|
isInlineable = false;
|
|
|
|
break;
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
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. */
|
2010-10-29 08:05:55 -07:00
|
|
|
unsigned newStackDepth = stackDepth;
|
2010-10-26 12:00:54 -07:00
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
case JSOP_OR:
|
|
|
|
case JSOP_AND:
|
|
|
|
case JSOP_ORX:
|
|
|
|
case JSOP_ANDX:
|
2010-10-29 08:05:55 -07:00
|
|
|
/*
|
|
|
|
* OR/AND instructions push the operation result when branching.
|
|
|
|
* We accounted for this in GetDefCount, so subtract the pushed value
|
|
|
|
* for the fallthrough case.
|
|
|
|
*/
|
|
|
|
stackDepth--;
|
2010-10-26 12:00:54 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_CASE:
|
|
|
|
case JSOP_CASEX:
|
|
|
|
/* Case instructions do not push the lvalue back when branching. */
|
2010-10-29 08:05:55 -07:00
|
|
|
newStackDepth--;
|
2010-10-26 12:00:54 -07:00
|
|
|
break;
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
default:;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
unsigned targetOffset = offset + GetJumpOffset(pc, pc);
|
|
|
|
if (!addJump(cx, targetOffset, &nextOffset, &forwardJump,
|
2010-12-28 11:53:50 -08:00
|
|
|
newStackDepth, defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle any fallthrough from this opcode. */
|
|
|
|
if (!BytecodeNoFallThrough(op)) {
|
|
|
|
JS_ASSERT(successorOffset < script->length);
|
|
|
|
|
2010-10-29 08:05:55 -07:00
|
|
|
Bytecode *&nextcode = codeArray[successorOffset];
|
2010-10-26 12:00:54 -07:00
|
|
|
bool initial = (nextcode == NULL);
|
|
|
|
|
|
|
|
if (initial) {
|
2011-03-03 14:07:48 -08:00
|
|
|
nextcode = ArenaNew<Bytecode>(pool);
|
2010-10-26 12:00:54 -07:00
|
|
|
if (!nextcode) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-06 11:53:53 -08:00
|
|
|
if (type == JOF_JUMP || type == JOF_JUMPX)
|
|
|
|
nextcode->jumpFallthrough = true;
|
|
|
|
|
2010-12-28 11:53:50 -08:00
|
|
|
if (!nextcode->mergeDefines(cx, this, initial, stackDepth,
|
2010-10-29 08:05:55 -07:00
|
|
|
defineArray, defineCount)) {
|
2010-10-26 12:00:54 -07:00
|
|
|
return;
|
2010-10-29 08:05:55 -07:00
|
|
|
}
|
2010-11-12 06:25:52 -08:00
|
|
|
|
|
|
|
/* Treat the fallthrough of a branch instruction as a jump target. */
|
|
|
|
if (type == JOF_JUMP || type == JOF_JUMPX)
|
|
|
|
nextcode->jumpTarget = true;
|
|
|
|
else
|
|
|
|
nextcode->fallthrough = true;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JS_ASSERT(!failed());
|
|
|
|
JS_ASSERT(forwardJump == 0 && forwardCatch == 0);
|
2011-04-16 06:54:01 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
ranBytecode_ = true;
|
2011-04-16 06:54:01 -07:00
|
|
|
}
|
|
|
|
|
2010-12-06 11:53:53 -08:00
|
|
|
/////////////////////////////////////////////////////////////////////
|
2011-04-22 07:59:45 -07:00
|
|
|
// Lifetime Analysis
|
2010-12-06 11:53:53 -08:00
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
void
|
|
|
|
ScriptAnalysis::analyzeLifetimes(JSContext *cx)
|
2010-12-06 11:53:53 -08:00
|
|
|
{
|
2011-04-22 07:59:45 -07:00
|
|
|
JS_ASSERT(cx->compartment->activeAnalysis && !ranLifetimes() && !failed());
|
2011-03-26 19:07:13 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
if (!ranBytecode()) {
|
|
|
|
analyzeBytecode(cx);
|
|
|
|
if (failed())
|
|
|
|
return;
|
|
|
|
}
|
2011-04-05 18:12:03 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
JSArenaPool &pool = cx->compartment->pool;
|
2010-12-06 11:53:53 -08:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
lifetimes = ArenaArray<LifetimeVariable>(pool, numSlots);
|
|
|
|
if (!lifetimes) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PodZero(lifetimes, numSlots);
|
2010-12-06 11:53:53 -08:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
/*
|
|
|
|
* Variables which are currently dead. On forward branches to locations
|
|
|
|
* where these are live, they need to be marked as live.
|
|
|
|
*/
|
|
|
|
LifetimeVariable **saved = (LifetimeVariable **)
|
|
|
|
cx->calloc_(numSlots * sizeof(LifetimeVariable*));
|
|
|
|
if (!saved) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
unsigned savedCount = 0;
|
2010-12-06 11:53:53 -08:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
LoopAnalysis *loop = NULL;
|
2011-04-05 18:12:03 -07:00
|
|
|
|
2010-12-06 11:53:53 -08:00
|
|
|
uint32 offset = script->length - 1;
|
|
|
|
while (offset < script->length) {
|
2011-04-22 07:59:45 -07:00
|
|
|
Bytecode *code = maybeCode(offset);
|
2010-12-06 11:53:53 -08:00
|
|
|
if (!code) {
|
|
|
|
offset--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-04-05 18:12:03 -07:00
|
|
|
if (loop && code->safePoint)
|
|
|
|
loop->hasSafePoints = true;
|
|
|
|
|
2010-12-06 11:53:53 -08:00
|
|
|
UntrapOpcode untrap(cx, script, script->code + offset);
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
if (code->loop) {
|
2010-12-06 11:53:53 -08:00
|
|
|
/*
|
|
|
|
* This is the head of a loop, we need to go and make sure that any
|
|
|
|
* variables live at the head are live at the backedge and points prior.
|
|
|
|
* For each such variable, look for the last lifetime segment in the body
|
|
|
|
* and extend it to the end of the loop.
|
|
|
|
*/
|
2011-04-22 07:59:45 -07:00
|
|
|
JS_ASSERT(loop == code->loop);
|
|
|
|
unsigned backedge = code->loop->backedge;
|
|
|
|
for (unsigned i = 0; i < numSlots; i++) {
|
|
|
|
if (lifetimes[i].lifetime)
|
|
|
|
extendVariable(cx, lifetimes[i], offset, backedge);
|
2010-12-06 11:53:53 -08:00
|
|
|
}
|
2011-04-05 18:12:03 -07:00
|
|
|
|
2011-04-06 14:04:24 -07:00
|
|
|
loop = loop->parent;
|
|
|
|
JS_ASSERT_IF(loop, loop->head < offset);
|
2010-12-06 11:53:53 -08:00
|
|
|
}
|
|
|
|
|
2011-04-05 18:12:03 -07:00
|
|
|
/* Find the last jump target in the loop, other than the initial entry point. */
|
|
|
|
if (loop && code->jumpTarget && offset != loop->entry && offset > loop->lastBlock)
|
|
|
|
loop->lastBlock = offset;
|
|
|
|
|
2010-12-06 11:53:53 -08:00
|
|
|
jsbytecode *pc = script->code + offset;
|
|
|
|
JSOp op = (JSOp) *pc;
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
case JSOP_GETARG:
|
2011-04-22 07:59:45 -07:00
|
|
|
case JSOP_CALLARG:
|
2010-12-06 11:53:53 -08:00
|
|
|
case JSOP_GETLOCAL:
|
2011-04-22 07:59:45 -07:00
|
|
|
case JSOP_CALLLOCAL:
|
|
|
|
case JSOP_THIS: {
|
|
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
|
|
if (!slotEscapes(slot))
|
|
|
|
addVariable(cx, lifetimes[slot], offset, saved, savedCount);
|
2010-12-06 11:53:53 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
case JSOP_SETARG:
|
2010-12-06 11:53:53 -08:00
|
|
|
case JSOP_SETLOCAL:
|
2010-12-28 11:53:50 -08:00
|
|
|
case JSOP_SETLOCALPOP:
|
2011-04-22 07:59:45 -07:00
|
|
|
case JSOP_DEFLOCALFUN:
|
|
|
|
case JSOP_DEFLOCALFUN_FC:
|
|
|
|
case JSOP_FORARG:
|
|
|
|
case JSOP_FORLOCAL: {
|
|
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
|
|
if (!slotEscapes(slot))
|
|
|
|
killVariable(cx, lifetimes[slot], offset, saved, savedCount);
|
2011-04-05 18:12:03 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
case JSOP_INCARG:
|
|
|
|
case JSOP_DECARG:
|
|
|
|
case JSOP_ARGINC:
|
|
|
|
case JSOP_ARGDEC:
|
2011-04-05 18:12:03 -07:00
|
|
|
case JSOP_INCLOCAL:
|
|
|
|
case JSOP_DECLOCAL:
|
|
|
|
case JSOP_LOCALINC:
|
|
|
|
case JSOP_LOCALDEC: {
|
2011-04-22 07:59:45 -07:00
|
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
|
|
if (!slotEscapes(slot)) {
|
|
|
|
killVariable(cx, lifetimes[slot], offset, saved, savedCount);
|
|
|
|
addVariable(cx, lifetimes[slot], offset, saved, savedCount);
|
2011-03-20 08:23:27 -07:00
|
|
|
}
|
2010-12-06 11:53:53 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-04-28 13:02:47 -07:00
|
|
|
case JSOP_LOOKUPSWITCH:
|
|
|
|
case JSOP_LOOKUPSWITCHX:
|
|
|
|
case JSOP_TABLESWITCH:
|
|
|
|
case JSOP_TABLESWITCHX:
|
|
|
|
case JSOP_TRY:
|
|
|
|
/* Restore all saved variables. :FIXME: maybe do this precisely. */
|
|
|
|
for (unsigned i = 0; i < savedCount; i++) {
|
|
|
|
LifetimeVariable &var = *saved[i];
|
|
|
|
var.lifetime = ArenaNew<Lifetime>(pool, offset, var.savedEnd, var.saved);
|
|
|
|
if (!var.lifetime) {
|
|
|
|
cx->free_(saved);
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var.saved = NULL;
|
|
|
|
saved[i--] = saved[--savedCount];
|
|
|
|
}
|
|
|
|
savedCount = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_NEW:
|
|
|
|
case JSOP_CALL:
|
|
|
|
case JSOP_EVAL:
|
|
|
|
case JSOP_FUNAPPLY:
|
|
|
|
case JSOP_FUNCALL:
|
|
|
|
if (loop)
|
|
|
|
loop->hasCallsLoops = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 type = JOF_TYPE(js_CodeSpec[op].format);
|
|
|
|
if (type == JOF_JUMP || type == JOF_JUMPX) {
|
2011-04-05 18:12:03 -07:00
|
|
|
/*
|
|
|
|
* Forward jumps need to pull in all variables which are live at
|
|
|
|
* their target offset --- the variables live before the jump are
|
|
|
|
* the union of those live at the fallthrough and at the target.
|
|
|
|
*/
|
2011-04-22 07:59:45 -07:00
|
|
|
uint32 targetOffset = FollowBranch(script, offset);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Watch for 'continue' statements in the loop body, which are
|
|
|
|
* jumps to the entry offset separate from the initial jump.
|
|
|
|
*/
|
|
|
|
if (loop && loop->entry == targetOffset && loop->entry > loop->lastBlock)
|
|
|
|
loop->lastBlock = loop->entry;
|
|
|
|
|
2010-12-06 11:53:53 -08:00
|
|
|
if (targetOffset < offset) {
|
2011-04-22 07:59:45 -07:00
|
|
|
/* This is a loop back edge, no lifetime to pull in yet. */
|
2011-04-27 23:28:54 -07:00
|
|
|
|
|
|
|
#ifdef DEBUG
|
2010-12-06 11:53:53 -08:00
|
|
|
JSOp nop = JSOp(script->code[targetOffset]);
|
2011-04-22 07:59:45 -07:00
|
|
|
JS_ASSERT(nop == JSOP_TRACE || nop == JSOP_NOTRACE);
|
2011-04-27 23:28:54 -07:00
|
|
|
#endif
|
2011-04-05 18:12:03 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
/*
|
|
|
|
* If we already have a loop, it is an outer loop and we
|
|
|
|
* need to prune the last block in the loop --- we do not
|
|
|
|
* track 'continue' statements for outer loops.
|
|
|
|
*/
|
|
|
|
if (loop && loop->entry > loop->lastBlock)
|
|
|
|
loop->lastBlock = loop->entry;
|
2011-04-05 18:12:03 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
LoopAnalysis *nloop = ArenaNew<LoopAnalysis>(pool);
|
|
|
|
if (!nloop) {
|
|
|
|
cx->free_(saved);
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PodZero(nloop);
|
2011-04-06 14:04:24 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
if (loop)
|
|
|
|
loop->hasCallsLoops = true;
|
2011-04-07 16:12:37 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
nloop->parent = loop;
|
|
|
|
loop = nloop;
|
2011-04-05 18:12:03 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
getCode(targetOffset).loop = loop;
|
|
|
|
loop->head = targetOffset;
|
|
|
|
loop->backedge = offset;
|
|
|
|
loop->lastBlock = loop->head;
|
2011-04-05 18:12:03 -07:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
/*
|
|
|
|
* Find the entry jump, which will be a GOTO for 'for' or
|
|
|
|
* 'while' loops or a fallthrough for 'do while' loops.
|
|
|
|
*/
|
|
|
|
uint32 entry = targetOffset;
|
|
|
|
if (entry) {
|
|
|
|
do {
|
|
|
|
entry--;
|
|
|
|
} while (!maybeCode(entry));
|
|
|
|
|
|
|
|
jsbytecode *entrypc = script->code + entry;
|
|
|
|
if (JSOp(*entrypc) == JSOP_GOTO || JSOp(*entrypc) == JSOP_GOTOX)
|
|
|
|
loop->entry = entry + GetJumpOffset(entrypc, entrypc);
|
|
|
|
else
|
2011-04-05 18:12:03 -07:00
|
|
|
loop->entry = targetOffset;
|
2011-04-22 07:59:45 -07:00
|
|
|
} else {
|
|
|
|
/* Do-while loop at the start of the script. */
|
|
|
|
loop->entry = targetOffset;
|
2010-12-06 11:53:53 -08:00
|
|
|
}
|
2011-04-28 13:02:47 -07:00
|
|
|
} else {
|
|
|
|
for (unsigned i = 0; i < savedCount; i++) {
|
|
|
|
LifetimeVariable &var = *saved[i];
|
|
|
|
JS_ASSERT(!var.lifetime && var.saved);
|
|
|
|
if (var.live(targetOffset)) {
|
|
|
|
/*
|
|
|
|
* Jumping to a place where this variable is live. Make a new
|
|
|
|
* lifetime segment for the variable.
|
|
|
|
*/
|
|
|
|
var.lifetime = ArenaNew<Lifetime>(pool, offset, var.savedEnd, var.saved);
|
|
|
|
if (!var.lifetime) {
|
|
|
|
cx->free_(saved);
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var.saved = NULL;
|
|
|
|
saved[i--] = saved[--savedCount];
|
|
|
|
} else if (loop && !var.savedEnd) {
|
|
|
|
/*
|
|
|
|
* This jump precedes the basic block which killed the variable,
|
|
|
|
* remember it and use it for the end of the next lifetime
|
|
|
|
* segment should the variable become live again. This is needed
|
|
|
|
* for loops, as if we wrap liveness around the loop the isLive
|
|
|
|
* test below may have given the wrong answer.
|
|
|
|
*/
|
|
|
|
var.savedEnd = offset;
|
2011-04-22 07:59:45 -07:00
|
|
|
}
|
2010-12-06 11:53:53 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
offset--;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
cx->free_(saved);
|
|
|
|
|
|
|
|
ranLifetimes_ = true;
|
2010-12-06 11:53:53 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
void
|
2011-04-22 07:59:45 -07:00
|
|
|
LifetimeVariable::print() const
|
2010-12-06 11:53:53 -08:00
|
|
|
{
|
2011-04-22 07:59:45 -07:00
|
|
|
Lifetime *segment = lifetime ? lifetime : saved;
|
2010-12-06 11:53:53 -08:00
|
|
|
while (segment) {
|
|
|
|
printf(" (%u,%u%s)", segment->start, segment->end, segment->loopTail ? ",tail" : "");
|
|
|
|
segment = segment->next;
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
#endif /* DEBUG */
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
inline void
|
|
|
|
ScriptAnalysis::addVariable(JSContext *cx, LifetimeVariable &var, unsigned offset,
|
|
|
|
LifetimeVariable **&saved, unsigned &savedCount)
|
2010-12-06 11:53:53 -08:00
|
|
|
{
|
|
|
|
if (var.lifetime) {
|
|
|
|
JS_ASSERT(offset < var.lifetime->start);
|
|
|
|
var.lifetime->start = offset;
|
|
|
|
} else {
|
|
|
|
if (var.saved) {
|
|
|
|
/* Remove from the list of saved entries. */
|
|
|
|
for (unsigned i = 0; i < savedCount; i++) {
|
|
|
|
if (saved[i] == &var) {
|
|
|
|
JS_ASSERT(savedCount);
|
|
|
|
saved[i--] = saved[--savedCount];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-04-22 07:59:45 -07:00
|
|
|
var.lifetime = ArenaNew<Lifetime>(cx->compartment->pool, offset, var.savedEnd, var.saved);
|
|
|
|
if (!var.lifetime) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
2010-12-06 11:53:53 -08:00
|
|
|
var.saved = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
inline void
|
|
|
|
ScriptAnalysis::killVariable(JSContext *cx, LifetimeVariable &var, unsigned offset,
|
|
|
|
LifetimeVariable **&saved, unsigned &savedCount)
|
2010-12-06 11:53:53 -08:00
|
|
|
{
|
2011-04-05 18:12:03 -07:00
|
|
|
if (!var.lifetime) {
|
|
|
|
/* Make a point lifetime indicating the write. */
|
2011-04-19 06:39:49 -07:00
|
|
|
if (!var.saved)
|
|
|
|
saved[savedCount++] = &var;
|
2011-04-22 07:59:45 -07:00
|
|
|
var.saved = ArenaNew<Lifetime>(cx->compartment->pool, offset, var.savedEnd, var.saved);
|
|
|
|
if (!var.saved) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
2011-04-05 18:12:03 -07:00
|
|
|
var.saved->write = true;
|
2011-04-19 06:39:49 -07:00
|
|
|
var.savedEnd = 0;
|
2011-04-22 07:59:45 -07:00
|
|
|
return;
|
2011-04-05 18:12:03 -07:00
|
|
|
}
|
2010-12-06 11:53:53 -08:00
|
|
|
JS_ASSERT(offset < var.lifetime->start);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The variable is considered to be live at the bytecode which kills it
|
|
|
|
* (just not at earlier bytecodes). This behavior is needed by downstream
|
|
|
|
* register allocation (see FrameState::bestEvictReg).
|
|
|
|
*/
|
|
|
|
var.lifetime->start = offset;
|
2011-04-05 18:12:03 -07:00
|
|
|
var.lifetime->write = true;
|
2010-12-06 11:53:53 -08:00
|
|
|
|
|
|
|
var.saved = var.lifetime;
|
|
|
|
var.savedEnd = 0;
|
|
|
|
var.lifetime = NULL;
|
|
|
|
|
|
|
|
saved[savedCount++] = &var;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
inline void
|
|
|
|
ScriptAnalysis::extendVariable(JSContext *cx, LifetimeVariable &var,
|
|
|
|
unsigned start, unsigned end)
|
2010-12-06 11:53:53 -08:00
|
|
|
{
|
|
|
|
JS_ASSERT(var.lifetime);
|
|
|
|
var.lifetime->start = start;
|
|
|
|
|
2011-04-19 06:39:49 -07:00
|
|
|
/*
|
|
|
|
* When walking backwards through loop bodies, we don't know which vars
|
|
|
|
* are live at the loop's backedge. We save the endpoints for lifetime
|
|
|
|
* segments which we *would* use if the variables were live at the backedge
|
|
|
|
* and extend the variable with new lifetimes if we find the variable is
|
|
|
|
* indeed live at the head of the loop.
|
|
|
|
*
|
|
|
|
* while (...) {
|
|
|
|
* if (x #1) { ... }
|
|
|
|
* ...
|
|
|
|
* if (... #2) { x = 0; #3}
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* If x is not live after the loop, we treat it as dead in the walk and
|
|
|
|
* make a point lifetime for the write at #3. At the beginning of that
|
|
|
|
* basic block (#2), we save the loop endpoint; if we knew x was live in
|
|
|
|
* the next iteration then a new lifetime would be made here. At #1 we
|
|
|
|
* mark x live again, make a segment between the head of the loop and #1,
|
|
|
|
* and then extend x with loop tail lifetimes from #1 to #2, and from #3
|
|
|
|
* to the back edge.
|
|
|
|
*/
|
|
|
|
|
2010-12-06 11:53:53 -08:00
|
|
|
Lifetime *segment = var.lifetime;
|
2011-04-19 06:39:49 -07:00
|
|
|
while (segment && segment->start < end) {
|
|
|
|
uint32 savedEnd = segment->savedEnd;
|
|
|
|
if (!segment->next || segment->next->start >= end) {
|
|
|
|
/*
|
|
|
|
* savedEnd is only set for variables killed in the middle of the
|
|
|
|
* loop. Make a tail segment connecting the last use with the
|
|
|
|
* back edge.
|
|
|
|
*/
|
|
|
|
if (segment->end >= end) {
|
|
|
|
/* Variable known to be live after the loop finishes. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
savedEnd = end;
|
|
|
|
}
|
|
|
|
JS_ASSERT(savedEnd <= end);
|
|
|
|
if (savedEnd > segment->end) {
|
2011-04-22 07:59:45 -07:00
|
|
|
Lifetime *tail = ArenaNew<Lifetime>(cx->compartment->pool, savedEnd, 0, segment->next);
|
|
|
|
if (!tail) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
2011-04-19 06:39:49 -07:00
|
|
|
tail->start = segment->end;
|
|
|
|
tail->loopTail = true;
|
2010-12-06 11:53:53 -08:00
|
|
|
|
2011-04-19 06:39:49 -07:00
|
|
|
/*
|
|
|
|
* Clear the segment's saved end, but preserve in the tail if this
|
|
|
|
* is the last segment in the loop and the variable is killed in an
|
|
|
|
* outer loop before the backedge.
|
|
|
|
*/
|
|
|
|
if (segment->savedEnd > end) {
|
|
|
|
JS_ASSERT(savedEnd == end);
|
|
|
|
tail->savedEnd = segment->savedEnd;
|
|
|
|
}
|
|
|
|
segment->savedEnd = 0;
|
|
|
|
|
|
|
|
segment->next = tail;
|
|
|
|
segment = tail->next;
|
|
|
|
} else {
|
|
|
|
JS_ASSERT(segment->savedEnd == 0);
|
|
|
|
segment = segment->next;
|
|
|
|
}
|
|
|
|
}
|
2011-04-22 07:59:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ScriptAnalysis::clearAllocations()
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Clear out storage used for register allocations in a compilation once
|
|
|
|
* that compilation has finished. Register allocations are only used for
|
|
|
|
* a single compilation.
|
|
|
|
*/
|
|
|
|
for (unsigned i = 0; i < script->length; i++) {
|
|
|
|
Bytecode *code = maybeCode(i);
|
|
|
|
if (code)
|
|
|
|
code->allocation = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
// SSA Analysis
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
2010-12-06 11:53:53 -08:00
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
void
|
|
|
|
ScriptAnalysis::analyzeSSA(JSContext *cx)
|
|
|
|
{
|
|
|
|
JS_ASSERT(cx->compartment->activeAnalysis && !ranSSA() && !failed());
|
|
|
|
|
|
|
|
if (!ranLifetimes()) {
|
|
|
|
analyzeLifetimes(cx);
|
|
|
|
if (failed())
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSArenaPool &pool = cx->compartment->pool;
|
|
|
|
unsigned maxDepth = script->nslots - script->nfixed;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Current value of each variable and stack value. Empty for missing or
|
|
|
|
* untracked entries, i.e. escaping locals and arguments.
|
|
|
|
*/
|
|
|
|
SSAValue *values = (SSAValue *)
|
|
|
|
cx->calloc_((numSlots + maxDepth) * sizeof(SSAValue));
|
|
|
|
if (!values) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SSAValue *stack = values + numSlots;
|
|
|
|
uint32 stackDepth = 0;
|
|
|
|
|
|
|
|
for (uint32 slot = ArgSlot(0); slot < numSlots; slot++) {
|
|
|
|
if (trackSlot(slot))
|
|
|
|
values[slot].initInitial(slot);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* All target offsets for forward jumps we in the middle of. We lazily add
|
|
|
|
* pending entries at these targets for the original value of variables
|
|
|
|
* modified before the branch rejoins.
|
|
|
|
*/
|
|
|
|
Vector<uint32> branchTargets(cx);
|
|
|
|
|
|
|
|
uint32 offset = 0;
|
|
|
|
while (offset < script->length) {
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
|
|
UntrapOpcode untrap(cx, script, pc);
|
|
|
|
|
|
|
|
uint32 successorOffset = offset + GetBytecodeLength(pc);
|
|
|
|
|
|
|
|
Bytecode *code = maybeCode(pc);
|
|
|
|
if (!code) {
|
|
|
|
offset = successorOffset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (code->stackDepth > stackDepth)
|
|
|
|
PodZero(stack + stackDepth, code->stackDepth - stackDepth);
|
|
|
|
stackDepth = code->stackDepth;
|
|
|
|
|
|
|
|
if (code->loop) {
|
|
|
|
/*
|
|
|
|
* Make sure there is a pending value array for phi nodes at the
|
|
|
|
* loop head. We won't be able to clear these until we reach the
|
|
|
|
* loop's back edge.
|
|
|
|
*
|
|
|
|
* We need phi nodes for all variables which might be modified
|
|
|
|
* during the loop. This ensures that in the loop body we have
|
|
|
|
* already updated state to reflect possible changes that happen
|
|
|
|
* before the back edge, and don't need to go back and fix things
|
|
|
|
* up when we *do* get to the back edge. This could be made lazier.
|
|
|
|
*
|
|
|
|
* We don't make phi nodes for values on the stack at the head of
|
|
|
|
* the loop. These may be popped during the loop (i.e. for ITER
|
|
|
|
* loops), but in such cases the original value is pushed back.
|
|
|
|
*/
|
|
|
|
Vector<SlotValue> *&pending = code->pendingValues;
|
|
|
|
if (pending) {
|
|
|
|
removeBranchTarget(branchTargets, offset);
|
|
|
|
} else {
|
|
|
|
pending = cx->new_< Vector<SlotValue> >(cx);
|
|
|
|
if (!pending) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make phi nodes and update state for slots which are already in
|
|
|
|
* pending from previous branches to the loop head, and which are
|
|
|
|
* modified in the body of the loop.
|
|
|
|
*/
|
|
|
|
for (unsigned i = 0; i < pending->length(); i++) {
|
|
|
|
SlotValue &v = (*pending)[i];
|
|
|
|
if (v.slot < numSlots && liveness(v.slot).firstWrite(code->loop) != uint32(-1)) {
|
|
|
|
if (v.value.kind() != SSAValue::PHI || v.value.phiOffset() < offset) {
|
|
|
|
SSAValue ov = v.value;
|
|
|
|
if (!makePhi(cx, v.slot, offset, &ov))
|
|
|
|
return;
|
|
|
|
insertPhi(cx, ov, v.value);
|
|
|
|
v.value = ov;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (code->fallthrough || code->jumpFallthrough)
|
|
|
|
mergeValue(cx, offset, values[v.slot], &v);
|
|
|
|
mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets);
|
|
|
|
values[v.slot] = v.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make phi nodes for all other slots which might be modified
|
|
|
|
* during the loop. This ensures that in the loop body we have
|
|
|
|
* already updated state to reflect possible changes that happen
|
|
|
|
* before the back edge, and don't need to go back and fix things
|
|
|
|
* up when we *do* get to the back edge. This could be made lazier.
|
|
|
|
*/
|
|
|
|
for (uint32 slot = ArgSlot(0); slot < numSlots + stackDepth; slot++) {
|
|
|
|
if (slot >= numSlots || !trackSlot(slot))
|
|
|
|
continue;
|
|
|
|
if (liveness(slot).firstWrite(code->loop) == uint32(-1))
|
|
|
|
continue;
|
|
|
|
if (values[slot].kind() == SSAValue::PHI && values[slot].phiOffset() == offset) {
|
|
|
|
/* There is already a pending entry for this slot. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
SSAValue ov;
|
|
|
|
if (!makePhi(cx, slot, offset, &ov))
|
|
|
|
return;
|
|
|
|
if (code->fallthrough || code->jumpFallthrough)
|
|
|
|
insertPhi(cx, ov, values[slot]);
|
|
|
|
mergeBranchTarget(cx, values[slot], slot, branchTargets);
|
|
|
|
values[slot] = ov;
|
|
|
|
if (!pending->append(SlotValue(slot, ov))) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (code->pendingValues) {
|
|
|
|
/*
|
|
|
|
* New values at this point from a previous jump to this bytecode.
|
|
|
|
* If there is fallthrough from the previous instruction, merge
|
|
|
|
* with the current state and create phi nodes where necessary,
|
|
|
|
* otherwise replace current values with the new values.
|
|
|
|
*/
|
|
|
|
removeBranchTarget(branchTargets, offset);
|
|
|
|
Vector<SlotValue> *pending = code->pendingValues;
|
|
|
|
for (unsigned i = 0; i < pending->length(); i++) {
|
|
|
|
SlotValue &v = (*pending)[i];
|
|
|
|
if (code->fallthrough || code->jumpFallthrough)
|
|
|
|
mergeValue(cx, offset, values[v.slot], &v);
|
|
|
|
mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets);
|
|
|
|
values[v.slot] = v.value;
|
|
|
|
}
|
|
|
|
freezeNewValues(cx, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned nuses = GetUseCount(script, offset);
|
|
|
|
unsigned ndefs = GetDefCount(script, offset);
|
|
|
|
JS_ASSERT(stackDepth >= nuses);
|
|
|
|
|
|
|
|
unsigned xuses = ExtendedUse(pc) ? nuses + 1 : nuses;
|
|
|
|
|
|
|
|
if (xuses) {
|
|
|
|
code->poppedValues = (SSAValue *)ArenaArray<SSAValue>(pool, xuses);
|
|
|
|
if (!code->poppedValues) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (unsigned i = 0; i < nuses; i++) {
|
|
|
|
SSAValue &v = stack[stackDepth - 1 - i];
|
|
|
|
code->poppedValues[i] = v;
|
|
|
|
v.clear();
|
|
|
|
}
|
|
|
|
if (xuses > nuses) {
|
|
|
|
/*
|
|
|
|
* For SETLOCAL, INCLOCAL, etc. opcodes, add an extra popped
|
|
|
|
* value holding the value of the local before the op.
|
|
|
|
*/
|
|
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
|
|
if (trackSlot(slot))
|
|
|
|
code->poppedValues[nuses] = values[slot];
|
|
|
|
else
|
|
|
|
code->poppedValues[nuses].clear();
|
|
|
|
}
|
2011-04-27 23:28:54 -07:00
|
|
|
|
|
|
|
if (xuses) {
|
|
|
|
SSAUseChain *useChains = ArenaArray<SSAUseChain>(cx->compartment->pool, xuses);
|
|
|
|
if (!useChains) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PodZero(useChains, xuses);
|
|
|
|
for (unsigned i = 0; i < xuses; i++) {
|
|
|
|
const SSAValue &v = code->poppedValues[i];
|
|
|
|
if (trackUseChain(v)) {
|
|
|
|
SSAUseChain *&uses = useChain(v);
|
|
|
|
useChains[i].popped = true;
|
|
|
|
useChains[i].offset = offset;
|
|
|
|
useChains[i].u.which = i;
|
|
|
|
useChains[i].next = uses;
|
|
|
|
uses = &useChains[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-04-22 07:59:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
stackDepth -= nuses;
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < ndefs; i++)
|
|
|
|
stack[stackDepth + i].initPushed(offset, i);
|
|
|
|
|
2011-04-27 23:28:54 -07:00
|
|
|
unsigned xdefs = ExtendedDef(pc) ? ndefs + 1 : ndefs;
|
|
|
|
if (xdefs) {
|
|
|
|
code->pushedUses = ArenaArray<SSAUseChain *>(cx->compartment->pool, xdefs);
|
|
|
|
if (!code->pushedUses) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PodZero(code->pushedUses, xdefs);
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
stackDepth += ndefs;
|
|
|
|
|
|
|
|
JSOp op = (JSOp)*pc;
|
|
|
|
switch (op) {
|
|
|
|
case JSOP_SETARG:
|
|
|
|
case JSOP_SETLOCAL:
|
|
|
|
case JSOP_SETLOCALPOP:
|
|
|
|
case JSOP_DEFLOCALFUN:
|
|
|
|
case JSOP_DEFLOCALFUN_FC:
|
|
|
|
case JSOP_FORARG:
|
|
|
|
case JSOP_FORLOCAL:
|
|
|
|
case JSOP_INCARG:
|
|
|
|
case JSOP_DECARG:
|
|
|
|
case JSOP_ARGINC:
|
|
|
|
case JSOP_ARGDEC:
|
|
|
|
case JSOP_INCLOCAL:
|
|
|
|
case JSOP_DECLOCAL:
|
|
|
|
case JSOP_LOCALINC:
|
|
|
|
case JSOP_LOCALDEC: {
|
|
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
|
|
if (trackSlot(slot)) {
|
|
|
|
mergeBranchTarget(cx, values[slot], slot, branchTargets);
|
|
|
|
values[slot].initWritten(slot, offset);
|
|
|
|
}
|
|
|
|
if (op == JSOP_FORARG || op == JSOP_FORLOCAL)
|
|
|
|
stack[stackDepth - 1] = code->poppedValues[0];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case JSOP_GETARG:
|
2011-05-05 13:59:29 -07:00
|
|
|
case JSOP_GETLOCAL: {
|
2011-04-22 07:59:45 -07:00
|
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
|
|
if (trackSlot(slot)) {
|
|
|
|
/*
|
|
|
|
* Propagate the current value of the local to the pushed value,
|
|
|
|
* and remember it with an extended use on the opcode.
|
|
|
|
*/
|
|
|
|
stack[stackDepth - 1] = code->poppedValues[0] = values[slot];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-05 13:59:29 -07:00
|
|
|
case JSOP_CALLARG:
|
|
|
|
case JSOP_CALLLOCAL: {
|
|
|
|
uint32 slot = GetBytecodeSlot(script, pc);
|
|
|
|
if (trackSlot(slot))
|
|
|
|
stack[stackDepth - 2] = code->poppedValues[0] = values[slot];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
/* Short circuit ops which push back one of their operands. */
|
|
|
|
|
|
|
|
case JSOP_MOREITER:
|
|
|
|
case JSOP_FORELEM:
|
|
|
|
stack[stackDepth - 2] = code->poppedValues[0];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_FORNAME:
|
|
|
|
case JSOP_FORGNAME:
|
|
|
|
stack[stackDepth - 1] = code->poppedValues[0];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_FORPROP:
|
|
|
|
case JSOP_INITPROP:
|
|
|
|
case JSOP_INITMETHOD:
|
|
|
|
stack[stackDepth - 1] = code->poppedValues[1];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_INITELEM:
|
|
|
|
stack[stackDepth - 1] = code->poppedValues[2];
|
|
|
|
break;
|
|
|
|
|
2011-04-26 14:32:52 -07:00
|
|
|
/*
|
|
|
|
* Switch and try blocks preserve the stack between the original op
|
|
|
|
* and all case statements or exception/finally handlers. Even though
|
|
|
|
* we don't track the values of variables in scripts containing such
|
|
|
|
* switch and try blocks, propagate the stack now to all targets as
|
|
|
|
* the values on the stack may be popped by intervening cleanup ops
|
|
|
|
* (e.g. LEAVEBLOCK, ENDITER).
|
|
|
|
*/
|
2011-04-22 07:59:45 -07:00
|
|
|
|
2011-04-26 14:32:52 -07:00
|
|
|
case JSOP_TABLESWITCH:
|
|
|
|
case JSOP_TABLESWITCHX: {
|
|
|
|
jsbytecode *pc2 = pc;
|
|
|
|
unsigned jmplen = (op == JSOP_TABLESWITCH) ? JUMP_OFFSET_LEN : JUMPX_OFFSET_LEN;
|
|
|
|
unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
pc2 += jmplen;
|
|
|
|
jsint low = GET_JUMP_OFFSET(pc2);
|
|
|
|
pc2 += JUMP_OFFSET_LEN;
|
|
|
|
jsint high = GET_JUMP_OFFSET(pc2);
|
|
|
|
pc2 += JUMP_OFFSET_LEN;
|
2011-04-22 07:59:45 -07:00
|
|
|
|
2011-04-26 14:32:52 -07:00
|
|
|
checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth);
|
2011-04-22 07:59:45 -07:00
|
|
|
|
2011-04-26 14:32:52 -07:00
|
|
|
for (jsint i = low; i <= high; i++) {
|
|
|
|
unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
if (targetOffset != offset)
|
|
|
|
checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth);
|
|
|
|
pc2 += jmplen;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case JSOP_LOOKUPSWITCH:
|
|
|
|
case JSOP_LOOKUPSWITCHX: {
|
|
|
|
jsbytecode *pc2 = pc;
|
|
|
|
unsigned jmplen = (op == JSOP_LOOKUPSWITCH) ? JUMP_OFFSET_LEN : JUMPX_OFFSET_LEN;
|
|
|
|
unsigned defaultOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
pc2 += jmplen;
|
|
|
|
unsigned npairs = GET_UINT16(pc2);
|
|
|
|
pc2 += UINT16_LEN;
|
|
|
|
|
|
|
|
checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth);
|
|
|
|
|
|
|
|
while (npairs) {
|
|
|
|
pc2 += INDEX_LEN;
|
|
|
|
unsigned targetOffset = offset + GetJumpOffset(pc, pc2);
|
|
|
|
checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth);
|
|
|
|
pc2 += jmplen;
|
|
|
|
npairs--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case JSOP_TRY: {
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (tn->kind != JSTRY_ITER)
|
|
|
|
checkBranchTarget(cx, catchOffset, branchTargets, values, stackDepth);
|
2011-04-22 07:59:45 -07:00
|
|
|
}
|
|
|
|
}
|
2011-04-26 14:32:52 -07:00
|
|
|
break;
|
|
|
|
}
|
2011-04-22 07:59:45 -07:00
|
|
|
|
2011-04-26 14:32:52 -07:00
|
|
|
default:;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 type = JOF_TYPE(js_CodeSpec[op].format);
|
|
|
|
if (type == JOF_JUMP || type == JOF_JUMPX) {
|
|
|
|
unsigned targetOffset = FollowBranch(script, offset);
|
|
|
|
checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth);
|
2011-04-22 07:59:45 -07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is a back edge, we're done with the loop and can freeze
|
|
|
|
* the phi values at the head now.
|
|
|
|
*/
|
|
|
|
if (targetOffset < offset)
|
|
|
|
freezeNewValues(cx, targetOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = successorOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
ranSSA_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get a phi node's capacity for a given length. */
|
|
|
|
static inline unsigned
|
|
|
|
PhiNodeCapacity(unsigned length)
|
|
|
|
{
|
|
|
|
if (length <= 4)
|
|
|
|
return 4;
|
|
|
|
|
|
|
|
unsigned log2;
|
|
|
|
JS_FLOOR_LOG2(log2, length - 1);
|
|
|
|
return 1 << (log2 + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
ScriptAnalysis::makePhi(JSContext *cx, uint32 slot, uint32 offset, SSAValue *pv)
|
|
|
|
{
|
|
|
|
SSAPhiNode *node = ArenaNew<SSAPhiNode>(cx->compartment->pool);
|
|
|
|
SSAValue *options = ArenaArray<SSAValue>(cx->compartment->pool, PhiNodeCapacity(0));
|
|
|
|
if (!node || !options) {
|
|
|
|
setOOM(cx);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
node->slot = slot;
|
|
|
|
node->options = options;
|
|
|
|
pv->initPhi(offset, node);
|
2010-12-06 11:53:53 -08:00
|
|
|
return true;
|
2010-10-26 12:00:54 -07:00
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
void
|
|
|
|
ScriptAnalysis::insertPhi(JSContext *cx, SSAValue &phi, const SSAValue &v)
|
|
|
|
{
|
|
|
|
JS_ASSERT(phi.kind() == SSAValue::PHI);
|
|
|
|
SSAPhiNode *node = phi.phiNode();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Filter dupes inserted into small nodes to keep things clean and avoid
|
|
|
|
* extra type constraints, but don't bother on large phi nodes to avoid
|
|
|
|
* quadratic behavior.
|
|
|
|
*/
|
|
|
|
if (node->length <= 8) {
|
|
|
|
for (unsigned i = 0; i < node->length; i++) {
|
|
|
|
if (v.equals(node->options[i]))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-27 23:28:54 -07:00
|
|
|
if (trackUseChain(v)) {
|
|
|
|
SSAUseChain *&uses = useChain(v);
|
|
|
|
|
|
|
|
SSAUseChain *use = ArenaNew<SSAUseChain>(cx->compartment->pool);
|
|
|
|
if (!use) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
use->popped = false;
|
|
|
|
use->offset = phi.phiOffset();
|
|
|
|
use->u.phi = node;
|
|
|
|
use->next = uses;
|
|
|
|
uses = use;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
if (node->length < PhiNodeCapacity(node->length)) {
|
|
|
|
node->options[node->length++] = v;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SSAValue *newOptions = ArenaArray<SSAValue>(cx->compartment->pool,
|
|
|
|
PhiNodeCapacity(node->length + 1));
|
|
|
|
if (!newOptions) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
PodCopy(newOptions, node->options, node->length);
|
|
|
|
node->options = newOptions;
|
|
|
|
node->options[node->length++] = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void
|
|
|
|
ScriptAnalysis::mergeValue(JSContext *cx, uint32 offset, const SSAValue &v, SlotValue *pv)
|
|
|
|
{
|
2011-04-23 22:36:43 -07:00
|
|
|
/* Make sure that v is accounted for in the pending value or phi value at pv. */
|
2011-04-26 14:32:52 -07:00
|
|
|
JS_ASSERT(v.kind() != SSAValue::EMPTY && pv->value.kind() != SSAValue::EMPTY);
|
2011-04-22 07:59:45 -07:00
|
|
|
|
2011-04-26 14:32:52 -07:00
|
|
|
if (v.equals(pv->value))
|
2011-04-22 07:59:45 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (pv->value.kind() != SSAValue::PHI || pv->value.phiOffset() < offset) {
|
|
|
|
SSAValue ov = pv->value;
|
|
|
|
if (makePhi(cx, pv->slot, offset, &pv->value)) {
|
|
|
|
insertPhi(cx, pv->value, v);
|
|
|
|
insertPhi(cx, pv->value, ov);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS_ASSERT(pv->value.phiOffset() == offset);
|
|
|
|
insertPhi(cx, pv->value, v);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ScriptAnalysis::checkPendingValue(JSContext *cx, const SSAValue &v, uint32 slot,
|
|
|
|
Vector<SlotValue> *pending)
|
|
|
|
{
|
2011-04-27 23:28:54 -07:00
|
|
|
JS_ASSERT(v.kind() != SSAValue::EMPTY);
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
for (unsigned i = 0; i < pending->length(); i++) {
|
|
|
|
if ((*pending)[i].slot == slot)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pending->append(SlotValue(slot, v)))
|
|
|
|
setOOM(cx);
|
|
|
|
}
|
|
|
|
|
2011-04-26 14:32:52 -07:00
|
|
|
void
|
|
|
|
ScriptAnalysis::checkBranchTarget(JSContext *cx, uint32 targetOffset,
|
|
|
|
Vector<uint32> &branchTargets,
|
|
|
|
SSAValue *values, uint32 stackDepth)
|
|
|
|
{
|
|
|
|
unsigned targetDepth = getCode(targetOffset).stackDepth;
|
|
|
|
JS_ASSERT(targetDepth <= stackDepth);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there is already an active branch to target, make sure its pending
|
|
|
|
* values reflect any changes made since the first branch. Otherwise, add a
|
|
|
|
* new pending branch and determine its pending values lazily.
|
|
|
|
*/
|
|
|
|
Vector<SlotValue> *&pending = getCode(targetOffset).pendingValues;
|
|
|
|
if (pending) {
|
|
|
|
for (unsigned i = 0; i < pending->length(); i++) {
|
|
|
|
SlotValue &v = (*pending)[i];
|
|
|
|
mergeValue(cx, targetOffset, values[v.slot], &v);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pending = cx->new_< Vector<SlotValue> >(cx);
|
|
|
|
if (!pending || !branchTargets.append(targetOffset)) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure there is a pending entry for each value on the stack.
|
|
|
|
* The number of stack entries at join points is usually zero, and
|
|
|
|
* we don't want to look at the active branches while popping and
|
|
|
|
* pushing values in each opcode.
|
|
|
|
*/
|
|
|
|
for (unsigned i = 0; i < targetDepth; i++) {
|
|
|
|
uint32 slot = StackSlot(script, i);
|
|
|
|
checkPendingValue(cx, values[slot], slot, pending);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
void
|
|
|
|
ScriptAnalysis::mergeBranchTarget(JSContext *cx, const SSAValue &value, uint32 slot,
|
|
|
|
const Vector<uint32> &branchTargets)
|
|
|
|
{
|
|
|
|
if (slot >= numSlots) {
|
|
|
|
/*
|
|
|
|
* There is no need to lazily check that there are pending values at
|
|
|
|
* branch targets for slots on the stack, these are added to pending
|
|
|
|
* eagerly.
|
|
|
|
*/
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS_ASSERT(trackSlot(slot));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Before changing the value of a variable, make sure the old value is
|
|
|
|
* marked at the target of any branches jumping over the current opcode.
|
|
|
|
*/
|
|
|
|
for (unsigned i = 0; i < branchTargets.length(); i++) {
|
|
|
|
Vector<SlotValue> *pending = getCode(branchTargets[i]).pendingValues;
|
|
|
|
checkPendingValue(cx, value, slot, pending);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ScriptAnalysis::removeBranchTarget(Vector<uint32> &branchTargets, uint32 offset)
|
|
|
|
{
|
|
|
|
for (unsigned i = 0; i < branchTargets.length(); i++) {
|
|
|
|
if (branchTargets[i] == offset) {
|
|
|
|
branchTargets[i] = branchTargets.back();
|
|
|
|
branchTargets.popBack();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
JS_NOT_REACHED("Missing target");
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ScriptAnalysis::freezeNewValues(JSContext *cx, uint32 offset)
|
|
|
|
{
|
|
|
|
Bytecode &code = getCode(offset);
|
|
|
|
|
|
|
|
Vector<SlotValue> *pending = code.pendingValues;
|
|
|
|
code.pendingValues = NULL;
|
|
|
|
|
|
|
|
unsigned count = pending->length();
|
|
|
|
if (count == 0) {
|
|
|
|
cx->delete_(pending);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
code.newValues = ArenaArray<SlotValue>(cx->compartment->pool, count + 1);
|
|
|
|
if (!code.newValues) {
|
|
|
|
setOOM(cx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < count; i++)
|
|
|
|
code.newValues[i] = (*pending)[i];
|
|
|
|
code.newValues[count].slot = 0;
|
|
|
|
code.newValues[count].value.clear();
|
|
|
|
|
|
|
|
cx->delete_(pending);
|
|
|
|
}
|
|
|
|
|
2011-05-05 13:59:29 -07:00
|
|
|
CrossSSAValue
|
|
|
|
CrossScriptSSA::foldValue(const CrossSSAValue &cv)
|
|
|
|
{
|
|
|
|
const Frame &frame = getFrame(cv.frame);
|
|
|
|
const SSAValue &v = cv.v;
|
|
|
|
|
|
|
|
JSScript *parentScript = NULL;
|
|
|
|
ScriptAnalysis *parentAnalysis = NULL;
|
|
|
|
if (frame.parent != INVALID_FRAME) {
|
|
|
|
parentScript = getFrame(frame.parent).script;
|
|
|
|
parentAnalysis = parentScript->analysis(cx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (v.kind() == SSAValue::VAR && v.varInitial() && parentScript) {
|
|
|
|
uint32 slot = v.varSlot();
|
|
|
|
if (slot >= ArgSlot(0) && slot < LocalSlot(frame.script, 0)) {
|
|
|
|
uint32 argc = GET_ARGC(frame.parentpc);
|
|
|
|
SSAValue argv = parentAnalysis->poppedValue(frame.parentpc, argc - 1 - (slot - ArgSlot(0)));
|
|
|
|
return foldValue(CrossSSAValue(frame.parent, argv));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (v.kind() == SSAValue::PUSHED) {
|
|
|
|
jsbytecode *pc = frame.script->code + v.pushedOffset();
|
|
|
|
switch (JSOp(*pc)) {
|
|
|
|
case JSOP_THIS:
|
|
|
|
if (parentScript) {
|
|
|
|
uint32 argc = GET_ARGC(frame.parentpc);
|
|
|
|
SSAValue thisv = parentAnalysis->poppedValue(frame.parentpc, argc);
|
|
|
|
return foldValue(CrossSSAValue(frame.parent, thisv));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JSOP_CALL: {
|
|
|
|
/*
|
|
|
|
* If there is a single inline callee with a single return site,
|
|
|
|
* propagate back to that.
|
|
|
|
*/
|
|
|
|
JSScript *callee = NULL;
|
|
|
|
uint32 calleeFrame = INVALID_FRAME;
|
|
|
|
for (unsigned i = 0; i < numFrames(); i++) {
|
|
|
|
if (iterFrame(i).parent == cv.frame && iterFrame(i).parentpc == pc) {
|
|
|
|
if (callee)
|
|
|
|
return cv; /* Multiple callees */
|
|
|
|
callee = iterFrame(i).script;
|
|
|
|
calleeFrame = iterFrame(i).index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (callee && callee->analysis(cx)->numReturnSites() == 1) {
|
|
|
|
ScriptAnalysis *analysis = callee->analysis(cx);
|
|
|
|
uint32 offset = 0;
|
|
|
|
while (offset < callee->length) {
|
|
|
|
jsbytecode *pc = callee->code + offset;
|
|
|
|
if (analysis->maybeCode(pc) && JSOp(*pc) == JSOP_RETURN)
|
|
|
|
return foldValue(CrossSSAValue(calleeFrame, analysis->poppedValue(pc, 0)));
|
|
|
|
offset += GetBytecodeLength(pc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-10 13:21:44 -07:00
|
|
|
case JSOP_CALLPROP: {
|
|
|
|
/*
|
|
|
|
* The second value pushed by CALLPROP is the same as its popped
|
|
|
|
* value. We don't do this folding during the SSA analysis itself
|
|
|
|
* as we still need to distinguish the two values during type
|
|
|
|
* inference --- any popped null or undefined value will throw an
|
|
|
|
* exception, and not actually end up in the pushed set.
|
|
|
|
*/
|
|
|
|
if (v.pushedIndex() == 1) {
|
|
|
|
ScriptAnalysis *analysis = frame.script->analysis(cx);
|
|
|
|
return foldValue(CrossSSAValue(cv.frame, analysis->poppedValue(pc, 0)));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-05 13:59:29 -07:00
|
|
|
default:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cv;
|
|
|
|
}
|
|
|
|
|
2011-04-22 07:59:45 -07:00
|
|
|
#ifdef DEBUG
|
|
|
|
|
|
|
|
void
|
|
|
|
ScriptAnalysis::printSSA(JSContext *cx)
|
|
|
|
{
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
for (unsigned offset = 0; offset < script->length; offset++) {
|
|
|
|
Bytecode *code = maybeCode(offset);
|
|
|
|
if (!code)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
jsbytecode *pc = script->code + offset;
|
|
|
|
PrintBytecode(cx, script, pc);
|
|
|
|
|
|
|
|
SlotValue *newv = code->newValues;
|
|
|
|
if (newv) {
|
|
|
|
while (newv->slot) {
|
|
|
|
if (newv->value.kind() != SSAValue::PHI || newv->value.phiOffset() != offset) {
|
|
|
|
newv++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
printf(" phi ");
|
|
|
|
newv->value.print();
|
|
|
|
printf(" [");
|
|
|
|
for (unsigned i = 0; i < newv->value.phiLength(); i++) {
|
|
|
|
if (i)
|
|
|
|
printf(",");
|
|
|
|
newv->value.phiValue(i).print();
|
|
|
|
}
|
|
|
|
printf("]\n");
|
|
|
|
newv++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned nuses = GetUseCount(script, offset);
|
|
|
|
unsigned xuses = ExtendedUse(pc) ? nuses + 1 : nuses;
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < xuses; i++) {
|
|
|
|
printf(" popped%d: ", i);
|
|
|
|
code->poppedValues[i].print();
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SSAValue::print() const
|
|
|
|
{
|
|
|
|
switch (kind()) {
|
|
|
|
|
|
|
|
case EMPTY:
|
|
|
|
printf("empty");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PUSHED:
|
|
|
|
printf("pushed:%05u#%u", pushedOffset(), pushedIndex());
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VAR:
|
|
|
|
if (varInitial())
|
|
|
|
printf("initial:%u", varSlot());
|
|
|
|
else
|
|
|
|
printf("write:%05u", varOffset());
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PHI:
|
|
|
|
printf("phi:%05u#%u", phiOffset(), phiSlot());
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
JS_NOT_REACHED("Bad kind");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* DEBUG */
|
|
|
|
|
2010-10-26 12:00:54 -07:00
|
|
|
} /* namespace analyze */
|
|
|
|
} /* namespace js */
|