From 7c7bcb48fdedbaad81a8bd6428ff923bdf025eac Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Fri, 22 Apr 2011 07:59:45 -0700 Subject: [PATCH] [INFER] Bytecode SSA analysis, bug 650715. --- js/src/jsanalyze.cpp | 1176 +++++++++++++++++++-------- js/src/jsanalyze.h | 929 ++++++++++++++++----- js/src/jsarray.cpp | 4 +- js/src/jscompartment.cpp | 16 +- js/src/jscompartment.h | 9 + js/src/jsgc.cpp | 2 +- js/src/jsinfer.cpp | 1113 ++++++++++--------------- js/src/jsinfer.h | 46 +- js/src/jsinferinlines.h | 187 ++--- js/src/jsscript.cpp | 2 +- js/src/jsscript.h | 22 +- js/src/methodjit/Compiler.cpp | 580 +++++++------ js/src/methodjit/Compiler.h | 39 +- js/src/methodjit/FastArithmetic.cpp | 8 +- js/src/methodjit/FastOps.cpp | 47 +- js/src/methodjit/FrameState-inl.h | 20 +- js/src/methodjit/FrameState.cpp | 128 ++- js/src/methodjit/FrameState.h | 22 +- js/src/methodjit/InvokeHelpers.cpp | 2 - js/src/methodjit/LoopState.cpp | 344 +++----- js/src/methodjit/LoopState.h | 26 +- js/src/methodjit/MethodJIT.cpp | 29 - js/src/methodjit/PolyIC.cpp | 8 +- js/src/methodjit/Retcon.cpp | 4 - js/src/methodjit/StubCalls.cpp | 25 +- 25 files changed, 2585 insertions(+), 2203 deletions(-) diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index ff436950b41f..1b9ad95175d5 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -45,29 +45,22 @@ #include "jsinferinlines.h" #include "jsobjinlines.h" +void +JSScript::makeAnalysis(JSContext *cx) +{ + JS_ASSERT(!analysis_); + analysis_ = cx->new_(this); +} + namespace js { namespace analyze { -///////////////////////////////////////////////////////////////////// -// Script -///////////////////////////////////////////////////////////////////// - -Script::Script() -{ - PodZero(this); -} - -Script::~Script() -{ - JS_FinishArenaPool(&pool); -} - ///////////////////////////////////////////////////////////////////// // Bytecode ///////////////////////////////////////////////////////////////////// bool -Bytecode::mergeDefines(JSContext *cx, Script *script, bool initial, +Bytecode::mergeDefines(JSContext *cx, ScriptAnalysis *script, bool initial, unsigned newDepth, uint32 *newArray, unsigned newCount) { if (initial) { @@ -123,7 +116,8 @@ Bytecode::mergeDefines(JSContext *cx, Script *script, bool initial, * with progressively smaller sets of defined variables. */ if (!owned) { - uint32 *reallocArray = ArenaArray(script->pool, defineCount); + uint32 *reallocArray = + ArenaArray(cx->compartment->pool, defineCount); if (!reallocArray) { script->setOOM(cx); return false; @@ -142,21 +136,35 @@ Bytecode::mergeDefines(JSContext *cx, Script *script, bool initial, return true; } +#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 + ///////////////////////////////////////////////////////////////////// -// Analysis +// Bytecode Analysis ///////////////////////////////////////////////////////////////////// inline bool -Script::addJump(JSContext *cx, unsigned offset, - unsigned *currentOffset, unsigned *forwardJump, - unsigned stackDepth, uint32 *defineArray, unsigned defineCount) +ScriptAnalysis::addJump(JSContext *cx, unsigned offset, + unsigned *currentOffset, unsigned *forwardJump, + unsigned stackDepth, uint32 *defineArray, unsigned defineCount) { JS_ASSERT(offset < script->length); Bytecode *&code = codeArray[offset]; bool initial = (code == NULL); if (initial) { - code = ArenaNew(pool); + code = ArenaNew(cx->compartment->pool); if (!code) { setOOM(cx); return false; @@ -168,6 +176,10 @@ Script::addJump(JSContext *cx, unsigned offset, code->jumpTarget = true; if (offset < *currentOffset) { + JSOp op = JSOp(script->code[offset]); + if (op == JSOP_TRACE || op == JSOP_NOTRACE) + code->loopHead = true; + /* Scripts containing loops are never inlined. */ isInlineable = false; @@ -185,9 +197,9 @@ Script::addJump(JSContext *cx, unsigned offset, } inline void -Script::setLocal(uint32 local, uint32 offset) +ScriptAnalysis::setLocal(uint32 local, uint32 offset) { - JS_ASSERT(local < localCount()); + JS_ASSERT(local < script->nfixed); JS_ASSERT(offset != LOCAL_CONDITIONALLY_DEFINED); /* @@ -203,19 +215,10 @@ Script::setLocal(uint32 local, uint32 offset) * 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); + JS_ASSERT(definedLocals[local] == LOCAL_CONDITIONALLY_DEFINED || + definedLocals[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); + definedLocals[local] = offset; } // return whether op bytecodes do not fallthrough (they may do a jump). @@ -247,62 +250,69 @@ BytecodeNoFallThrough(JSOp op) } void -Script::analyze(JSContext *cx, JSScript *script) +ScriptAnalysis::analyzeBytecode(JSContext *cx) { - JS_InitArenaPool(&pool, "script_analyze", 256, 8, NULL); - - JS_ASSERT(script && !codeArray && !locals); - this->script = script; + JS_ASSERT(cx->compartment->activeAnalysis); + JS_ASSERT(!ranBytecode()); + JSArenaPool &pool = cx->compartment->pool; unsigned length = script->length; unsigned nargs = script->fun ? script->fun->nargs : 0; - unsigned nfixed = localCount(); + + numSlots = TotalSlots(script); codeArray = ArenaArray(pool, length); - locals = ArenaArray(pool, nfixed); - closedArgs = ArenaArray(pool, nargs); - closedVars = ArenaArray(pool, nfixed); + definedLocals = ArenaArray(pool, script->nfixed); + escapedSlots = ArenaArray(pool, numSlots); - if (!codeArray || !locals || !closedArgs || !closedVars) { + if (!codeArray || !definedLocals || !escapedSlots) { setOOM(cx); return; } PodZero(codeArray, length); - for (unsigned i = 0; i < nfixed; i++) - locals[i] = LOCAL_CONDITIONALLY_DEFINED; - - PodZero(closedArgs, nargs); - for (uint32 i = 0; i < script->nClosedArgs; i++) { - unsigned arg = script->getClosedArg(i); - JS_ASSERT(arg < nargs); - closedArgs[arg] = true; - } - - PodZero(closedVars, nfixed); - for (uint32 i = 0; i < script->nClosedVars; i++) { - unsigned local = script->getClosedVar(i); - if (local < nfixed) - closedVars[local] = true; - } + for (unsigned i = 0; i < script->nfixed; i++) + definedLocals[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. + * 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. */ - if (script->usesEval || cx->compartment->debugMode) { - for (uint32 i = 0; i < nfixed; i++) - setLocal(i, LOCAL_USE_BEFORE_DEF); + 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; + } } - for (uint32 i = 0; i < script->nClosedVars; i++) { - uint32 slot = script->getClosedVar(i); - if (slot < nfixed) - setLocal(slot, LOCAL_USE_BEFORE_DEF); + 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); + } } + /* 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); + /* * If the script is in debug mode, JS_SetFrameReturnValue can be called at * any safe point. @@ -317,6 +327,8 @@ Script::analyze(JSContext *cx, JSScript *script) isInlineable = false; } + canTrackVars = true; + /* * 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 @@ -386,7 +398,7 @@ Script::analyze(JSContext *cx, JSScript *script) if (untrap.trap) { code->safePoint = true; - isInlineable = false; + isInlineable = canTrackVars = false; } unsigned stackDepth = code->stackDepth; @@ -405,10 +417,10 @@ Script::analyze(JSContext *cx, JSScript *script) */ 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) + JS_ASSERT_IF(definedLocals[local] != LOCAL_CONDITIONALLY_DEFINED && + definedLocals[local] != LOCAL_USE_BEFORE_DEF, + definedLocals[local] <= offset); + if (definedLocals[local] == LOCAL_CONDITIONALLY_DEFINED) setLocal(local, offset); } defineArray = code->defineArray = NULL; @@ -444,6 +456,13 @@ Script::analyze(JSContext *cx, JSScript *script) isInlineable = false; break; + case JSOP_DEFFUN: + case JSOP_DEFVAR: + case JSOP_DEFCONST: + case JSOP_SETCONST: + isInlineable = canTrackVars = false; + break; + case JSOP_THIS: usesThis = true; break; @@ -456,7 +475,7 @@ Script::analyze(JSContext *cx, JSScript *script) case JSOP_TABLESWITCH: case JSOP_TABLESWITCHX: { - isInlineable = false; + isInlineable = canTrackVars = false; jsbytecode *pc2 = pc; unsigned jmplen = (op == JSOP_TABLESWITCH) ? JUMP_OFFSET_LEN : JUMPX_OFFSET_LEN; unsigned defaultOffset = offset + GetJumpOffset(pc, pc2); @@ -490,7 +509,7 @@ Script::analyze(JSContext *cx, JSScript *script) case JSOP_LOOKUPSWITCH: case JSOP_LOOKUPSWITCHX: { - isInlineable = false; + isInlineable = canTrackVars = false; jsbytecode *pc2 = pc; unsigned jmplen = (op == JSOP_LOOKUPSWITCH) ? JUMP_OFFSET_LEN : JUMPX_OFFSET_LEN; unsigned defaultOffset = offset + GetJumpOffset(pc, pc2); @@ -527,7 +546,7 @@ Script::analyze(JSContext *cx, JSScript *script) * 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. */ - isInlineable = false; + isInlineable = canTrackVars = false; JSTryNote *tn = script->trynotes()->vector; JSTryNote *tnlimit = tn + script->trynotes()->length; for (; tn < tnlimit; tn++) { @@ -560,7 +579,7 @@ Script::analyze(JSContext *cx, JSScript *script) */ if (pc[JSOP_GETLOCAL_LENGTH] != JSOP_POP) { uint32 local = GET_SLOTNO(pc); - if (local < nfixed && !localDefined(local, offset)) { + if (local < script->nfixed && !localDefined(local, offset)) { setLocal(local, LOCAL_USE_BEFORE_DEF); isInlineable = false; } @@ -573,7 +592,7 @@ Script::analyze(JSContext *cx, JSScript *script) case JSOP_LOCALINC: case JSOP_LOCALDEC: { uint32 local = GET_SLOTNO(pc); - if (local < nfixed && !localDefined(local, offset)) { + if (local < script->nfixed && !localDefined(local, offset)) { setLocal(local, LOCAL_USE_BEFORE_DEF); isInlineable = false; } @@ -591,7 +610,7 @@ Script::analyze(JSContext *cx, JSScript *script) * will not treat the variable as ever being defined in the loop body * (see setLocal). */ - if (local < nfixed && locals[local] == LOCAL_CONDITIONALLY_DEFINED) { + if (local < script->nfixed && definedLocals[local] == LOCAL_CONDITIONALLY_DEFINED) { if (forwardJump) { /* Add this local to the variables defined after this bytecode. */ uint32 *newArray = ArenaArray(pool, defineCount + 1); @@ -622,10 +641,6 @@ Script::analyze(JSContext *cx, JSScript *script) case JSOP_ARGDEC: case JSOP_THROW: case JSOP_EXCEPTION: - case JSOP_DEFFUN: - case JSOP_DEFVAR: - case JSOP_DEFCONST: - case JSOP_SETCONST: case JSOP_DEFLOCALFUN: case JSOP_DEFLOCALFUN_FC: case JSOP_LAMBDA: @@ -715,132 +730,51 @@ Script::analyze(JSContext *cx, JSScript *script) JS_ASSERT(!failed()); JS_ASSERT(forwardJump == 0 && forwardCatch == 0); + + ranBytecode_ = true; } ///////////////////////////////////////////////////////////////////// -// Stack Analysis +// Lifetime Analysis ///////////////////////////////////////////////////////////////////// -bool -StackAnalysis::analyze(JSArenaPool &pool, JSScript *script, - uint32 start, uint32 length, Script *analysis) +void +ScriptAnalysis::analyzeLifetimes(JSContext *cx) { - this->script = script; - this->start = start; - this->length = length; + JS_ASSERT(cx->compartment->activeAnalysis && !ranLifetimes() && !failed()); - poppedArray = ArenaArray(pool, length); - if (!poppedArray) - return false; - PodZero(poppedArray, length); - - PoppedValue *stack = ArenaArray(pool, script->nslots - script->nfixed); - if (!stack) - return false; - - unsigned depth = analysis->getCode(start).stackDepth; - for (unsigned i = 0; i < depth; i++) - stack[i].reset(); - - unsigned offset = start; - while (offset < start + length) { - jsbytecode *pc = script->code + offset; - uint32 successorOffset = offset + GetBytecodeLength(pc); - - Bytecode *code = analysis->maybeCode(pc); - if (!code) { - offset = successorOffset; - continue; - } - - for (unsigned i = depth; i < code->stackDepth; i++) - stack[i].reset(); - depth = code->stackDepth; - - if (code->jumpTarget) { - for (unsigned i = 0; i < depth; i++) - stack[i].reset(); - } - - unsigned nuses = GetUseCount(script, offset); - unsigned ndefs = GetDefCount(script, offset); - - if (nuses) { - PoppedValue *popped = ArenaArray(pool, nuses); - if (!popped) - return false; - for (unsigned i = 0; i < nuses; i++) - popped[i] = stack[depth - 1 - i]; - poppedArray[offset - start] = popped; - } - - for (unsigned i = 0; i < ndefs; i++) { - PoppedValue &value = stack[depth - nuses + i]; - value.offset = offset; - value.which = i; - } - - depth -= nuses; - depth += ndefs; - - offset = successorOffset; + if (!ranBytecode()) { + analyzeBytecode(cx); + if (failed()) + return; } - return true; -} + JSArenaPool &pool = cx->compartment->pool; -///////////////////////////////////////////////////////////////////// -// Live Range Analysis -///////////////////////////////////////////////////////////////////// + lifetimes = ArenaArray(pool, numSlots); + if (!lifetimes) { + setOOM(cx); + return; + } + PodZero(lifetimes, numSlots); -LifetimeScript::LifetimeScript() -{ - PodZero(this); -} + /* + * 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; -LifetimeScript::~LifetimeScript() -{ - JS_FinishArenaPool(&pool); -} - -bool -LifetimeScript::analyze(JSContext *cx, analyze::Script *analysis, JSScript *script) -{ - JS_ASSERT(analysis->hasAnalyzed() && !analysis->failed()); - - JS_InitArenaPool(&pool, "script_liverange", 256, 8, NULL); - - this->analysis = analysis; - this->script = script; - - codeArray = ArenaArray(pool, script->length); - if (!codeArray) - return false; - PodZero(codeArray, script->length); - - unsigned nfixed = analysis->localCount(); - unsigned nargs = script->fun ? script->fun->nargs : 0; - - nLifetimes = 2 + nargs + nfixed; - lifetimes = ArenaArray(pool, nLifetimes); - if (!lifetimes) - return false; - PodZero(lifetimes, nLifetimes); - - LifetimeVariable *thisVar = lifetimes + 1; - LifetimeVariable *args = lifetimes + 2; - LifetimeVariable *locals = lifetimes + 2 + nargs; - - saved = ArenaArray(pool, nLifetimes); - if (!saved) - return false; - savedCount = 0; - - LifetimeLoop *loop = NULL; + LoopAnalysis *loop = NULL; uint32 offset = script->length - 1; while (offset < script->length) { - Bytecode *code = analysis->maybeCode(offset); + Bytecode *code = maybeCode(offset); if (!code) { offset--; continue; @@ -851,22 +785,18 @@ LifetimeScript::analyze(JSContext *cx, analyze::Script *analysis, JSScript *scri UntrapOpcode untrap(cx, script, script->code + offset); - if (codeArray[offset].loop) { + if (code->loop) { /* * 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. */ - JS_ASSERT(loop == codeArray[offset].loop); - unsigned backedge = codeArray[offset].loop->backedge; - for (unsigned i = 0; i < nfixed; i++) { - if (locals[i].lifetime && !extendVariable(cx, locals[i], offset, backedge)) - return false; - } - for (unsigned i = 0; i < nargs; i++) { - if (args[i].lifetime && !extendVariable(cx, args[i], offset, backedge)) - return false; + 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); } loop = loop->parent; @@ -882,80 +812,45 @@ LifetimeScript::analyze(JSContext *cx, analyze::Script *analysis, JSScript *scri switch (op) { case JSOP_GETARG: - case JSOP_CALLARG: { - unsigned arg = GET_ARGNO(pc); - if (!analysis->argEscapes(arg)) { - if (!addVariable(cx, args[arg], offset)) - return false; - } + case JSOP_CALLARG: + case JSOP_GETLOCAL: + case JSOP_CALLLOCAL: + case JSOP_THIS: { + uint32 slot = GetBytecodeSlot(script, pc); + if (!slotEscapes(slot)) + addVariable(cx, lifetimes[slot], offset, saved, savedCount); break; } - case JSOP_SETARG: { - unsigned arg = GET_ARGNO(pc); - if (!analysis->argEscapes(arg)) { - if (!killVariable(cx, args[arg], offset)) - return false; - } + case JSOP_SETARG: + case JSOP_SETLOCAL: + case JSOP_SETLOCALPOP: + 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); break; } case JSOP_INCARG: case JSOP_DECARG: case JSOP_ARGINC: - case JSOP_ARGDEC: { - unsigned arg = GET_ARGNO(pc); - if (!analysis->argEscapes(arg)) { - if (!killVariable(cx, args[arg], offset)) - return false; - if (!addVariable(cx, args[arg], offset)) - return false; - } - break; - } - - case JSOP_GETLOCAL: - case JSOP_CALLLOCAL: { - unsigned local = GET_SLOTNO(pc); - if (!analysis->localEscapes(local)) { - JS_ASSERT(local < nfixed); - if (!addVariable(cx, locals[local], offset)) - return false; - } - break; - } - - case JSOP_SETLOCAL: - case JSOP_SETLOCALPOP: - case JSOP_DEFLOCALFUN: { - unsigned local = GET_SLOTNO(pc); - if (!analysis->localEscapes(local)) { - JS_ASSERT(local < nfixed); - if (!killVariable(cx, locals[local], offset)) - return false; - } - break; - } - + case JSOP_ARGDEC: case JSOP_INCLOCAL: case JSOP_DECLOCAL: case JSOP_LOCALINC: case JSOP_LOCALDEC: { - unsigned local = GET_SLOTNO(pc); - if (!analysis->localEscapes(local)) { - if (!killVariable(cx, locals[local], offset)) - return false; - if (!addVariable(cx, locals[local], offset)) - return false; + uint32 slot = GetBytecodeSlot(script, pc); + if (!slotEscapes(slot)) { + killVariable(cx, lifetimes[slot], offset, saved, savedCount); + addVariable(cx, lifetimes[slot], offset, saved, savedCount); } break; } - case JSOP_THIS: - if (!addVariable(cx, *thisVar, offset)) - return false; - break; - case JSOP_IFEQ: case JSOP_IFEQX: case JSOP_IFNE: @@ -965,78 +860,75 @@ LifetimeScript::analyze(JSContext *cx, analyze::Script *analysis, JSScript *scri case JSOP_AND: case JSOP_ANDX: case JSOP_GOTO: - case JSOP_GOTOX: { + case JSOP_GOTOX: + case JSOP_ENDFILTER: { /* * 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. */ - uint32 targetOffset = offset + GetJumpOffset(pc, pc); + 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; + if (targetOffset < offset) { + /* This is a loop back edge, no lifetime to pull in yet. */ JSOp nop = JSOp(script->code[targetOffset]); - if (nop == JSOP_GOTO || nop == JSOP_GOTOX) { - /* This is a continue, short circuit the backwards goto. */ - jsbytecode *target = script->code + targetOffset; - targetOffset = targetOffset + GetJumpOffset(target, target); + JS_ASSERT(nop == JSOP_TRACE || nop == JSOP_NOTRACE); - /* - * Unless this is continuing an outer loop, it is a jump to - * the entry offset separate from the initial jump. - * Prune the last block in the loop. - */ - JS_ASSERT(loop); - if (loop->entry == targetOffset && loop->entry > loop->lastBlock) - loop->lastBlock = loop->entry; - } else { - /* This is a loop back edge, no lifetime to pull in yet. */ - JS_ASSERT(nop == JSOP_TRACE || nop == JSOP_NOTRACE); + /* + * 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; - /* - * 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; - - LifetimeLoop *nloop = ArenaNew(pool); - if (!nloop) - return false; - PodZero(nloop); - - if (loop) - loop->hasCallsLoops = true; - - nloop->parent = loop; - loop = nloop; - - codeArray[targetOffset].loop = loop; - loop->head = targetOffset; - loop->backedge = offset; - loop->lastBlock = loop->head; - - /* - * 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 (!analysis->maybeCode(entry)); - - jsbytecode *entrypc = script->code + entry; - if (JSOp(*entrypc) == JSOP_GOTO || JSOp(*entrypc) == JSOP_GOTOX) - loop->entry = entry + GetJumpOffset(entrypc, entrypc); - else - loop->entry = targetOffset; - } else { - /* Do-while loop at the start of the script. */ - loop->entry = targetOffset; - } - - break; + LoopAnalysis *nloop = ArenaNew(pool); + if (!nloop) { + cx->free_(saved); + setOOM(cx); + return; } + PodZero(nloop); + + if (loop) + loop->hasCallsLoops = true; + + nloop->parent = loop; + loop = nloop; + + getCode(targetOffset).loop = loop; + loop->head = targetOffset; + loop->backedge = offset; + loop->lastBlock = loop->head; + + /* + * 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 + loop->entry = targetOffset; + } else { + /* Do-while loop at the start of the script. */ + loop->entry = targetOffset; + } + + break; } for (unsigned i = 0; i < savedCount; i++) { LifetimeVariable &var = *saved[i]; @@ -1047,8 +939,11 @@ LifetimeScript::analyze(JSContext *cx, analyze::Script *analysis, JSScript *scri * lifetime segment for the variable. */ var.lifetime = ArenaNew(pool, offset, var.savedEnd, var.saved); - if (!var.lifetime) - return false; + if (!var.lifetime) { + cx->free_(saved); + setOOM(cx); + return; + } var.saved = NULL; saved[i--] = saved[--savedCount]; } else if (loop && !var.savedEnd) { @@ -1073,8 +968,11 @@ LifetimeScript::analyze(JSContext *cx, analyze::Script *analysis, JSScript *scri for (unsigned i = 0; i < savedCount; i++) { LifetimeVariable &var = *saved[i]; var.lifetime = ArenaNew(pool, offset, var.savedEnd, var.saved); - if (!var.lifetime) - return false; + if (!var.lifetime) { + cx->free_(saved); + setOOM(cx); + return; + } var.saved = NULL; saved[i--] = saved[--savedCount]; } @@ -1096,14 +994,16 @@ LifetimeScript::analyze(JSContext *cx, analyze::Script *analysis, JSScript *scri offset--; } - return true; + cx->free_(saved); + + ranLifetimes_ = true; } #ifdef DEBUG void -LifetimeScript::dumpVariable(LifetimeVariable &var) +LifetimeVariable::print() const { - Lifetime *segment = var.lifetime ? var.lifetime : var.saved; + Lifetime *segment = lifetime ? lifetime : saved; while (segment) { printf(" (%u,%u%s)", segment->start, segment->end, segment->loopTail ? ",tail" : ""); segment = segment->next; @@ -1112,8 +1012,9 @@ LifetimeScript::dumpVariable(LifetimeVariable &var) } #endif /* DEBUG */ -inline bool -LifetimeScript::addVariable(JSContext *cx, LifetimeVariable &var, unsigned offset) +inline void +ScriptAnalysis::addVariable(JSContext *cx, LifetimeVariable &var, unsigned offset, + LifetimeVariable **&saved, unsigned &savedCount) { if (var.lifetime) { JS_ASSERT(offset < var.lifetime->start); @@ -1129,27 +1030,31 @@ LifetimeScript::addVariable(JSContext *cx, LifetimeVariable &var, unsigned offse } } } - var.lifetime = ArenaNew(pool, offset, var.savedEnd, var.saved); - if (!var.lifetime) - return false; + var.lifetime = ArenaNew(cx->compartment->pool, offset, var.savedEnd, var.saved); + if (!var.lifetime) { + setOOM(cx); + return; + } var.saved = NULL; } - return true; } -inline bool -LifetimeScript::killVariable(JSContext *cx, LifetimeVariable &var, unsigned offset) +inline void +ScriptAnalysis::killVariable(JSContext *cx, LifetimeVariable &var, unsigned offset, + LifetimeVariable **&saved, unsigned &savedCount) { if (!var.lifetime) { /* Make a point lifetime indicating the write. */ if (!var.saved) saved[savedCount++] = &var; - var.saved = ArenaNew(pool, offset, var.savedEnd, var.saved); - if (!var.saved) - return false; + var.saved = ArenaNew(cx->compartment->pool, offset, var.savedEnd, var.saved); + if (!var.saved) { + setOOM(cx); + return; + } var.saved->write = true; var.savedEnd = 0; - return true; + return; } JS_ASSERT(offset < var.lifetime->start); @@ -1166,12 +1071,11 @@ LifetimeScript::killVariable(JSContext *cx, LifetimeVariable &var, unsigned offs var.lifetime = NULL; saved[savedCount++] = &var; - - return true; } -inline bool -LifetimeScript::extendVariable(JSContext *cx, LifetimeVariable &var, unsigned start, unsigned end) +inline void +ScriptAnalysis::extendVariable(JSContext *cx, LifetimeVariable &var, + unsigned start, unsigned end) { JS_ASSERT(var.lifetime); var.lifetime->start = start; @@ -1215,9 +1119,11 @@ LifetimeScript::extendVariable(JSContext *cx, LifetimeVariable &var, unsigned st } JS_ASSERT(savedEnd <= end); if (savedEnd > segment->end) { - Lifetime *tail = ArenaNew(pool, savedEnd, 0, segment->next); - if (!tail) - return false; + Lifetime *tail = ArenaNew(cx->compartment->pool, savedEnd, 0, segment->next); + if (!tail) { + setOOM(cx); + return; + } tail->start = segment->end; tail->loopTail = true; @@ -1239,9 +1145,577 @@ LifetimeScript::extendVariable(JSContext *cx, LifetimeVariable &var, unsigned st segment = segment->next; } } +} +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 +///////////////////////////////////////////////////////////////////// + +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 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 *&pending = code->pendingValues; + if (pending) { + removeBranchTarget(branchTargets, offset); + } else { + pending = cx->new_< Vector >(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 *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(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(); + } + } + + stackDepth -= nuses; + + for (unsigned i = 0; i < ndefs; i++) + stack[stackDepth + i].initPushed(offset, i); + + 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: + case JSOP_CALLARG: + case JSOP_GETLOCAL: + case JSOP_CALLLOCAL: { + 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; + } + + /* 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; + + default:; + } + + uint32 type = JOF_TYPE(js_CodeSpec[op].format); + if (type == JOF_JUMP || type == JOF_JUMPX) { + unsigned targetOffset = FollowBranch(script, offset); + + 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 *&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 { + JS_ASSERT(targetOffset > offset); + pending = cx->new_< Vector >(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++) + checkPendingValue(cx, stack[i], StackSlot(script, i), pending); + + /* + * 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(cx->compartment->pool); + SSAValue *options = ArenaArray(cx->compartment->pool, PhiNodeCapacity(0)); + if (!node || !options) { + setOOM(cx); + return false; + } + node->slot = slot; + node->options = options; + pv->initPhi(offset, node); return true; } +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; + } + } + + if (node->length < PhiNodeCapacity(node->length)) { + node->options[node->length++] = v; + return; + } + + SSAValue *newOptions = ArenaArray(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) +{ + JS_ASSERT(v.kind() != SSAValue::EMPTY && pv->value.kind() != SSAValue::EMPTY); + + if (v.equals(pv->value)) + 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 *pending) +{ + JS_ASSERT(v.kind() != SSAValue::EMPTY); + + for (unsigned i = 0; i < pending->length(); i++) { + if ((*pending)[i].slot == slot) + return; + } + + if (!pending->append(SlotValue(slot, v))) + setOOM(cx); +} + +void +ScriptAnalysis::mergeBranchTarget(JSContext *cx, const SSAValue &value, uint32 slot, + const Vector &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 *pending = getCode(branchTargets[i]).pendingValues; + checkPendingValue(cx, value, slot, pending); + } +} + +void +ScriptAnalysis::removeBranchTarget(Vector &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 *pending = code.pendingValues; + code.pendingValues = NULL; + + unsigned count = pending->length(); + if (count == 0) { + cx->delete_(pending); + return; + } + + code.newValues = ArenaArray(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); +} + +#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 */ + } /* namespace analyze */ } /* namespace js */ diff --git a/js/src/jsanalyze.h b/js/src/jsanalyze.h index fc25c90be206..02f8b2224b7a 100644 --- a/js/src/jsanalyze.h +++ b/js/src/jsanalyze.h @@ -55,15 +55,53 @@ namespace js { namespace mjit { struct RegisterAllocation; } } namespace js { namespace analyze { -class Script; +/* + * There are three analyses we can perform on a JSScript, outlined below. + * The results of all three are stored in ScriptAnalysis, but the analyses + * themselves can be performed separately. Along with type inference results, + * per-script analysis results are tied to the per-compartment analysis pool + * and are freed on every garbage collection. + * + * - Basic bytecode analysis. For each bytecode, determine the stack depth at + * that point and control flow information needed for compilation. Also does + * a defined-variables analysis to look for local variables which have uses + * before definitions. + * + * - Lifetime analysis. Makes a backwards pass over the script to approximate + * the regions where each variable is live, avoiding a full fixpointing + * live-variables pass. This is based on the algorithm described in: + * + * "Quality and Speed in Linear-scan Register Allocation" + * Traub et. al. + * PLDI, 1998 + * + * - SSA analysis of the script's variables and stack values. For each stack + * value popped and non-escaping local variable or argument read, determines + * which push(es) or write(s) produced that value. + * + * Intermediate type inference results are additionally stored here. The above + * analyses are independent from type inference. + */ + +class SSAValue; +struct LoopAnalysis; +struct SlotValue; /* Information about a bytecode instruction. */ -struct Bytecode +class Bytecode { - friend class Script; + friend class ScriptAnalysis; + + public: + Bytecode() { PodZero(this); } + + /* --------- Bytecode analysis --------- */ /* Whether there are any incoming jumps to this instruction. */ - bool jumpTarget : 1; + bool jumpTarget : 1; + + /* There is a backwards jump to this instruction. */ + bool loopHead : 1; /* Whether there is fallthrough to this instruction from a non-branching instruction. */ bool fallthrough : 1; @@ -71,7 +109,7 @@ struct Bytecode /* Whether this instruction is the fall through point of a conditional jump. */ bool jumpFallthrough : 1; - /* Whether this instruction can be branched to from a switch statement. Implies jumpTarget. */ + /* Whether this instruction can be branched to from a switch statement. Implies jumpTarget. */ bool switchTarget : 1; /* Whether this instruction has been analyzed to get its output defines and stack. */ @@ -89,20 +127,59 @@ struct Bytecode /* Stack depth before this opcode. */ uint32 stackDepth; + private: /* - * The set of locals defined at this point. This does not include locals which + * 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); - } + /* --------- Lifetime analysis --------- */ - private: - bool mergeDefines(JSContext *cx, Script *script, bool initial, + /* If this is a loop head, information about the loop. */ + LoopAnalysis *loop; + + /* Any allocation computed downstream for this bytecode. */ + mjit::RegisterAllocation *allocation; + + /* --------- SSA analysis --------- */ + + /* Generated location of each value popped by this bytecode. */ + SSAValue *poppedValues; + + union { + /* + * If this is a join point (implies jumpTarget), any slots at this + * point which can have a different values than at the immediate + * predecessor in the bytecode. Array is terminated by an entry with + * a zero slot. + */ + SlotValue *newValues; + + /* + * Vector used during SSA analysis to store values in need of merging + * at this point. If this has incoming forward jumps and we have not + * yet reached this point, stores values for entries on the stack and + * for variables which have changed since the branch. If this is a loop + * head and we haven't reached the back edge yet, stores loop phi nodes + * for variables and entries live at the head of the loop. + */ + Vector *pendingValues; + }; + + /* --------- Type inference --------- */ + + /* + * Types for all values pushed by this bytecode. Low bit is set for + * bytecodes which are monitored (side effects were not determined + * statically). + */ + types::TypeSet *pushedTypes; + + /* --------- Helpers --------- */ + + bool mergeDefines(JSContext *cx, ScriptAnalysis *script, bool initial, uint32 newDepth, uint32 *newArray, uint32 newCount); /* Whether a local variable is in the define set at this bytecode. */ @@ -117,130 +194,6 @@ struct Bytecode } }; -/* Information about a script. */ -class Script -{ - friend struct Bytecode; - - JSScript *script; - - Bytecode **codeArray; - - /* 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; - bool usesThis; - bool hasCalls; - - bool isInlineable; - - JSPackedBool *closedVars; - JSPackedBool *closedArgs; - - public: - /* Pool for allocating analysis structures which will not outlive this script. */ - JSArenaPool pool; - - Script(); - ~Script(); - - void analyze(JSContext *cx, JSScript *script); - - bool OOM() { return outOfMemory; } - bool failed() { return hadFailure; } - bool inlineable(uint32 argc) { return isInlineable && argc == script->fun->nargs; } - - /* 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; } - - bool usesThisValue() const { return usesThis; } - bool hasFunctionCalls() const { return hasCalls; } - - bool hasAnalyzed() const { return !!codeArray; } - JSScript *getScript() const { return script; } - - /* Accessors for bytecode information. */ - - Bytecode& getCode(uint32 offset) { - JS_ASSERT(offset < script->length); - JS_ASSERT(codeArray[offset]); - return *codeArray[offset]; - } - Bytecode& getCode(const jsbytecode *pc) { return getCode(pc - script->code); } - - Bytecode* maybeCode(uint32 offset) { - JS_ASSERT(offset < script->length); - return codeArray[offset]; - } - Bytecode* maybeCode(const jsbytecode *pc) { return maybeCode(pc - script->code); } - - bool jumpTarget(uint32 offset) { - JS_ASSERT(offset < script->length); - return codeArray[offset] && codeArray[offset]->jumpTarget; - } - bool jumpTarget(const 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(!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); - } - - bool argEscapes(unsigned arg) - { - JS_ASSERT(script->fun && arg < script->fun->nargs); - return script->usesEval || script->usesArguments || script->compartment->debugMode || - closedArgs[arg]; - } - - bool localEscapes(unsigned local) - { - return script->usesEval || script->compartment->debugMode || local >= localCount() || - closedVars[local]; - } - - 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); -}; - static inline unsigned GetBytecodeLength(jsbytecode *pc) { @@ -263,6 +216,57 @@ GetUseCount(JSScript *script, unsigned offset) return js_CodeSpec[*pc].nuses; } +/* + * For opcodes which access local variables or arguments, we track an extra + * use during SSA analysis for the value of the variable before/after the op. + */ +static inline bool +ExtendedUse(jsbytecode *pc) +{ + switch ((JSOp)*pc) { + case JSOP_SETARG: + case JSOP_GETARG: + case JSOP_CALLARG: + case JSOP_INCARG: + case JSOP_DECARG: + case JSOP_ARGINC: + case JSOP_ARGDEC: + case JSOP_SETLOCAL: + case JSOP_SETLOCALPOP: + case JSOP_GETLOCAL: + case JSOP_CALLLOCAL: + case JSOP_DEFLOCALFUN: + case JSOP_DEFLOCALFUN_FC: + case JSOP_INCLOCAL: + case JSOP_DECLOCAL: + case JSOP_LOCALINC: + case JSOP_LOCALDEC: + return true; + default: + return false; + } +} + +/* + * For opcodes which assign to a local variable or argument but don't push a + * value with the same type set, we track an extra def during type inference + * for the value assigned here. + */ +static inline bool +ExtendedDef(jsbytecode *pc) +{ + switch ((JSOp)*pc) { + case JSOP_DEFLOCALFUN: + case JSOP_DEFLOCALFUN_FC: + case JSOP_SETLOCALPOP: + case JSOP_FORARG: + case JSOP_FORLOCAL: + return true; + default: + return false; + } +} + static inline unsigned GetDefCount(JSScript *script, unsigned offset) { @@ -288,6 +292,35 @@ GetDefCount(JSScript *script, unsigned 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 +FollowBranch(JSScript *script, unsigned offset) +{ + /* + * Get the target offset of a branch. For GOTO opcodes implementing + * 'continue' statements, short circuit any artificial backwards jump + * inserted by the emitter. + */ + jsbytecode *pc = script->code + offset; + unsigned targetOffset = offset + GetJumpOffset(pc, pc); + if (targetOffset < offset) { + JSOp nop = JSOp(script->code[targetOffset]); + if (nop == JSOP_GOTO || nop == JSOP_GOTOX) { + jsbytecode *target = script->code + targetOffset; + return targetOffset + GetJumpOffset(target, target); + } + } + return targetOffset; +} + static inline JSOp ReverseCompareOp(JSOp op) { @@ -326,48 +359,78 @@ struct UntrapOpcode } }; -/* - * Analysis over a range of bytecode to determine where each popped value was - * originally pushed. - */ -struct StackAnalysis +/* Common representation of slots throughout analyses and the compiler. */ +static inline uint32 CalleeSlot() { + return 0; +} +static inline uint32 ThisSlot() { + return 1; +} +static inline uint32 ArgSlot(uint32 arg) { + return 2 + arg; +} +static inline uint32 LocalSlot(JSScript *script, uint32 local) { + return 2 + (script->fun ? script->fun->nargs : 0) + local; +} +static inline uint32 TotalSlots(JSScript *script) { + return 2 + (script->fun ? script->fun->nargs : 0) + script->nfixed; +} + +static inline uint32 StackSlot(JSScript *script, uint32 index) { + return TotalSlots(script) + index; +} + +static inline uint32 GetBytecodeSlot(JSScript *script, jsbytecode *pc) { - /* For values whose pushed location is not known. */ - static const uint32 UNKNOWN_PUSHED = uint32(-1); + switch (JSOp(*pc)) { - struct PoppedValue { - uint32 offset; - uint32 which; - void reset() { offset = UNKNOWN_PUSHED; which = 0; } - }; + case JSOP_GETARG: + case JSOP_CALLARG: + case JSOP_SETARG: + case JSOP_INCARG: + case JSOP_DECARG: + case JSOP_ARGINC: + case JSOP_ARGDEC: + case JSOP_FORARG: + return ArgSlot(GET_SLOTNO(pc)); - PoppedValue **poppedArray; - uint32 start; - uint32 length; + case JSOP_GETLOCAL: + case JSOP_CALLLOCAL: + case JSOP_SETLOCAL: + case JSOP_SETLOCALPOP: + case JSOP_DEFLOCALFUN: + case JSOP_DEFLOCALFUN_FC: + case JSOP_INCLOCAL: + case JSOP_DECLOCAL: + case JSOP_LOCALINC: + case JSOP_LOCALDEC: + case JSOP_FORLOCAL: + return LocalSlot(script, GET_SLOTNO(pc)); - JSScript *script; + case JSOP_THIS: + return ThisSlot(); - bool analyze(JSArenaPool &pool, JSScript *script, uint32 start, uint32 length, - Script *analysis); - - const PoppedValue &popped(uint32 offset, uint32 which) { - return poppedArray[offset - start][which]; + default: + JS_NOT_REACHED("Bad slot opcode"); + return 0; } - const PoppedValue &popped(jsbytecode *pc, uint32 which) { - return popped(pc - script->code, which); - } -}; +} -/* - * Lifetime analysis. The goal of this analysis is to make a single backwards pass - * over a script to approximate the regions where each variable is live, without - * doing a full fixpointing live-variables pass. This is based on the algorithm - * described in: - * - * "Quality and Speed in Linear-scan Register Allocation" - * Traub et. al. - * PLDI, 1998 - */ +static inline int32 +GetBytecodeInteger(jsbytecode *pc) +{ + switch (JSOp(*pc)) { + case JSOP_ZERO: return 0; + case JSOP_ONE: return 1; + case JSOP_UINT16: return GET_UINT16(pc); + case JSOP_UINT24: return GET_UINT24(pc); + case JSOP_INT8: return GET_INT8(pc); + case JSOP_INT32: return GET_INT32(pc); + default: + JS_NOT_REACHED("Bad op"); + return 0; + } +} /* * Information about the lifetime of a local or argument. These form a linked @@ -413,11 +476,11 @@ struct Lifetime {} }; -/* Lifetime and modset information for a loop. */ -struct LifetimeLoop +/* Basic information for a loop. */ +struct LoopAnalysis { /* Any loop this one is nested in. */ - LifetimeLoop *parent; + LoopAnalysis *parent; /* Offset of the head of the loop. */ uint32 head; @@ -449,16 +512,6 @@ struct LifetimeLoop bool hasCallsLoops; }; -/* Lifetime and register information for a bytecode. */ -struct LifetimeBytecode -{ - /* If this is a loop head, information about the loop. */ - LifetimeLoop *loop; - - /* Any allocation computed downstream for this bytecode. */ - mjit::RegisterAllocation *allocation; -}; - /* Current lifetime information for a variable. */ struct LifetimeVariable { @@ -472,7 +525,7 @@ struct LifetimeVariable uint32 savedEnd; /* Whether this variable is live at offset. */ - Lifetime * live(uint32 offset) { + Lifetime * live(uint32 offset) const { if (lifetime && lifetime->end >= offset) return lifetime; Lifetime *segment = lifetime ? lifetime : saved; @@ -488,7 +541,7 @@ struct LifetimeVariable * Get the offset of the first write to the variable in the body of a loop, * -1 if the loop never writes the variable. */ - uint32 firstWrite(LifetimeLoop *loop) { + uint32 firstWrite(LoopAnalysis *loop) const { Lifetime *segment = lifetime ? lifetime : saved; while (segment && segment->start <= loop->backedge) { if (segment->start >= loop->head && segment->write) @@ -499,7 +552,7 @@ struct LifetimeVariable } /* Return true if the variable cannot decrease during the body of a loop. */ - bool nonDecreasing(JSScript *script, LifetimeLoop *loop) { + bool nonDecreasing(JSScript *script, LoopAnalysis *loop) const { Lifetime *segment = lifetime ? lifetime : saved; while (segment && segment->start <= loop->backedge) { if (segment->start >= loop->head && segment->write) { @@ -522,7 +575,7 @@ struct LifetimeVariable * If the variable is only written once in the body of a loop, offset of * that write. -1 otherwise. */ - uint32 onlyWrite(LifetimeLoop *loop) { + uint32 onlyWrite(LoopAnalysis *loop) const { uint32 offset = uint32(-1); Lifetime *segment = lifetime ? lifetime : saved; while (segment && segment->start <= loop->backedge) { @@ -535,74 +588,498 @@ struct LifetimeVariable } return offset; } + +#ifdef DEBUG + void print() const; +#endif +}; + +struct SSAPhiNode; + +/* + * Representation of values on stack or in slots at each point in the script. + * Values are independent from the bytecode position, and mean the same thing + * everywhere in the script. SSA values are immutable, except for contents of + * the values and types in an SSAPhiNode. + */ +class SSAValue +{ + friend class ScriptAnalysis; + + public: + enum Kind { + EMPTY = 0, /* Empty entry in a phi node. */ + PUSHED, /* Value pushed by some bytecode. */ + VAR, /* Initial or written value to some argument or local. */ + PHI /* Selector for one of several values. */ + }; + + Kind kind() const { + JS_ASSERT(u.pushed.kind == u.var.kind && u.pushed.kind == u.phi.kind); + return u.pushed.kind; + } + + bool equals(const SSAValue &o) const { + return !memcmp(this, &o, sizeof(SSAValue)); + } + + /* Accessors for values pushed by a bytecode within this script. */ + + uint32 pushedOffset() const { + JS_ASSERT(kind() == PUSHED); + return u.pushed.offset; + } + + uint32 pushedIndex() const { + JS_ASSERT(kind() == PUSHED); + return u.pushed.index; + } + + /* Accessors for initial and written values of arguments and (undefined) locals. */ + + bool varInitial() const { + JS_ASSERT(kind() == VAR); + return u.var.initial; + } + + uint32 varSlot() const { + JS_ASSERT(kind() == VAR); + return u.var.slot; + } + + uint32 varOffset() const { + JS_ASSERT(!varInitial()); + return u.var.offset; + } + + /* Accessors for phi nodes. */ + + uint32 phiSlot() const; + uint32 phiLength() const; + const SSAValue &phiValue(uint32 i) const; + types::TypeSet *phiTypes() const; + + /* Offset at which this phi node was created. */ + uint32 phiOffset() const { + JS_ASSERT(kind() == PHI); + return u.phi.offset; + } + +#ifdef DEBUG + void print() const; +#endif + + private: + union { + struct { + Kind kind : 2; + uint32 offset : 30; + uint32 index; + } pushed; + struct { + Kind kind : 2; + bool initial : 1; + uint32 slot : 29; + uint32 offset; + } var; + struct { + Kind kind : 2; + uint32 offset : 30; + SSAPhiNode *node; + } phi; + } u; + + void clear() { + PodZero(this); + JS_ASSERT(kind() == EMPTY); + } + + void initPushed(uint32 offset, uint32 index) { + clear(); + u.pushed.kind = PUSHED; + u.pushed.offset = offset; + u.pushed.index = index; + } + + void initInitial(uint32 slot) { + clear(); + u.var.kind = VAR; + u.var.initial = true; + u.var.slot = slot; + } + + void initWritten(uint32 slot, uint32 offset) { + clear(); + u.var.kind = VAR; + u.var.initial = false; + u.var.slot = slot; + u.var.offset = offset; + } + + void initPhi(uint32 offset, SSAPhiNode *node) { + clear(); + u.phi.kind = PHI; + u.phi.offset = offset; + u.phi.node = node; + } + + SSAPhiNode *phiNode() const { + JS_ASSERT(kind() == PHI); + return u.phi.node; + } }; /* - * Analysis approximating variable liveness information at points in a script. - * This is separate from analyze::Script as it is computed on every compilation - * and thrown away afterwards. + * Mutable component of a phi node, with the possible values of the phi + * and the possible types of the node as determined by type inference. + * When phi nodes are copied around, any updates to the original will + * be seen by all copies made. */ -class LifetimeScript +struct SSAPhiNode { - analyze::Script *analysis; + types::TypeSet types; + uint32 slot; + uint32 length; + SSAValue *options; + SSAPhiNode() { PodZero(this); } +}; + +inline uint32 +SSAValue::phiSlot() const +{ + return u.phi.node->slot; +} + +inline uint32 +SSAValue::phiLength() const +{ + JS_ASSERT(kind() == PHI); + return u.phi.node->length; +} + +inline const SSAValue & +SSAValue::phiValue(uint32 i) const +{ + JS_ASSERT(kind() == PHI && i < phiLength()); + return u.phi.node->options[i]; +} + +inline types::TypeSet * +SSAValue::phiTypes() const +{ + JS_ASSERT(kind() == PHI); + return &u.phi.node->types; +} + +struct SlotValue +{ + uint32 slot; + SSAValue value; + SlotValue(uint32 slot, const SSAValue &value) : slot(slot), value(value) {} +}; + +/* Analysis information about a script. */ +class ScriptAnalysis +{ + friend struct Bytecode; + JSScript *script; - LifetimeBytecode *codeArray; - LifetimeVariable *lifetimes; - uint32 nLifetimes; + Bytecode **codeArray; - LifetimeVariable **saved; - unsigned savedCount; + uint32 numSlots; + + bool outOfMemory; + bool hadFailure; + + JSPackedBool *escapedSlots; + + /* Which analyses have been performed. */ + bool ranBytecode_; + bool ranSSA_; + bool ranLifetimes_; + bool ranInference_; + + /* --------- Bytecode analysis --------- */ + + bool usesRval; + bool usesScope; + bool usesThis; + bool hasCalls; + bool canTrackVars; + bool isInlineable; + + /* Offsets at which each local becomes unconditionally defined, or a value below. */ + uint32 *definedLocals; + + static const uint32 LOCAL_USE_BEFORE_DEF = uint32(-1); + static const uint32 LOCAL_CONDITIONALLY_DEFINED = uint32(-2); + + /* --------- Lifetime analysis --------- */ + + LifetimeVariable *lifetimes; public: - JSArenaPool pool; - LifetimeScript(); - ~LifetimeScript(); + ScriptAnalysis(JSScript *script) { PodZero(this); this->script = script; } - bool analyze(JSContext *cx, analyze::Script *analysis, JSScript *script); + bool ranBytecode() { return ranBytecode_; } + bool ranSSA() { return ranSSA_; } + bool ranLifetimes() { return ranLifetimes_; } + bool ranInference() { return ranInference_; } - LifetimeBytecode &getCode(uint32 offset) { - JS_ASSERT(analysis->maybeCode(offset)); + void analyzeBytecode(JSContext *cx); + void analyzeSSA(JSContext *cx); + void analyzeLifetimes(JSContext *cx); + void analyzeTypes(JSContext *cx); + + /* Analyze the effect of invoking 'new' on script. */ + void analyzeTypesNew(JSContext *cx); + + bool OOM() { return outOfMemory; } + bool failed() { return hadFailure; } + bool inlineable(uint32 argc) { return isInlineable && argc == script->fun->nargs; } + + /* 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; } + + bool usesThisValue() const { return usesThis; } + bool hasFunctionCalls() const { return hasCalls; } + + /* Accessors for bytecode information. */ + + Bytecode& getCode(uint32 offset) { + JS_ASSERT(script->compartment->activeAnalysis); + JS_ASSERT(offset < script->length); + JS_ASSERT(codeArray[offset]); + return *codeArray[offset]; + } + Bytecode& getCode(const jsbytecode *pc) { return getCode(pc - script->code); } + + Bytecode* maybeCode(uint32 offset) { + JS_ASSERT(script->compartment->activeAnalysis); + JS_ASSERT(offset < script->length); return codeArray[offset]; } - LifetimeBytecode &getCode(jsbytecode *pc) { return getCode(pc - script->code); } + Bytecode* maybeCode(const jsbytecode *pc) { return maybeCode(pc - script->code); } -#ifdef DEBUG - void dumpVariable(LifetimeVariable &var); - void dumpSlot(unsigned slot) { - JS_ASSERT(slot < nLifetimes); - dumpVariable(lifetimes[slot]); + bool jumpTarget(uint32 offset) { + JS_ASSERT(offset < script->length); + return codeArray[offset] && codeArray[offset]->jumpTarget; } -#endif + bool jumpTarget(const jsbytecode *pc) { return jumpTarget(pc - script->code); } - Lifetime * live(uint32 slot, uint32 offset) { - JS_ASSERT(slot < nLifetimes); - return lifetimes[slot].live(offset); + const SSAValue &poppedValue(uint32 offset, uint32 which) { + JS_ASSERT(offset < script->length); + JS_ASSERT_IF(script->code[offset] != JSOP_TRAP, + which < GetUseCount(script, offset) + + (ExtendedUse(script->code + offset) ? 1 : 0)); + return getCode(offset).poppedValues[which]; + } + const SSAValue &poppedValue(const jsbytecode *pc, uint32 which) { + return poppedValue(pc - script->code, which); } - uint32 firstWrite(uint32 slot, LifetimeLoop *loop) { - JS_ASSERT(slot < nLifetimes); - return lifetimes[slot].firstWrite(loop); + const SlotValue *newValues(uint32 offset) { + JS_ASSERT(offset < script->length); + return getCode(offset).newValues; + } + const SlotValue *newValues(const jsbytecode *pc) { return newValues(pc - script->code); } + + types::TypeSet *pushedTypes(uint32 offset, uint32 which = 0) { + JS_ASSERT(offset < script->length); + JS_ASSERT_IF(script->code[offset] != JSOP_TRAP, + which < GetDefCount(script, offset) + + (ExtendedDef(script->code + offset) ? 1 : 0)); + types::TypeSet *array = (types::TypeSet *) (~0x1 & (size_t) getCode(offset).pushedTypes); + JS_ASSERT(array); + return array + which; + } + types::TypeSet *pushedTypes(const jsbytecode *pc, uint32 which) { + return pushedTypes(pc - script->code, which); } - bool nonDecreasing(uint32 slot, LifetimeLoop *loop) { - JS_ASSERT(slot < nLifetimes); - return lifetimes[slot].nonDecreasing(script, loop); + inline void addPushedType(JSContext *cx, uint32 offset, uint32 which, types::jstype type); + + bool monitoredTypes(uint32 offset) { + JS_ASSERT(offset < script->length); + return 0x1 & (size_t) getCode(offset).pushedTypes; } - uint32 onlyWrite(uint32 slot, LifetimeLoop *loop) { - JS_ASSERT(slot < nLifetimes); - return lifetimes[slot].onlyWrite(loop); + void setMonitoredTypes(uint32 offset) { + JS_ASSERT(offset < script->length); + types::TypeSet *&array = getCode(offset).pushedTypes; + array = (types::TypeSet *) (0x1 | (size_t) array); } + types::TypeSet *getValueTypes(const SSAValue &v) { + switch (v.kind()) { + case SSAValue::PUSHED: + return pushedTypes(v.pushedOffset(), v.pushedIndex()); + case SSAValue::VAR: + JS_ASSERT(!slotEscapes(v.varSlot())); + if (v.varInitial()) { + return script->slotTypes(v.varSlot()); + } else { + /* + * Results of intermediate assignments have the same type as + * the first type pushed by the assignment op, except FOR* ops + * which push the iterator first. Note that this may not be the + * exact same value as was pushed, due to post-inc/dec ops. + */ + switch (script->code[v.varOffset()]) { + case JSOP_FORARG: + case JSOP_FORLOCAL: + return pushedTypes(v.varOffset(), 1); + default: + return pushedTypes(v.varOffset(), 0); + } + } + case SSAValue::PHI: + return &v.phiNode()->types; + default: + /* Cannot compute types for empty SSA values. */ + JS_NOT_REACHED("Bad SSA value"); + return NULL; + } + } + + types::TypeSet *poppedTypes(uint32 offset, uint32 which) { + return getValueTypes(poppedValue(offset, which)); + } + types::TypeSet *poppedTypes(const jsbytecode *pc, uint32 which) { + return getValueTypes(poppedValue(pc, which)); + } + + mjit::RegisterAllocation *&getAllocation(uint32 offset) { + JS_ASSERT(offset < script->length); + return getCode(offset).allocation; + } + mjit::RegisterAllocation *&getAllocation(const jsbytecode *pc) { + return getAllocation(pc - script->code); + } + + LoopAnalysis *getLoop(uint32 offset) { + JS_ASSERT(offset < script->length); + JS_ASSERT(getCode(offset).loop); + return getCode(offset).loop; + } + LoopAnalysis *getLoop(const jsbytecode *pc) { return getLoop(pc - script->code); } + + /* Accessors for local variable information. */ + + bool localHasUseBeforeDef(uint32 local) { + JS_ASSERT(!failed()); + return slotEscapes(LocalSlot(script, local)) || + definedLocals[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) || (definedLocals[local] <= offset) || + getCode(offset).isDefined(local); + } + bool localDefined(uint32 local, jsbytecode *pc) { + return localDefined(local, pc - script->code); + } + + bool slotEscapes(uint32 slot) { + JS_ASSERT(script->compartment->activeAnalysis); + if (slot >= numSlots) + return true; + return escapedSlots[slot]; + } + + /* + * Whether we distinguish different writes of this variable while doing + * SSA analysis. Escaping locals can be written in other scripts, and the + * presence of NAME opcodes, switch or try blocks keeps us from tracking + * variable values at each point. + */ + bool trackSlot(uint32 slot) { return !slotEscapes(slot) && canTrackVars; } + + const LifetimeVariable & liveness(uint32 slot) { + JS_ASSERT(script->compartment->activeAnalysis); + JS_ASSERT(!slotEscapes(slot)); + return lifetimes[slot]; + } + + void printSSA(JSContext *cx); + void printTypes(JSContext *cx); + + void clearAllocations(); + private: + void setOOM(JSContext *cx) { + if (!outOfMemory) + js_ReportOutOfMemory(cx); + outOfMemory = true; + hadFailure = true; + } - inline bool addVariable(JSContext *cx, LifetimeVariable &var, unsigned offset); - inline bool killVariable(JSContext *cx, LifetimeVariable &var, unsigned offset); - inline bool extendVariable(JSContext *cx, LifetimeVariable &var, unsigned start, unsigned end); + /* Bytecode helpers */ + inline bool addJump(JSContext *cx, unsigned offset, + unsigned *currentOffset, unsigned *forwardJump, + unsigned stackDepth, uint32 *defineArray, unsigned defineCount); + inline void setLocal(uint32 local, uint32 offset); + + /* Lifetime helpers */ + inline void addVariable(JSContext *cx, LifetimeVariable &var, unsigned offset, + LifetimeVariable **&saved, unsigned &savedCount); + inline void killVariable(JSContext *cx, LifetimeVariable &var, unsigned offset, + LifetimeVariable **&saved, unsigned &savedCount); + inline void extendVariable(JSContext *cx, LifetimeVariable &var, unsigned start, unsigned end); + + /* SSA helpers */ + bool makePhi(JSContext *cx, uint32 slot, uint32 offset, SSAValue *pv); + void insertPhi(JSContext *cx, SSAValue &phi, const SSAValue &v); + void mergeValue(JSContext *cx, uint32 offset, const SSAValue &v, SlotValue *pv); + void checkPendingValue(JSContext *cx, const SSAValue &v, uint32 slot, + Vector *pending); + void mergeBranchTarget(JSContext *cx, const SSAValue &value, uint32 slot, + const Vector &branchTargets); + void removeBranchTarget(Vector &branchTargets, uint32 offset); + void freezeNewValues(JSContext *cx, uint32 offset); + + struct TypeInferenceState { + Vector phiNodes; + bool hasGetSet; + bool hasHole; + TypeInferenceState(JSContext *cx) + : phiNodes(cx), hasGetSet(false), hasHole(false) + {} + }; + + /* Type inference helpers */ + bool analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferenceState &state); + inline void setForTypes(JSContext *cx, jsbytecode *pc, types::TypeSet *types); }; +/* Protect analysis structures from GC while they are being used. */ +struct AutoEnterAnalysis +{ + JSContext *cx; + bool oldActiveAnalysis; + + AutoEnterAnalysis(JSContext *cx) + : cx(cx), oldActiveAnalysis(cx->compartment->activeAnalysis) + { + cx->compartment->activeAnalysis = true; + } + + ~AutoEnterAnalysis() + { + cx->compartment->activeAnalysis = oldActiveAnalysis; + } +}; + +#ifdef DEBUG +void PrintBytecode(JSContext *cx, JSScript *script, jsbytecode *pc); +#endif + } /* namespace analyze */ } /* namespace js */ diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 36fad090864c..b2a23e820420 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -1492,7 +1492,7 @@ InitArrayTypes(JSContext *cx, TypeObject *type, const Value *vector, unsigned co TypeSet *types = type->getProperty(cx, JSID_VOID, true); if (!types) - return JS_FALSE; + return false; for (unsigned i = 0; i < count; i++) { if (vector[i].isMagic(JS_ARRAY_HOLE)) @@ -1500,8 +1500,6 @@ InitArrayTypes(JSContext *cx, TypeObject *type, const Value *vector, unsigned co jstype valtype = GetValueType(cx, vector[i]); types->addType(cx, valtype); } - - return cx->compartment->types.checkPendingRecompiles(cx); } return true; } diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 66459375314f..d7e24b8246d1 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -131,8 +131,12 @@ JSCompartment::init(JSContext *cx) #ifdef JS_GCMETER memset(&compartmentStats, 0, sizeof(JSGCArenaStats) * FINALIZE_LIMIT); #endif + + activeAnalysis = activeInference = false; types.init(cx); + JS_InitArenaPool(&pool, "analysis", 4096, 8, NULL); + if (!crossCompartmentWrappers.init()) return false; @@ -495,7 +499,7 @@ void JSCompartment::markTypes(JSTracer *trc) { /* Mark all scripts and type objects in the compartment. */ - JS_ASSERT(types.inferenceDepth); + JS_ASSERT(activeAnalysis); for (JSCList *cursor = scripts.next; cursor != &scripts; cursor = cursor->next) { JSScript *script = reinterpret_cast(cursor); @@ -585,7 +589,7 @@ JSCompartment::sweep(JSContext *cx, uint32 releaseInterval) #endif - if (!types.inferenceDepth && types.inferenceEnabled) { + if (!activeAnalysis && types.inferenceEnabled) { for (JSCList *cursor = scripts.next; cursor != &scripts; cursor = cursor->next) { JSScript *script = reinterpret_cast(cursor); script->condenseTypes(cx); @@ -598,12 +602,12 @@ JSCompartment::sweep(JSContext *cx, uint32 releaseInterval) for (JSCList *cursor = scripts.next; cursor != &scripts; cursor = cursor->next) { JSScript *script = reinterpret_cast(cursor); - script->sweepTypes(cx); + script->sweepAnalysis(cx); } - if (!types.inferenceDepth) { - /* Reset the inference pool, releasing all intermediate type data. */ - JS_FinishArenaPool(&types.pool); + if (!activeAnalysis) { + /* Reset the analysis pool, releasing all analysis and intermediate type data. */ + JS_FinishArenaPool(&pool); /* * Destroy eval'ed scripts, now that any type inference information referring diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index b3a7a5cc81a3..51c77ffd17f5 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -387,6 +387,15 @@ struct JS_FRIEND_API(JSCompartment) { js::gc::JSGCArenaStats compartmentStats[js::gc::FINALIZE_LIMIT]; #endif + /* + * Pool for analysis and intermediate type information in this compartment. + * Cleared on every GC, unless the GC happens during analysis (indicated + * by activeAnalysis, which is implied by activeInference). + */ + JSArenaPool pool; + bool activeAnalysis; + bool activeInference; + /* Type information about the scripts and objects in this compartment. */ js::types::TypeCompartment types; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 36880d411f2c..a22ff2dbf506 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1829,7 +1829,7 @@ MarkRuntime(JSTracer *trc) MarkContext(trc, acx); for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) { - if ((*c)->types.inferenceDepth) + if ((*c)->activeAnalysis) (*c)->markTypes(trc); #ifdef JS_TRACER (*c)->traceMonitor.mark(trc); diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 47bbeaf87e6e..f55bda2779d0 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -70,6 +70,9 @@ #include "jsxml.h" #endif +using namespace js; +using namespace js::types; + static inline jsid id_prototype(JSContext *cx) { return ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom); @@ -112,18 +115,8 @@ id_toSource(JSContext *cx) return ATOM_TO_JSID(cx->runtime->atomState.toSourceAtom); } -namespace js { -namespace types { - -static const char *js_CodeNameTwo[] = { -#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ - name, -#include "jsopcode.tbl" -#undef OPDEF -}; - const char * -TypeIdStringImpl(jsid id) +types::TypeIdStringImpl(jsid id) { if (JSID_IS_VOID(id)) return "(index)"; @@ -165,7 +158,7 @@ static bool InferSpewActive(SpewChannel channel) #ifdef DEBUG const char * -TypeString(jstype type) +types::TypeString(jstype type) { switch (type) { case TYPE_UNDEFINED: @@ -190,7 +183,8 @@ TypeString(jstype type) } } -void InferSpew(SpewChannel channel, const char *fmt, ...) +void +types::InferSpew(SpewChannel channel, const char *fmt, ...) { if (!InferSpewActive(channel)) return; @@ -229,7 +223,7 @@ TypeSetMatches(JSContext *cx, TypeSet *types, jstype type) } bool -TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &value) +types::TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &value) { /* * Check the correctness of the type information in the object's property @@ -261,15 +255,14 @@ TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &value) TypeFailure(cx, "Missing type in object %s %s: %s", obj->name(), TypeIdString(id), TypeString(type)); } - - cx->compartment->types.checkPendingRecompiles(cx); } return true; } #endif -void TypeFailure(JSContext *cx, const char *fmt, ...) +void +types::TypeFailure(JSContext *cx, const char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -288,78 +281,6 @@ void TypeFailure(JSContext *cx, const char *fmt, ...) // TypeSet ///////////////////////////////////////////////////////////////////// -/* Type state maintained during the inference pass through the script. */ - -struct AnalyzeStateStack { - TypeSet *types; - - /* Whether this node is the iterator for a 'for each' loop. */ - bool isForEach; - - /* Any active initializer. */ - TypeObject *initializer; -}; - -struct AnalyzeState { - JSContext *cx; - - analyze::Script &analysis; - JSArenaPool &pool; - - AnalyzeStateStack *stack; - - /* Current stack depth. */ - unsigned stackDepth; - - /* Stack types at join points. */ - TypeSet ***joinTypes; - - /* Last opcode was JSOP_GETTER or JSOP_SETTER. */ - bool hasGetSet; - - /* Last opcode was JSOP_HOLE. */ - bool hasHole; - - AnalyzeState(JSContext *cx, analyze::Script &analysis) - : cx(cx), analysis(analysis), pool(analysis.pool), - stack(NULL), stackDepth(0), hasGetSet(false), hasHole(false) - {} - - bool init(JSScript *script) - { - unsigned length = (script->nslots * sizeof(AnalyzeStateStack)) - + (script->length * sizeof(TypeSet**)); - unsigned char *cursor = (unsigned char *) cx->calloc_(length); - if (!cursor) - return false; - - stack = (AnalyzeStateStack *) cursor; - - cursor += (script->nslots * sizeof(AnalyzeStateStack)); - joinTypes = (TypeSet ***) cursor; - return true; - } - - ~AnalyzeState() - { - cx->free_(stack); - } - - AnalyzeStateStack &popped(unsigned i) { - JS_ASSERT(i < stackDepth); - return stack[stackDepth - 1 - i]; - } - - const AnalyzeStateStack &popped(unsigned i) const { - JS_ASSERT(i < stackDepth); - return stack[stackDepth - 1 - i]; - } -}; - -///////////////////////////////////////////////////////////////////// -// TypeSet -///////////////////////////////////////////////////////////////////// - void TypeSet::addTypeSet(JSContext *cx, ClonedTypeSet *types) { @@ -386,7 +307,7 @@ TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting) { JS_ASSERT_IF(!constraint->condensed() && !constraint->persistentObject(), constraint->script->compartment == cx->compartment); - JS_ASSERT_IF(!constraint->condensed(), cx->compartment->types.inferenceDepth); + JS_ASSERT_IF(!constraint->condensed(), cx->compartment->activeInference); JS_ASSERT_IF(typeFlags & TYPE_FLAG_INTERMEDIATE_SET, !constraint->persistentObject() && !constraint->condensed()); @@ -500,7 +421,7 @@ public: void TypeSet::addSubset(JSContext *cx, JSScript *script, TypeSet *target) { - add(cx, ArenaNew(cx->compartment->types.pool, script, target)); + add(cx, ArenaNew(cx->compartment->pool, script, target)); } /* Subset constraint not associated with a script's analysis. */ @@ -558,7 +479,7 @@ public: void checkAnalysis(JSContext *cx) { - if (script->types) { + if (script->hasAnalysis() && script->analysis(cx)->ranInference()) { /* * The script was analyzed, had the analysis collected/condensed, * and then was reanalyzed. There are other constraints specifying @@ -572,7 +493,7 @@ public: return; } - AnalyzeScriptTypes(cx, script); + script->analysis(cx)->analyzeTypes(cx); } void newType(JSContext *cx, TypeSet*, jstype) { checkAnalysis(cx); } @@ -631,14 +552,14 @@ void TypeSet::addGetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeSet *target, jsid id) { - add(cx, ArenaNew(cx->compartment->types.pool, script, pc, target, id, false)); + add(cx, ArenaNew(cx->compartment->pool, script, pc, target, id, false)); } void TypeSet::addSetProperty(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeSet *target, jsid id) { - add(cx, ArenaNew(cx->compartment->types.pool, script, pc, target, id, true)); + add(cx, ArenaNew(cx->compartment->pool, script, pc, target, id, true)); } /* Constraints for determining the 'this' object at sites invoked using 'new'. */ @@ -658,7 +579,7 @@ class TypeConstraintNewObject : public TypeConstraint void TypeSet::addNewObject(JSContext *cx, JSScript *script, TypeFunction *fun, TypeSet *target) { - add(cx, ArenaNew(cx->compartment->types.pool, script, fun, target)); + add(cx, ArenaNew(cx->compartment->pool, script, fun, target)); } /* @@ -682,7 +603,7 @@ public: void TypeSet::addCall(JSContext *cx, TypeCallsite *site) { - add(cx, ArenaNew(cx->compartment->types.pool, site)); + add(cx, ArenaNew(cx->compartment->pool, site)); } /* Constraints for arithmetic operations. */ @@ -707,7 +628,7 @@ public: void TypeSet::addArith(JSContext *cx, JSScript *script, TypeSet *target, TypeSet *other) { - add(cx, ArenaNew(cx->compartment->types.pool, script, target, other)); + add(cx, ArenaNew(cx->compartment->pool, script, target, other)); } /* Subset constraint which transforms primitive values into appropriate objects. */ @@ -726,7 +647,7 @@ public: void TypeSet::addTransformThis(JSContext *cx, JSScript *script, TypeSet *target) { - add(cx, ArenaNew(cx->compartment->types.pool, script, target)); + add(cx, ArenaNew(cx->compartment->pool, script, target)); } /* Subset constraint which filters out primitive types. */ @@ -748,7 +669,7 @@ public: void TypeSet::addFilterPrimitives(JSContext *cx, JSScript *script, TypeSet *target, bool onlyNullVoid) { - add(cx, ArenaNew(cx->compartment->types.pool, + add(cx, ArenaNew(cx->compartment->pool, script, target, onlyNullVoid)); } @@ -771,7 +692,7 @@ public: void TypeSet::addMonitorRead(JSContext *cx, JSScript *script, TypeSet *target) { - add(cx, ArenaNew(cx->compartment->types.pool, script, target)); + add(cx, ArenaNew(cx->compartment->pool, script, target)); } /* @@ -790,19 +711,6 @@ public: void newType(JSContext *cx, TypeSet *source, jstype type); }; -/* Update types with the possible values bound by the for loop in code. */ -static inline void -SetForTypes(JSContext *cx, JSScript *script, const AnalyzeState &state, TypeSet *types) -{ - if (state.popped(0).isForEach) - types->addType(cx, TYPE_UNKNOWN); - else - types->addType(cx, TYPE_STRING); - - state.popped(0).types->add(cx, - ArenaNew(cx->compartment->types.pool, script, types)); -} - ///////////////////////////////////////////////////////////////////// // TypeConstraint ///////////////////////////////////////////////////////////////////// @@ -895,7 +803,7 @@ PropertyAccess(JSContext *cx, JSScript *script, const jsbytecode *pc, TypeObject } /* Reads from objects with unknown properties are unknown, writes to such objects are ignored. */ - if (object->unknownProperties()) { + if (object->unknownProperties() || cx->compartment->debugMode) { if (!assign) target->addType(cx, TYPE_UNKNOWN); return; @@ -1029,7 +937,7 @@ TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type) return; callsite->argumentTypes[0]->addTransformThis(cx, script, thisTypes); - TypeCallsite *newSite = ArenaNew(cx->compartment->types.pool, + TypeCallsite *newSite = ArenaNew(cx->compartment->pool, cx, script, pc, callsite->isNew, callsite->argumentCount - 1); if (!newSite || (callsite->argumentCount > 1 && !newSite->argumentTypes)) { @@ -1055,8 +963,14 @@ TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type) unsigned nargs = callee->fun->nargs; /* Analyze the function if we have not already done so. */ - if (!callee->analyzed) - AnalyzeScriptTypes(cx, callee); + if (!callee->analyzed) { + analyze::ScriptAnalysis *calleeAnalysis = callee->analysis(cx); + if (!calleeAnalysis) { + cx->compartment->types.setPendingNukeTypes(cx); + return; + } + calleeAnalysis->analyzeTypes(cx); + } /* Add bindings for the arguments of the call. */ for (unsigned i = 0; i < callsite->argumentCount && i < nargs; i++) { @@ -1260,7 +1174,7 @@ public: void TypeSet::pushAllTypes(JSContext *cx, JSScript *script, const jsbytecode *pc) { - add(cx, ArenaNew(cx->compartment->types.pool, script, pc)); + add(cx, ArenaNew(cx->compartment->pool, script, pc)); } /* Constraint which triggers recompilation of a script if any type is added to a type set. */ @@ -1287,7 +1201,7 @@ public: void TypeSet::addFreeze(JSContext *cx) { - add(cx, ArenaNew(cx->compartment->types.pool, + add(cx, ArenaNew(cx->compartment->pool, cx->compartment->types.compiledScript), false); } @@ -1392,7 +1306,7 @@ TypeSet::getKnownTypeTag(JSContext *cx) type = GetValueTypeFromTypeFlags(flags); if (cx->compartment->types.compiledScript && type != JSVAL_TYPE_UNKNOWN) { - add(cx, ArenaNew(cx->compartment->types.pool, + add(cx, ArenaNew(cx->compartment->pool, cx->compartment->types.compiledScript), false); } @@ -1519,7 +1433,7 @@ public: if (!elementTypes) return; elementTypes->add(cx, - ArenaNew(cx->compartment->types.pool, + ArenaNew(cx->compartment->pool, &kind, script), false); } @@ -1556,7 +1470,7 @@ TypeSet::getKnownObjectKind(JSContext *cx) * Watch for new objects of different kind, and re-traverse existing types * in this set to add any needed FreezeArray constraints. */ - add(cx, ArenaNew(cx->compartment->types.pool, kind, + add(cx, ArenaNew(cx->compartment->pool, kind, cx->compartment->types.compiledScript)); } @@ -1573,7 +1487,7 @@ TypeSet::GetObjectKind(JSContext *cx, TypeObject *object) if (!elementTypes) return OBJECT_UNKNOWN; elementTypes->add(cx, - ArenaNew(cx->compartment->types.pool, + ArenaNew(cx->compartment->pool, kind, cx->compartment->types.compiledScript), false); } @@ -1620,7 +1534,7 @@ TypeSet::WatchObjectReallocation(JSContext *cx, JSObject *obj) if (!types) return; - types->add(cx, ArenaNew(cx->compartment->types.pool, + types->add(cx, ArenaNew(cx->compartment->pool, cx->compartment->types.compiledScript), false); } @@ -1659,7 +1573,7 @@ TypeSet::isOwnProperty(JSContext *cx, bool configurable) return true; } - add(cx, ArenaNew(cx->compartment->types.pool, + add(cx, ArenaNew(cx->compartment->pool, cx->compartment->types.compiledScript, configurable), false); return false; @@ -1671,7 +1585,7 @@ TypeSet::knownNonEmpty(JSContext *cx) if (baseFlags() != 0 || objectCount != 0) return true; - add(cx, ArenaNew(cx->compartment->types.pool, + add(cx, ArenaNew(cx->compartment->pool, cx->compartment->types.compiledScript), false); return false; @@ -1687,7 +1601,7 @@ TypeSet::getSingleton(JSContext *cx) if (!object->singleton) return NULL; - add(cx, ArenaNew(cx->compartment->types.pool, + add(cx, ArenaNew(cx->compartment->pool, cx->compartment->types.compiledScript), false); return object->singleton; @@ -1714,8 +1628,6 @@ TypeCompartment::init(JSContext *cx) if (cx && cx->getRunOptions() & JSOPTION_TYPE_INFERENCE) inferenceEnabled = true; - - JS_InitArenaPool(&pool, "typeinfer", 512, 8, NULL); } TypeObject * @@ -1835,7 +1747,7 @@ GetScriptConst(JSContext *cx, JSScript *script, const jsbytecode *pc) } bool -UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc) +types::UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc) { JS_ASSERT(cx->typeInferenceEnabled()); @@ -1923,6 +1835,9 @@ TypeCompartment::dynamicCall(JSContext *cx, JSObject *callee, bool TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jstype type) { + JS_ASSERT(cx->typeInferenceEnabled()); + AutoEnterTypeInference enter(cx); + /* * For inc/dec ops, we need to go back and reanalyze the affected opcode * taking the overflow into account. We won't see an explicit adjustment @@ -1935,8 +1850,6 @@ TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jst JSOp op = JSOp(*pc); const JSCodeSpec *cs = &js_CodeSpec[op]; if (cs->format & (JOF_INC | JOF_DEC)) { - AutoEnterTypeInference enter(cx); - switch (op) { case JSOP_INCGNAME: case JSOP_DECGNAME: @@ -1957,40 +1870,40 @@ TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jst case JSOP_DECLOCAL: case JSOP_LOCALINC: case JSOP_LOCALDEC: - if (GET_SLOTNO(pc) < script->nfixed) { - TypeSet *types = script->localTypes(GET_SLOTNO(pc)); - types->addType(cx, type); - } - break; - case JSOP_INCARG: case JSOP_DECARG: case JSOP_ARGINC: case JSOP_ARGDEC: { - TypeSet *types = script->argTypes(GET_SLOTNO(pc)); - types->addType(cx, type); + /* + * Just mark the slot's type as holding the new type. This captures + * the effect if the slot is not being tracked, and if the slot + * doesn't escape we will update the pushed types below to capture + * the slot's value after this write. + */ + uint32 slot = analyze::GetBytecodeSlot(script, pc); + if (slot < analyze::TotalSlots(script)) { + TypeSet *types = script->slotTypes(slot); + types->addType(cx, type); + } break; } default:; } - - if (!checkPendingRecompiles(cx)) - return false; } - if (script->types) { + if (script->hasAnalysis() && script->analysis(cx)->ranInference()) { /* * If the pushed set already has this type, we don't need to ensure * there is a TypeResult. Either there already is a TypeResult, or the * type could be determined from the script's other input type sets. */ - js::types::TypeSet *pushed = script->types->pushed(offset, 0); + TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0); if (pushed->hasType(type)) return true; } else { /* Scan all TypeResults on the script to check for a duplicate. */ - js::types::TypeResult *result, **presult = &script->typeResults; + TypeResult *result, **presult = &script->typeResults; while (*presult) { result = *presult; if (result->offset == offset && result->type == type) { @@ -2006,15 +1919,13 @@ TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jst } } - AutoEnterTypeInference enter(cx); - InferSpew(ISpewOps, "externalType: monitorResult #%u:%05u: %s", script->id(), offset, TypeString(type)); TypeResult *result = (TypeResult *) cx->calloc_(sizeof(TypeResult)); if (!result) { setPendingNukeTypes(cx); - return checkPendingRecompiles(cx); + return false; } result->offset = offset; @@ -2022,12 +1933,17 @@ TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jst result->next = script->typeResults; script->typeResults = result; - if (script->types) { - TypeSet *pushed = script->types->pushed(offset, 0); + if (script->hasAnalysis() && script->analysis(cx)->ranInference()) { + TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0); pushed->addType(cx, type); } else if (script->analyzed) { /* Any new dynamic result triggers reanalysis and recompilation. */ - AnalyzeScriptTypes(cx, script); + analyze::ScriptAnalysis *analysis = script->analysis(cx); + if (!analysis) { + setPendingNukeTypes(cx); + return false; + } + analysis->analyzeTypes(cx); } /* @@ -2045,17 +1961,19 @@ TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jst * If we unexpectedly read a hole out of an array, mark all array reads in * the script as undefined. :FIXME: bug 650163 remove hack. */ - if (script->types && JSOp(*pc) == JSOP_GETELEM && type == TYPE_UNDEFINED) { + if (script->hasAnalysis() && script->analysis(cx)->ranInference() && + JSOp(*pc) == JSOP_GETELEM && type == TYPE_UNDEFINED) { + analyze::ScriptAnalysis *analysis = script->analysis(cx); unsigned offset = 0; while (offset < script->length) { - if (JSOp(script->code[offset]) == JSOP_GETELEM && script->types->pushedArray[offset]) { - js::types::TypeSet *pushed = script->types->pushed(offset, 0); + if (JSOp(script->code[offset]) == JSOP_GETELEM && analysis->maybeCode(offset)) { + TypeSet *pushed = analysis->pushedTypes(offset, 0); if (!pushed->hasType(TYPE_UNDEFINED)) { pushed->addType(cx, TYPE_UNDEFINED); TypeResult *result = (TypeResult *) cx->calloc_(sizeof(TypeResult)); if (!result) { setPendingNukeTypes(cx); - return checkPendingRecompiles(cx); + return false; } result->offset = offset; result->type = TYPE_UNDEFINED; @@ -2067,7 +1985,7 @@ TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jst } } - return checkPendingRecompiles(cx); + return true; } bool @@ -2189,19 +2107,19 @@ TypeCompartment::dynamicAssign(JSContext *cx, JSObject *obj, jsid id, const Valu TypeSet *assignTypes = object->getProperty(cx, id, true); if (!assignTypes || assignTypes->hasType(rvtype)) - return cx->compartment->types.checkPendingRecompiles(cx); + return true; InferSpew(ISpewOps, "externalType: monitorAssign %s %s: %s", object->name(), TypeIdString(id), TypeString(rvtype)); assignTypes->addType(cx, rvtype); - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } void TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset) { - if (script->types->monitored(offset)) + if (script->analysis(cx)->monitoredTypes(offset)) return; /* @@ -2251,15 +2169,15 @@ TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32 offset) case JSOP_FUNCALL: case JSOP_FUNAPPLY: case JSOP_NEW: - script->types->addType(cx, offset, 0, TYPE_UNKNOWN); + script->analysis(cx)->addPushedType(cx, offset, 0, TYPE_UNKNOWN); break; default: - TypeFailure(cx, "Monitoring unknown bytecode: %s", js_CodeNameTwo[op]); + TypeFailure(cx, "Monitoring unknown bytecode at #%u:%05u", script->id(), offset); } InferSpew(ISpewOps, "addMonitorNeeded: #%u:%05u", script->id(), offset); - script->types->setMonitored(offset); + script->analysis(cx)->setMonitoredTypes(offset); /* :FIXME: Also mark scripts this was inlined into as needing recompilation? */ if (script->hasJITCode()) @@ -2277,8 +2195,8 @@ TypeCompartment::print(JSContext *cx, JSCompartment *compartment) for (JSScript *script = (JSScript *)compartment->scripts.next; &script->links != &compartment->scripts; script = (JSScript *)script->links.next) { - if (script->types) - script->types->print(cx, script); + if (script->hasAnalysis() && script->analysis(cx)->ranInference()) + script->analysis(cx)->printTypes(cx); TypeObject *object = script->typeObjects; while (object) { object->print(cx); @@ -2327,7 +2245,7 @@ NumberTypes(jstype a, jstype b) return (a == TYPE_INT32 || a == TYPE_DOUBLE) && (b == TYPE_INT32 || b == TYPE_DOUBLE); } -struct ArrayTableKey +struct types::ArrayTableKey { jstype type; JSObject *proto; @@ -2416,7 +2334,7 @@ TypeCompartment::fixArrayType(JSContext *cx, JSObject *obj) * are weak the hash entries would usually be collected on GC even if objects * with the new type/shape are still live. */ -struct ObjectTableKey +struct types::ObjectTableKey { jsid *ids; uint32 nslots; @@ -2447,7 +2365,7 @@ struct ObjectTableKey } }; -struct ObjectTableEntry +struct types::ObjectTableEntry { TypeObject *object; Shape *newShape; @@ -2649,8 +2567,6 @@ TypeObject::splicePrototype(JSContext *cx, JSObject *proto) if (prop && !JSID_IS_EMPTY(prop->id)) getFromPrototypes(cx, prop); } - - JS_ALWAYS_TRUE(cx->compartment->types.checkPendingRecompiles(cx)); } bool @@ -2714,10 +2630,8 @@ TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj, bool clearUnknow * initializers). */ TypeSet *parentTypes = proto->getType()->getProperty(cx, id, false); - if (!parentTypes || parentTypes->unknown()) { - cx->compartment->types.checkPendingRecompiles(cx); + if (!parentTypes || parentTypes->unknown()) return false; - } parentTypes->addBaseClearDefinite(cx, this); } } else { @@ -2730,13 +2644,13 @@ TypeObject::addDefiniteProperties(JSContext *cx, JSObject *obj, bool clearUnknow shape = shape->previous(); } - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } void TypeObject::setFlags(JSContext *cx, TypeObjectFlags flags) { - JS_ASSERT(cx->compartment->types.inferenceDepth); + JS_ASSERT(cx->compartment->activeInference); JS_ASSERT((this->flags & flags) != flags); this->flags |= flags; @@ -2749,7 +2663,7 @@ TypeObject::setFlags(JSContext *cx, TypeObjectFlags flags) void TypeObject::markUnknown(JSContext *cx) { - JS_ASSERT(cx->compartment->types.inferenceDepth); + JS_ASSERT(cx->compartment->activeInference); JS_ASSERT(!unknownProperties()); InferSpew(ISpewOps, "UnknownProperties: %s", name()); @@ -2808,8 +2722,6 @@ TypeObject::clearNewScript(JSContext *cx) if (prop->types.isDefiniteProperty()) prop->types.setOwnProperty(cx, true); } - - cx->compartment->types.checkPendingRecompiles(cx); // :XXX: handle failure } void @@ -2850,7 +2762,7 @@ TypeObject::print(JSContext *cx) } ///////////////////////////////////////////////////////////////////// -// TypeScript +// Type Analysis ///////////////////////////////////////////////////////////////////// static inline ptrdiff_t @@ -2890,30 +2802,6 @@ BytecodeNoFallThrough(JSOp op) } } -/* Merge any types currently in the state with those computed for the join point at offset. */ -void -MergeTypes(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 offset) -{ - unsigned targetDepth = state.analysis.getCode(offset).stackDepth; - JS_ASSERT(state.stackDepth >= targetDepth); - if (!state.joinTypes[offset]) { - TypeSet **joinTypes = ArenaArray(state.pool, targetDepth); - if (!joinTypes) { - cx->compartment->types.setPendingNukeTypes(cx); - return; - } - state.joinTypes[offset] = joinTypes; - for (unsigned i = 0; i < targetDepth; i++) - joinTypes[i] = state.stack[i].types; - } - for (unsigned i = 0; i < targetDepth; i++) { - if (!state.joinTypes[offset][i]) - state.joinTypes[offset][i] = state.stack[i].types; - else if (state.stack[i].types && state.joinTypes[offset][i] != state.stack[i].types) - state.stack[i].types->addSubset(cx, script, state.joinTypes[offset][i]); - } -} - /* * If the bytecode immediately following code/pc is a test of the value * pushed by code, that value should be marked as possibly void. @@ -2938,55 +2826,94 @@ CheckNextTest(jsbytecode *pc) } } +static inline TypeObject * +GetInitializerType(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + if (!script->compileAndGo) + return NULL; + + JSOp op = JSOp(*pc); + JS_ASSERT(op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT || op == JSOP_NEWINIT); + + bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && pc[1] == JSProto_Array)); + return script->getTypeInitObject(cx, pc, isArray); +} + +inline void +analyze::ScriptAnalysis::setForTypes(JSContext *cx, jsbytecode *pc, TypeSet *types) +{ + /* Find the initial ITER opcode which constructed the active iterator. */ + const SSAValue &iterv = poppedValue(pc, 0); + jsbytecode *iterpc = script->code + iterv.pushedOffset(); + JS_ASSERT(JSOp(*iterpc) == JSOP_ITER); + + uintN flags = iterpc[1]; + if (flags & JSITER_FOREACH) { + types->addType(cx, TYPE_UNKNOWN); + return; + } + + /* + * This is a plain 'for in' loop. The value bound is a string, unless the + * iterated object is a generator or has an __iterator__ hook, which we'll + * detect dynamically. + */ + types->addType(cx, TYPE_STRING); + + pushedTypes(iterpc, 0)->add(cx, + ArenaNew(cx->compartment->pool, script, types)); +} + /* Analyze type information for a single bytecode. */ -static bool -AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 offset) +bool +analyze::ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, + TypeInferenceState &state) { jsbytecode *pc = script->code + offset; JSOp op = (JSOp)*pc; + Bytecode &code = getCode(offset); + JS_ASSERT(!code.pushedTypes); + InferSpew(ISpewOps, "analyze: #%u:%05u", script->id(), offset); - /* - * Track the state's stack depth against the stack depth computed by the bytecode - * analysis, and adjust as necessary. - */ - uint32 stackDepth = state.analysis.getCode(offset).stackDepth; - if (stackDepth > state.stackDepth) { -#ifdef DEBUG - /* - * Check that we aren't destroying any useful information. This should only - * occur around exception handling bytecode. - */ - for (unsigned i = state.stackDepth; i < stackDepth; i++) - JS_ASSERT(!state.stack[i].isForEach); -#endif - unsigned ndefs = stackDepth - state.stackDepth; - memset(&state.stack[state.stackDepth], 0, ndefs * sizeof(AnalyzeStateStack)); - } - state.stackDepth = stackDepth; - - /* - * If this is a join point, merge existing types with the join and then pull - * in the types already computed. - */ - if (state.joinTypes[offset]) { - MergeTypes(cx, state, script, offset); - for (unsigned i = 0; i < stackDepth; i++) - state.stack[i].types = state.joinTypes[offset][i]; - } - - TypeObject *initializer = NULL; - unsigned defCount = analyze::GetDefCount(script, offset); - TypeSet *pushed = ArenaArray(cx->compartment->types.pool, defCount); + if (analyze::ExtendedDef(pc)) + defCount++; + + TypeSet *pushed = ArenaArray(cx->compartment->pool, defCount); if (!pushed) return false; - - JS_ASSERT(!script->types->pushedArray[offset]); - script->types->pushedArray[offset] = pushed; - PodZero(pushed, defCount); + code.pushedTypes = pushed; + + /* + * Add phi nodes introduced at this point to the list of all phi nodes in + * the script. Types for these are not generated until after the script has + * been processed, as types can flow backwards into phi nodes and the + * source sets may not exist if we try to process these eagerly. + */ + if (code.newValues) { + SlotValue *newv = code.newValues; + while (newv->slot) { + if (newv->value.kind() != SSAValue::PHI || newv->value.phiOffset() != offset) { + newv++; + continue; + } + + /* + * The phi nodes at join points should all be unique, and every phi + * node created should be in the phiValues list on some bytecode. + */ + if (!state.phiNodes.append(newv->value.phiNode())) + return false; + TypeSet &types = newv->value.phiNode()->types; + types.setIntermediate(); + InferSpew(ISpewOps, "typeSet: T%p phi #%u:%05u:%u", &types, + script->id(), offset, newv->slot); + newv++; + } + } for (unsigned i = 0; i < defCount; i++) { pushed[i].setIntermediate(); @@ -3036,6 +2963,11 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off case JSOP_POPV: case JSOP_DEBUGGER: case JSOP_SETCALL: + case JSOP_TABLESWITCH: + case JSOP_TABLESWITCHX: + case JSOP_LOOKUPSWITCH: + case JSOP_LOOKUPSWITCHX: + case JSOP_TRY: break; /* Bytecodes pushing values of known type. */ @@ -3121,19 +3053,19 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off case JSOP_AND: case JSOP_ANDX: /* OR/AND push whichever operand determined the result. */ - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; case JSOP_DUP: - state.popped(0).types->addSubset(cx, script, &pushed[0]); - state.popped(0).types->addSubset(cx, script, &pushed[1]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[1]); break; case JSOP_DUP2: - state.popped(1).types->addSubset(cx, script, &pushed[0]); - state.popped(0).types->addSubset(cx, script, &pushed[1]); - state.popped(1).types->addSubset(cx, script, &pushed[2]); - state.popped(0).types->addSubset(cx, script, &pushed[3]); + poppedTypes(pc, 1)->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[1]); + poppedTypes(pc, 1)->addSubset(cx, script, &pushed[2]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[3]); break; case JSOP_GETGLOBAL: @@ -3170,6 +3102,16 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off break; } + case JSOP_INCGNAME: + case JSOP_DECGNAME: + case JSOP_GNAMEINC: + case JSOP_GNAMEDEC: { + jsid id = GetAtomId(cx, script, pc, 0); + PropertyAccess(cx, script, pc, script->getGlobalType(), true, NULL, id); + PropertyAccess(cx, script, pc, script->getGlobalType(), false, &pushed[0], id); + break; + } + case JSOP_NAME: case JSOP_CALLNAME: /* The first value pushed by NAME/CALLNAME must always be reported to inference. */ @@ -3184,31 +3126,17 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off case JSOP_SETGNAME: { jsid id = GetAtomId(cx, script, pc, 0); PropertyAccess(cx, script, pc, script->getGlobalType(), - true, state.popped(0).types, id); - state.popped(0).types->addSubset(cx, script, &pushed[0]); + true, poppedTypes(pc, 0), id); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; } case JSOP_SETNAME: case JSOP_SETCONST: cx->compartment->types.monitorBytecode(cx, script, offset); - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; - case JSOP_GETXPROP: - pushed[0].addType(cx, TYPE_UNKNOWN); - break; - - case JSOP_INCGNAME: - case JSOP_DECGNAME: - case JSOP_GNAMEINC: - case JSOP_GNAMEDEC: { - jsid id = GetAtomId(cx, script, pc, 0); - PropertyAccess(cx, script, pc, script->getGlobalType(), true, NULL, id); - PropertyAccess(cx, script, pc, script->getGlobalType(), false, &pushed[0], id); - break; - } - case JSOP_INCNAME: case JSOP_DECNAME: case JSOP_NAMEINC: @@ -3216,6 +3144,10 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off cx->compartment->types.monitorBytecode(cx, script, offset); break; + case JSOP_GETXPROP: + pushed[0].addType(cx, TYPE_UNKNOWN); + break; + case JSOP_GETFCSLOT: case JSOP_CALLFCSLOT: { unsigned index = GET_UINT16(pc); @@ -3234,71 +3166,60 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off break; case JSOP_GETARG: - case JSOP_SETARG: - case JSOP_CALLARG: { - TypeSet *types = script->argTypes(GET_ARGNO(pc)); - types->addSubset(cx, script, &pushed[0]); - if (op == JSOP_SETARG) - state.popped(0).types->addSubset(cx, script, types); - if (op == JSOP_CALLARG) + case JSOP_CALLARG: + case JSOP_GETLOCAL: + case JSOP_CALLLOCAL: { + uint32 slot = GetBytecodeSlot(script, pc); + if (trackSlot(slot)) { + /* + * Normally these opcodes don't pop anything, but they are given + * an extended use holding the variable's SSA value before the + * access. Use the types from here. + */ + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); + } else if (slot < TotalSlots(script)) { + TypeSet *types = script->slotTypes(slot); + types->addSubset(cx, script, &pushed[0]); + } else { + /* Local 'let' variable. Punt on types for these, for now. */ + pushed[0].addType(cx, TYPE_UNKNOWN); + } + if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL) pushed[1].addType(cx, TYPE_UNDEFINED); break; } + case JSOP_SETARG: + case JSOP_SETLOCAL: + case JSOP_SETLOCALPOP: { + uint32 slot = GetBytecodeSlot(script, pc); + if (!trackSlot(slot) && slot < TotalSlots(script)) { + TypeSet *types = script->slotTypes(slot); + poppedTypes(pc, 0)->addSubset(cx, script, types); + } + + /* + * For assignments to non-escaping locals/args, we don't need to update + * the possible types of the var, as for each read of the var SSA gives + * us the writes that could have produced that read. + */ + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); + break; + } + case JSOP_INCARG: case JSOP_DECARG: case JSOP_ARGINC: - case JSOP_ARGDEC: { - TypeSet *types = script->argTypes(GET_ARGNO(pc)); - types->addArith(cx, script, types); - types->addSubset(cx, script, &pushed[0]); - break; - } - - case JSOP_ARGSUB: - pushed[0].addType(cx, TYPE_UNKNOWN); - break; - - case JSOP_GETLOCAL: - case JSOP_SETLOCAL: - case JSOP_SETLOCALPOP: - case JSOP_CALLLOCAL: { - uint32 local = GET_SLOTNO(pc); - TypeSet *types = local < script->nfixed ? script->localTypes(local) : NULL; - - if (op != JSOP_SETLOCALPOP) { - if (types) - types->addSubset(cx, script, &pushed[0]); - else - pushed[0].addType(cx, TYPE_UNKNOWN); - } - if (op == JSOP_CALLLOCAL) - pushed[1].addType(cx, TYPE_UNDEFINED); - - if (op == JSOP_SETLOCAL || op == JSOP_SETLOCALPOP) { - if (types) - state.popped(0).types->addSubset(cx, script, types); - } else { - /* - * Add void type if the variable might be undefined. TODO: monitor for - * undefined read instead? - */ - if (state.analysis.localHasUseBeforeDef(local) || - !state.analysis.localDefined(local, pc)) { - pushed[0].addType(cx, TYPE_UNDEFINED); - } - } - - break; - } - + case JSOP_ARGDEC: case JSOP_INCLOCAL: case JSOP_DECLOCAL: case JSOP_LOCALINC: case JSOP_LOCALDEC: { - uint32 local = GET_SLOTNO(pc); - TypeSet *types = local < script->nfixed ? script->localTypes(local) : NULL; - if (types) { + uint32 slot = GetBytecodeSlot(script, pc); + if (trackSlot(slot)) { + poppedTypes(pc, 0)->addArith(cx, script, &pushed[0]); + } else if (slot < TotalSlots(script)) { + TypeSet *types = script->slotTypes(slot); types->addArith(cx, script, types); types->addSubset(cx, script, &pushed[0]); } else { @@ -3307,6 +3228,10 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off break; } + case JSOP_ARGSUB: + pushed[0].addType(cx, TYPE_UNKNOWN); + break; + case JSOP_ARGUMENTS: case JSOP_ARGCNT: pushed[0].addType(cx, TYPE_UNKNOWN); @@ -3315,64 +3240,50 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off case JSOP_SETPROP: case JSOP_SETMETHOD: { jsid id = GetAtomId(cx, script, pc, 0); - state.popped(1).types->addSetProperty(cx, script, pc, state.popped(0).types, id); - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 1)->addSetProperty(cx, script, pc, poppedTypes(pc, 0), id); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; } + case JSOP_LENGTH: case JSOP_GETPROP: - case JSOP_CALLPROP: { - jsid id = GetAtomId(cx, script, pc, 0); - state.popped(0).types->addGetProperty(cx, script, pc, &pushed[0], id); - - if (op == JSOP_CALLPROP) - state.popped(0).types->addFilterPrimitives(cx, script, &pushed[1], true); - if (CheckNextTest(pc)) - pushed[0].addType(cx, TYPE_UNDEFINED); - break; - } - + case JSOP_CALLPROP: case JSOP_INCPROP: case JSOP_DECPROP: case JSOP_PROPINC: case JSOP_PROPDEC: { jsid id = GetAtomId(cx, script, pc, 0); - state.popped(0).types->addGetProperty(cx, script, pc, &pushed[0], id); - state.popped(0).types->addSetProperty(cx, script, pc, NULL, id); + poppedTypes(pc, 0)->addGetProperty(cx, script, pc, &pushed[0], id); + + if (op == JSOP_CALLPROP) + poppedTypes(pc, 0)->addFilterPrimitives(cx, script, &pushed[1], true); + if (CheckNextTest(pc)) + pushed[0].addType(cx, TYPE_UNDEFINED); break; } case JSOP_GETELEM: case JSOP_CALLELEM: + case JSOP_INCELEM: + case JSOP_DECELEM: + case JSOP_ELEMINC: + case JSOP_ELEMDEC: /* * We only consider ELEM accesses on integers here. Any element access * which is accessing a non-integer property must be monitored. */ - state.popped(1).types->addGetProperty(cx, script, pc, &pushed[0], JSID_VOID); + poppedTypes(pc, 1)->addGetProperty(cx, script, pc, &pushed[0], JSID_VOID); if (op == JSOP_CALLELEM) - state.popped(1).types->addFilterPrimitives(cx, script, &pushed[1], true); + poppedTypes(pc, 1)->addFilterPrimitives(cx, script, &pushed[1], true); if (CheckNextTest(pc)) pushed[0].addType(cx, TYPE_UNDEFINED); break; case JSOP_SETELEM: case JSOP_SETHOLE: - state.popped(2).types->addSetProperty(cx, script, pc, state.popped(0).types, JSID_VOID); - state.popped(0).types->addSubset(cx, script, &pushed[0]); - break; - - case JSOP_INCELEM: - case JSOP_DECELEM: - case JSOP_ELEMINC: - case JSOP_ELEMDEC: - state.popped(1).types->addGetProperty(cx, script, pc, &pushed[0], JSID_VOID); - state.popped(1).types->addSetProperty(cx, script, pc, NULL, JSID_VOID); - break; - - case JSOP_LENGTH: - /* Treat this as an access to the length property. */ - state.popped(0).types->addGetProperty(cx, script, pc, &pushed[0], id_length(cx)); + poppedTypes(pc, 2)->addSetProperty(cx, script, pc, poppedTypes(pc, 0), JSID_VOID); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; case JSOP_THIS: @@ -3382,25 +3293,25 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off case JSOP_RETURN: case JSOP_SETRVAL: if (script->fun) - state.popped(0).types->addSubset(cx, script, script->returnTypes()); + poppedTypes(pc, 0)->addSubset(cx, script, script->returnTypes()); break; case JSOP_ADD: - state.popped(0).types->addArith(cx, script, &pushed[0], state.popped(1).types); - state.popped(1).types->addArith(cx, script, &pushed[0], state.popped(0).types); + poppedTypes(pc, 0)->addArith(cx, script, &pushed[0], poppedTypes(pc, 1)); + poppedTypes(pc, 1)->addArith(cx, script, &pushed[0], poppedTypes(pc, 0)); break; case JSOP_SUB: case JSOP_MUL: case JSOP_MOD: case JSOP_DIV: - state.popped(0).types->addArith(cx, script, &pushed[0]); - state.popped(1).types->addArith(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addArith(cx, script, &pushed[0]); + poppedTypes(pc, 1)->addArith(cx, script, &pushed[0]); break; case JSOP_NEG: case JSOP_POS: - state.popped(0).types->addArith(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addArith(cx, script, &pushed[0]); break; case JSOP_LAMBDA: @@ -3413,10 +3324,18 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off JSObject *obj = GetScriptObject(cx, script, pc, off); TypeSet *res = NULL; - if (op == JSOP_LAMBDA || op == JSOP_LAMBDA_FC) + if (op == JSOP_LAMBDA || op == JSOP_LAMBDA_FC) { res = &pushed[0]; - else if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) - res = script->localTypes(GET_SLOTNO(pc)); + } else if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) { + uint32 slot = GetBytecodeSlot(script, pc); + if (trackSlot(slot)) { + res = &pushed[0]; + } else { + /* Should not see 'let' vars here. */ + JS_ASSERT(slot < TotalSlots(script)); + res = script->slotTypes(slot); + } + } if (res) { if (script->compileAndGo) @@ -3439,41 +3358,45 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off case JSOP_NEW: { /* Construct the base call information about this site. */ unsigned argCount = analyze::GetUseCount(script, offset) - 2; - TypeCallsite *callsite = ArenaNew(cx->compartment->types.pool, + TypeCallsite *callsite = ArenaNew(cx->compartment->pool, cx, script, pc, op == JSOP_NEW, argCount); if (!callsite || (argCount && !callsite->argumentTypes)) { cx->compartment->types.setPendingNukeTypes(cx); break; } - callsite->thisTypes = state.popped(argCount).types; + callsite->thisTypes = poppedTypes(pc, argCount); callsite->returnTypes = &pushed[0]; for (unsigned i = 0; i < argCount; i++) - callsite->argumentTypes[i] = state.popped(argCount - 1 - i).types; + callsite->argumentTypes[i] = poppedTypes(pc, argCount - 1 - i); - state.popped(argCount + 1).types->addCall(cx, callsite); + poppedTypes(pc, argCount + 1)->addCall(cx, callsite); break; } case JSOP_NEWINIT: case JSOP_NEWARRAY: - case JSOP_NEWOBJECT: + case JSOP_NEWOBJECT: { + TypeObject *initializer = GetInitializerType(cx, script, pc); if (script->compileAndGo) { - bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && pc[1] == JSProto_Array)); - initializer = script->getTypeInitObject(cx, pc, isArray); if (!initializer) return false; pushed[0].addType(cx, (jstype) initializer); } else { + JS_ASSERT(!initializer); pushed[0].addType(cx, TYPE_UNKNOWN); } break; + } case JSOP_ENDINIT: break; - case JSOP_INITELEM: - initializer = state.popped(2).initializer; + case JSOP_INITELEM: { + const SSAValue &objv = poppedValue(pc, 2); + jsbytecode *initpc = script->code + objv.pushedOffset(); + TypeObject *initializer = GetInitializerType(cx, script, initpc); + JS_ASSERT((initializer != NULL) == script->compileAndGo); if (initializer) { pushed[0].addType(cx, (jstype) initializer); @@ -3491,7 +3414,7 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off else if (state.hasHole) cx->markTypeArrayNotPacked(initializer, false); else - state.popped(0).types->addSubset(cx, script, types); + poppedTypes(pc, 0)->addSubset(cx, script, types); } } else { pushed[0].addType(cx, TYPE_UNKNOWN); @@ -3499,6 +3422,7 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off state.hasGetSet = false; state.hasHole = false; break; + } case JSOP_GETTER: case JSOP_SETTER: @@ -3510,8 +3434,11 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off break; case JSOP_INITPROP: - case JSOP_INITMETHOD: - initializer = state.popped(1).initializer; + case JSOP_INITMETHOD: { + const SSAValue &objv = poppedValue(pc, 1); + jsbytecode *initpc = script->code + objv.pushedOffset(); + TypeObject *initializer = GetInitializerType(cx, script, initpc); + JS_ASSERT((initializer != NULL) == script->compileAndGo); if (initializer) { pushed[0].addType(cx, (jstype) initializer); @@ -3525,7 +3452,7 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off else if (state.hasGetSet) types->addType(cx, TYPE_UNKNOWN); else - state.popped(0).types->addSubset(cx, script, types); + poppedTypes(pc, 0)->addSubset(cx, script, types); } } else { pushed[0].addType(cx, TYPE_UNKNOWN); @@ -3533,6 +3460,7 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off state.hasGetSet = false; JS_ASSERT(!state.hasHole); break; + } case JSOP_ENTERWITH: case JSOP_ENTERBLOCK: @@ -3550,11 +3478,11 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off * when an object of Iterator class flows to the JSOP_FOR* opcode, which could * be a generator that produces arbitrary values with 'for in' syntax. */ - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; case JSOP_MOREITER: - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); pushed[1].addType(cx, TYPE_BOOLEAN); break; @@ -3565,7 +3493,7 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off TypeSet *types = global->getProperty(cx, id, true); if (!types) return false; - SetForTypes(cx, script, state, types); + setForTypes(cx, pc, types); } break; } @@ -3574,37 +3502,30 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off cx->compartment->types.monitorBytecode(cx, script, offset); break; + case JSOP_FORARG: case JSOP_FORLOCAL: { - uint32 local = GET_SLOTNO(pc); - TypeSet *types = local < script->nfixed ? script->localTypes(local) : NULL; - if (types) - SetForTypes(cx, script, state, types); - break; - } - - case JSOP_FORARG: { - TypeSet *types = script->argTypes(GET_ARGNO(pc)); - SetForTypes(cx, script, state, types); + uint32 slot = GetBytecodeSlot(script, pc); + if (trackSlot(slot)) { + setForTypes(cx, pc, &pushed[1]); + } else { + if (slot < TotalSlots(script)) + setForTypes(cx, pc, script->slotTypes(slot)); + } break; } case JSOP_FORELEM: - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); pushed[1].addType(cx, TYPE_UNKNOWN); break; case JSOP_FORPROP: case JSOP_ENUMELEM: case JSOP_ENUMCONSTELEM: + case JSOP_ARRAYPUSH: cx->compartment->types.monitorBytecode(cx, script, offset); break; - case JSOP_ARRAYPUSH: { - TypeSet *types = state.stack[GET_SLOTNO(pc) - script->nfixed].types; - types->addSetProperty(cx, script, pc, state.popped(0).types, JSID_VOID); - break; - } - case JSOP_THROW: /* There will be a monitor on the bytecode catching the exception. */ break; @@ -3625,16 +3546,16 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off break; case JSOP_LEAVEBLOCKEXPR: - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; case JSOP_CASE: case JSOP_CASEX: - state.popped(1).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 1)->addSubset(cx, script, &pushed[0]); break; case JSOP_UNBRAND: - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; case JSOP_GENERATOR: @@ -3663,7 +3584,7 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off case JSOP_SETXMLNAME: cx->compartment->types.monitorBytecode(cx, script, offset); - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; case JSOP_BINDXMLNAME: @@ -3685,11 +3606,11 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off case JSOP_FILTER: /* Note: the second value pushed by filter is a hole, and not modelled. */ - state.popped(0).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 0)->addSubset(cx, script, &pushed[0]); break; case JSOP_ENDFILTER: - state.popped(1).types->addSubset(cx, script, &pushed[0]); + poppedTypes(pc, 1)->addSubset(cx, script, &pushed[0]); break; case JSOP_DEFSHARP: @@ -3706,132 +3627,26 @@ AnalyzeBytecode(JSContext *cx, AnalyzeState &state, JSScript *script, uint32 off pushed[0].addType(cx, TYPE_UNKNOWN); break; - 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; - - MergeTypes(cx, state, script, defaultOffset); - - for (jsint i = low; i <= high; i++) { - unsigned targetOffset = offset + GetJumpOffset(pc, pc2); - if (targetOffset != offset) - MergeTypes(cx, state, script, targetOffset); - 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; - - MergeTypes(cx, state, script, defaultOffset); - - while (npairs) { - pc2 += INDEX_LEN; - unsigned targetOffset = offset + GetJumpOffset(pc, pc2); - MergeTypes(cx, state, script, targetOffset); - 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) - MergeTypes(cx, state, script, catchOffset); - } - } - break; - } - default: - TypeFailure(cx, "Unknown bytecode: %s", js_CodeNameTwo[op]); - } - - /* Compute temporary analysis state after the bytecode. */ - - if (op == JSOP_DUP) { - state.stack[stackDepth] = state.stack[stackDepth - 1]; - state.stackDepth = stackDepth + 1; - } else if (op == JSOP_DUP2) { - state.stack[stackDepth] = state.stack[stackDepth - 2]; - state.stack[stackDepth + 1] = state.stack[stackDepth - 1]; - state.stackDepth = stackDepth + 2; - } else { - unsigned nuses = analyze::GetUseCount(script, offset); - unsigned ndefs = analyze::GetDefCount(script, offset); - memset(&state.stack[stackDepth - nuses], 0, ndefs * sizeof(AnalyzeStateStack)); - state.stackDepth = stackDepth - nuses + ndefs; - } - - for (unsigned i = 0; i < defCount; i++) - state.popped(defCount -1 - i).types = &pushed[i]; - - switch (op) { - case JSOP_ITER: { - uintN flags = pc[1]; - if (flags & JSITER_FOREACH) - state.popped(0).isForEach = true; - break; - } - - case JSOP_NEWINIT: - case JSOP_NEWARRAY: - case JSOP_NEWOBJECT: - case JSOP_INITELEM: - case JSOP_INITPROP: - case JSOP_INITMETHOD: - state.popped(0).initializer = initializer; - break; - - default:; - } - - /* Merge types with other jump targets of this opcode. */ - uint32 type = JOF_TYPE(js_CodeSpec[op].format); - if (type == JOF_JUMP || type == JOF_JUMPX) { - unsigned targetOffset = offset + GetJumpOffset(pc, pc); - MergeTypes(cx, state, script, targetOffset); + TypeFailure(cx, "Unknown bytecode at #%u:%05u", script->id(), offset); } return true; } void -AnalyzeScriptTypes(JSContext *cx, JSScript *script) +analyze::ScriptAnalysis::analyzeTypes(JSContext *cx) { - JS_ASSERT(!script->types && !script->isUncachedEval); + JS_ASSERT(!ranInference() && !failed()); - analyze::Script analysis; - analysis.analyze(cx, script); + if (!ranSSA()) { + analyzeSSA(cx); + if (failed()) + return; + } - AnalyzeState state(cx, analysis); - - unsigned length = sizeof(TypeScript) - + (script->length * sizeof(TypeScript*)); - unsigned char *cursor = (unsigned char *) cx->calloc_(length); - - if (analysis.failed() || !script->ensureVarTypes(cx) || !state.init(script) || !cursor) { - cx->compartment->types.setPendingNukeTypes(cx); + if (!script->ensureVarTypes(cx)) { + setOOM(cx); return; } @@ -3844,27 +3659,32 @@ AnalyzeScriptTypes(JSContext *cx, JSScript *script) cx->compartment->types.addPendingRecompile(cx, script); } - TypeScript *types = (TypeScript *) cursor; - script->types = types; + /* Future OOM failures need to setPendingNukeTypes. */ script->analyzed = true; -#ifdef DEBUG - types->script = script; -#endif - cursor += sizeof(TypeScript); - types->pushedArray = (TypeSet **) cursor; + /* + * Set this early to avoid reentrance. Any failures are OOMs, and will nuke + * all types in the compartment. + */ + ranInference_ = true; if (script->calledWithNew) - AnalyzeScriptNew(cx, script); + analyzeTypesNew(cx); + + /* Make sure the initial type set of all local vars includes void. */ + for (unsigned i = 0; i < script->nfixed; i++) + script->localTypes(i)->addType(cx, TYPE_UNDEFINED); + + TypeInferenceState state(cx); unsigned offset = 0; while (offset < script->length) { - analyze::Bytecode *code = analysis.maybeCode(offset); + analyze::Bytecode *code = maybeCode(offset); jsbytecode *pc = script->code + offset; analyze::UntrapOpcode untrap(cx, script, pc); - if (code && !AnalyzeBytecode(cx, state, script, offset)) { + if (code && !analyzeTypesBytecode(cx, offset, state)) { cx->compartment->types.setPendingNukeTypes(cx); return; } @@ -3872,6 +3692,14 @@ AnalyzeScriptTypes(JSContext *cx, JSScript *script) offset += analyze::GetBytecodeLength(pc); } + for (unsigned i = 0; i < state.phiNodes.length(); i++) { + SSAPhiNode *node = state.phiNodes[i]; + for (unsigned j = 0; j < node->length; j++) { + const SSAValue &v = node->options[j]; + getValueTypes(v)->addSubset(cx, script, &node->types); + } + } + /* * Sync with any dynamic types previously generated either because * we ran the interpreter some before analyzing or because we @@ -3879,14 +3707,14 @@ AnalyzeScriptTypes(JSContext *cx, JSScript *script) */ TypeResult *result = script->typeResults; while (result) { - TypeSet *pushed = script->types->pushed(result->offset); + TypeSet *pushed = pushedTypes(result->offset); pushed->addType(cx, result->type); result = result->next; } } void -AnalyzeScriptNew(JSContext *cx, JSScript *script) +analyze::ScriptAnalysis::analyzeTypesNew(JSContext *cx) { JS_ASSERT(script->calledWithNew && script->fun); @@ -4077,115 +3905,10 @@ AnalyzeScriptProperties(JSContext *cx, JSScript *script) // Printing ///////////////////////////////////////////////////////////////////// -#ifdef DEBUG - void -PrintBytecode(JSContext *cx, JSScript *script, jsbytecode *pc) -{ - unsigned offset = pc - script->code; - - JSOp op = (JSOp)*pc; - JS_ASSERT(op < JSOP_LIMIT); - - const JSCodeSpec *cs = &js_CodeSpec[op]; - const char *name = js_CodeNameTwo[op]; - - uint32 type = JOF_TYPE(cs->format); - switch (type) { - case JOF_BYTE: - case JOF_TABLESWITCH: - case JOF_TABLESWITCHX: - case JOF_LOOKUPSWITCH: - case JOF_LOOKUPSWITCHX: - printf("%s", name); - break; - - case JOF_JUMP: - case JOF_JUMPX: { - ptrdiff_t off = GetJumpOffset(pc, pc); - printf("%s %u", name, unsigned(offset + off)); - break; - } - - case JOF_ATOM: { - if (op == JSOP_DOUBLE) { - printf("%s", name); - } else { - jsid id = GetAtomId(cx, script, pc, 0); - if (JSID_IS_STRING(id)) - printf("%s %s", name, TypeIdString(id)); - else - printf("%s (index)", name); - } - break; - } - - case JOF_OBJECT: - printf("%s (object)", name); - break; - - case JOF_REGEXP: - printf("%s (regexp)", name); - break; - - case JOF_UINT16PAIR: - printf("%s %d %d", name, GET_UINT16(pc), GET_UINT16(pc + UINT16_LEN)); - break; - - case JOF_UINT16: - printf("%s %d", name, GET_UINT16(pc)); - break; - - case JOF_QARG: - printf("%s %d", name, GET_ARGNO(pc)); - break; - - case JOF_GLOBAL: - printf("%s %s", name, TypeIdString(GetGlobalId(cx, script, pc))); - break; - - case JOF_LOCAL: - printf("%s %d", name, GET_SLOTNO(pc)); - break; - - case JOF_SLOTATOM: { - jsid id = GetAtomId(cx, script, pc, SLOTNO_LEN); - - printf("%s %d %s", name, GET_SLOTNO(pc), TypeIdString(id)); - break; - } - - case JOF_SLOTOBJECT: - printf("%s %u (object)", name, GET_SLOTNO(pc)); - break; - - case JOF_UINT24: - JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY); - printf("%s %d", name, (jsint)GET_UINT24(pc)); - break; - - case JOF_UINT8: - printf("%s %d", name, (jsint)pc[1]); - break; - - case JOF_INT8: - printf("%s %d", name, (jsint)GET_INT8(pc)); - break; - - case JOF_INT32: - printf("%s %d", name, (jsint)GET_INT32(pc)); - break; - - default: - JS_NOT_REACHED("Unknown opcode type"); - } -} - -#endif - -void -TypeScript::print(JSContext *cx, JSScript *script) +analyze::ScriptAnalysis::printTypes(JSContext *cx) { + AutoEnterAnalysis enter(cx); TypeCompartment *compartment = &script->compartment->types; /* @@ -4193,8 +3916,7 @@ TypeScript::print(JSContext *cx, JSScript *script) * statistics about the size of type sets found for stack values. */ for (unsigned offset = 0; offset < script->length; offset++) { - TypeSet *array = pushed(offset); - if (!array) + if (!maybeCode(offset)) continue; unsigned defCount = analyze::GetDefCount(script, offset); @@ -4202,7 +3924,7 @@ TypeScript::print(JSContext *cx, JSScript *script) continue; for (unsigned i = 0; i < defCount; i++) { - TypeSet *types = &array[i]; + TypeSet *types = pushedTypes(offset, i); unsigned typeCount = types->getObjectCount() ? 1 : 0; for (jstype type = TYPE_UNDEFINED; type <= TYPE_STRING; type++) { @@ -4251,8 +3973,10 @@ TypeScript::print(JSContext *cx, JSScript *script) script->argTypes(i)->print(cx); } for (unsigned i = 0; i < script->nfixed; i++) { - printf("\n local%u:", i); - script->localTypes(i)->print(cx); + if (!trackSlot(LocalSlot(script, i))) { + printf("\n local%u:", i); + script->localTypes(i)->print(cx); + } } for (unsigned i = 0; i < script->bindings.countUpvars(); i++) { printf("\n upvar%u:", i); @@ -4261,22 +3985,19 @@ TypeScript::print(JSContext *cx, JSScript *script) printf("\n"); for (unsigned offset = 0; offset < script->length; offset++) { - TypeSet *array = pushed(offset); - if (!array) + if (!maybeCode(offset)) continue; - printf("#%u:%05u: ", script->id(), offset); PrintBytecode(cx, script, script->code + offset); - printf("\n"); unsigned defCount = analyze::GetDefCount(script, offset); for (unsigned i = 0; i < defCount; i++) { printf(" type %d:", i); - array[i].print(cx); + pushedTypes(offset, i)->print(cx); printf("\n"); } - if (monitored(offset)) + if (monitoredTypes(offset)) printf(" monitored\n"); } @@ -4286,26 +4007,24 @@ TypeScript::print(JSContext *cx, JSScript *script) } -} } /* namespace js::types */ - ///////////////////////////////////////////////////////////////////// // JSContext ///////////////////////////////////////////////////////////////////// -js::types::TypeFunction * +TypeFunction * JSContext::newTypeFunction(const char *name, JSObject *proto) { - return (js::types::TypeFunction *) + return (TypeFunction *) compartment->types.newTypeObject(this, NULL, name, true, false, proto); } -js::types::TypeObject * +TypeObject * JSContext::newTypeObject(const char *name, JSObject *proto) { return compartment->types.newTypeObject(this, NULL, name, false, false, proto); } -js::types::TypeObject * +TypeObject * JSContext::newTypeObject(const char *base, const char *postfix, JSObject *proto, bool isFunction) { char *name = NULL; @@ -4396,19 +4115,19 @@ JSScript::makeVarTypes(JSContext *cx) unsigned nargs = fun ? fun->nargs : 0; unsigned count = 2 + nargs + nfixed + bindings.countUpvars(); - varTypes = (js::types::TypeSet *) cx->calloc_(sizeof(js::types::TypeSet) * count); + varTypes = (TypeSet *) cx->calloc_(sizeof(TypeSet) * count); if (!varTypes) return false; #ifdef DEBUG - InferSpew(js::types::ISpewOps, "typeSet: T%p return #%u", returnTypes(), id()); - InferSpew(js::types::ISpewOps, "typeSet: T%p this #%u", thisTypes(), id()); + InferSpew(ISpewOps, "typeSet: T%p return #%u", returnTypes(), id()); + InferSpew(ISpewOps, "typeSet: T%p this #%u", thisTypes(), id()); for (unsigned i = 0; i < nargs; i++) - InferSpew(js::types::ISpewOps, "typeSet: T%p arg%u #%u", argTypes(i), i, id()); + InferSpew(ISpewOps, "typeSet: T%p arg%u #%u", argTypes(i), i, id()); for (unsigned i = 0; i < nfixed; i++) - InferSpew(js::types::ISpewOps, "typeSet: T%p local%u #%u", localTypes(i), i, id()); + InferSpew(ISpewOps, "typeSet: T%p local%u #%u", localTypes(i), i, id()); for (unsigned i = 0; i < bindings.countUpvars(); i++) - InferSpew(js::types::ISpewOps, "typeSet: T%p upvar%u #%u", upvarTypes(i), i, id()); + InferSpew(ISpewOps, "typeSet: T%p upvar%u #%u", upvarTypes(i), i, id()); #endif return true; @@ -4427,7 +4146,7 @@ JSScript::typeSetFunction(JSContext *cx, JSFunction *fun) name = (char *) alloca(10); JS_snprintf(name, 10, "#%u", id()); #endif - js::types::TypeFunction *type = cx->newTypeFunction(name, fun->getProto())->asFunction(); + TypeFunction *type = cx->newTypeFunction(name, fun->getProto())->asFunction(); if (!type) return false; @@ -4444,40 +4163,41 @@ JSScript::typeSetFunction(JSContext *cx, JSFunction *fun) void JSScript::typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value *sp) { - if (!types) + AutoEnterTypeInference enter(cx); + + if (!(analysis_ && analysis_->ranInference())) return; int defCount = js::analyze::GetDefCount(this, pc - code); - js::types::TypeSet *array = types->pushed(pc - code); for (int i = 0; i < defCount; i++) { const js::Value &val = sp[-defCount + i]; - js::types::TypeSet *types = &array[i]; + TypeSet *types = analysis_->pushedTypes(pc, i); if (IgnorePushed(JSOp(*pc), i)) continue; - js::types::jstype type = js::types::GetValueType(cx, val); + jstype type = GetValueType(cx, val); - if (!js::types::TypeSetMatches(cx, types, type)) { - js::types::TypeFailure(cx, "Missing type at #%u:%05u pushed %u: %s", - id(), pc - code, i, js::types::TypeString(type)); + if (!TypeSetMatches(cx, types, type)) { + TypeFailure(cx, "Missing type at #%u:%05u pushed %u: %s", + id(), pc - code, i, TypeString(type)); } - if (js::types::TypeIsObject(type)) { + if (TypeIsObject(type)) { JS_ASSERT(val.isObject()); JSObject *obj = &val.toObject(); - js::types::TypeObject *object = (js::types::TypeObject *) type; + TypeObject *object = (TypeObject *) type; if (object->unknownProperties()) continue; /* Make sure information about the array status of this object is right. */ - bool dense = !object->hasFlags(js::types::OBJECT_FLAG_NON_DENSE_ARRAY); - bool packed = !object->hasFlags(js::types::OBJECT_FLAG_NON_PACKED_ARRAY); + bool dense = !object->hasFlags(OBJECT_FLAG_NON_DENSE_ARRAY); + bool packed = !object->hasFlags(OBJECT_FLAG_NON_PACKED_ARRAY); JS_ASSERT_IF(packed, dense); if (dense) { if (!obj->isDenseArray() || (packed && !obj->isPackedDenseArray())) { - js::types::TypeFailure(cx, "Object not %s array at #%u:%05u popped %u: %s", + TypeFailure(cx, "Object not %s array at #%u:%05u popped %u: %s", packed ? "packed" : "dense", id(), pc - code, i, object->name()); } @@ -4497,7 +4217,7 @@ JSObject::makeNewType(JSContext *cx, JSScript *newScript) { JS_ASSERT(!newType); - js::types::TypeObject *type = cx->newTypeObject(getType()->name(), "new", this); + TypeObject *type = cx->newTypeObject(getType()->name(), "new", this); if (!type) return; @@ -4507,17 +4227,17 @@ JSObject::makeNewType(JSContext *cx, JSScript *newScript) return; } - js::types::AutoEnterTypeInference enter(cx); + AutoEnterTypeInference enter(cx); if (!getType()->unknownProperties()) { /* Update the possible 'new' types for all prototype objects sharing the same type object. */ - js::types::TypeSet *types = getType()->getProperty(cx, JSID_EMPTY, true); + TypeSet *types = getType()->getProperty(cx, JSID_EMPTY, true); if (types) - types->addType(cx, (js::types::jstype) type); + types->addType(cx, (jstype) type); } if (newScript && !type->unknownProperties()) { - JSObject *baseobj = js::types::AnalyzeScriptProperties(cx, newScript); + JSObject *baseobj = AnalyzeScriptProperties(cx, newScript); if (baseobj && baseobj->slotSpan() > 0) { js::gc::FinalizeKind kind = js::gc::GetGCObjectKind(baseobj->slotSpan()); @@ -4531,15 +4251,11 @@ JSObject::makeNewType(JSContext *cx, JSScript *newScript) */ baseobj = NewReshapedObject(cx, type, baseobj->getParent(), kind, (const js::Shape *) baseobj->lastProperty()); - if (!baseobj) { - cx->compartment->types.checkPendingRecompiles(cx); + if (!baseobj) return; - } - if (!type->addDefiniteProperties(cx, baseobj, true)) { - cx->compartment->types.checkPendingRecompiles(cx); + if (!type->addDefiniteProperties(cx, baseobj, true)) return; - } type->newScript = newScript; type->newScriptFinalizeKind = unsigned(kind); @@ -4547,9 +4263,6 @@ JSObject::makeNewType(JSContext *cx, JSScript *newScript) } } - if (!cx->compartment->types.checkPendingRecompiles(cx)) - return; - newType = type; setDelegate(); } @@ -4558,9 +4271,6 @@ JSObject::makeNewType(JSContext *cx, JSScript *newScript) // Tracing ///////////////////////////////////////////////////////////////////// -namespace js { -namespace types { - void types::TypeObject::trace(JSTracer *trc) { @@ -4898,12 +4608,10 @@ TypeCompartment::~TypeCompartment() Foreground::delete_(objectTypeTable); } -} } /* namespace js::types */ - void JSScript::condenseTypes(JSContext *cx) { - js::types::CondenseTypeObjectList(cx, &compartment->types, typeObjects); + CondenseTypeObjectList(cx, &compartment->types, typeObjects); if (varTypes) { js::HashSet condensed(cx), *pcondensed = &condensed; @@ -4923,22 +4631,22 @@ JSScript::condenseTypes(JSContext *cx) varTypes = NULL; } else { for (unsigned i = 0; i < num; i++) - js::types::TypeSet::CondenseSweepTypeSet(cx, &compartment->types, pcondensed, &varTypes[i]); + TypeSet::CondenseSweepTypeSet(cx, &compartment->types, pcondensed, &varTypes[i]); } } - js::types::TypeResult **presult = &typeResults; + TypeResult **presult = &typeResults; while (*presult) { - js::types::TypeResult *result = *presult; - if (js::types::TypeIsObject(result->type)) { - js::types::TypeObject *object = (js::types::TypeObject *) result->type; + TypeResult *result = *presult; + if (TypeIsObject(result->type)) { + TypeObject *object = (TypeObject *) result->type; if (!object->marked) { if (!object->unknownProperties()) { *presult = result->next; cx->free_(result); continue; } else { - result->type = (js::types::jstype) &compartment->types.typeEmpty; + result->type = (jstype) &compartment->types.typeEmpty; } } } @@ -4947,12 +4655,15 @@ JSScript::condenseTypes(JSContext *cx) } void -JSScript::sweepTypes(JSContext *cx) +JSScript::sweepAnalysis(JSContext *cx) { SweepTypeObjectList(cx, typeObjects); - if (types && !compartment->types.inferenceDepth) { - cx->free_(types); - types = NULL; + if (analysis_ && !compartment->activeAnalysis) { + /* + * The analysis and everything in it is allocated using the analysis + * pool in the compartment (to be cleared shortly). + */ + analysis_ = NULL; } } diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index da72764841e0..47b853fad183 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -49,10 +49,7 @@ namespace js { struct CallArgs; -namespace analyze { - struct Bytecode; - class Script; -} } +} namespace js { namespace types { @@ -330,7 +327,7 @@ class TypeSet inline unsigned getObjectCount(); inline TypeObject *getObject(unsigned i); - void setIntermediate() { typeFlags |= TYPE_FLAG_INTERMEDIATE_SET; } + void setIntermediate() { JS_ASSERT(!typeFlags); typeFlags = TYPE_FLAG_INTERMEDIATE_SET; } void setOwnProperty(bool configurable) { typeFlags |= TYPE_FLAG_OWN_PROPERTY; if (configurable) @@ -689,37 +686,6 @@ struct TypeResult TypeResult *next; }; -/* Type information for a script, result of AnalyzeTypes. */ -struct TypeScript -{ -#ifdef DEBUG - JSScript *script; -#endif - - /* - * Stack values pushed by all bytecodes in the script. Low bit is set for - * bytecodes which are monitored (side effects were not determined statically). - */ - TypeSet **pushedArray; - - /* Gather statistics off this script and print it if necessary. */ - void print(JSContext *cx, JSScript *script); - - inline bool monitored(uint32 offset); - inline void setMonitored(uint32 offset); - - inline TypeSet *pushed(uint32 offset); - inline TypeSet *pushed(uint32 offset, uint32 index); - - inline void addType(JSContext *cx, uint32 offset, uint32 index, jstype type); -}; - -/* Analyzes all types in script, constructing its TypeScript. */ -void AnalyzeScriptTypes(JSContext *cx, JSScript *script); - -/* Analyze the effect of invoking 'new' on script. */ -void AnalyzeScriptNew(JSContext *cx, JSScript *script); - struct ArrayTableKey; typedef HashMap ArrayTypeTable; @@ -736,12 +702,6 @@ struct TypeCompartment /* Whether type inference is enabled in this compartment. */ bool inferenceEnabled; - /* Whether type inference is active, see AutoEnterTypeInference. */ - unsigned inferenceDepth; - - /* Pool for all intermediate type information in this compartment. Cleared on every GC. */ - JSArenaPool pool; - /* Number of scripts in this compartment. */ unsigned scriptCount; @@ -842,8 +802,6 @@ struct TypeCompartment bool dynamicAssign(JSContext *cx, JSObject *obj, jsid id, const Value &rval); bool dynamicCall(JSContext *cx, JSObject *callee, const CallArgs &args, bool constructing); - inline bool checkPendingRecompiles(JSContext *cx); - bool nukeTypes(JSContext *cx); bool processPendingRecompiles(JSContext *cx); diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index 85fe0064e689..a357c7e0a973 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -143,50 +143,39 @@ TypeIdString(jsid id) struct AutoEnterTypeInference { JSContext *cx; -#ifdef DEBUG - unsigned depth; -#endif + bool oldActiveAnalysis; + bool oldActiveInference; AutoEnterTypeInference(JSContext *cx, bool compiling = false) - : cx(cx) + : cx(cx), oldActiveAnalysis(cx->compartment->activeAnalysis), + oldActiveInference(cx->compartment->activeInference) { -#ifdef DEBUG - depth = cx->compartment->types.inferenceDepth; -#endif JS_ASSERT_IF(!compiling, cx->compartment->types.inferenceEnabled); - cx->compartment->types.inferenceDepth++; + cx->compartment->activeAnalysis = true; + cx->compartment->activeInference = true; } ~AutoEnterTypeInference() { + cx->compartment->activeAnalysis = oldActiveAnalysis; + cx->compartment->activeInference = oldActiveInference; + /* - * This should have been reset by checkPendingRecompiles. - * :FIXME: be more tolerant and clean up anyways, the caller may be - * propagating an OOM or other error. + * If there are no more type inference activations on the stack, + * process any triggered recompilations. Note that we should not be + * invoking any scripted code while type inference is running. + * :TODO: assert this. */ - JS_ASSERT(cx->compartment->types.inferenceDepth == depth); + if (!cx->compartment->activeInference) { + TypeCompartment *types = &cx->compartment->types; + if (types->pendingNukeTypes) + types->nukeTypes(cx); + else if (types->pendingRecompiles) + types->processPendingRecompiles(cx); + } } }; -bool -TypeCompartment::checkPendingRecompiles(JSContext *cx) -{ - JS_ASSERT(inferenceDepth); - if (--inferenceDepth != 0) { - /* - * There is still a type inference activation on the stack, wait for it to - * finish before handling any recompilations. Note that we should not be - * invoking any scripted code while the inference is running :TODO: assert this. - */ - return true; - } - if (pendingNukeTypes) - return nukeTypes(cx); - else if (pendingRecompiles && !processPendingRecompiles(cx)) - return false; - return true; -} - /* * Structure marking the currently compiled script, for constraints which can * trigger recompilation. @@ -345,14 +334,14 @@ JSContext::addTypePropertyId(js::types::TypeObject *obj, jsid id, js::types::jst js::types::TypeSet *types = obj->getProperty(this, id, true); if (!types || types->hasType(type)) - return compartment->types.checkPendingRecompiles(this); + return true; js::types::InferSpew(js::types::ISpewOps, "externalType: property %s %s: %s", obj->name(), js::types::TypeIdString(id), js::types::TypeString(type)); types->addType(this, type); - return compartment->types.checkPendingRecompiles(this); + return true; } inline bool @@ -374,13 +363,13 @@ JSContext::addTypePropertyId(js::types::TypeObject *obj, jsid id, js::types::Clo js::types::TypeSet *types = obj->getProperty(this, id, true); if (!types) - return compartment->types.checkPendingRecompiles(this); + return true; js::types::InferSpew(js::types::ISpewOps, "externalType: property %s %s", obj->name(), js::types::TypeIdString(id)); types->addTypeSet(this, set); - return compartment->types.checkPendingRecompiles(this); + return true; } inline js::types::TypeObject * @@ -408,7 +397,7 @@ JSContext::aliasTypeProperties(js::types::TypeObject *obj, jsid first, jsid seco firstTypes->addBaseSubset(this, obj, secondTypes); secondTypes->addBaseSubset(this, obj, firstTypes); - return compartment->types.checkPendingRecompiles(this); + return true; } inline bool @@ -419,7 +408,7 @@ JSContext::addTypeFlags(js::types::TypeObject *obj, js::types::TypeObjectFlags f js::types::AutoEnterTypeInference enter(this); obj->setFlags(this, flags); - return compartment->types.checkPendingRecompiles(this); + return true; } inline bool @@ -456,7 +445,7 @@ JSContext::markTypePropertyConfigured(js::types::TypeObject *obj, jsid id) if (types) types->setOwnProperty(this, true); - return compartment->types.checkPendingRecompiles(this); + return true; } inline bool @@ -480,7 +469,7 @@ JSContext::markGlobalReallocation(JSObject *obj) } } - return compartment->types.checkPendingRecompiles(this); + return true; } inline bool @@ -491,7 +480,7 @@ JSContext::markTypeObjectUnknownProperties(js::types::TypeObject *obj) js::types::AutoEnterTypeInference enter(this); obj->markUnknown(this); - return compartment->types.checkPendingRecompiles(this); + return true; } inline bool @@ -543,39 +532,48 @@ inline js::types::TypeSet * JSScript::returnTypes() { JS_ASSERT(varTypes); - return &varTypes[0]; + return &varTypes[js::analyze::CalleeSlot()]; } inline js::types::TypeSet * JSScript::thisTypes() { JS_ASSERT(varTypes); - return &varTypes[1]; + return &varTypes[js::analyze::ThisSlot()]; } +/* + * Note: for non-escaping arguments and locals, argTypes/localTypes reflect + * only the initial type of the variable (e.g. passed values for argTypes, + * or undefined for localTypes) and not types from subsequent assignments. + */ + inline js::types::TypeSet * JSScript::argTypes(unsigned i) { JS_ASSERT(varTypes && fun && i < fun->nargs); - return &varTypes[2 + i]; + return &varTypes[js::analyze::ArgSlot(i)]; } inline js::types::TypeSet * JSScript::localTypes(unsigned i) { JS_ASSERT(varTypes && i < nfixed); - if (fun) - i += fun->nargs; - return &varTypes[2 + i]; + return &varTypes[js::analyze::LocalSlot(this, i)]; } inline js::types::TypeSet * JSScript::upvarTypes(unsigned i) { JS_ASSERT(varTypes && i < bindings.countUpvars()); - if (fun) - i += fun->nargs; - return &varTypes[2 + nfixed + i]; + return &varTypes[js::analyze::LocalSlot(this, nfixed) + i]; +} + +inline js::types::TypeSet * +JSScript::slotTypes(unsigned slot) +{ + JS_ASSERT(slot < js::analyze::TotalSlots(this)); + return &varTypes[slot]; } inline JSObject * @@ -676,7 +674,8 @@ JSScript::typeSetThis(JSContext *cx, js::types::jstype type) return false; /* Analyze the script regardless if -a was used. */ - bool analyze = !types && cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS) && !isUncachedEval; + bool analyze = !(analysis_ && analysis_->ranInference()) && + cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS) && !isUncachedEval; if (!thisTypes()->hasType(type) || analyze) { js::types::AutoEnterTypeInference enter(cx); @@ -685,10 +684,14 @@ JSScript::typeSetThis(JSContext *cx, js::types::jstype type) id(), js::types::TypeString(type)); thisTypes()->addType(cx, type); - if (analyze && !types) - js::types::AnalyzeScriptTypes(cx, this); + if (analyze && !(analysis_ && analysis_->ranInference())) { + js::analyze::ScriptAnalysis *analysis = this->analysis(cx); + if (!analysis) + return false; + analysis->analyzeTypes(cx); + } - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } return true; @@ -712,7 +715,7 @@ JSScript::typeSetThis(JSContext *cx, js::types::ClonedTypeSet *set) js::types::InferSpew(js::types::ISpewOps, "externalType: setThis #%u", id()); thisTypes()->addTypeSet(cx, set); - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } inline bool @@ -732,9 +735,10 @@ JSScript::typeSetNewCalled(JSContext *cx) if (analyzed) { /* Regenerate types for the function. */ js::types::AutoEnterTypeInference enter(cx); - js::types::AnalyzeScriptNew(cx, this); - if (!cx->compartment->types.checkPendingRecompiles(cx)) + js::analyze::ScriptAnalysis *analysis = this->analysis(cx); + if (!analysis) return false; + analysis->analyzeTypesNew(cx); } return true; } @@ -753,7 +757,7 @@ JSScript::typeSetLocal(JSContext *cx, unsigned local, js::types::jstype type) id(), local, js::types::TypeString(type)); localTypes(local)->addType(cx, type); - return compartment->types.checkPendingRecompiles(cx); + return true; } return true; } @@ -778,7 +782,7 @@ JSScript::typeSetLocal(JSContext *cx, unsigned local, js::types::ClonedTypeSet * js::types::InferSpew(js::types::ISpewOps, "externalType: setLocal #%u %u", id(), local); localTypes(local)->addTypeSet(cx, set); - return compartment->types.checkPendingRecompiles(cx); + return true; } inline bool @@ -795,7 +799,7 @@ JSScript::typeSetArgument(JSContext *cx, unsigned arg, js::types::jstype type) id(), arg, js::types::TypeString(type)); argTypes(arg)->addType(cx, type); - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } return true; } @@ -820,7 +824,7 @@ JSScript::typeSetArgument(JSContext *cx, unsigned arg, js::types::ClonedTypeSet js::types::InferSpew(js::types::ISpewOps, "externalType: setArg #%u %u", id(), arg); argTypes(arg)->addTypeSet(cx, set); - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } inline bool @@ -838,7 +842,7 @@ JSScript::typeSetUpvar(JSContext *cx, unsigned upvar, const js::Value &value) id(), upvar, js::types::TypeString(type)); upvarTypes(upvar)->addType(cx, type); - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } return true; } @@ -963,7 +967,7 @@ HashSetInsertTry(JSContext *cx, U **&values, unsigned &count, T key, bool pool) } U **newValues = pool - ? ArenaArray(cx->compartment->types.pool, newCapacity) + ? ArenaArray(cx->compartment->pool, newCapacity) : (U **) js::OffTheBooks::malloc_(newCapacity * sizeof(U*)); if (!newValues) { cx->compartment->types.setPendingNukeTypes(cx); @@ -1010,7 +1014,7 @@ HashSetInsert(JSContext *cx, U **&values, unsigned &count, T key, bool pool) return (U **) &values; values = pool - ? ArenaArray(cx->compartment->types.pool, SET_ARRAY_SIZE) + ? ArenaArray(cx->compartment->pool, SET_ARRAY_SIZE) : (U **) js::OffTheBooks::malloc_(SET_ARRAY_SIZE * sizeof(U*)); if (!values) { values = (U **) oldData; @@ -1119,7 +1123,7 @@ inline void TypeSet::addType(JSContext *cx, jstype type) { JS_ASSERT(type); - JS_ASSERT(cx->compartment->types.inferenceDepth); + JS_ASSERT(cx->compartment->activeInference); if (unknown()) return; @@ -1208,9 +1212,9 @@ TypeSet::getObject(unsigned i) inline TypeSet * TypeSet::make(JSContext *cx, const char *name) { - JS_ASSERT(cx->compartment->types.inferenceDepth); + JS_ASSERT(cx->compartment->activeInference); - TypeSet *res = ArenaNew(cx->compartment->types.pool); + TypeSet *res = ArenaNew(cx->compartment->pool); if (!res) { cx->compartment->types.setPendingNukeTypes(cx); return NULL; @@ -1233,7 +1237,7 @@ TypeCallsite::TypeCallsite(JSContext *cx, JSScript *script, const jsbytecode *pc thisTypes(NULL), thisType(0), returnTypes(NULL) { /* Caller must check for failure. */ - argumentTypes = ArenaArray(cx->compartment->types.pool, argumentCount); + argumentTypes = ArenaArray(cx->compartment->pool, argumentCount); } inline bool @@ -1269,7 +1273,7 @@ TypeCallsite::compileAndGo() inline TypeSet * TypeObject::getProperty(JSContext *cx, jsid id, bool assign) { - JS_ASSERT(cx->compartment->types.inferenceDepth); + JS_ASSERT(cx->compartment->activeInference); JS_ASSERT(JSID_IS_VOID(id) || JSID_IS_EMPTY(id) || JSID_IS_STRING(id)); JS_ASSERT_IF(JSID_IS_STRING(id), JSID_TO_STRING(id) != NULL); JS_ASSERT(!unknownProperties()); @@ -1304,46 +1308,9 @@ TypeObject::getProperty(unsigned i) } ///////////////////////////////////////////////////////////////////// -// TypeScript +// TypeObject ///////////////////////////////////////////////////////////////////// -inline bool -TypeScript::monitored(uint32 offset) -{ - JS_ASSERT(offset < script->length); - return 0x1 & (size_t) pushedArray[offset]; -} - -inline void -TypeScript::setMonitored(uint32 offset) -{ - JS_ASSERT(script->compartment->types.inferenceDepth); - JS_ASSERT(offset < script->length); - pushedArray[offset] = (TypeSet *) (0x1 | (size_t) pushedArray[offset]); -} - -inline TypeSet * -TypeScript::pushed(uint32 offset) -{ - JS_ASSERT(offset < script->length); - return (TypeSet *) (~0x1 & (size_t) pushedArray[offset]); -} - -inline TypeSet * -TypeScript::pushed(uint32 offset, uint32 index) -{ - JS_ASSERT(offset < script->length); - JS_ASSERT(index < js::analyze::GetDefCount(script, offset)); - return pushed(offset) + index; -} - -inline void -TypeScript::addType(JSContext *cx, uint32 offset, uint32 index, jstype type) -{ - TypeSet *types = pushed(offset, index); - types->addType(cx, type); -} - inline const char * TypeObject::name() { @@ -1420,4 +1387,12 @@ class AutoTypeRooter : private AutoGCRooter { } } /* namespace js::types */ +inline void +js::analyze::ScriptAnalysis::addPushedType(JSContext *cx, uint32 offset, uint32 which, + js::types::jstype type) +{ + js::types::TypeSet *pushed = pushedTypes(offset, which); + pushed->addType(cx, type); +} + #endif // jsinferinlines_h___ diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index da3ebb3aa4b5..b3d5f2fcb2a6 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1502,7 +1502,7 @@ DestroyScript(JSContext *cx, JSScript *script) PurgeScriptFragments(&script->compartment->traceMonitor, script); #endif - JS_ASSERT(!script->types); + JS_ASSERT(!script->hasAnalysis()); /* Migrate any type objects associated with this script to the compartment. */ types::TypeObject *obj = script->typeObjects; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 65b814aa019a..3e104ccfec13 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -392,7 +392,7 @@ enum JITScriptStatus { namespace js { namespace mjit { struct JITScript; } -namespace analyze { class Script; } +namespace analyze { class ScriptAnalysis; } } #endif @@ -532,8 +532,19 @@ struct JSScript { /* Any possibly unexpected values pushed by opcodes in this script. */ js::types::TypeResult *typeResults; - /* Results of type inference analysis for this script. Destroyed on GC. */ - js::types::TypeScript *types; + /* Bytecode analysis and type inference results for this script. Destroyed on GC. */ + private: + js::analyze::ScriptAnalysis *analysis_; + void makeAnalysis(JSContext *cx); + public: + + bool hasAnalysis() { return analysis_ != NULL; } + + js::analyze::ScriptAnalysis *analysis(JSContext *cx) { + if (!analysis_) + makeAnalysis(cx); + return analysis_; + } inline JSObject *getGlobal(); inline js::types::TypeObject *getGlobalType(); @@ -550,6 +561,9 @@ struct JSScript { inline js::types::TypeSet *localTypes(unsigned i); inline js::types::TypeSet *upvarTypes(unsigned i); + /* Follows slot layout in jsanalyze.h, can get this/arg/local type sets. */ + inline js::types::TypeSet *slotTypes(unsigned slot); + private: bool makeVarTypes(JSContext *cx); public: @@ -563,7 +577,7 @@ struct JSScript { inline js::types::TypeObject *getTypeNewObject(JSContext *cx, JSProtoKey key); void condenseTypes(JSContext *cx); - void sweepTypes(JSContext *cx); + void sweepAnalysis(JSContext *cx); /* Get a type object for an allocation site in this script. */ inline js::types::TypeObject * diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index eaea7b4706db..43f8c8e3633d 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -133,6 +133,8 @@ mjit::Compiler::Compiler(JSContext *cx, JSScript *outerScript, bool isConstructi oomInVector(false), applyTricks(NoApplyTricks) { + JS_ASSERT(!outerScript->isUncachedEval); + /* :FIXME: bug 637856 disabling traceJit if inference is enabled */ if (cx->typeInferenceEnabled()) addTraceHints = false; @@ -198,33 +200,45 @@ mjit::Compiler::pushActiveFrame(JSScript *script, uint32 argc) outer = newa; } - newa->analysis.analyze(cx, script); - - if (newa->analysis.OOM()) + analyze::ScriptAnalysis *newAnalysis = script->analysis(cx); + if (!newAnalysis) return Compile_Error; - if (newa->analysis.failed()) { + if (!newAnalysis->failed() && !newAnalysis->ranBytecode()) + newAnalysis->analyzeBytecode(cx); + + if (newAnalysis->OOM()) + return Compile_Error; + if (newAnalysis->failed()) { JaegerSpew(JSpew_Abort, "couldn't analyze bytecode; probably switchX or OOM\n"); return Compile_Abort; } - if (cx->typeInferenceEnabled() && !newa->liveness.analyze(cx, &newa->analysis, script)) { - js_ReportOutOfMemory(cx); - return Compile_Error; + if (cx->typeInferenceEnabled()) { + if (!newAnalysis->ranSSA()) + newAnalysis->analyzeSSA(cx); + if (!newAnalysis->failed() && !newAnalysis->ranLifetimes()) + newAnalysis->analyzeLifetimes(cx); + if (newAnalysis->failed()) { + js_ReportOutOfMemory(cx); + return Compile_Error; + } } #ifdef JS_METHODJIT_SPEW if (cx->typeInferenceEnabled() && IsJaegerSpewChannelActive(JSpew_Regalloc)) { unsigned nargs = script->fun ? script->fun->nargs : 0; for (unsigned i = 0; i < nargs; i++) { - if (!newa->analysis.argEscapes(i)) { + uint32 slot = analyze::ArgSlot(i); + if (!newAnalysis->slotEscapes(slot)) { JaegerSpew(JSpew_Regalloc, "Argument %u:", i); - newa->liveness.dumpSlot(2 + i); + newAnalysis->liveness(slot).print(); } } for (unsigned i = 0; i < script->nfixed; i++) { - if (!newa->analysis.localEscapes(i)) { + uint32 slot = analyze::LocalSlot(script, i); + if (!newAnalysis->slotEscapes(slot)) { JaegerSpew(JSpew_Regalloc, "Local %u:", i); - newa->liveness.dumpSlot(2 + nargs + i); + newAnalysis->liveness(slot).print(); } } } @@ -233,7 +247,7 @@ mjit::Compiler::pushActiveFrame(JSScript *script, uint32 argc) if (a) frame.getUnsyncedEntries(&newa->depth, &newa->unsyncedEntries); - if (!frame.pushActiveFrame(script, argc, &newa->analysis, &newa->liveness)) { + if (!frame.pushActiveFrame(script, argc)) { js_ReportOutOfMemory(cx); return Compile_Error; } @@ -255,6 +269,7 @@ mjit::Compiler::pushActiveFrame(JSScript *script, uint32 argc) } this->script = script; + this->analysis = newAnalysis; this->PC = script->code; this->a = newa; @@ -270,6 +285,7 @@ mjit::Compiler::popActiveFrame() this->PC = a->parentPC; this->a = a->parent; this->script = a->script; + this->analysis = this->script->analysis(cx); frame.popActiveFrame(); } @@ -304,7 +320,7 @@ mjit::Compiler::performCompilation(JITScript **jitp) outerScript->debugMode = debugMode(); #endif - JS_ASSERT(cx->compartment->types.inferenceDepth); + JS_ASSERT(cx->compartment->activeInference); { types::AutoEnterCompilation enter(cx, outerScript); @@ -372,8 +388,7 @@ mjit::Compiler::performCompilation(JITScript **jitp) mjit::Compiler::ActiveFrame::ActiveFrame(JSContext *cx) : parent(NULL), parentPC(NULL), script(NULL), inlineIndex(uint32(-1)), - jumpMap(NULL), hasThisType(false), argumentTypes(NULL), localTypes(NULL), - unsyncedEntries(cx), + jumpMap(NULL), unsyncedEntries(cx), needReturnValue(false), syncReturnValue(false), returnValueDouble(false), returnSet(false), returnParentRegs(0), temporaryParentRegs(0), returnJumps(NULL) @@ -382,8 +397,6 @@ mjit::Compiler::ActiveFrame::ActiveFrame(JSContext *cx) mjit::Compiler::ActiveFrame::~ActiveFrame() { js::Foreground::free_(jumpMap); - js::Foreground::array_delete(argumentTypes); - js::Foreground::array_delete(localTypes); } mjit::Compiler::~Compiler() @@ -400,40 +413,45 @@ CompileStatus mjit::Compiler::prepareInferenceTypes(JSScript *script, ActiveFrame *a) { /* Analyze the script if we have not already done so. */ - if (!script->types) { - /* Uncached eval scripts are not analyzed or compiled. */ - if (script->isUncachedEval) - return Compile_Abort; - types::AnalyzeScriptTypes(cx, script); - if (!script->types) + analyze::ScriptAnalysis *analysis = script->analysis(cx); + if (!analysis->ranInference()) { + analysis->analyzeTypes(cx); + if (!analysis->ranInference()) return Compile_Error; } - /* Get the known types of arguments and locals. */ + /* + * During our walk of the script, we need to preserve the invariant that at + * join points the in memory type tag is always in sync with the known type + * tag of the variable's SSA value at that join point. In particular, SSA + * values inferred as (int|double) must in fact be doubles, stored either + * in floating point registers or in memory. (There is an exception for + * locals with a dead value at the current point, whose type may or may not + * be synced). + * + * To ensure this, we need to know the SSA values for each variable at each + * join point, which the SSA analysis does not store explicitly. These can + * be recovered, though. During the forward walk, the SSA value of a var + * (and its associated type set) change only when we see an explicit assign + * to the var or get to a join point with a phi node for that var. So we + * can duplicate the effects of that walk here by watching for writes to + * vars (updateVarTypes) and new phi nodes at join points. + * + * When we get to a branch and need to know a variable's value at the + * branch target, we know it will either be a phi node at the target or + * the variable's current value, as no phi node is created at the target + * only if a variable has the same value on all incoming edges. + */ - uint32 nargs = script->fun ? script->fun->nargs : 0; - if (nargs) { - a->argumentTypes = cx->array_new(nargs); - if (!a->argumentTypes) - return Compile_Error; - for (unsigned i = 0; i < nargs; i++) { - JSValueType type = JSVAL_TYPE_UNKNOWN; - if (!a->analysis.argEscapes(i)) - type = script->argTypes(i)->getKnownTypeTag(cx); - a->argumentTypes[i] = type; - } - } + a->varTypes = (VarType *) + cx->calloc_(analyze::TotalSlots(script) * sizeof(VarType)); + if (!a->varTypes) + return Compile_Error; - if (script->nfixed) { - a->localTypes = cx->array_new(script->nfixed); - if (!a->localTypes) - return Compile_Error; - for (unsigned i = 0; i < script->nfixed; i++) { - JSValueType type = JSVAL_TYPE_UNKNOWN; - if (!a->analysis.localHasUseBeforeDef(i)) - type = script->localTypes(i)->getKnownTypeTag(cx); - a->localTypes[i] = type; - } + for (uint32 slot = analyze::ArgSlot(0); slot < analyze::TotalSlots(script); slot++) { + VarType &vt = a->varTypes[slot]; + vt.types = script->slotTypes(slot); + vt.type = vt.types->getKnownTypeTag(cx); } return Compile_Okay; @@ -449,6 +467,10 @@ mjit::TryCompile(JSContext *cx, JSStackFrame *fp) return Compile_Abort; #endif + // Uncached eval scripts are not analyzed or compiled. + if (fp->script()->isUncachedEval) + return Compile_Abort; + // Ensure that constructors have at least one slot. if (fp->isConstructing() && !fp->script()->nslots) fp->script()->nslots++; @@ -464,9 +486,6 @@ mjit::TryCompile(JSContext *cx, JSStackFrame *fp) status = cc.compile(); } - if (!cx->compartment->types.checkPendingRecompiles(cx)) - return Compile_Error; - return status; } @@ -576,7 +595,7 @@ mjit::Compiler::generatePrologue() * :FIXME: bug 604541: write undefined if we might be using the tracer, so it works. */ for (uint32 i = 0; i < script->nfixed; i++) { - if (a->analysis.localHasUseBeforeDef(i) || addTraceHints) { + if (analysis->localHasUseBeforeDef(i) || addTraceHints) { Address local(JSFrameReg, sizeof(JSStackFrame) + i * sizeof(Value)); masm.storeValue(UndefinedValue(), local); } @@ -590,7 +609,7 @@ mjit::Compiler::generatePrologue() j.linkTo(masm.label(), &masm); - if (a->analysis.usesScopeChain() && !script->fun->isHeavyweight()) { + if (analysis->usesScopeChain() && !script->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 @@ -614,49 +633,12 @@ mjit::Compiler::generatePrologue() INLINE_STUBCALL(stubs::ScriptDebugPrologue); } - /* - * Set initial types of locals with known type. These will stay synced - * through the rest of the script, allowing us to avoid syncing the types - * of locals after writing their payloads. Notes: - * - * - We don't call generatePrologue and perform this syncing when inlining - * frames; such locals are not assumed to be synced after being assigned. - * - * - If we are recompiling, the earlier compilation might not have known - * the type of the local (its type set was empty, say), in which case - * it wouldn't have stored that type tag. We need to walk the frames and - * fixup the type tags accordingly. - */ - for (uint32 i = 0; i < script->nfixed; i++) { - JSValueType type = knownLocalType(i); - if (type != JSVAL_TYPE_UNKNOWN) { - JS_ASSERT(!a->analysis.localHasUseBeforeDef(i)); - /* Doubles will be written entirely when syncing. */ - if (type != JSVAL_TYPE_DOUBLE) { - Address local(JSFrameReg, sizeof(JSStackFrame) + i * sizeof(Value)); - masm.storeTypeTag(ImmType(type), local); - for (unsigned j = 0; patchFrames && j < patchFrames->length(); j++) { - JSStackFrame *patchfp = (*patchFrames)[j].fp; - patchfp->varSlot(i).boxNonDoubleFrom(type, (uint64*) &patchfp->varSlot(i)); - } - } - frame.learnType(frame.getLocal(i), type, false); - } - } - - /* - * Learn types of arguments with known type, and make sure double arguments - * are actually doubles and not ints. - */ - for (uint32 i = 0; script->fun && i < script->fun->nargs; i++) { - JSValueType type = knownArgumentType(i); - if (type != JSVAL_TYPE_UNKNOWN) { - if (type == JSVAL_TYPE_DOUBLE) { - if (!a->analysis.argEscapes(i)) - frame.ensureDouble(frame.getArg(i)); - } else { - frame.learnType(frame.getArg(i), type, false); - } + if (cx->typeInferenceEnabled()) { + /* Convert integer arguments which were inferred as (int|double) to doubles. */ + for (uint32 i = 0; script->fun && i < script->fun->nargs; i++) { + uint32 slot = analyze::ArgSlot(i); + if (a->varTypes[slot].type == JSVAL_TYPE_DOUBLE && analysis->trackSlot(slot)) + frame.ensureDouble(frame.getArg(i)); } } @@ -665,32 +647,6 @@ mjit::Compiler::generatePrologue() return Compile_Okay; } -void -mjit::Compiler::generateInlinePrologue() -{ - /* - * The types of locals in inlined frames are left unsynced even if known. - * (Note that we mark as uninlineable all scripts containing locals with - * uses before defs). We will treat these locals as synced while compiling - * the script, and need to actually do the syncing if we ever expand the - * inline frame (which happens on recompilation, and recompilation is the - * only way we will ever need to observe the known type of the local). - */ - for (uint32 i = 0; i < script->nfixed; i++) { - JS_ASSERT(!a->analysis.localHasUseBeforeDef(i)); - JSValueType type = knownLocalType(i); - if (type != JSVAL_TYPE_UNKNOWN && type != JSVAL_TYPE_DOUBLE) { - FrameEntry *fe = frame.getLocal(i); - UnsyncedEntry entry; - PodZero(&entry); - entry.offset = frame.frameOffset(fe); - entry.knownType = true; - entry.u.type = type; - a->unsyncedEntries.append(entry); - } - } -} - CompileStatus mjit::Compiler::generateEpilogue() { @@ -743,10 +699,10 @@ mjit::Compiler::finishThisUp(JITScript **jitp) size_t nNmapLive = loopEntries.length(); for (size_t i = 0; i < script->length; i++) { - analyze::Bytecode *opinfo = a->analysis.maybeCode(i); + analyze::Bytecode *opinfo = analysis->maybeCode(i); if (opinfo && opinfo->safePoint) { /* loopEntries cover any safe points which are at loop heads. */ - if (!cx->typeInferenceEnabled() || !a->liveness.getCode(i).loop) + if (!cx->typeInferenceEnabled() || !opinfo->loopHead) nNmapLive++; } } @@ -812,7 +768,7 @@ mjit::Compiler::finishThisUp(JITScript **jitp) size_t ix = 0; if (jit->nNmapPairs > 0) { for (size_t i = 0; i < script->length; i++) { - analyze::Bytecode *opinfo = a->analysis.maybeCode(i); + analyze::Bytecode *opinfo = analysis->maybeCode(i); if (opinfo && opinfo->safePoint) { Label L = jumpMap[i]; JS_ASSERT(L.isValid()); @@ -1341,7 +1297,7 @@ mjit::Compiler::generateMethod() trap |= stubs::JSTRAP_SINGLESTEP; variadicRejoin = false; - analyze::Bytecode *opinfo = a->analysis.maybeCode(PC); + analyze::Bytecode *opinfo = analysis->maybeCode(PC); if (!opinfo) { if (op == JSOP_STOP) @@ -1360,7 +1316,7 @@ mjit::Compiler::generateMethod() frame.setInTryBlock(opinfo->inTryBlock); if (opinfo->jumpTarget || trap) { if (fallthrough) { - fixDoubleTypes(); + fixDoubleTypes(PC); /* * Watch for fallthrough to the head of a 'do while' loop. @@ -1368,7 +1324,7 @@ mjit::Compiler::generateMethod() * of the loop so sync, branch, and fix it up after the loop * has been processed. */ - if (cx->typeInferenceEnabled() && a->liveness.getCode(PC).loop) { + if (cx->typeInferenceEnabled() && analysis->getCode(PC).loopHead) { frame.syncAndForgetEverything(); Jump j = masm.jump(); if (!startLoop(PC, j, PC)) @@ -1382,7 +1338,7 @@ mjit::Compiler::generateMethod() if (!frame.discardForJoin(PC, opinfo->stackDepth)) return Compile_Error; - restoreAnalysisTypes(opinfo->stackDepth); + restoreAnalysisTypes(); fallthrough = true; if (!cx->typeInferenceEnabled()) { @@ -1482,24 +1438,10 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_GOTO) BEGIN_CASE(JSOP_DEFAULT) { - jsbytecode *target = PC + GET_JUMP_OFFSET(PC); - fixDoubleTypes(); + unsigned targetOffset = analyze::FollowBranch(script, PC - script->code); + jsbytecode *target = script->code + targetOffset; - /* - * Watch out for backward jumps linking 'continue' statements - * together. These are jumping to another GOTO at the head of the - * loop, which should be short circuited so we don't mistake this - * for an actual loop back edge. :XXX: could there be a trap at - * the target? - */ - if (target < PC) { - if (JSOp(*target) == JSOP_GOTO) { - target = target + GET_JUMP_OFFSET(target); - JS_ASSERT(target >= PC); - } else if (JSOp(*target) == JSOP_GOTOX) { - return Compile_Abort; - } - } + fixDoubleTypes(target); /* * Watch for gotos which are entering a 'for' or 'while' loop. @@ -1507,8 +1449,8 @@ mjit::Compiler::generateMethod() * followed by the head of the loop. */ jsbytecode *next = PC + JSOP_GOTO_LENGTH; - if (cx->typeInferenceEnabled() && a->analysis.maybeCode(next) && - a->liveness.getCode(next).loop) { + if (cx->typeInferenceEnabled() && analysis->maybeCode(next) && + analysis->getCode(next).loopHead) { frame.syncAndForgetEverything(); Jump j = masm.jump(); if (!startLoop(next, j, target)) @@ -1526,7 +1468,6 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_IFEQ) BEGIN_CASE(JSOP_IFNE) - fixDoubleTypes(); if (!jsop_ifneq(op, PC + GET_JUMP_OFFSET(PC))) return Compile_Error; END_CASE(JSOP_IFNE) @@ -1548,18 +1489,20 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_FORARG) { + updateVarType(); uint32 arg = GET_SLOTNO(PC); iterNext(); - frame.storeArg(arg, knownArgumentType(arg), true); + frame.storeArg(arg, true); frame.pop(); } END_CASE(JSOP_FORARG) BEGIN_CASE(JSOP_FORLOCAL) { + updateVarType(); uint32 slot = GET_SLOTNO(PC); iterNext(); - frame.storeLocal(slot, knownLocalType(slot), true, true); + frame.storeLocal(slot, true, true); frame.pop(); } END_CASE(JSOP_FORLOCAL) @@ -1588,14 +1531,14 @@ mjit::Compiler::generateMethod() /* Detect fusions. */ jsbytecode *next = &PC[JSOP_GE_LENGTH]; JSOp fused = JSOp(*next); - if ((fused != JSOP_IFEQ && fused != JSOP_IFNE) || a->analysis.jumpTarget(next)) + if ((fused != JSOP_IFEQ && fused != JSOP_IFNE) || analysis->jumpTarget(next)) fused = JSOP_NOP; /* Get jump target, if any. */ jsbytecode *target = NULL; if (fused != JSOP_NOP) { target = next + GET_JUMP_OFFSET(next); - fixDoubleTypes(); + fixDoubleTypes(target); } BoolStub stub = NULL; @@ -1653,7 +1596,7 @@ mjit::Compiler::generateMethod() result = !result; if (result) { - fixDoubleTypes(); + fixDoubleTypes(target); if (!frame.syncForBranch(target, Uses(0))) return Compile_Error; if (needRejoins(PC)) { @@ -1958,7 +1901,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_SETHOLE) { jsbytecode *next = &PC[JSOP_SETELEM_LENGTH]; - bool pop = (JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next)); + bool pop = (JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next)); if (!jsop_setelem(pop)) return Compile_Error; } @@ -2146,7 +2089,6 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_OR) BEGIN_CASE(JSOP_AND) - fixDoubleTypes(); if (!jsop_andor(op, PC + GET_JUMP_OFFSET(PC))) return Compile_Error; END_CASE(JSOP_AND) @@ -2154,10 +2096,11 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_TABLESWITCH) /* * Note: there is no need to syncForBranch for the various targets of - * switch statement. The liveness analysis has already marked these as - * allocated with no registers in use. + * switch statement. The liveness analysis has already marked these as + * allocated with no registers in use. There is also no need to fix + * double types, as we don't track types of slots in scripts with + * switch statements (could be fixed). */ - fixDoubleTypes(); #if defined JS_CPU_ARM /* Need to implement jump(BaseIndex) for ARM */ frame.syncAndKillEverything(); masm.move(ImmPtr(PC), Registers::ArgReg1); @@ -2176,7 +2119,6 @@ mjit::Compiler::generateMethod() END_CASE(JSOP_TABLESWITCH) BEGIN_CASE(JSOP_LOOKUPSWITCH) - fixDoubleTypes(); frame.syncAndForgetEverything(); masm.move(ImmPtr(PC), Registers::ArgReg1); @@ -2198,7 +2140,6 @@ mjit::Compiler::generateMethod() jsop_stricteq(JSOP_STRICTEQ); // X cond - fixDoubleTypes(); if (!jsop_ifneq(JSOP_IFNE, PC + GET_JUMP_OFFSET(PC))) return Compile_Error; END_CASE(JSOP_CASE) @@ -2239,7 +2180,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_GETARG) { uint32 arg = GET_SLOTNO(PC); - frame.pushArg(arg, knownArgumentType(arg)); + frame.pushArg(arg); } END_CASE(JSOP_GETARG) @@ -2249,7 +2190,7 @@ mjit::Compiler::generateMethod() if (JSObject *singleton = pushedSingleton(0)) frame.push(ObjectValue(*singleton)); else - frame.pushArg(arg, knownArgumentType(arg)); + frame.pushArg(arg); frame.push(UndefinedValue()); } END_CASE(JSOP_GETARG) @@ -2260,10 +2201,10 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_SETARG) { - uint32 arg = GET_SLOTNO(PC); + updateVarType(); jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; - bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next); - frame.storeArg(arg, knownArgumentType(arg), pop); + bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); + frame.storeArg(GET_SLOTNO(PC), pop); if (pop) { frame.pop(); PC += JSOP_SETARG_LENGTH + JSOP_POP_LENGTH; @@ -2275,16 +2216,16 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_GETLOCAL) { uint32 slot = GET_SLOTNO(PC); - frame.pushLocal(slot, knownPushedType(0)); + frame.pushLocal(slot); } END_CASE(JSOP_GETLOCAL) BEGIN_CASE(JSOP_SETLOCAL) { - uint32 slot = GET_SLOTNO(PC); + updateVarType(); jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; - bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next); - frame.storeLocal(slot, knownLocalType(slot), pop, true); + bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); + frame.storeLocal(GET_SLOTNO(PC), pop, true); if (pop) { frame.pop(); PC += JSOP_SETLOCAL_LENGTH + JSOP_POP_LENGTH; @@ -2295,8 +2236,9 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_SETLOCALPOP) { + updateVarType(); uint32 slot = GET_SLOTNO(PC); - frame.storeLocal(slot, knownLocalType(slot), true, true); + frame.storeLocal(slot, true, true); frame.pop(); } END_CASE(JSOP_SETLOCALPOP) @@ -2345,7 +2287,7 @@ mjit::Compiler::generateMethod() { jsbytecode *next = &PC[JSOP_ARGINC_LENGTH]; bool popped = false; - if (JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next)) + if (JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next)) popped = true; if (!jsop_arginc(op, GET_SLOTNO(PC), popped)) return Compile_Retry; @@ -2363,7 +2305,7 @@ mjit::Compiler::generateMethod() { jsbytecode *next = &PC[JSOP_LOCALINC_LENGTH]; bool popped = false; - if (JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next)) + if (JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next)) popped = true; /* These manually advance the PC. */ if (!jsop_localinc(op, GET_SLOTNO(PC), popped)) @@ -2400,7 +2342,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_SETPROP) { jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; - bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next); + bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true, pop)) return Compile_Error; } @@ -2410,7 +2352,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_SETMETHOD) { jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; - bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next); + bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true, pop)) return Compile_Error; } @@ -2530,6 +2472,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_DEFLOCALFUN_FC) { REJOIN_SITE_ANY(); + updateVarType(); uint32 slot = GET_SLOTNO(PC); JSFunction *fun = script->getFunction(fullAtomIndex(&PC[SLOTNO_LEN])); prepareStubCall(Uses(frame.frameSlots())); @@ -2632,6 +2575,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_DEFLOCALFUN) { REJOIN_SITE_ANY(); + updateVarType(); uint32 slot = GET_SLOTNO(PC); JSFunction *fun = script->getFunction(fullAtomIndex(&PC[SLOTNO_LEN])); prepareStubCall(Uses(0)); @@ -2662,7 +2606,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_SETGNAME) { jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; - bool pop = JSOp(*next) == JSOP_POP && !a->analysis.jumpTarget(next); + bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); jsop_setgname(script->getAtom(fullAtomIndex(PC)), true, pop); } END_CASE(JSOP_SETGNAME) @@ -2741,7 +2685,7 @@ mjit::Compiler::generateMethod() if (JSObject *singleton = pushedSingleton(0)) frame.push(ObjectValue(*singleton)); else - frame.pushLocal(slot, knownPushedType(0)); + frame.pushLocal(slot); frame.push(UndefinedValue()); } END_CASE(JSOP_CALLLOCAL) @@ -2775,7 +2719,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_TRACE) BEGIN_CASE(JSOP_NOTRACE) { - if (a->analysis.jumpTarget(PC)) { + if (analysis->jumpTarget(PC)) { interruptCheckHelper(); recompileCheckHelper(); } @@ -2833,7 +2777,7 @@ mjit::Compiler::generateMethod() FrameEntry *fe = frame.getStack(opinfo->stackDepth - nuses + i); if (fe) { /* fe may be NULL for conditionally pushed entries, e.g. JSOP_AND */ - frame.extra(fe).types = script->types->pushed(oldPC - script->code, i); + frame.extra(fe).types = analysis->pushedTypes(oldPC - script->code, i); } } } @@ -2983,7 +2927,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 (a->analysis.usesReturnValue()) { + if (analysis->usesReturnValue()) { Jump rvalClear = masm->branchTest32(Assembler::Zero, FrameFlagsAddress(), Imm32(JSFRAME_HAS_RVAL)); @@ -3009,7 +2953,7 @@ mjit::Compiler::fixPrimitiveReturn(Assembler *masm, FrameEntry *fe) // We can just load |thisv| if either of the following is true: // (1) There is no explicit return value, AND fp->rval is not used. // (2) There is an explicit return value, and it's known to be primitive. - if ((!fe && !a->analysis.usesReturnValue()) || + if ((!fe && !analysis->usesReturnValue()) || (fe && fe->isTypeKnown() && fe->getKnownType() != JSVAL_TYPE_OBJECT)) { if (ool) @@ -3142,7 +3086,7 @@ mjit::Compiler::emitReturn(FrameEntry *fe) (JSOp(*PC) == JSOP_STOP) || (JSOp(*PC) == JSOP_RETURN && (JSOp(*(PC + JSOP_RETURN_LENGTH)) == JSOP_STOP && - !a->analysis.maybeCode(PC + JSOP_RETURN_LENGTH))); + !analysis->maybeCode(PC + JSOP_RETURN_LENGTH))); if (!endOfScript) a->returnJumps->append(masm.jump()); @@ -3249,7 +3193,7 @@ mjit::Compiler::recompileCheckHelper() { REJOIN_SITE(stubs::RecompileForInline); - if (!a->analysis.hasFunctionCalls() || !cx->typeInferenceEnabled() || + if (!analysis->hasFunctionCalls() || !cx->typeInferenceEnabled() || script->callCount() >= CALLS_BACKEDGES_BEFORE_INLINING) { return; } @@ -3413,7 +3357,7 @@ mjit::Compiler::canUseApplyTricks() jsbytecode *nextpc = PC + JSOP_ARGUMENTS_LENGTH; return *nextpc == JSOP_FUNAPPLY && IsLowerableFunCallOrApply(nextpc) && - !a->analysis.jumpTarget(nextpc) && + !analysis->jumpTarget(nextpc) && !debugMode() && !a->parent; } @@ -3956,18 +3900,18 @@ mjit::Compiler::inlineScriptedFunction(uint32 argc, bool callingNew) if (frame.totalDepth() + VALUES_PER_STACK_FRAME + fun->script()->nslots >= stackLimit) return Compile_InlineAbort; - analyze::Script analysis; - analysis.analyze(cx, script); - - if (analysis.OOM()) + analyze::ScriptAnalysis *analysis = script->analysis(cx); + if (analysis && !analysis->failed() && !analysis->ranBytecode()) + analysis->analyzeBytecode(cx); + if (!analysis || analysis->OOM()) return Compile_Error; - if (analysis.failed()) + if (analysis->failed()) return Compile_Abort; - if (!analysis.inlineable(argc)) + if (!analysis->inlineable(argc)) return Compile_InlineAbort; - if (analysis.usesThisValue() && origThis->isNotType(JSVAL_TYPE_OBJECT)) + if (analysis->usesThisValue() && origThis->isNotType(JSVAL_TYPE_OBJECT)) return Compile_InlineAbort; } @@ -4051,7 +3995,6 @@ mjit::Compiler::inlineScriptedFunction(uint32 argc, bool callingNew) } a->temporaryParentRegs = temporaryParentRegs; - generateInlinePrologue(); status = generateMethod(); if (status != Compile_Okay) { popActiveFrame(); @@ -4257,11 +4200,12 @@ mjit::Compiler::compareTwoValues(JSContext *cx, JSOp op, const Value &lhs, const bool mjit::Compiler::emitStubCmpOp(BoolStub stub, AutoRejoinSite &autoRejoin, jsbytecode *target, JSOp fused) { - fixDoubleTypes(); - if (target) + if (target) { + fixDoubleTypes(target); frame.syncAndKillEverything(); - else + } else { frame.syncAndKill(Uses(2)); + } prepareStubCall(Uses(2)); INLINE_STUBCALL(stub); @@ -4376,13 +4320,13 @@ mjit::Compiler::jsop_getprop(JSAtom *atom, JSValueType knownType, frame.forgetMismatchedObject(top); - if (atom == cx->runtime->atomState.lengthAtom) { + if (JSOp(*PC) == JSOP_LENGTH) { /* * Check if this is an array we can make a loop invariant entry for. * This will fail for objects which are not definitely dense arrays. */ if (loop && loop->generatingInvariants()) { - FrameEntry *fe = loop->invariantLength(top); + FrameEntry *fe = loop->invariantLength(top, frame.extra(top).types); if (fe) { frame.pop(); frame.pushTemporary(fe); @@ -5265,7 +5209,7 @@ mjit::Compiler::jsop_bindname(JSAtom *atom, 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(a->analysis.usesScopeChain()); + JS_ASSERT(analysis->usesScopeChain()); pic.shapeReg = frame.allocReg(); pic.objReg = frame.allocReg(); @@ -5390,7 +5334,9 @@ mjit::Compiler::jsop_this() if (script->fun && !script->strictModeCode) { FrameEntry *thisFe = frame.peek(-1); if (!thisFe->isTypeKnown()) { - JSValueType type = knownThisType(); + JSValueType type = cx->typeInferenceEnabled() + ? script->thisTypes()->getKnownTypeTag(cx) + : JSVAL_TYPE_UNKNOWN; if (type != JSVAL_TYPE_OBJECT) { Jump notObj = frame.testObject(Assembler::NotEqual, thisFe); stubcc.linkExit(notObj, Uses(1)); @@ -5416,7 +5362,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) && !a->analysis.jumpTarget(next); + 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)) { @@ -5507,7 +5453,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) && !a->analysis.jumpTarget(next); + 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)) { @@ -5584,7 +5530,7 @@ mjit::Compiler::jsop_propinc(JSOp op, VoidStubAtom stub, uint32 index) JSAtom *atom = script->getAtom(index); #if defined JS_POLYIC jsbytecode *next = &PC[JSOP_PROPINC_LENGTH]; - bool pop = (JSOp(*next) == JSOP_POP) && !a->analysis.jumpTarget(next); + 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)) { @@ -5863,7 +5809,7 @@ mjit::Compiler::iterMore() ? GET_JUMP_OFFSET(target) : GET_JUMPX_OFFSET(target); - fixDoubleTypes(); + fixDoubleTypes(target); if (!frame.syncForBranch(target, Uses(1))) return false; @@ -6529,7 +6475,7 @@ mjit::Compiler::startLoop(jsbytecode *head, Jump entry, jsbytecode *entryTarget) loop->clearLoopRegisters(); } - LoopState *nloop = cx->new_(cx, script, this, &frame, &a->analysis, &a->liveness); + LoopState *nloop = cx->new_(cx, script, this, &frame); if (!nloop || !nloop->init(head, entry, entryTarget)) return false; @@ -6564,7 +6510,7 @@ mjit::Compiler::finishLoop(jsbytecode *head) #ifdef DEBUG if (IsJaegerSpewChannelActive(JSpew_Regalloc)) { - RegisterAllocation *alloc = a->liveness.getCode(head).allocation; + RegisterAllocation *alloc = analysis->getAllocation(head); JaegerSpew(JSpew_Regalloc, "loop allocation at %u:", head - script->code); frame.dumpAllocation(alloc); } @@ -6599,7 +6545,7 @@ mjit::Compiler::finishLoop(jsbytecode *head) return false; PC = head; - if (!a->analysis.getCode(head).safePoint) { + if (!analysis->getCode(head).safePoint) { /* * Emit a stub into the OOL path which loads registers from a synced state * and jumps to the loop head, for rejoining from the interpreter. @@ -6618,6 +6564,20 @@ mjit::Compiler::finishLoop(jsbytecode *head) entry.label = stubcc.masm.label(); } + /* + * The interpreter may store integers in slots we assume are doubles, + * make sure state is consistent before joining. Note that we don't + * need any handling for other safe points the interpreter can enter + * from, i.e. from switch and try blocks, as we don't assume double + * variables are coherent in such cases. + */ + for (uint32 slot = analyze::ArgSlot(0); slot < analyze::TotalSlots(script); slot++) { + if (a->varTypes[slot].type == JSVAL_TYPE_DOUBLE) { + FrameEntry *fe = frame.getOrTrack(slot); + stubcc.masm.ensureInMemoryDouble(frame.addressOf(fe)); + } + } + autoRejoinHead.oolRejoin(stubcc.masm.label()); frame.prepareForJump(head, stubcc.masm, true); if (!stubcc.jumpInScript(stubcc.masm.jump(), head)) @@ -6674,9 +6634,9 @@ mjit::Compiler::jumpAndTrace(Jump j, jsbytecode *target, Jump *slow, bool *tramp RegisterAllocation *lvtarget = NULL; bool consistent = true; if (cx->typeInferenceEnabled()) { - RegisterAllocation *&alloc = a->liveness.getCode(target).allocation; + RegisterAllocation *&alloc = analysis->getAllocation(target); if (!alloc) { - alloc = ArenaNew(a->liveness.pool, false); + alloc = ArenaNew(cx->compartment->pool, false); if (!alloc) return false; } @@ -6829,7 +6789,7 @@ mjit::Compiler::enterBlock(JSObject *obj) // Otherwise, we would simply be using a stale FpReg value. // Additionally, we check the interrupt flag to allow interrupting // deeply nested exception handling. - if (a->analysis.getCode(PC).exceptionEntry) { + if (analysis->getCode(PC).exceptionEntry) { masm.loadPtr(FrameAddress(offsetof(VMFrame, regs.fp)), JSFrameReg); interruptCheckHelper(); } @@ -7071,68 +7031,96 @@ mjit::Compiler::jsop_forgname(JSAtom *atom) * Whether to ensure that locals/args known to be ints or doubles should be * preserved as doubles across control flow edges. */ - inline bool -mjit::Compiler::preserveLocalType(unsigned i) +mjit::Compiler::fixDoubleSlot(uint32 slot) { - return !a->analysis.localEscapes(i); -} + if (!analysis->trackSlot(slot)) + return false; -inline bool -mjit::Compiler::preserveArgType(unsigned i) -{ /* * Don't preserve double arguments in inline calls across branches, as we * can't mutate them when inlining. :XXX: could be more precise here. */ - return !a->analysis.argEscapes(i) && !a->parent; + if (slot < analyze::LocalSlot(script, 0) && a->parent) + return false; + + return true; } void -mjit::Compiler::fixDoubleTypes() +mjit::Compiler::fixDoubleTypes(jsbytecode *target) { if (!cx->typeInferenceEnabled()) return; - for (uint32 i = 0; !a->parent && script->fun && i < script->fun->nargs; i++) { - JSValueType type = knownArgumentType(i); - if (type == JSVAL_TYPE_DOUBLE && preserveArgType(i)) { - FrameEntry *fe = frame.getArg(i); + /* + * Temporarily fix up the variable types to reflect state at the branch + * target. As described in prepareInferenceTypes, the target state consists + * of the current state plus any phi nodes introduced at the target. + */ + Vector restoreTypes(CompilerAllocPolicy(cx, *this)); + const analyze::SlotValue *newv = analysis->newValues(target); + if (newv) { + while (newv->slot) { + if (newv->value.kind() != analyze::SSAValue::PHI || + newv->value.phiOffset() != uint32(target - script->code)) { + newv++; + continue; + } + if (newv->slot < analyze::TotalSlots(script)) { + VarType &vt = a->varTypes[newv->slot]; + restoreTypes.append(SlotType(newv->slot, vt)); + vt.types = analysis->getValueTypes(newv->value); + vt.type = vt.types->getKnownTypeTag(cx); + } + newv++; + } + } + + for (uint32 slot = analyze::ArgSlot(0); + slot < analyze::LocalSlot(script, script->nfixed); + slot++) { + if (!fixDoubleSlot(slot)) + continue; + if (a->varTypes[slot].type == JSVAL_TYPE_DOUBLE) { + FrameEntry *fe = frame.getOrTrack(slot); if (!fe->isType(JSVAL_TYPE_DOUBLE)) frame.ensureDouble(fe); } } - for (uint32 i = 0; i < script->nfixed; i++) { - JSValueType type = knownLocalType(i); - if (type == JSVAL_TYPE_DOUBLE && preserveLocalType(i)) { - FrameEntry *fe = frame.getLocal(i); - if (!fe->isType(JSVAL_TYPE_DOUBLE)) - frame.ensureDouble(fe); - } + for (unsigned i = 0; i < restoreTypes.length(); i++) { + const SlotType &rt = restoreTypes[i]; + a->varTypes[rt.slot] = rt.vt; } } void -mjit::Compiler::restoreAnalysisTypes(uint32 stackDepth) +mjit::Compiler::restoreAnalysisTypes() { if (!cx->typeInferenceEnabled()) return; - /* Restore known types of locals/args, for join points or after forgetting everything. */ - for (uint32 i = 0; i < script->nfixed; i++) { - JSValueType type = knownLocalType(i); - if (type != JSVAL_TYPE_UNKNOWN && (type != JSVAL_TYPE_DOUBLE || preserveLocalType(i))) { - FrameEntry *fe = frame.getLocal(i); - JS_ASSERT_IF(fe->isTypeKnown(), fe->isType(type)); - if (!fe->isTypeKnown()) - frame.learnType(fe, type, false); + /* Update variable types for all new values at this bytecode. */ + const analyze::SlotValue *newv = analysis->newValues(PC); + if (newv) { + while (newv->slot) { + if (newv->slot < analyze::TotalSlots(script)) { + VarType &vt = a->varTypes[newv->slot]; + vt.types = analysis->getValueTypes(newv->value); + vt.type = vt.types->getKnownTypeTag(cx); + } + newv++; } } - for (uint32 i = 0; script->fun && i < script->fun->nargs; i++) { - JSValueType type = knownArgumentType(i); - if (type != JSVAL_TYPE_UNKNOWN && (type != JSVAL_TYPE_DOUBLE || preserveArgType(i))) { - FrameEntry *fe = frame.getArg(i); + + /* Restore known types of locals/args. */ + for (uint32 slot = analyze::ArgSlot(0); + slot < analyze::LocalSlot(script, script->nfixed); + slot++) { + JSValueType type = a->varTypes[slot].type; + if (type != JSVAL_TYPE_UNKNOWN && (type != JSVAL_TYPE_DOUBLE || fixDoubleSlot(slot))) { + FrameEntry *fe = frame.getOrTrack(slot); JS_ASSERT_IF(fe->isTypeKnown(), fe->isType(type)); if (!fe->isTypeKnown()) frame.learnType(fe, type, false); @@ -7150,33 +7138,51 @@ mjit::Compiler::watchGlobalReallocation() hasGlobalReallocation = true; } -JSValueType -mjit::Compiler::knownThisType() +void +mjit::Compiler::updateVarType() { if (!cx->typeInferenceEnabled()) - return JSVAL_TYPE_UNKNOWN; - if (a->hasThisType) - return a->thisType; - a->hasThisType = true; - a->thisType = script->thisTypes()->getKnownTypeTag(cx); - return a->thisType; -} + return; -JSValueType -mjit::Compiler::knownArgumentType(uint32 arg) -{ - if (!cx->typeInferenceEnabled()) - return JSVAL_TYPE_UNKNOWN; - JS_ASSERT(script->fun && arg < script->fun->nargs); - return a->argumentTypes[arg]; -} + /* + * For any non-escaping variable written at the current opcode, update the + * associated type sets according to the written type, keeping the type set + * for each variable in sync with what the SSA analysis has determined + * (see prepareInferenceTypes). + */ -JSValueType -mjit::Compiler::knownLocalType(uint32 local) -{ - if (!cx->typeInferenceEnabled() || local >= script->nfixed) - return JSVAL_TYPE_UNKNOWN; - return a->localTypes[local]; + types::TypeSet *types = NULL; + switch (JSOp(*PC)) { + case JSOP_SETARG: + case JSOP_SETLOCAL: + case JSOP_SETLOCALPOP: + case JSOP_DEFLOCALFUN: + case JSOP_DEFLOCALFUN_FC: + case JSOP_INCARG: + case JSOP_DECARG: + case JSOP_ARGINC: + case JSOP_ARGDEC: + case JSOP_INCLOCAL: + case JSOP_DECLOCAL: + case JSOP_LOCALINC: + case JSOP_LOCALDEC: + types = pushedTypeSet(0); + break; + case JSOP_FORARG: + case JSOP_FORLOCAL: + types = pushedTypeSet(1); + break; + default: + JS_NOT_REACHED("Bad op"); + } + + uint32 slot = analyze::GetBytecodeSlot(script, PC); + + if (analysis->trackSlot(slot)) { + VarType &vt = a->varTypes[slot]; + vt.types = types; + vt.type = types->getKnownTypeTag(cx); + } } JSValueType @@ -7184,7 +7190,7 @@ mjit::Compiler::knownPushedType(uint32 pushed) { if (!cx->typeInferenceEnabled()) return JSVAL_TYPE_UNKNOWN; - types::TypeSet *types = script->types->pushed(PC - script->code, pushed); + types::TypeSet *types = analysis->pushedTypes(PC, pushed); return types->getKnownTypeTag(cx); } @@ -7199,60 +7205,22 @@ mjit::Compiler::mayPushUndefined(uint32 pushed) * If this returns false and undefined subsequently becomes a feasible * value pushed by the bytecode, recompilation will *NOT* be triggered. */ - types::TypeSet *types = script->types->pushed(PC - script->code, pushed); + types::TypeSet *types = analysis->pushedTypes(PC, pushed); return types->hasType(types::TYPE_UNDEFINED); } -types::TypeSet * -mjit::Compiler::argTypeSet(uint32 arg) -{ - return cx->typeInferenceEnabled() ? script->argTypes(arg) : NULL; -} - -types::TypeSet * -mjit::Compiler::localTypeSet(uint32 local) -{ - if (!cx->typeInferenceEnabled() || local >= script->nfixed) - return NULL; - return script->localTypes(local); -} - types::TypeSet * mjit::Compiler::pushedTypeSet(uint32 pushed) { if (!cx->typeInferenceEnabled()) return NULL; - return script->types->pushed(PC - script->code, pushed); -} - -types::TypeSet * -mjit::Compiler::getTypeSet(uint32 slot) -{ - if (!cx->typeInferenceEnabled()) - return NULL; - - if (slot == 0) /* callee */ - return NULL; - if (slot == 1) /* this */ - return script->thisTypes(); - slot -= 2; - - unsigned nargs = script->fun ? script->fun->nargs : 0; - - if (slot < nargs) - return script->argTypes(slot); - slot -= nargs; - - if (slot < script->nfixed) - return script->localTypes(slot); - - return frame.extra(2 + nargs + slot).types; + return analysis->pushedTypes(PC, pushed); } bool mjit::Compiler::monitored(jsbytecode *pc) { - return cx->typeInferenceEnabled() && script->types->monitored(pc - script->code); + return cx->typeInferenceEnabled() && analysis->monitoredTypes(pc - script->code); } void @@ -7267,7 +7235,7 @@ mjit::Compiler::pushedSingleton(unsigned pushed) if (!cx->typeInferenceEnabled()) return NULL; - types::TypeSet *types = script->types->pushed(PC - script->code, pushed); + types::TypeSet *types = analysis->pushedTypes(PC, pushed); return types->getSingleton(cx); } diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index b49a5f7305ba..6ca4e0387ae3 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -437,6 +437,18 @@ class Compiler : public BaseCompiler Label label; }; + struct VarType { + JSValueType type; + types::TypeSet *types; + }; + + struct SlotType + { + uint32 slot; + VarType vt; + SlotType(uint32 slot, VarType vt) : slot(slot), vt(vt) {} + }; + JSScript *outerScript; bool isConstructing; @@ -464,16 +476,13 @@ class Compiler : public BaseCompiler jsbytecode *parentPC; JSScript *script; uint32 inlineIndex; - analyze::Script analysis; - analyze::LifetimeScript liveness; Label *jumpMap; - bool hasThisType; - JSValueType thisType; - JSValueType *argumentTypes; - JSValueType *localTypes; uint32 depth; Vector unsyncedEntries; // :XXX: handle OOM + /* Current types for non-escaping vars in the script. */ + VarType *varTypes; + /* State for managing return from inlined frames. */ bool needReturnValue; bool syncReturnValue; @@ -491,6 +500,7 @@ class Compiler : public BaseCompiler ActiveFrame *outer; JSScript *script; + analyze::ScriptAnalysis *analysis; jsbytecode *PC; bool variadicRejoin; /* There is a variadic rejoin for PC. */ @@ -589,9 +599,6 @@ class Compiler : public BaseCompiler jsbytecode *inlinePC() { return PC; } uint32 inlineIndex() { return a->inlineIndex; } - types::TypeSet *getTypeSet(uint32 slot); - types::TypeSet *getTypeSet(const FrameEntry *fe) { return getTypeSet(frame.indexOfFe(fe)); } - Assembler &getAssembler(bool ool) { return ool ? stubcc.masm : masm; } InvariantCodePatch *getInvariantPatch(unsigned index, bool call) { @@ -609,23 +616,17 @@ class Compiler : public BaseCompiler CompileStatus finishThisUp(JITScript **jitp); CompileStatus pushActiveFrame(JSScript *script, uint32 argc); void popActiveFrame(); - void generateInlinePrologue(); /* Analysis helpers. */ CompileStatus prepareInferenceTypes(JSScript *script, ActiveFrame *a); - inline bool preserveLocalType(unsigned i); - inline bool preserveArgType(unsigned i); - void fixDoubleTypes(); - void restoreAnalysisTypes(uint32 stackDepth); + inline bool fixDoubleSlot(uint32 slot); + void fixDoubleTypes(jsbytecode *target); + void restoreAnalysisTypes(); void watchGlobalReallocation(); - JSValueType knownThisType(); - JSValueType knownArgumentType(uint32 arg); - JSValueType knownLocalType(uint32 local); + void updateVarType(); JSValueType knownPushedType(uint32 pushed); bool arrayPrototypeHasIndexedProperty(); bool mayPushUndefined(uint32 pushed); - types::TypeSet *argTypeSet(uint32 arg); - types::TypeSet *localTypeSet(uint32 local); types::TypeSet *pushedTypeSet(uint32 which); bool monitored(jsbytecode *pc); bool testSingletonProperty(JSObject *obj, jsid id); diff --git a/js/src/methodjit/FastArithmetic.cpp b/js/src/methodjit/FastArithmetic.cpp index 71a330af83c2..7032651677a9 100644 --- a/js/src/methodjit/FastArithmetic.cpp +++ b/js/src/methodjit/FastArithmetic.cpp @@ -1114,7 +1114,7 @@ mjit::Compiler::jsop_equality_int_string(JSOp op, BoolStub stub, AutoRejoinSite * Sync everything except the top two entries. * We will handle the lhs/rhs in the stub call path. */ - fixDoubleTypes(); + fixDoubleTypes(target); frame.syncAndKill(Registers(Registers::AvailRegs), Uses(frame.frameSlots()), Uses(2)); RegisterID tempReg = frame.allocReg(); @@ -1389,7 +1389,7 @@ mjit::Compiler::jsop_relational_double(JSOp op, BoolStub stub, AutoRejoinSite &a FrameEntry *lhs = frame.peek(-2); if (target) - fixDoubleTypes(); + fixDoubleTypes(target); JS_ASSERT_IF(!target, fused != JSOP_IFEQ); @@ -1487,7 +1487,7 @@ mjit::Compiler::jsop_relational_int(JSOp op, AutoRejoinSite &autoRejoin, jsbytec Assembler::Condition cond = GetCompareCondition(op, fused); if (target) { - fixDoubleTypes(); + fixDoubleTypes(target); if (!frame.syncForBranch(target, Uses(2))) return false; @@ -1538,7 +1538,7 @@ mjit::Compiler::jsop_relational_full(JSOp op, BoolStub stub, AutoRejoinSite &rej FrameEntry *lhs = frame.peek(-2); if (target) - fixDoubleTypes(); + fixDoubleTypes(target); /* Allocate all registers up-front. */ FrameState::BinaryAlloc regs; diff --git a/js/src/methodjit/FastOps.cpp b/js/src/methodjit/FastOps.cpp index d23e8971266f..c857289da697 100644 --- a/js/src/methodjit/FastOps.cpp +++ b/js/src/methodjit/FastOps.cpp @@ -430,7 +430,7 @@ mjit::Compiler::jsop_equality(JSOp op, BoolStub stub, AutoRejoinSite &autoRejoin */ if (target) { - fixDoubleTypes(); + fixDoubleTypes(target); frame.syncAndKillEverything(); frame.freeReg(reg); @@ -498,7 +498,7 @@ mjit::Compiler::jsop_equality(JSOp op, BoolStub stub, AutoRejoinSite &autoRejoin frame.forgetMismatchedObject(rhs); Assembler::Condition cond = GetCompareCondition(op, fused); if (target) { - fixDoubleTypes(); + fixDoubleTypes(target); autoRejoin.oolRejoin(stubcc.masm.label()); Jump sj = stubcc.masm.branchTest32(GetStubCompareCondition(fused), Registers::ReturnReg, Registers::ReturnReg); @@ -873,6 +873,7 @@ mjit::Compiler::booleanJumpScript(JSOp op, jsbytecode *target) bool mjit::Compiler::jsop_ifneq(JSOp op, jsbytecode *target) { + fixDoubleTypes(target); FrameEntry *fe = frame.peek(-1); if (fe->isConstant()) { @@ -900,6 +901,7 @@ mjit::Compiler::jsop_ifneq(JSOp op, jsbytecode *target) bool mjit::Compiler::jsop_andor(JSOp op, jsbytecode *target) { + fixDoubleTypes(target); FrameEntry *fe = frame.peek(-1); if (fe->isConstant()) { @@ -908,7 +910,6 @@ mjit::Compiler::jsop_andor(JSOp op, jsbytecode *target) /* Short-circuit. */ if ((op == JSOP_OR && b == JS_TRUE) || (op == JSOP_AND && b == JS_FALSE)) { - fixDoubleTypes(); if (!frame.syncForBranch(target, Uses(0))) return false; if (!jumpAndTrace(masm.jump(), target)) @@ -925,14 +926,17 @@ mjit::Compiler::jsop_andor(JSOp op, jsbytecode *target) bool mjit::Compiler::jsop_localinc(JSOp op, uint32 slot, bool popped) { - JSValueType type = knownLocalType(slot); + updateVarType(); + + types::TypeSet *types = pushedTypeSet(0); + JSValueType type = types ? types->getKnownTypeTag(cx) : JSVAL_TYPE_UNKNOWN; if (popped || (op == JSOP_INCLOCAL || op == JSOP_DECLOCAL)) { int amt = (op == JSOP_LOCALINC || op == JSOP_INCLOCAL) ? -1 : 1; // Before: // After: V - frame.pushLocal(slot, type); + frame.pushLocal(slot); // Before: V // After: V 1 @@ -941,12 +945,12 @@ mjit::Compiler::jsop_localinc(JSOp op, uint32 slot, bool popped) // Note, SUB will perform integer conversion for us. // Before: V 1 // After: N+1 - if (!jsop_binary(JSOP_SUB, stubs::Sub, type, localTypeSet(slot))) + if (!jsop_binary(JSOP_SUB, stubs::Sub, type, types)) return false; // Before: N+1 // After: N+1 - frame.storeLocal(slot, type, popped, true); + frame.storeLocal(slot, popped, true); if (popped) frame.pop(); @@ -955,7 +959,7 @@ mjit::Compiler::jsop_localinc(JSOp op, uint32 slot, bool popped) // Before: // After: V - frame.pushLocal(slot, type); + frame.pushLocal(slot); // Before: V // After: N @@ -971,12 +975,12 @@ mjit::Compiler::jsop_localinc(JSOp op, uint32 slot, bool popped) // Before: N N 1 // After: N N+1 - if (!jsop_binary(JSOP_ADD, stubs::Add, type, localTypeSet(slot))) + if (!jsop_binary(JSOP_ADD, stubs::Add, type, types)) return false; // Before: N N+1 // After: N N+1 - frame.storeLocal(slot, type, true, true); + frame.storeLocal(slot, true, true); // Before: N N+1 // After: N @@ -989,14 +993,17 @@ mjit::Compiler::jsop_localinc(JSOp op, uint32 slot, bool popped) bool mjit::Compiler::jsop_arginc(JSOp op, uint32 slot, bool popped) { - JSValueType type = knownArgumentType(slot); + updateVarType(); + + types::TypeSet *types = pushedTypeSet(0); + JSValueType type = types ? types->getKnownTypeTag(cx) : JSVAL_TYPE_UNKNOWN; if (popped || (op == JSOP_INCARG || op == JSOP_DECARG)) { int amt = (op == JSOP_ARGINC || op == JSOP_INCARG) ? -1 : 1; // Before: // After: V - frame.pushArg(slot, type); + frame.pushArg(slot); // Before: V // After: V 1 @@ -1005,12 +1012,12 @@ mjit::Compiler::jsop_arginc(JSOp op, uint32 slot, bool popped) // Note, SUB will perform integer conversion for us. // Before: V 1 // After: N+1 - if (!jsop_binary(JSOP_SUB, stubs::Sub, type, argTypeSet(slot))) + if (!jsop_binary(JSOP_SUB, stubs::Sub, type, types)) return false; // Before: N+1 // After: N+1 - frame.storeArg(slot, type, popped); + frame.storeArg(slot, popped); if (popped) frame.pop(); @@ -1019,7 +1026,7 @@ mjit::Compiler::jsop_arginc(JSOp op, uint32 slot, bool popped) // Before: // After: V - frame.pushArg(slot, type); + frame.pushArg(slot); // Before: V // After: N @@ -1035,12 +1042,12 @@ mjit::Compiler::jsop_arginc(JSOp op, uint32 slot, bool popped) // Before: N N 1 // After: N N+1 - if (!jsop_binary(JSOP_ADD, stubs::Add, type, argTypeSet(slot))) + if (!jsop_binary(JSOP_ADD, stubs::Add, type, types)) return false; // Before: N N+1 // After: N N+1 - frame.storeArg(slot, type, true); + frame.storeArg(slot, true); // Before: N N+1 // After: N @@ -1109,7 +1116,8 @@ mjit::Compiler::jsop_setelem_dense() // hoist the initialized length check, we make the slots pointer loop // invariant and never access the object itself. RegisterID slotsReg; - bool hoisted = loop && !a->parent && loop->hoistArrayLengthCheck(obj, 1); + bool hoisted = loop && !a->parent && + loop->hoistArrayLengthCheck(obj, frame.extra(obj).types, 1); if (hoisted) { FrameEntry *slotsFe = loop->invariantSlots(obj); @@ -1433,7 +1441,8 @@ mjit::Compiler::jsop_getelem_dense(bool isPacked) // We checked in the caller that prototypes do not have indexed properties. bool allowUndefined = mayPushUndefined(0); - bool hoisted = loop && !a->parent && loop->hoistArrayLengthCheck(obj, 0); + bool hoisted = loop && !a->parent && + loop->hoistArrayLengthCheck(obj, frame.extra(obj).types, 0); // Get a register with either the object or its slots, depending on whether // we are hoisting the bounds check. diff --git a/js/src/methodjit/FrameState-inl.h b/js/src/methodjit/FrameState-inl.h index 94d3b91cd09b..e885a6d2846f 100644 --- a/js/src/methodjit/FrameState-inl.h +++ b/js/src/methodjit/FrameState-inl.h @@ -1187,10 +1187,10 @@ FrameState::syncAt(int32 n) } inline void -FrameState::pushLocal(uint32 n, JSValueType knownType) +FrameState::pushLocal(uint32 n) { FrameEntry *fe = getLocal(n); - if (!a->analysis->localEscapes(n)) { + if (!analysis->slotEscapes(analyze::LocalSlot(script, n))) { pushCopyOf(indexOfFe(fe)); } else { #ifdef DEBUG @@ -1199,29 +1199,27 @@ FrameState::pushLocal(uint32 n, JSValueType knownType) * SETLOCAL equivocation of stack slots, and let expressions, just * weakly assert on the fixed local vars. */ - FrameEntry *fe = &locals[n]; - if (fe->isTracked() && n < script->nfixed) { - JS_ASSERT(fe->type.inMemory()); + if (fe->isTracked() && n < script->nfixed) JS_ASSERT(fe->data.inMemory()); - } #endif - push(addressOf(fe), knownType); + JSValueType type = fe->isTypeKnown() ? fe->getKnownType() : JSVAL_TYPE_UNKNOWN; + push(addressOf(fe), type); } } inline void -FrameState::pushArg(uint32 n, JSValueType knownType) +FrameState::pushArg(uint32 n) { FrameEntry *fe = getArg(n); - if (!a->analysis->argEscapes(n)) { + if (!analysis->slotEscapes(analyze::ArgSlot(n))) { pushCopyOf(indexOfFe(fe)); } else { #ifdef DEBUG - FrameEntry *fe = &args[n]; if (fe->isTracked()) JS_ASSERT(fe->data.inMemory()); #endif - push(addressOf(fe), knownType); + JSValueType type = fe->isTypeKnown() ? fe->getKnownType() : JSVAL_TYPE_UNKNOWN; + push(addressOf(fe), type); } } diff --git a/js/src/methodjit/FrameState.cpp b/js/src/methodjit/FrameState.cpp index cf1ca66afa3d..0c8b65496d55 100644 --- a/js/src/methodjit/FrameState.cpp +++ b/js/src/methodjit/FrameState.cpp @@ -63,6 +63,7 @@ FrameState::~FrameState() { while (a) { ActiveFrame *parent = a->parent; + a->script->analysis(cx)->clearAllocations(); #if defined JS_NUNBOX32 a->reifier.~ImmutableSync(); #endif @@ -115,8 +116,7 @@ FrameState::getUnsyncedEntries(uint32 *pdepth, Vector *unsyncedEn } bool -FrameState::pushActiveFrame(JSScript *script, uint32 argc, - analyze::Script *analysis, analyze::LifetimeScript *liveness) +FrameState::pushActiveFrame(JSScript *script, uint32 argc) { uint32 depth = a ? totalDepth() : 0; uint32 nentries = feLimit(script); @@ -147,9 +147,6 @@ FrameState::pushActiveFrame(JSScript *script, uint32 argc, newa->script = script; newa->freeRegs = Registers(Registers::AvailAnyRegs); - newa->analysis = analysis; - newa->liveness = liveness; - newa->entries = (FrameEntry *)cursor; cursor += sizeof(FrameEntry) * nentries; @@ -169,7 +166,7 @@ FrameState::pushActiveFrame(JSScript *script, uint32 argc, this->a = newa; updateActiveFrame(); - if (a->parent && a->analysis->inlineable(argc)) { + if (a->parent && script->analysis(cx)->inlineable(argc)) { a->depth = depth + VALUES_PER_STACK_FRAME; /* Mark all registers which are in use by the parent or its own parent. */ @@ -257,6 +254,8 @@ FrameState::popActiveFrame() FrameEntry *parentSP = a->parentSP; ActiveFrame *parent = a->parent; + analysis->clearAllocations(); + #if defined JS_NUNBOX32 a->reifier.~ImmutableSync(); #endif @@ -272,6 +271,7 @@ void FrameState::updateActiveFrame() { script = a->script; + analysis = script->analysis(cx); entries = a->entries; callee_ = a->callee_; this_ = a->this_; @@ -464,7 +464,7 @@ FrameState::variableLive(FrameEntry *fe, jsbytecode *pc) const JS_ASSERT(fe < spBase && fe != callee_); uint32 offset = pc - script->code; - return a->liveness->live(indexOfFe(fe), offset); + return analysis->liveness(indexOfFe(fe)).live(offset); } bool @@ -713,11 +713,11 @@ RegisterAllocation * FrameState::computeAllocation(jsbytecode *target) { JS_ASSERT(cx->typeInferenceEnabled()); - RegisterAllocation *alloc = ArenaNew(a->liveness->pool, false); + RegisterAllocation *alloc = ArenaNew(cx->compartment->pool, false); if (!alloc) return NULL; - if (a->analysis->getCode(target).exceptionEntry || a->analysis->getCode(target).switchTarget || + if (analysis->getCode(target).exceptionEntry || analysis->getCode(target).switchTarget || JSOp(*target) == JSOP_TRAP) { /* State must be synced at exception and switch targets, and at traps. */ #ifdef DEBUG @@ -821,7 +821,7 @@ FrameState::syncForBranch(jsbytecode *target, Uses uses) Registers regs = 0; - RegisterAllocation *&alloc = a->liveness->getCode(target).allocation; + RegisterAllocation *&alloc = analysis->getAllocation(target); if (!alloc) { alloc = computeAllocation(target); if (!alloc) @@ -955,14 +955,14 @@ FrameState::discardForJoin(jsbytecode *target, uint32 stackDepth) return true; } - RegisterAllocation *&alloc = a->liveness->getCode(target).allocation; + RegisterAllocation *&alloc = analysis->getAllocation(target); if (!alloc) { /* * This shows up for loop entries which are not reachable from the * loop head, and for exception, switch target and trap safe points. */ - alloc = ArenaNew(a->liveness->pool, false); + alloc = ArenaNew(cx->compartment->pool, false); if (!alloc) return false; } @@ -1019,7 +1019,7 @@ FrameState::consistentRegisters(jsbytecode *target) * which is not consistent with the target's register state has already * been synced, and no stores will need to be issued by prepareForJump. */ - RegisterAllocation *alloc = a->liveness->getCode(target).allocation; + RegisterAllocation *alloc = analysis->getAllocation(target); JS_ASSERT(alloc); Registers regs(Registers::AvailAnyRegs); @@ -1051,7 +1051,7 @@ FrameState::prepareForJump(jsbytecode *target, Assembler &masm, bool synced) JS_ASSERT_IF(!synced, !consistentRegisters(target)); - RegisterAllocation *alloc = a->liveness->getCode(target).allocation; + RegisterAllocation *alloc = analysis->getAllocation(target); JS_ASSERT(alloc); Registers regs = 0; @@ -2306,58 +2306,43 @@ FrameState::separateBinaryEntries(FrameEntry *lhs, FrameEntry *rhs) } void -FrameState::storeLocal(uint32 n, JSValueType type, bool popGuaranteed, bool fixedType) +FrameState::storeLocal(uint32 n, bool popGuaranteed, bool fixedType) { FrameEntry *local = getLocal(n); - if (a->analysis->localEscapes(n)) { + if (analysis->slotEscapes(indexOfFe(local))) { JS_ASSERT(local->data.inMemory()); storeTo(peek(-1), addressOf(local), popGuaranteed); return; } - storeTop(local, type, popGuaranteed); + storeTop(local, popGuaranteed); if (loop) local->lastLoop = loop->headOffset(); - if (type != JSVAL_TYPE_UNKNOWN && type != JSVAL_TYPE_DOUBLE && - fixedType && !local->type.synced()) { - /* - * Except when inlining, known types are always in sync for locals. - * If we are inlining, the known type is filled in when the frame is - * expanded (which happens upon any recompilation activity). - */ - local->type.sync(); - } - if (inTryBlock) syncFe(local); } void -FrameState::storeArg(uint32 n, JSValueType type, bool popGuaranteed) +FrameState::storeArg(uint32 n, bool popGuaranteed) { // Note that args are always immediately synced, because they can be // aliased (but not written to) via f.arguments. FrameEntry *arg = getArg(n); - if (a->analysis->argEscapes(n)) { + if (analysis->slotEscapes(indexOfFe(arg))) { JS_ASSERT(arg->data.inMemory()); storeTo(peek(-1), addressOf(arg), popGuaranteed); return; } - storeTop(arg, type, popGuaranteed); + storeTop(arg, popGuaranteed); if (loop) arg->lastLoop = loop->headOffset(); - if (type != JSVAL_TYPE_UNKNOWN && type != JSVAL_TYPE_DOUBLE && !arg->type.synced()) { - /* Known types are always in sync for args. (Frames which update args are not inlined). */ - arg->type.sync(); - } - syncFe(arg); } @@ -2377,7 +2362,7 @@ FrameState::forgetEntry(FrameEntry *fe) } void -FrameState::storeTop(FrameEntry *target, JSValueType type, bool popGuaranteed) +FrameState::storeTop(FrameEntry *target, bool popGuaranteed) { JS_ASSERT(!isTemporary(target)); @@ -2388,6 +2373,15 @@ FrameState::storeTop(FrameEntry *target, JSValueType type, bool popGuaranteed) return; } + /* + * If this is overwriting a known non-double type with another value of the + * same type, then make sure we keep the type marked as synced after doing + * the copy. + */ + bool wasSynced = target->type.synced(); + JSValueType oldType = target->isTypeKnown() ? target->getKnownType() : JSVAL_TYPE_UNKNOWN; + bool trySyncType = wasSynced && oldType != JSVAL_TYPE_UNKNOWN && oldType != JSVAL_TYPE_DOUBLE; + /* Completely invalidate the local variable. */ forgetEntry(target); target->resetUnsynced(); @@ -2397,6 +2391,8 @@ FrameState::storeTop(FrameEntry *target, JSValueType type, bool popGuaranteed) target->setCopyOf(NULL); target->setNotCopied(); target->setConstant(Jsvalify(top->getValue())); + if (trySyncType && target->isType(oldType)) + target->type.sync(); return; } @@ -2425,6 +2421,8 @@ FrameState::storeTop(FrameEntry *target, JSValueType type, bool popGuaranteed) swapInTracker(backing, target); target->setNotCopied(); target->setCopyOf(backing); + if (trySyncType && target->isType(oldType)) + target->type.sync(); return; } @@ -2468,18 +2466,9 @@ FrameState::storeTop(FrameEntry *target, JSValueType type, bool popGuaranteed) if (backing->isType(JSVAL_TYPE_DOUBLE)) { FPRegisterID fpreg = tempFPRegForData(backing); - if (type != JSVAL_TYPE_DOUBLE) { - masm.storeDouble(fpreg, addressOf(target)); - target->resetSynced(); - - /* We're about to invalidate the backing, so forget the FP register. */ - forgetReg(fpreg); - } else { - target->data.setFPRegister(fpreg); - regstate(fpreg).reassociate(target); - } - target->setType(JSVAL_TYPE_DOUBLE); + target->data.setFPRegister(fpreg); + regstate(fpreg).reassociate(target); } else { /* * Move the backing store down - we spill registers here, but we could be @@ -2495,44 +2484,23 @@ FrameState::storeTop(FrameEntry *target, JSValueType type, bool popGuaranteed) target->data.setRegister(reg); regstate(reg).reassociate(target); - if (type == JSVAL_TYPE_UNKNOWN) { - if (backing->isTypeKnown()) { - target->setType(backing->getKnownType()); - } else { - pinReg(reg); - RegisterID typeReg = tempRegForType(backing); - unpinReg(reg); - target->type.setRegister(typeReg); - regstate(typeReg).reassociate(target); - } - } else if (type != JSVAL_TYPE_DOUBLE || backing->isType(JSVAL_TYPE_INT32)) { - /* - * Treat the stored entry as an int even if inference has marked it - * as a float (we will fixDoubles on it before branching), to avoid - * demoting the backing. - */ - if (type == JSVAL_TYPE_DOUBLE) - type = JSVAL_TYPE_INT32; - JS_ASSERT_IF(backing->isTypeKnown(), backing->isType(type)); - if (!backing->isTypeKnown()) - learnType(backing, type); - target->setType(type); + if (backing->isTypeKnown()) { + target->setType(backing->getKnownType()); } else { - FPRegisterID fpreg = allocFPReg(); - syncFe(backing); - masm.moveInt32OrDouble(addressOf(backing), fpreg); - - forgetAllRegs(backing); - backing->setType(JSVAL_TYPE_DOUBLE); - target->setType(JSVAL_TYPE_DOUBLE); - target->data.setFPRegister(fpreg); - regstate(fpreg).associate(target, RematInfo::DATA); + pinReg(reg); + RegisterID typeReg = tempRegForType(backing); + unpinReg(reg); + target->type.setRegister(typeReg); + regstate(typeReg).reassociate(target); } } backing->setCopyOf(target); JS_ASSERT(top->copyOf() == target); + if (trySyncType && target->isType(oldType)) + target->type.sync(); + /* * Right now, |backing| is a copy of |target| (note the reversal), but * |target| is not marked as copied. This is an optimization so uncopy() @@ -2551,7 +2519,7 @@ FrameState::shimmy(uint32 n) { JS_ASSERT(sp - n >= spBase); int32 depth = 0 - int32(n); - storeTop(peek(depth - 1), JSVAL_TYPE_UNKNOWN, true); + storeTop(peek(depth - 1), true); popn(n); } @@ -2560,7 +2528,7 @@ FrameState::shift(int32 n) { JS_ASSERT(n < 0); JS_ASSERT(sp + n - 1 >= spBase); - storeTop(peek(n - 1), JSVAL_TYPE_UNKNOWN, true); + storeTop(peek(n - 1), true); pop(); } diff --git a/js/src/methodjit/FrameState.h b/js/src/methodjit/FrameState.h index 920a03c549bd..5efcad3a10d7 100644 --- a/js/src/methodjit/FrameState.h +++ b/js/src/methodjit/FrameState.h @@ -368,8 +368,8 @@ class FrameState // Pushes a copy of a slot (formal argument, local variable, or stack slot) // onto the operation stack. - void pushLocal(uint32 n, JSValueType knownType); - void pushArg(uint32 n, JSValueType knownType); + void pushLocal(uint32 n); + void pushArg(uint32 n); void pushCallee(); void pushThis(); void pushTemporary(FrameEntry *fe); @@ -603,13 +603,10 @@ class FrameState void loadForReturn(FrameEntry *fe, RegisterID typeReg, RegisterID dataReg, RegisterID tempReg); void loadThisForReturn(RegisterID typeReg, RegisterID dataReg, RegisterID tempReg); - /* - * Stores the top stack slot back to a local or slot. type indicates any known - * type for the local/slot. - */ - void storeLocal(uint32 n, JSValueType type, bool popGuaranteed = false, bool fixedType = false); - void storeArg(uint32 n, JSValueType type, bool popGuaranteed = false); - void storeTop(FrameEntry *target, JSValueType type, bool popGuaranteed); + /* Stores the top stack slot back to a local or slot. */ + void storeLocal(uint32 n, bool popGuaranteed = false, bool fixedType = false); + void storeArg(uint32 n, bool popGuaranteed = false); + void storeTop(FrameEntry *target, bool popGuaranteed); /* * Restores state from a slow path. @@ -872,8 +869,7 @@ class FrameState void getUnsyncedEntries(uint32 *pdepth, Vector *unsyncedEntries); - bool pushActiveFrame(JSScript *script, uint32 argc, - analyze::Script *analysis, analyze::LifetimeScript *liveness); + bool pushActiveFrame(JSScript *script, uint32 argc); void popActiveFrame(); void discardLocalRegisters(); @@ -1077,15 +1073,13 @@ class FrameState #if defined JS_NUNBOX32 mutable ImmutableSync reifier; #endif - - analyze::Script *analysis; - analyze::LifetimeScript *liveness; }; ActiveFrame *a; /* State derived/copied from the active frame. :XXX: remove? */ JSScript *script; + analyze::ScriptAnalysis *analysis; FrameEntry *entries; FrameEntry *callee_; diff --git a/js/src/methodjit/InvokeHelpers.cpp b/js/src/methodjit/InvokeHelpers.cpp index 1f651f2bee96..aa784da5b654 100644 --- a/js/src/methodjit/InvokeHelpers.cpp +++ b/js/src/methodjit/InvokeHelpers.cpp @@ -381,8 +381,6 @@ UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, bool *unjittable, uint if (!newscript->typeSetArgument(cx, i, &argTypes[1 + i])) return false; } - if (!cx->compartment->types.checkPendingRecompiles(cx)) - return false; } else { CallArgs args = CallArgsFromVp(argc, vp); if (!cx->typeMonitorCall(args, flags & JSFRAME_CONSTRUCTING)) diff --git a/js/src/methodjit/LoopState.cpp b/js/src/methodjit/LoopState.cpp index 145e601ffc84..91e58086c58e 100644 --- a/js/src/methodjit/LoopState.cpp +++ b/js/src/methodjit/LoopState.cpp @@ -46,9 +46,8 @@ using namespace js::mjit; using namespace js::analyze; LoopState::LoopState(JSContext *cx, JSScript *script, - mjit::Compiler *cc, FrameState *frame, - Script *analysis, LifetimeScript *liveness) - : cx(cx), script(script), cc(*cc), frame(*frame), analysis(analysis), liveness(liveness), + mjit::Compiler *cc, FrameState *frame) + : cx(cx), script(script), analysis(script->analysis(cx)), cc(*cc), frame(*frame), lifetime(NULL), alloc(NULL), loopRegs(0), skipAnalysis(false), loopJoins(CompilerAllocPolicy(cx, *cc)), loopPatches(CompilerAllocPolicy(cx, *cc)), @@ -67,18 +66,13 @@ LoopState::LoopState(JSContext *cx, JSScript *script, bool LoopState::init(jsbytecode *head, Jump entry, jsbytecode *entryTarget) { - this->lifetime = liveness->getCode(head).loop; + this->lifetime = analysis->getLoop(head); JS_ASSERT(lifetime && lifetime->head == uint32(head - script->code) && lifetime->entry == uint32(entryTarget - script->code)); this->entry = entry; - if (!stack.analyze(liveness->pool, script, lifetime->head, - lifetime->backedge - lifetime->head + 1, analysis)) { - return false; - } - analyzeLoopTest(); analyzeLoopIncrements(); analyzeModset(); @@ -109,10 +103,10 @@ LoopState::init(jsbytecode *head, Jump entry, jsbytecode *entryTarget) types::TypeIdString(modifiedProperties[i].id)); } - RegisterAllocation *&alloc = liveness->getCode(head).allocation; + RegisterAllocation *&alloc = analysis->getAllocation(head); JS_ASSERT(!alloc); - alloc = ArenaNew(liveness->pool, true); + alloc = ArenaNew(cx->compartment->pool, true); if (!alloc) return false; @@ -242,26 +236,9 @@ LoopState::clearLoopRegisters() bool LoopState::loopInvariantEntry(uint32 slot) { - unsigned nargs = script->fun ? script->fun->nargs : 0; - - if (slot >= 2 + nargs + script->nfixed) + if (slot == analyze::CalleeSlot() || analysis->slotEscapes(slot)) return false; - - if (liveness->firstWrite(slot, lifetime) != uint32(-1)) - return false; - - if (slot == 0) /* callee */ - return false; - if (slot == 1) /* this */ - return true; - slot -= 2; - - if (slot < nargs && !analysis->argEscapes(slot)) - return true; - if (script->fun) - slot -= script->fun->nargs; - - return !analysis->localEscapes(slot); + return analysis->liveness(slot).firstWrite(lifetime) == uint32(-1); } bool @@ -394,7 +371,7 @@ LoopState::setLoopReg(AnyRegisterID reg, FrameEntry *fe) * so need to update the register state at that entry point so that the right * things get loaded when we enter the loop. */ - RegisterAllocation *entry = liveness->getCode(lifetime->entry).allocation; + RegisterAllocation *entry = analysis->getAllocation(lifetime->entry); JS_ASSERT(entry && !entry->assigned(reg)); entry->set(reg, slot, true); } @@ -423,16 +400,12 @@ SafeSub(int32 one, int32 two, int32 *res) } bool -LoopState::hoistArrayLengthCheck(const FrameEntry *obj, unsigned indexPopped) +LoopState::hoistArrayLengthCheck(const FrameEntry *obj, types::TypeSet *objTypes, + unsigned indexPopped) { if (skipAnalysis || script->failedBoundsCheck) return false; - /* - * Note: this should only be used when the object is known to be a dense - * array (if it is an object at all). - */ - obj = obj->backing(); JaegerSpew(JSpew_Analysis, "Trying to hoist bounds check on %s\n", @@ -449,7 +422,6 @@ LoopState::hoistArrayLengthCheck(const FrameEntry *obj, unsigned indexPopped) * but it actually can, we will probably recompile after the hoisted * bounds check fails. */ - types::TypeSet *objTypes = cc.getTypeSet(obj); JS_ASSERT(objTypes && !objTypes->unknown()); if (!growArrays.empty()) { unsigned count = objTypes->getObjectCount(); @@ -492,7 +464,7 @@ LoopState::hoistArrayLengthCheck(const FrameEntry *obj, unsigned indexPopped) * underflow the array. We currently only hoist bounds checks for loops * which walk arrays going forward. */ - if (!liveness->nonDecreasing(index, lifetime)) { + if (!analysis->liveness(index).nonDecreasing(script, lifetime)) { JaegerSpew(JSpew_Analysis, "Index may decrease in future iterations\n"); return false; } @@ -509,21 +481,17 @@ LoopState::hoistArrayLengthCheck(const FrameEntry *obj, unsigned indexPopped) if (index == testLHS && testLessEqual) { uint32 rhs = testRHS; - if (rhs != UNASSIGNED) { - types::TypeSet *types = cc.getTypeSet(rhs); - if (!types) { - JaegerSpew(JSpew_Analysis, "Unknown type of branch test\n"); - return false; - } - if (testLength) { - FrameEntry *rhsFE = frame.getOrTrack(rhs); - FrameEntry *lengthEntry = invariantLength(rhsFE); - if (!lengthEntry) { - JaegerSpew(JSpew_Analysis, "Could not get invariant entry for length\n"); - return false; - } - rhs = frame.indexOfFe(lengthEntry); - } + if (testLength) { + FrameEntry *rhsFE = frame.getOrTrack(rhs); + FrameEntry *lengthEntry = invariantLength(rhsFE, NULL); + + /* + * An entry for the length should have been constructed while + * processing the test. + */ + JS_ASSERT(lengthEntry); + + rhs = frame.indexOfFe(lengthEntry); } int32 constant; @@ -612,7 +580,7 @@ LoopState::invariantSlots(const FrameEntry *obj) } FrameEntry * -LoopState::invariantLength(const FrameEntry *obj) +LoopState::invariantLength(const FrameEntry *obj, types::TypeSet *objTypes) { if (skipAnalysis || script->failedBoundsCheck) return NULL; @@ -630,12 +598,13 @@ LoopState::invariantLength(const FrameEntry *obj) } } + if (!objTypes) + return NULL; + if (!loopInvariantEntry(frame.indexOfFe(obj))) return NULL; - /* Make sure this is a dense array whose length fits in an int32. */ - types::TypeSet *types = cc.getTypeSet(slot); - types::ObjectKind kind = types ? types->getKnownObjectKind(cx) : types::OBJECT_UNKNOWN; + types::ObjectKind kind = objTypes->getKnownObjectKind(cx); if (kind != types::OBJECT_DENSE_ARRAY && kind != types::OBJECT_PACKED_ARRAY) return NULL; @@ -644,14 +613,14 @@ LoopState::invariantLength(const FrameEntry *obj) * to the elements of any of the accessed arrays. This could invoke an * inline path which updates the length. */ - for (unsigned i = 0; i < types->getObjectCount(); i++) { - types::TypeObject *object = types->getObject(i); + for (unsigned i = 0; i < objTypes->getObjectCount(); i++) { + types::TypeObject *object = objTypes->getObject(i); if (!object) continue; if (object->unknownProperties() || hasModifiedProperty(object, JSID_VOID)) return NULL; } - types->addFreeze(cx); + objTypes->addFreeze(cx); uint32 which = frame.allocTemporary(); if (which == uint32(-1)) @@ -676,9 +645,9 @@ LoopState::restoreInvariants(jsbytecode *pc, Assembler &masm, Vector *jump { /* * Restore all invariants in memory when entering the loop or after any - * scripted or C++ call, and check that all hoisted conditions. Care should - * be taken not to clobber the return register or callee-saved registers, - * which may still be live after some calls. + * scripted or C++ call, and check that all hoisted conditions still hold. + * Care should be taken not to clobber the return register or callee-saved + * registers, which may still be live after some calls. */ Registers regs(Registers::TempRegs); @@ -739,12 +708,9 @@ LoopState::restoreInvariants(jsbytecode *pc, Assembler &masm, Vector *jump case InvariantEntry::INVARIANT_SLOTS: case InvariantEntry::INVARIANT_LENGTH: { - /* Make sure this is an object before trying to access its slots or length. */ uint32 array = entry.u.array.arraySlot; - if (cc.getTypeSet(array)->getKnownTypeTag(cx) != JSVAL_TYPE_OBJECT) { - Jump notObject = masm.testObject(Assembler::NotEqual, frame.addressOf(array)); - jumps->append(notObject); - } + Jump notObject = masm.testObject(Assembler::NotEqual, frame.addressOf(array)); + jumps->append(notObject); masm.loadPayload(frame.addressOf(array), T0); uint32 offset = (entry.kind == InvariantEntry::INVARIANT_SLOTS) @@ -764,17 +730,6 @@ LoopState::restoreInvariants(jsbytecode *pc, Assembler &masm, Vector *jump /* Loop analysis methods. */ -/* :XXX: factor out into more common code. */ -static inline uint32 localSlot(JSScript *script, uint32 local) { - return 2 + (script->fun ? script->fun->nargs : 0) + local; -} -static inline uint32 argSlot(uint32 arg) { - return 2 + arg; -} -static inline uint32 thisSlot() { - return 1; -} - /* Whether pc is a loop test operand accessing a variable modified by the loop. */ bool LoopState::loopVariableAccess(jsbytecode *pc) @@ -785,51 +740,21 @@ LoopState::loopVariableAccess(jsbytecode *pc) case JSOP_DECLOCAL: case JSOP_LOCALINC: case JSOP_LOCALDEC: - if (analysis->localEscapes(GET_SLOTNO(pc))) - return false; - return liveness->firstWrite(localSlot(script, GET_SLOTNO(pc)), lifetime) != uint32(-1); case JSOP_GETARG: case JSOP_INCARG: case JSOP_DECARG: case JSOP_ARGINC: - case JSOP_ARGDEC: - if (analysis->argEscapes(GET_SLOTNO(pc))) + case JSOP_ARGDEC: { + uint32 slot = GetBytecodeSlot(script, pc); + if (analysis->slotEscapes(slot)) return false; - return liveness->firstWrite(argSlot(GET_SLOTNO(pc)), lifetime) != uint32(-1); + return analysis->liveness(slot).firstWrite(lifetime) != uint32(-1); + } default: return false; } } -static inline int32 -GetBytecodeInteger(jsbytecode *pc) -{ - switch (JSOp(*pc)) { - - case JSOP_ZERO: - return 0; - - case JSOP_ONE: - return 1; - - case JSOP_UINT16: - return (int32_t) GET_UINT16(pc); - - case JSOP_UINT24: - return (int32_t) GET_UINT24(pc); - - case JSOP_INT8: - return GET_INT8(pc); - - case JSOP_INT32: - return GET_INT32(pc); - - default: - JS_NOT_REACHED("Bad op"); - return 0; - } -} - /* * Get any slot/constant accessed by a loop test operand, in terms of its value * at the start of the next loop iteration. @@ -850,37 +775,36 @@ LoopState::getLoopTestAccess(jsbytecode *pc, uint32 *pslot, int32 *pconstant) */ JSOp op = JSOp(*pc); + const JSCodeSpec *cs = &js_CodeSpec[op]; + switch (op) { case JSOP_GETLOCAL: case JSOP_INCLOCAL: case JSOP_DECLOCAL: case JSOP_LOCALINC: - case JSOP_LOCALDEC: { - uint32 local = GET_SLOTNO(pc); - if (analysis->localEscapes(local)) - return false; - *pslot = localSlot(script, local); - if (op == JSOP_LOCALINC) - *pconstant = -1; - else if (op == JSOP_LOCALDEC) - *pconstant = 1; - return true; - } - + case JSOP_LOCALDEC: case JSOP_GETARG: case JSOP_INCARG: case JSOP_DECARG: case JSOP_ARGINC: case JSOP_ARGDEC: { - uint32 arg = GET_SLOTNO(pc); - if (analysis->argEscapes(arg)) + uint32 slot = GetBytecodeSlot(script, pc); + if (analysis->slotEscapes(slot)) return false; - *pslot = argSlot(arg); - if (op == JSOP_ARGINC) - *pconstant = -1; - else if (op == JSOP_ARGDEC) - *pconstant = 1; + + /* Only consider tests on known integers. */ + types::TypeSet *types = analysis->pushedTypes(pc, 0); + if (types->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) + return false; + + *pslot = slot; + if (cs->format & JOF_POST) { + if (cs->format & JOF_INC) + *pconstant = -1; + else + *pconstant = 1; + } return true; } @@ -913,10 +837,10 @@ LoopState::analyzeLoopTest() jsbytecode *backedge = script->code + lifetime->backedge; if (JSOp(*backedge) != JSOP_IFNE) return; - StackAnalysis::PoppedValue test = stack.popped(backedge, 0); - if (test.offset == StackAnalysis::UNKNOWN_PUSHED) + const SSAValue &test = analysis->poppedValue(backedge, 0); + if (test.kind() != SSAValue::PUSHED) return; - JSOp cmpop = JSOp(script->code[test.offset]); + JSOp cmpop = JSOp(script->code[test.pushedOffset()]); switch (cmpop) { case JSOP_GT: case JSOP_GE: @@ -927,16 +851,14 @@ LoopState::analyzeLoopTest() return; } - StackAnalysis::PoppedValue poppedOne = stack.popped(test.offset, 1); - StackAnalysis::PoppedValue poppedTwo = stack.popped(test.offset, 0); + const SSAValue &poppedOne = analysis->poppedValue(test.pushedOffset(), 1); + const SSAValue &poppedTwo = analysis->poppedValue(test.pushedOffset(), 0); - if (poppedOne.offset == StackAnalysis::UNKNOWN_PUSHED || - poppedTwo.offset == StackAnalysis::UNKNOWN_PUSHED) { + if (poppedOne.kind() != SSAValue::PUSHED || poppedTwo.kind() != SSAValue::PUSHED) return; - } - jsbytecode *one = script->code + poppedOne.offset; - jsbytecode *two = script->code + poppedTwo.offset; + jsbytecode *one = script->code + poppedOne.pushedOffset(); + jsbytecode *two = script->code + poppedTwo.pushedOffset(); /* Reverse the condition if the RHS is modified by the loop. */ if (loopVariableAccess(two)) { @@ -961,33 +883,24 @@ LoopState::analyzeLoopTest() if (JSOp(*two) == JSOP_LENGTH) { /* Handle 'this.length' or 'x.length' for loop invariant 'x'. */ - StackAnalysis::PoppedValue array = stack.popped(two, 0); - if (array.offset == StackAnalysis::UNKNOWN_PUSHED) + const SSAValue &array = analysis->poppedValue(two, 0); + if (array.kind() != SSAValue::PUSHED) return; - jsbytecode *arraypc = script->code + array.offset; + jsbytecode *arraypc = script->code + array.pushedOffset(); if (loopVariableAccess(arraypc)) return; switch (JSOp(*arraypc)) { - case JSOP_GETLOCAL: { - uint32 local = GET_SLOTNO(arraypc); - if (analysis->localEscapes(local)) - return; - rhs = localSlot(script, local); + case JSOP_GETLOCAL: + case JSOP_GETARG: + case JSOP_THIS: { + rhs = GetBytecodeSlot(script, arraypc); break; } - case JSOP_GETARG: { - uint32 arg = GET_SLOTNO(arraypc); - if (analysis->argEscapes(arg)) - return; - rhs = argSlot(arg); - break; - } - case JSOP_THIS: - rhs = thisSlot(); - break; default: return; } + if (!invariantLength(frame.getOrTrack(rhs), analysis->getValueTypes(array))) + return; rhsLength = true; } else { if (!getLoopTestAccess(two, &rhs, &rhsConstant)) @@ -997,16 +910,6 @@ LoopState::analyzeLoopTest() if (lhs == UNASSIGNED) return; - /* Only consider comparisons on known integers. */ - types::TypeSet *lhsTypes = cc.getTypeSet(lhs); - if (!lhsTypes || lhsTypes->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) - return; - if (rhs != UNASSIGNED && !rhsLength) { - types::TypeSet *rhsTypes = cc.getTypeSet(rhs); - if (!rhsTypes || rhsTypes->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) - return; - } - int32 constant; if (!SafeSub(rhsConstant, lhsConstant, &constant)) return; @@ -1037,49 +940,26 @@ LoopState::analyzeLoopIncrements() * also handle the first basic block). */ - unsigned nargs = script->fun ? script->fun->nargs : 0; - for (unsigned i = 0; i < nargs; i++) { - if (analysis->argEscapes(i)) + for (uint32 slot = ArgSlot(0); slot < LocalSlot(script, script->nfixed); slot++) { + if (analysis->slotEscapes(slot)) continue; - uint32 offset = liveness->onlyWrite(argSlot(i), lifetime); + uint32 offset = analysis->liveness(slot).onlyWrite(lifetime); if (offset == uint32(-1) || offset < lifetime->lastBlock) continue; JSOp op = JSOp(script->code[offset]); - if (op == JSOP_SETARG) - continue; + const JSCodeSpec *cs = &js_CodeSpec[op]; + if (cs->format & (JOF_INC | JOF_DEC)) { + types::TypeSet *types = analysis->pushedTypes(offset); + if (types->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) + continue; - types::TypeSet *types = cc.getTypeSet(argSlot(i)); - if (!types || types->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) - continue; - - Increment inc; - inc.slot = argSlot(i); - inc.offset = offset; - increments.append(inc); - } - - for (unsigned i = 0; i < script->nfixed; i++) { - if (analysis->localEscapes(i)) - continue; - - uint32 offset = liveness->onlyWrite(localSlot(script, i), lifetime); - if (offset == uint32(-1) || offset < lifetime->lastBlock) - continue; - - JSOp op = JSOp(script->code[offset]); - if (op == JSOP_SETLOCAL || op == JSOP_SETLOCALPOP) - continue; - - types::TypeSet *types = cc.getTypeSet(localSlot(script, i)); - if (!types || types->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) - continue; - - Increment inc; - inc.slot = localSlot(script, i); - inc.offset = offset; - increments.append(inc); + Increment inc; + inc.slot = slot; + inc.offset = offset; + increments.append(inc); + } } } @@ -1104,8 +984,8 @@ LoopState::analyzeModset() case JSOP_SETHOLE: case JSOP_SETELEM: { - types::TypeSet *objTypes = poppedTypes(pc, 2); - types::TypeSet *elemTypes = poppedTypes(pc, 1); + types::TypeSet *objTypes = analysis->poppedTypes(pc, 2); + types::TypeSet *elemTypes = analysis->poppedTypes(pc, 1); /* * Mark the modset as unknown if the index might be non-integer, @@ -1251,15 +1131,6 @@ LoopState::adjustConstantForIncrement(jsbytecode *pc, uint32 slot) } } -inline types::TypeSet * -LoopState::poppedTypes(jsbytecode *pc, unsigned which) -{ - StackAnalysis::PoppedValue value = stack.popped(pc, which); - if (value.offset == StackAnalysis::UNKNOWN_PUSHED) - return NULL; - return script->types->pushed(value.offset, value.which); -} - bool LoopState::getEntryValue(uint32 offset, uint32 popped, uint32 *pslot, int32 *pconstant) { @@ -1268,42 +1139,31 @@ LoopState::getEntryValue(uint32 offset, uint32 popped, uint32 *pslot, int32 *pco * expression 'slot + constant' with the same value as the stack value * and expressed in terms of the state at loop entry. */ - StackAnalysis::PoppedValue value = stack.popped(offset, popped); - if (value.offset == StackAnalysis::UNKNOWN_PUSHED) + const SSAValue &value = analysis->poppedValue(offset, popped); + if (value.kind() != SSAValue::PUSHED) return false; - jsbytecode *pc = script->code + value.offset; + jsbytecode *pc = script->code + value.pushedOffset(); JSOp op = (JSOp)*pc; switch (op) { case JSOP_GETLOCAL: case JSOP_LOCALINC: - case JSOP_INCLOCAL: { - uint32 local = GET_SLOTNO(pc); - if (analysis->localEscapes(local)) - return false; - uint32 write = liveness->firstWrite(localSlot(script, local), lifetime); - if (write != uint32(-1) && write < value.offset) { - /* Variable has been modified since the start of the loop. */ - return false; - } - *pslot = localSlot(script, local); - *pconstant = (op == JSOP_INCLOCAL) ? 1 : 0; - return true; - } - + case JSOP_INCLOCAL: case JSOP_GETARG: case JSOP_ARGINC: case JSOP_INCARG: { - uint32 arg = GET_SLOTNO(pc); - if (analysis->argEscapes(arg)) + uint32 slot = GetBytecodeSlot(script, pc); + if (analysis->slotEscapes(slot)) return false; - uint32 write = liveness->firstWrite(argSlot(arg), lifetime); - if (write != uint32(-1) && write < value.offset) + uint32 write = analysis->liveness(slot).firstWrite(lifetime); + if (write != uint32(-1) && write < value.pushedOffset()) { + /* Variable has been modified since the start of the loop. */ return false; - *pslot = argSlot(arg); - *pconstant = (op == JSOP_INCARG) ? 1 : 0; + } + *pslot = slot; + *pconstant = (op == JSOP_INCLOCAL || op == JSOP_INCARG) ? 1 : 0; return true; } diff --git a/js/src/methodjit/LoopState.h b/js/src/methodjit/LoopState.h index 0136d579064c..2958894de046 100644 --- a/js/src/methodjit/LoopState.h +++ b/js/src/methodjit/LoopState.h @@ -89,13 +89,12 @@ class LoopState : public MacroAssemblerTypedefs { JSContext *cx; JSScript *script; + analyze::ScriptAnalysis *analysis; Compiler &cc; FrameState &frame; - analyze::Script *analysis; - analyze::LifetimeScript *liveness; /* Basic information about this loop. */ - analyze::LifetimeLoop *lifetime; + analyze::LoopAnalysis *lifetime; /* Allocation at the head of the loop, has all loop carried variables. */ RegisterAllocation *alloc; @@ -179,7 +178,8 @@ class LoopState : public MacroAssemblerTypedefs Vector invariantEntries; bool loopInvariantEntry(uint32 slot); - bool addHoistedCheck(uint32 arraySlot, uint32 valueSlot1, uint32 valueSlot2, int32 constant); + bool addHoistedCheck(uint32 arraySlot, + uint32 valueSlot1, uint32 valueSlot2, int32 constant); void addNegativeCheck(uint32 valueSlot, int32 constant); bool hasInvariants() { return !invariantEntries.empty(); } @@ -194,8 +194,7 @@ class LoopState : public MacroAssemblerTypedefs jsbytecode *PC; LoopState(JSContext *cx, JSScript *script, - Compiler *cc, FrameState *frame, - analyze::Script *analysis, analyze::LifetimeScript *liveness); + Compiler *cc, FrameState *frame); bool init(jsbytecode *head, Jump entry, jsbytecode *entryTarget); bool generatingInvariants() { return !skipAnalysis; } @@ -234,16 +233,18 @@ class LoopState : public MacroAssemblerTypedefs void flushLoop(StubCompiler &stubcc); - bool hoistArrayLengthCheck(const FrameEntry *obj, unsigned indexPopped); + /* + * These should only be used for entries which are known to be dense arrays + * (if they are objects at all). + */ + bool hoistArrayLengthCheck(const FrameEntry *obj, types::TypeSet *objTypes, + unsigned indexPopped); FrameEntry *invariantSlots(const FrameEntry *obj); - FrameEntry *invariantLength(const FrameEntry *obj); + FrameEntry *invariantLength(const FrameEntry *obj, types::TypeSet *objTypes); private: /* Analysis information for the loop. */ - /* Stack information at points within this loop. */ - analyze::StackAnalysis stack; - /* * Any inequality known to hold at the head of the loop. This has the * form 'lhs <= rhs + constant' or 'lhs >= rhs + constant', depending on @@ -262,6 +263,7 @@ class LoopState : public MacroAssemblerTypedefs * length must not be directly modified within the loop. */ bool testLength; + bool testLengthKnownObject; /* * A variable which will be incremented or decremented exactly once in each @@ -306,8 +308,6 @@ class LoopState : public MacroAssemblerTypedefs uint32 getIncrement(uint32 slot); int32 adjustConstantForIncrement(jsbytecode *pc, uint32 slot); - inline types::TypeSet *poppedTypes(jsbytecode *pc, unsigned which); - bool getEntryValue(uint32 offset, uint32 popped, uint32 *pslot, int32 *pconstant); }; diff --git a/js/src/methodjit/MethodJIT.cpp b/js/src/methodjit/MethodJIT.cpp index 98c6b43c1daf..9ffa55406ead 100644 --- a/js/src/methodjit/MethodJIT.cpp +++ b/js/src/methodjit/MethodJIT.cpp @@ -744,35 +744,6 @@ js::mjit::JaegerShotAtSafePoint(JSContext *cx, void *safePoint) JS_ASSERT(!TRACE_RECORDER(cx)); #endif - JSScript *script = cx->fp()->script(); - if (cx->typeInferenceEnabled() && script->varTypes) { - /* - * Convert integer locals/args to doubles as required. The code we are - * jumping to may assume that non-escaping locals and args have double - * values if they were inferred as 'int or double'. The interpreter cannot - * guarantee this holds, so we check and fixup the args/locals here. - */ - - if (cx->fp()->hasArgs()) { - JSFunction *fun = cx->fp()->fun(); - Value *formals = cx->fp()->formalArgs(); - for (uint32 i = 0; i < fun->nargs; i++) { - if (formals[i].isInt32() && - script->argTypes(i)->getKnownTypeTag(cx) == JSVAL_TYPE_DOUBLE) { - formals[i].setDouble((double)formals[i].toInt32()); - } - } - } - - Value *fixed = cx->fp()->slots(); - for (uint32 i = 0; i < script->nfixed; i++) { - if (fixed[i].isInt32() && - script->localTypes(i)->getKnownTypeTag(cx) == JSVAL_TYPE_DOUBLE) { - fixed[i].setDouble((double)fixed[i].toInt32()); - } - } - } - return CheckStackAndEnterMethodJIT(cx, cx->fp(), safePoint); } diff --git a/js/src/methodjit/PolyIC.cpp b/js/src/methodjit/PolyIC.cpp index 77e165dc37cc..10d989ef0cde 100644 --- a/js/src/methodjit/PolyIC.cpp +++ b/js/src/methodjit/PolyIC.cpp @@ -1546,7 +1546,7 @@ class ScopeNameCompiler : public PICStubCompiler JSScript *newscript = getprop.obj->getCallObjCalleeFunction()->script(); uint16 slot = uint16(getprop.shape->shortid); if (!newscript->ensureVarTypes(cx)) - return cx->compartment->types.checkPendingRecompiles(cx); + return false; if (shape->getterOp() == GetCallArg) types = newscript->argTypes(slot); else if (shape->getterOp() == GetCallVar) @@ -1555,15 +1555,15 @@ class ScopeNameCompiler : public PICStubCompiler JS_ASSERT(!getprop.obj->getParent()); if (getprop.obj->getType()->unknownProperties()) { f.script()->typeMonitorResult(cx, f.pc(), types::TYPE_UNKNOWN); - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } types = getprop.obj->getType()->getProperty(cx, shape->id, false); if (!types) - return cx->compartment->types.checkPendingRecompiles(cx); + return false; } types->pushAllTypes(cx, f.script(), f.pc()); - return cx->compartment->types.checkPendingRecompiles(cx); + return true; } }; diff --git a/js/src/methodjit/Retcon.cpp b/js/src/methodjit/Retcon.cpp index ef981478b730..06639b929373 100644 --- a/js/src/methodjit/Retcon.cpp +++ b/js/src/methodjit/Retcon.cpp @@ -559,10 +559,6 @@ Recompiler::recompile() JS_ASSERT_IF(keepCtor, script->jitCtor); cx->compartment->types.recompilations++; - - if (!cx->compartment->types.checkPendingRecompiles(cx)) - return Compile_Error; - return true; } diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index e93c7a06e9b1..fe9c5a07900c 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -2840,22 +2840,21 @@ stubs::CheckArgumentTypes(VMFrame &f) JSScript *script = fun->script(); RecompilationMonitor monitor(f.cx); - /* Postpone recompilations until all args have been updated. */ - types::AutoEnterTypeInference enter(f.cx); + { + /* Postpone recompilations until all args have been updated. */ + types::AutoEnterTypeInference enter(f.cx); - if (!f.fp()->isConstructing()) { - if (!script->typeSetThis(f.cx, fp->thisValue())) - THROW(); + if (!f.fp()->isConstructing()) { + if (!script->typeSetThis(f.cx, fp->thisValue())) + THROW(); + } + + for (unsigned i = 0; i < fun->nargs; i++) { + if (!script->typeSetArgument(f.cx, i, fp->formalArg(i))) + THROW(); + } } - for (unsigned i = 0; i < fun->nargs; i++) { - if (!script->typeSetArgument(f.cx, i, fp->formalArg(i))) - THROW(); - } - - if (!f.cx->compartment->types.checkPendingRecompiles(f.cx)) - THROW(); - if (monitor.recompiled()) return;