From d436ffc425f136c904e04913998eca19880f9e3d Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Tue, 21 Jan 2014 14:09:34 +0100 Subject: [PATCH] Bug 960040 - Part 2: Hide more of ScriptAnalysis implementation. r=jandem --- js/src/jit/IonAnalysis.cpp | 3 +- js/src/jit/IonBuilder.cpp | 1 + js/src/jit/Lowering.cpp | 3 +- js/src/jit/RangeAnalysis.cpp | 6 +- js/src/jsanalyze.cpp | 681 ++++++++++++++++++++++++++++++++++- js/src/jsanalyze.h | 644 +-------------------------------- js/src/jsanalyzeinlines.h | 54 --- js/src/jsinferinlines.h | 1 - js/src/jsopcodeinlines.h | 47 +++ 9 files changed, 750 insertions(+), 690 deletions(-) delete mode 100644 js/src/jsanalyzeinlines.h diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 0f674b846d72..6266be456878 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -18,6 +18,7 @@ #include "jit/MIRGraph.h" #include "jsinferinlines.h" +#include "jsopcodeinlines.h" #include "jsobjinlines.h" using namespace js; @@ -1522,7 +1523,7 @@ jit::ExtractLinearInequality(MTest *test, BranchDirection direction, JSOp jsop = compare->jsop(); if (direction == FALSE_BRANCH) - jsop = analyze::NegateCompareOp(jsop); + jsop = NegateCompareOp(jsop); SimpleLinearSum lsum = ExtractLinearSum(lhs); SimpleLinearSum rsum = ExtractLinearSum(rhs); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index d051774dc5fa..122642bccf08 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -29,6 +29,7 @@ #include "jsinferinlines.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" +#include "jsopcodeinlines.h" #include "jit/CompileInfo-inl.h" diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 72918f633630..c7de18199d5f 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -17,6 +17,7 @@ #include "jsinferinlines.h" #include "jsobjinlines.h" +#include "jsopcodeinlines.h" #include "jit/shared/Lowering-shared-inl.h" @@ -592,7 +593,7 @@ ReorderComparison(JSOp op, MDefinition **lhsp, MDefinition **rhsp) if (lhs->isConstant()) { *rhsp = lhs; *lhsp = rhs; - return js::analyze::ReverseCompareOp(op); + return ReverseCompareOp(op); } return op; } diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 8c3cb8b3601f..a977f297a555 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -18,6 +18,8 @@ #include "jit/MIRGraph.h" #include "vm/NumericConversions.h" +#include "jsopcodeinlines.h" + using namespace js; using namespace js::jit; @@ -167,7 +169,7 @@ RangeAnalysis::addBetaNodes() JSOp jsop = compare->jsop(); if (branch_dir == FALSE_BRANCH) { - jsop = analyze::NegateCompareOp(jsop); + jsop = NegateCompareOp(jsop); conservativeLower = GenericNaN(); conservativeUpper = GenericNaN(); } @@ -175,7 +177,7 @@ RangeAnalysis::addBetaNodes() if (left->isConstant() && left->toConstant()->value().isNumber()) { bound = left->toConstant()->value().toNumber(); val = right; - jsop = analyze::ReverseCompareOp(jsop); + jsop = ReverseCompareOp(jsop); } else if (right->isConstant() && right->toConstant()->value().isNumber()) { bound = right->toConstant()->value().toNumber(); val = left; diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 1063a2ec7635..24ec03c1d4f6 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -4,8 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "jsanalyzeinlines.h" - #include "mozilla/DebugOnly.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/PodOperations.h" @@ -16,22 +14,471 @@ #include "jsinferinlines.h" #include "jsobjinlines.h" - -using namespace js; -using namespace js::analyze; +#include "jsopcodeinlines.h" using mozilla::DebugOnly; using mozilla::PodCopy; using mozilla::PodZero; using mozilla::FloorLog2; +namespace js { +namespace analyze { +class LoopAnalysis; +} // namespace analyze +} // namespace js + +namespace mozilla { + +template <> struct IsPod : TrueType {}; +template <> struct IsPod : TrueType {}; +template <> struct IsPod : TrueType {}; +template <> struct IsPod : TrueType {}; +template <> struct IsPod : TrueType {}; + +} /* namespace mozilla */ + +namespace js { +namespace analyze { + +/* + * 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. + */ + +/* Information about a bytecode instruction. */ +class Bytecode +{ + friend class ScriptAnalysis; + + public: + Bytecode() { mozilla::PodZero(this); } + + /* --------- Bytecode analysis --------- */ + + /* Whether there are any incoming jumps to this instruction. */ + bool jumpTarget : 1; + + /* Whether there is fallthrough to this instruction from a non-branching instruction. */ + bool fallthrough : 1; + + /* Whether this instruction is the fall through point of a conditional jump. */ + bool jumpFallthrough : 1; + + /* + * Whether this instruction must always execute, unless the script throws + * an exception which it does not later catch. + */ + bool unconditional : 1; + + /* Whether this instruction has been analyzed to get its output defines and stack. */ + bool analyzed : 1; + + /* Whether this is a catch/finally entry point. */ + bool exceptionEntry : 1; + + /* Stack depth before this opcode. */ + uint32_t stackDepth; + + private: + + /* If this is a JSOP_LOOPHEAD or JSOP_LOOPENTRY, information about the loop. */ + LoopAnalysis *loop; + + /* --------- SSA analysis --------- */ + + /* Generated location of each value popped by this bytecode. */ + SSAValue *poppedValues; + + /* Points where values pushed or written by this bytecode are popped. */ + SSAUseChain **pushedUses; + + 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; + }; +}; + +/* + * Information about the lifetime of a local or argument. These form a linked + * list describing successive intervals in the program where the variable's + * value may be live. At points in the script not in one of these segments + * (points in a 'lifetime hole'), the variable is dead and registers containing + * its type/payload can be discarded without needing to be synced. + */ +struct Lifetime +{ + /* + * Start and end offsets of this lifetime. The variable is live at the + * beginning of every bytecode in this (inclusive) range. + */ + uint32_t start; + uint32_t end; + + /* + * In a loop body, endpoint to extend this lifetime with if the variable is + * live in the next iteration. + */ + uint32_t savedEnd; + + /* + * The start of this lifetime is a bytecode writing the variable. Each + * write to a variable is associated with a lifetime. + */ + bool write; + + /* Next lifetime. The variable is dead from this->end to next->start. */ + Lifetime *next; + + Lifetime(uint32_t offset, uint32_t savedEnd, Lifetime *next) + : start(offset), end(offset), savedEnd(savedEnd), + write(false), next(next) + {} +}; + +/* Basic information for a loop. */ +class LoopAnalysis +{ + public: + /* Any loop this one is nested in. */ + LoopAnalysis *parent; + + /* Offset of the head of the loop. */ + uint32_t head; + + /* + * Offset of the unique jump going to the head of the loop. The code + * between the head and the backedge forms the loop body. + */ + uint32_t backedge; +}; + +/* Current lifetime information for a variable. */ +struct LifetimeVariable +{ + /* If the variable is currently live, the lifetime segment. */ + Lifetime *lifetime; + + /* If the variable is currently dead, the next live segment. */ + Lifetime *saved; + + /* Jump preceding the basic block which killed this variable. */ + uint32_t savedEnd : 31; + + /* If the variable needs to be kept alive until lifetime->start. */ + bool ensured : 1; + + /* Whether this variable is live at offset. */ + Lifetime * live(uint32_t offset) const { + if (lifetime && lifetime->end >= offset) + return lifetime; + Lifetime *segment = lifetime ? lifetime : saved; + while (segment && segment->start <= offset) { + if (segment->end >= offset) + return segment; + segment = segment->next; + } + return nullptr; + } + + /* + * Get the offset of the first write to the variable in an inclusive range, + * UINT32_MAX if the variable is not written in the range. + */ + uint32_t firstWrite(uint32_t start, uint32_t end) const { + Lifetime *segment = lifetime ? lifetime : saved; + while (segment && segment->start <= end) { + if (segment->start >= start && segment->write) + return segment->start; + segment = segment->next; + } + return UINT32_MAX; + } + uint32_t firstWrite(LoopAnalysis *loop) const { + return firstWrite(loop->head, loop->backedge); + } + + /* + * If the variable is only written once in the body of a loop, offset of + * that write. UINT32_MAX otherwise. + */ + uint32_t onlyWrite(LoopAnalysis *loop) const { + uint32_t offset = UINT32_MAX; + Lifetime *segment = lifetime ? lifetime : saved; + while (segment && segment->start <= loop->backedge) { + if (segment->start >= loop->head && segment->write) { + if (offset != UINT32_MAX) + return UINT32_MAX; + offset = segment->start; + } + segment = segment->next; + } + 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, /* Invalid entry. */ + PUSHED = 1, /* Value pushed by some bytecode. */ + VAR = 2, /* Initial or written value to some argument or local. */ + PHI = 3 /* Selector for one of several values. */ + }; + + Kind kind() const { + JS_ASSERT(u.pushed.kind == u.var.kind && u.pushed.kind == u.phi.kind); + + /* Use a bitmask because MSVC wants to use -1 for PHI nodes. */ + return (Kind) (u.pushed.kind & 0x3); + } + + bool operator==(const SSAValue &o) const { + return !memcmp(this, &o, sizeof(SSAValue)); + } + + /* Accessors for values pushed by a bytecode within this script. */ + + uint32_t pushedOffset() const { + JS_ASSERT(kind() == PUSHED); + return u.pushed.offset; + } + + uint32_t 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_t varSlot() const { + JS_ASSERT(kind() == VAR); + return u.var.slot; + } + + uint32_t varOffset() const { + JS_ASSERT(!varInitial()); + return u.var.offset; + } + + /* Accessors for phi nodes. */ + + uint32_t phiSlot() const; + uint32_t phiLength() const; + const SSAValue &phiValue(uint32_t i) const; + + /* Offset at which this phi node was created. */ + uint32_t phiOffset() const { + JS_ASSERT(kind() == PHI); + return u.phi.offset; + } + + SSAPhiNode *phiNode() const { + JS_ASSERT(kind() == PHI); + return u.phi.node; + } + + /* Other accessors. */ + +#ifdef DEBUG + void print() const; +#endif + + void clear() { + mozilla::PodZero(this); + JS_ASSERT(kind() == EMPTY); + } + + void initPushed(uint32_t offset, uint32_t index) { + clear(); + u.pushed.kind = PUSHED; + u.pushed.offset = offset; + u.pushed.index = index; + } + + static SSAValue PushedValue(uint32_t offset, uint32_t index) { + SSAValue v; + v.initPushed(offset, index); + return v; + } + + void initInitial(uint32_t slot) { + clear(); + u.var.kind = VAR; + u.var.initial = true; + u.var.slot = slot; + } + + void initWritten(uint32_t slot, uint32_t offset) { + clear(); + u.var.kind = VAR; + u.var.initial = false; + u.var.slot = slot; + u.var.offset = offset; + } + + static SSAValue WrittenVar(uint32_t slot, uint32_t offset) { + SSAValue v; + v.initWritten(slot, offset); + return v; + } + + void initPhi(uint32_t offset, SSAPhiNode *node) { + clear(); + u.phi.kind = PHI; + u.phi.offset = offset; + u.phi.node = node; + } + + static SSAValue PhiValue(uint32_t offset, SSAPhiNode *node) { + SSAValue v; + v.initPhi(offset, node); + return v; + } + + private: + union { + struct { + Kind kind : 2; + uint32_t offset : 30; + uint32_t index; + } pushed; + struct { + Kind kind : 2; + bool initial : 1; + uint32_t slot : 29; + uint32_t offset; + } var; + struct { + Kind kind : 2; + uint32_t offset : 30; + SSAPhiNode *node; + } phi; + } u; +}; + +/* + * 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. + */ +struct SSAPhiNode +{ + uint32_t slot; + uint32_t length; + SSAValue *options; + SSAUseChain *uses; + SSAPhiNode() { mozilla::PodZero(this); } +}; + +inline uint32_t +SSAValue::phiSlot() const +{ + return u.phi.node->slot; +} + +inline uint32_t +SSAValue::phiLength() const +{ + JS_ASSERT(kind() == PHI); + return u.phi.node->length; +} + +inline const SSAValue & +SSAValue::phiValue(uint32_t i) const +{ + JS_ASSERT(kind() == PHI && i < phiLength()); + return u.phi.node->options[i]; +} + +class SSAUseChain +{ + public: + bool popped : 1; + uint32_t offset : 31; + /* FIXME: Assert that only the proper arm of this union is accessed. */ + union { + uint32_t which; + SSAPhiNode *phi; + } u; + SSAUseChain *next; + + SSAUseChain() { mozilla::PodZero(this); } +}; + +class SlotValue +{ + public: + uint32_t slot; + SSAValue value; + SlotValue(uint32_t slot, const SSAValue &value) : slot(slot), value(value) {} +}; + ///////////////////////////////////////////////////////////////////// // Bytecode ///////////////////////////////////////////////////////////////////// #ifdef DEBUG void -analyze::PrintBytecode(JSContext *cx, HandleScript script, jsbytecode *pc) +PrintBytecode(JSContext *cx, HandleScript script, jsbytecode *pc) { fprintf(stderr, "#%u:", script->id()); Sprinter sprinter(cx); @@ -42,6 +489,87 @@ analyze::PrintBytecode(JSContext *cx, HandleScript script, jsbytecode *pc) } #endif +/* + * For opcodes which assign to a local variable or argument, track an extra def + * during SSA analysis for the value's use chain and assigned type. + */ +static inline bool +ExtendedDef(jsbytecode *pc) +{ + switch ((JSOp)*pc) { + case JSOP_SETARG: + case JSOP_SETLOCAL: + return true; + default: + return false; + } +} + +/* + * 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) +{ + if (ExtendedDef(pc)) + return true; + switch ((JSOp)*pc) { + case JSOP_GETARG: + case JSOP_CALLARG: + case JSOP_GETLOCAL: + case JSOP_CALLLOCAL: + return true; + default: + return false; + } +} + +static inline unsigned +FollowBranch(JSContext *cx, 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->offsetToPC(offset); + unsigned targetOffset = offset + GET_JUMP_OFFSET(pc); + if (targetOffset < offset) { + jsbytecode *target = script->offsetToPC(targetOffset); + JSOp nop = JSOp(*target); + if (nop == JSOP_GOTO) + return targetOffset + GET_JUMP_OFFSET(target); + } + return targetOffset; +} + +static inline uint32_t StackSlot(JSScript *script, uint32_t index) { + return TotalSlots(script) + index; +} + +static inline uint32_t GetBytecodeSlot(JSScript *script, jsbytecode *pc) +{ + switch (JSOp(*pc)) { + + case JSOP_GETARG: + case JSOP_CALLARG: + case JSOP_SETARG: + return ArgSlot(GET_ARGNO(pc)); + + case JSOP_GETLOCAL: + case JSOP_CALLLOCAL: + case JSOP_SETLOCAL: + return LocalSlot(script, GET_LOCALNO(pc)); + + case JSOP_THIS: + return ThisSlot(); + + default: + MOZ_ASSUME_UNREACHABLE("Bad slot opcode"); + } +} + ///////////////////////////////////////////////////////////////////// // Bytecode Analysis ///////////////////////////////////////////////////////////////////// @@ -86,6 +614,126 @@ ScriptAnalysis::addJump(JSContext *cx, unsigned offset, return true; } +inline bool +ScriptAnalysis::jumpTarget(uint32_t offset) +{ + JS_ASSERT(offset < script_->length()); + return codeArray[offset] && codeArray[offset]->jumpTarget; +} + +inline bool +ScriptAnalysis::jumpTarget(const jsbytecode *pc) +{ + return jumpTarget(script_->pcToOffset(pc)); +} + +inline const SSAValue & +ScriptAnalysis::poppedValue(uint32_t offset, uint32_t which) +{ + JS_ASSERT(which < GetUseCount(script_, offset) + + (ExtendedUse(script_->offsetToPC(offset)) ? 1 : 0)); + return getCode(offset).poppedValues[which]; +} + +inline const SSAValue & +ScriptAnalysis::poppedValue(const jsbytecode *pc, uint32_t which) +{ + return poppedValue(script_->pcToOffset(pc), which); +} + +inline const SlotValue * +ScriptAnalysis::newValues(uint32_t offset) +{ + JS_ASSERT(offset < script_->length()); + return getCode(offset).newValues; +} + +inline const SlotValue * +ScriptAnalysis::newValues(const jsbytecode *pc) +{ + return newValues(script_->pcToOffset(pc)); +} + +inline bool +ScriptAnalysis::trackUseChain(const SSAValue &v) +{ + JS_ASSERT_IF(v.kind() == SSAValue::VAR, trackSlot(v.varSlot())); + return v.kind() != SSAValue::EMPTY && + (v.kind() != SSAValue::VAR || !v.varInitial()); +} + +/* + * Get the use chain for an SSA value. May be invalid for some opcodes in + * scripts where localsAliasStack(). You have been warned! + */ +inline SSAUseChain *& +ScriptAnalysis::useChain(const SSAValue &v) +{ + JS_ASSERT(trackUseChain(v)); + if (v.kind() == SSAValue::PUSHED) + return getCode(v.pushedOffset()).pushedUses[v.pushedIndex()]; + if (v.kind() == SSAValue::VAR) + return getCode(v.varOffset()).pushedUses[GetDefCount(script_, v.varOffset())]; + return v.phiNode()->uses; +} + +/* For a JSOP_CALL* op, get the pc of the corresponding JSOP_CALL/NEW/etc. */ +inline jsbytecode * +ScriptAnalysis::getCallPC(jsbytecode *pc) +{ + SSAUseChain *uses = useChain(SSAValue::PushedValue(script_->pcToOffset(pc), 0)); + JS_ASSERT(uses && uses->popped); + JS_ASSERT(js_CodeSpec[script_->code()[uses->offset]].format & JOF_INVOKE); + return script_->offsetToPC(uses->offset); +} + +/* Accessors for local variable information. */ + +/* + * Escaping slots include all slots that can be accessed in ways other than + * through the corresponding LOCAL/ARG opcode. This includes all closed + * slots in the script, all slots in scripts which use eval or are in debug + * mode, and slots which are aliased by NAME or similar opcodes in the + * containing script (which does not imply the variable is closed). + */ +inline bool +ScriptAnalysis::slotEscapes(uint32_t 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 which could alias local variables or arguments + * keeps us from tracking variable values at each point. + */ +inline bool +ScriptAnalysis::trackSlot(uint32_t slot) +{ + return !slotEscapes(slot) && canTrackVars && slot < 1000; +} + +inline const LifetimeVariable & +ScriptAnalysis::liveness(uint32_t slot) +{ + JS_ASSERT(script_->compartment()->activeAnalysis); + JS_ASSERT(!slotEscapes(slot)); + return lifetimes[slot]; +} + +inline void +ScriptAnalysis::setOOM(JSContext *cx) +{ + if (!outOfMemory) + js_ReportOutOfMemory(cx); + outOfMemory = true; + hadFailure = true; +} + void ScriptAnalysis::analyzeBytecode(JSContext *cx) { @@ -836,6 +1484,19 @@ ScriptAnalysis::ensureVariable(LifetimeVariable &var, unsigned until) // SSA Analysis ///////////////////////////////////////////////////////////////////// +/* Current value for a variable or stack value, as tracked during SSA. */ +struct SSAValueInfo +{ + SSAValue v; + + /* + * Sizes of branchTargets the last time this slot was written. Branches less + * than this threshold do not need to be inspected if the slot is written + * again, as they will already reflect the slot's value at the branch. + */ + int32_t branchSize; +}; + void ScriptAnalysis::analyzeSSA(JSContext *cx) { @@ -1719,4 +2380,12 @@ ScriptAnalysis::assertMatchingDebugMode() JS_ASSERT(!!script_->compartment()->debugMode() == !!originalDebugMode_); } +void +ScriptAnalysis::assertMatchingStackDepthAtOffset(uint32_t offset, uint32_t stackDepth) { + JS_ASSERT_IF(maybeCode(offset), getCode(offset).stackDepth == stackDepth); +} + #endif /* DEBUG */ + +} // namespace analyze +} // namespace js diff --git a/js/src/jsanalyze.h b/js/src/jsanalyze.h index f38bcf9df26e..722545459384 100644 --- a/js/src/jsanalyze.h +++ b/js/src/jsanalyze.h @@ -14,210 +14,15 @@ namespace js { namespace analyze { -class LoopAnalysis; +class Bytecode; +struct LifetimeVariable; class SlotValue; class SSAValue; +struct SSAValueInfo; class SSAUseChain; -/* - * 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. - */ - -/* Information about a bytecode instruction. */ -class Bytecode -{ - friend class ScriptAnalysis; - - public: - Bytecode() { mozilla::PodZero(this); } - - /* --------- Bytecode analysis --------- */ - - /* Whether there are any incoming jumps to this instruction. */ - bool jumpTarget : 1; - - /* Whether there is fallthrough to this instruction from a non-branching instruction. */ - bool fallthrough : 1; - - /* Whether this instruction is the fall through point of a conditional jump. */ - bool jumpFallthrough : 1; - - /* - * Whether this instruction must always execute, unless the script throws - * an exception which it does not later catch. - */ - bool unconditional : 1; - - /* Whether this instruction has been analyzed to get its output defines and stack. */ - bool analyzed : 1; - - /* Whether this is a catch/finally entry point. */ - bool exceptionEntry : 1; - - /* Stack depth before this opcode. */ - uint32_t stackDepth; - - private: - - /* If this is a JSOP_LOOPHEAD or JSOP_LOOPENTRY, information about the loop. */ - LoopAnalysis *loop; - - /* --------- SSA analysis --------- */ - - /* Generated location of each value popped by this bytecode. */ - SSAValue *poppedValues; - - /* Points where values pushed or written by this bytecode are popped. */ - SSAUseChain **pushedUses; - - 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; - }; -}; - -/* - * For opcodes which assign to a local variable or argument, track an extra def - * during SSA analysis for the value's use chain and assigned type. - */ -static inline bool -ExtendedDef(jsbytecode *pc) -{ - switch ((JSOp)*pc) { - case JSOP_SETARG: - case JSOP_SETLOCAL: - return true; - default: - return false; - } -} - -/* - * 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) -{ - if (ExtendedDef(pc)) - return true; - switch ((JSOp)*pc) { - case JSOP_GETARG: - case JSOP_CALLARG: - case JSOP_GETLOCAL: - case JSOP_CALLLOCAL: - return true; - default: - return false; - } -} - -static inline JSOp -ReverseCompareOp(JSOp op) -{ - switch (op) { - case JSOP_GT: - return JSOP_LT; - case JSOP_GE: - return JSOP_LE; - case JSOP_LT: - return JSOP_GT; - case JSOP_LE: - return JSOP_GE; - case JSOP_EQ: - case JSOP_NE: - case JSOP_STRICTEQ: - case JSOP_STRICTNE: - return op; - default: - MOZ_ASSUME_UNREACHABLE("unrecognized op"); - } -} - -static inline JSOp -NegateCompareOp(JSOp op) -{ - switch (op) { - case JSOP_GT: - return JSOP_LE; - case JSOP_GE: - return JSOP_LT; - case JSOP_LT: - return JSOP_GE; - case JSOP_LE: - return JSOP_GT; - case JSOP_EQ: - return JSOP_NE; - case JSOP_NE: - return JSOP_EQ; - case JSOP_STRICTNE: - return JSOP_STRICTEQ; - case JSOP_STRICTEQ: - return JSOP_STRICTNE; - default: - MOZ_ASSUME_UNREACHABLE("unrecognized op"); - } -} - -static inline unsigned -FollowBranch(JSContext *cx, 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->offsetToPC(offset); - unsigned targetOffset = offset + GET_JUMP_OFFSET(pc); - if (targetOffset < offset) { - jsbytecode *target = script->offsetToPC(targetOffset); - JSOp nop = JSOp(*target); - if (nop == JSOP_GOTO) - return targetOffset + GET_JUMP_OFFSET(target); - } - return targetOffset; -} - -/* Common representation of slots throughout analyses and the compiler. */ +// Common representation of slots between ScriptAnalysis, TypeScript, and in the +// case of TotalSlots, Ion. static inline uint32_t ThisSlot() { return 0; } @@ -232,371 +37,10 @@ static inline uint32_t TotalSlots(JSScript *script) { return LocalSlot(script, 0) + script->nfixed(); } -static inline uint32_t StackSlot(JSScript *script, uint32_t index) { - return TotalSlots(script) + index; -} - -static inline uint32_t GetBytecodeSlot(JSScript *script, jsbytecode *pc) -{ - switch (JSOp(*pc)) { - - case JSOP_GETARG: - case JSOP_CALLARG: - case JSOP_SETARG: - return ArgSlot(GET_ARGNO(pc)); - - case JSOP_GETLOCAL: - case JSOP_CALLLOCAL: - case JSOP_SETLOCAL: - return LocalSlot(script, GET_LOCALNO(pc)); - - case JSOP_THIS: - return ThisSlot(); - - default: - MOZ_ASSUME_UNREACHABLE("Bad slot opcode"); - } -} - -/* - * Information about the lifetime of a local or argument. These form a linked - * list describing successive intervals in the program where the variable's - * value may be live. At points in the script not in one of these segments - * (points in a 'lifetime hole'), the variable is dead and registers containing - * its type/payload can be discarded without needing to be synced. - */ -struct Lifetime -{ - /* - * Start and end offsets of this lifetime. The variable is live at the - * beginning of every bytecode in this (inclusive) range. - */ - uint32_t start; - uint32_t end; - - /* - * In a loop body, endpoint to extend this lifetime with if the variable is - * live in the next iteration. - */ - uint32_t savedEnd; - - /* - * The start of this lifetime is a bytecode writing the variable. Each - * write to a variable is associated with a lifetime. - */ - bool write; - - /* Next lifetime. The variable is dead from this->end to next->start. */ - Lifetime *next; - - Lifetime(uint32_t offset, uint32_t savedEnd, Lifetime *next) - : start(offset), end(offset), savedEnd(savedEnd), - write(false), next(next) - {} -}; - -/* Basic information for a loop. */ -class LoopAnalysis -{ - public: - /* Any loop this one is nested in. */ - LoopAnalysis *parent; - - /* Offset of the head of the loop. */ - uint32_t head; - - /* - * Offset of the unique jump going to the head of the loop. The code - * between the head and the backedge forms the loop body. - */ - uint32_t backedge; -}; - -/* Current lifetime information for a variable. */ -struct LifetimeVariable -{ - /* If the variable is currently live, the lifetime segment. */ - Lifetime *lifetime; - - /* If the variable is currently dead, the next live segment. */ - Lifetime *saved; - - /* Jump preceding the basic block which killed this variable. */ - uint32_t savedEnd : 31; - - /* If the variable needs to be kept alive until lifetime->start. */ - bool ensured : 1; - - /* Whether this variable is live at offset. */ - Lifetime * live(uint32_t offset) const { - if (lifetime && lifetime->end >= offset) - return lifetime; - Lifetime *segment = lifetime ? lifetime : saved; - while (segment && segment->start <= offset) { - if (segment->end >= offset) - return segment; - segment = segment->next; - } - return nullptr; - } - - /* - * Get the offset of the first write to the variable in an inclusive range, - * UINT32_MAX if the variable is not written in the range. - */ - uint32_t firstWrite(uint32_t start, uint32_t end) const { - Lifetime *segment = lifetime ? lifetime : saved; - while (segment && segment->start <= end) { - if (segment->start >= start && segment->write) - return segment->start; - segment = segment->next; - } - return UINT32_MAX; - } - uint32_t firstWrite(LoopAnalysis *loop) const { - return firstWrite(loop->head, loop->backedge); - } - - /* - * If the variable is only written once in the body of a loop, offset of - * that write. UINT32_MAX otherwise. - */ - uint32_t onlyWrite(LoopAnalysis *loop) const { - uint32_t offset = UINT32_MAX; - Lifetime *segment = lifetime ? lifetime : saved; - while (segment && segment->start <= loop->backedge) { - if (segment->start >= loop->head && segment->write) { - if (offset != UINT32_MAX) - return UINT32_MAX; - offset = segment->start; - } - segment = segment->next; - } - 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, /* Invalid entry. */ - PUSHED = 1, /* Value pushed by some bytecode. */ - VAR = 2, /* Initial or written value to some argument or local. */ - PHI = 3 /* Selector for one of several values. */ - }; - - Kind kind() const { - JS_ASSERT(u.pushed.kind == u.var.kind && u.pushed.kind == u.phi.kind); - - /* Use a bitmask because MSVC wants to use -1 for PHI nodes. */ - return (Kind) (u.pushed.kind & 0x3); - } - - bool operator==(const SSAValue &o) const { - return !memcmp(this, &o, sizeof(SSAValue)); - } - - /* Accessors for values pushed by a bytecode within this script. */ - - uint32_t pushedOffset() const { - JS_ASSERT(kind() == PUSHED); - return u.pushed.offset; - } - - uint32_t 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_t varSlot() const { - JS_ASSERT(kind() == VAR); - return u.var.slot; - } - - uint32_t varOffset() const { - JS_ASSERT(!varInitial()); - return u.var.offset; - } - - /* Accessors for phi nodes. */ - - uint32_t phiSlot() const; - uint32_t phiLength() const; - const SSAValue &phiValue(uint32_t i) const; - - /* Offset at which this phi node was created. */ - uint32_t phiOffset() const { - JS_ASSERT(kind() == PHI); - return u.phi.offset; - } - - SSAPhiNode *phiNode() const { - JS_ASSERT(kind() == PHI); - return u.phi.node; - } - - /* Other accessors. */ - -#ifdef DEBUG - void print() const; -#endif - - void clear() { - mozilla::PodZero(this); - JS_ASSERT(kind() == EMPTY); - } - - void initPushed(uint32_t offset, uint32_t index) { - clear(); - u.pushed.kind = PUSHED; - u.pushed.offset = offset; - u.pushed.index = index; - } - - static SSAValue PushedValue(uint32_t offset, uint32_t index) { - SSAValue v; - v.initPushed(offset, index); - return v; - } - - void initInitial(uint32_t slot) { - clear(); - u.var.kind = VAR; - u.var.initial = true; - u.var.slot = slot; - } - - void initWritten(uint32_t slot, uint32_t offset) { - clear(); - u.var.kind = VAR; - u.var.initial = false; - u.var.slot = slot; - u.var.offset = offset; - } - - static SSAValue WrittenVar(uint32_t slot, uint32_t offset) { - SSAValue v; - v.initWritten(slot, offset); - return v; - } - - void initPhi(uint32_t offset, SSAPhiNode *node) { - clear(); - u.phi.kind = PHI; - u.phi.offset = offset; - u.phi.node = node; - } - - static SSAValue PhiValue(uint32_t offset, SSAPhiNode *node) { - SSAValue v; - v.initPhi(offset, node); - return v; - } - - private: - union { - struct { - Kind kind : 2; - uint32_t offset : 30; - uint32_t index; - } pushed; - struct { - Kind kind : 2; - bool initial : 1; - uint32_t slot : 29; - uint32_t offset; - } var; - struct { - Kind kind : 2; - uint32_t offset : 30; - SSAPhiNode *node; - } phi; - } u; -}; - -/* - * 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. - */ -struct SSAPhiNode -{ - uint32_t slot; - uint32_t length; - SSAValue *options; - SSAUseChain *uses; - SSAPhiNode() { mozilla::PodZero(this); } -}; - -inline uint32_t -SSAValue::phiSlot() const -{ - return u.phi.node->slot; -} - -inline uint32_t -SSAValue::phiLength() const -{ - JS_ASSERT(kind() == PHI); - return u.phi.node->length; -} - -inline const SSAValue & -SSAValue::phiValue(uint32_t i) const -{ - JS_ASSERT(kind() == PHI && i < phiLength()); - return u.phi.node->options[i]; -} - -class SSAUseChain -{ - public: - bool popped : 1; - uint32_t offset : 31; - /* FIXME: Assert that only the proper arm of this union is accessed. */ - union { - uint32_t which; - SSAPhiNode *phi; - } u; - SSAUseChain *next; - - SSAUseChain() { mozilla::PodZero(this); } -}; - -class SlotValue -{ - public: - uint32_t slot; - SSAValue value; - SlotValue(uint32_t slot, const SSAValue &value) : slot(slot), value(value) {} -}; - -struct NeedsArgsObjState; - -/* Analysis information about a script. */ +// Analysis information about a script. FIXME: At this point, the entire +// purpose of this class is to compute JSScript::needsArgsObj, and to support +// isReachable() in order for jsinfer.cpp:FindPreviousInnerInitializer to get +// the previous opcode. For that purpose, it is completely overkill. class ScriptAnalysis { friend class Bytecode; @@ -675,27 +119,16 @@ class ScriptAnalysis } Bytecode* maybeCode(const jsbytecode *pc) { return maybeCode(script_->pcToOffset(pc)); } - bool jumpTarget(uint32_t offset) { - JS_ASSERT(offset < script_->length()); - return codeArray[offset] && codeArray[offset]->jumpTarget; - } - bool jumpTarget(const jsbytecode *pc) { return jumpTarget(script_->pcToOffset(pc)); } + inline bool jumpTarget(uint32_t offset); + inline bool jumpTarget(const jsbytecode *pc); inline const SSAValue &poppedValue(uint32_t offset, uint32_t which); - inline const SSAValue &poppedValue(const jsbytecode *pc, uint32_t which); - const SlotValue *newValues(uint32_t offset) { - JS_ASSERT(offset < script_->length()); - return getCode(offset).newValues; - } - const SlotValue *newValues(const jsbytecode *pc) { return newValues(script_->pcToOffset(pc)); } + inline const SlotValue *newValues(uint32_t offset); + inline const SlotValue *newValues(const jsbytecode *pc); - bool trackUseChain(const SSAValue &v) { - JS_ASSERT_IF(v.kind() == SSAValue::VAR, trackSlot(v.varSlot())); - return v.kind() != SSAValue::EMPTY && - (v.kind() != SSAValue::VAR || !v.varInitial()); - } + inline bool trackUseChain(const SSAValue &v); /* * Get the use chain for an SSA value. May be invalid for some opcodes in @@ -716,12 +149,7 @@ class ScriptAnalysis * mode, and slots which are aliased by NAME or similar opcodes in the * containing script (which does not imply the variable is closed). */ - bool slotEscapes(uint32_t slot) { - JS_ASSERT(script_->compartment()->activeAnalysis); - if (slot >= numSlots) - return true; - return escapedSlots[slot]; - } + inline bool slotEscapes(uint32_t slot); /* * Whether we distinguish different writes of this variable while doing @@ -729,23 +157,14 @@ class ScriptAnalysis * presence of NAME opcodes which could alias local variables or arguments * keeps us from tracking variable values at each point. */ - bool trackSlot(uint32_t slot) { return !slotEscapes(slot) && canTrackVars && slot < 1000; } + inline bool trackSlot(uint32_t slot); - const LifetimeVariable & liveness(uint32_t slot) { - JS_ASSERT(script_->compartment()->activeAnalysis); - JS_ASSERT(!slotEscapes(slot)); - return lifetimes[slot]; - } + inline const LifetimeVariable & liveness(uint32_t slot); void printSSA(JSContext *cx); void printTypes(JSContext *cx); - void setOOM(JSContext *cx) { - if (!outOfMemory) - js_ReportOutOfMemory(cx); - outOfMemory = true; - hadFailure = true; - } + inline void setOOM(JSContext *cx); /* Bytecode helpers */ inline bool addJump(JSContext *cx, unsigned offset, @@ -760,19 +179,6 @@ class ScriptAnalysis inline void extendVariable(JSContext *cx, LifetimeVariable &var, unsigned start, unsigned end); inline void ensureVariable(LifetimeVariable &var, unsigned until); - /* Current value for a variable or stack value, as tracked during SSA. */ - struct SSAValueInfo - { - SSAValue v; - - /* - * Sizes of branchTargets the last time this slot was written. Branches less - * than this threshold do not need to be inspected if the slot is written - * again, as they will already reflect the slot's value at the branch. - */ - int32_t branchSize; - }; - /* SSA helpers */ bool makePhi(JSContext *cx, uint32_t slot, uint32_t offset, SSAValue *pv); void insertPhi(JSContext *cx, SSAValue &phi, const SSAValue &v); @@ -799,9 +205,7 @@ class ScriptAnalysis public: #ifdef DEBUG void assertMatchingDebugMode(); - void assertMatchingStackDepthAtOffset(uint32_t offset, uint32_t stackDepth) { - JS_ASSERT_IF(maybeCode(offset), getCode(offset).stackDepth == stackDepth); - } + void assertMatchingStackDepthAtOffset(uint32_t offset, uint32_t stackDepth); #else void assertMatchingDebugMode() { } void assertMatchingStackDepthAtOffset(uint32_t offset, uint32_t stackDepth) { } @@ -815,14 +219,4 @@ void PrintBytecode(JSContext *cx, HandleScript script, jsbytecode *pc); } /* namespace analyze */ } /* namespace js */ -namespace mozilla { - -template <> struct IsPod : TrueType {}; -template <> struct IsPod : TrueType {}; -template <> struct IsPod : TrueType {}; -template <> struct IsPod : TrueType {}; -template <> struct IsPod : TrueType {}; - -} /* namespace mozilla */ - #endif /* jsanalyze_h */ diff --git a/js/src/jsanalyzeinlines.h b/js/src/jsanalyzeinlines.h deleted file mode 100644 index 2b5674fbbb5d..000000000000 --- a/js/src/jsanalyzeinlines.h +++ /dev/null @@ -1,54 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 tw=99: - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef jsanalyzeinlines_h -#define jsanalyzeinlines_h - -#include "jsanalyze.h" - -#include "jsopcodeinlines.h" - -namespace js { -namespace analyze { - -inline const SSAValue & -ScriptAnalysis::poppedValue(uint32_t offset, uint32_t which) -{ - JS_ASSERT(which < GetUseCount(script_, offset) + - (ExtendedUse(script_->offsetToPC(offset)) ? 1 : 0)); - return getCode(offset).poppedValues[which]; -} - -inline const SSAValue & -ScriptAnalysis::poppedValue(const jsbytecode *pc, uint32_t which) -{ - return poppedValue(script_->pcToOffset(pc), which); -} - -inline SSAUseChain *& -ScriptAnalysis::useChain(const SSAValue &v) -{ - JS_ASSERT(trackUseChain(v)); - if (v.kind() == SSAValue::PUSHED) - return getCode(v.pushedOffset()).pushedUses[v.pushedIndex()]; - if (v.kind() == SSAValue::VAR) - return getCode(v.varOffset()).pushedUses[GetDefCount(script_, v.varOffset())]; - return v.phiNode()->uses; -} - -inline jsbytecode * -ScriptAnalysis::getCallPC(jsbytecode *pc) -{ - SSAUseChain *uses = useChain(SSAValue::PushedValue(script_->pcToOffset(pc), 0)); - JS_ASSERT(uses && uses->popped); - JS_ASSERT(js_CodeSpec[script_->code()[uses->offset]].format & JOF_INVOKE); - return script_->offsetToPC(uses->offset); -} - -} /* namespace analyze */ -} /* namespace js */ - -#endif /* jsanalyzeinlines_h */ diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index d139b0f31d87..c95ee4c0d613 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -22,7 +22,6 @@ #include "vm/StringObject.h" #include "vm/TypedArrayObject.h" -#include "jsanalyzeinlines.h" #include "jscntxtinlines.h" namespace js { diff --git a/js/src/jsopcodeinlines.h b/js/src/jsopcodeinlines.h index 1b460d6e5c2e..27cb1ec8f2f6 100644 --- a/js/src/jsopcodeinlines.h +++ b/js/src/jsopcodeinlines.h @@ -51,6 +51,53 @@ GetUseCount(JSScript *script, unsigned offset) return js_CodeSpec[*pc].nuses; } +static inline JSOp +ReverseCompareOp(JSOp op) +{ + switch (op) { + case JSOP_GT: + return JSOP_LT; + case JSOP_GE: + return JSOP_LE; + case JSOP_LT: + return JSOP_GT; + case JSOP_LE: + return JSOP_GE; + case JSOP_EQ: + case JSOP_NE: + case JSOP_STRICTEQ: + case JSOP_STRICTNE: + return op; + default: + MOZ_ASSUME_UNREACHABLE("unrecognized op"); + } +} + +static inline JSOp +NegateCompareOp(JSOp op) +{ + switch (op) { + case JSOP_GT: + return JSOP_LE; + case JSOP_GE: + return JSOP_LT; + case JSOP_LT: + return JSOP_GE; + case JSOP_LE: + return JSOP_GT; + case JSOP_EQ: + return JSOP_NE; + case JSOP_NE: + return JSOP_EQ; + case JSOP_STRICTNE: + return JSOP_STRICTEQ; + case JSOP_STRICTEQ: + return JSOP_STRICTNE; + default: + MOZ_ASSUME_UNREACHABLE("unrecognized op"); + } +} + class BytecodeRange { public: BytecodeRange(JSContext *cx, JSScript *script)