Efficiency improvements in ScriptAnalysis::analyzeSSA, bug 725920. r=dvander

This commit is contained in:
Brian Hackett 2012-02-23 13:01:27 -08:00
parent ef7237dbbe
commit 794ea5c208
2 changed files with 141 additions and 95 deletions

View File

@ -1218,38 +1218,39 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
* 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));
SSAValueInfo *values = (SSAValueInfo *)
cx->calloc_((numSlots + maxDepth) * sizeof(SSAValueInfo));
if (!values) {
setOOM(cx);
return;
}
struct FreeSSAValues {
JSContext *cx;
SSAValue *values;
FreeSSAValues(JSContext *cx, SSAValue *values) : cx(cx), values(values) {}
SSAValueInfo *values;
FreeSSAValues(JSContext *cx, SSAValueInfo *values) : cx(cx), values(values) {}
~FreeSSAValues() { cx->free_(values); }
} free(cx, values);
SSAValue *stack = values + numSlots;
SSAValueInfo *stack = values + numSlots;
uint32_t stackDepth = 0;
for (uint32_t slot = ArgSlot(0); slot < numSlots; slot++) {
if (trackSlot(slot))
values[slot].initInitial(slot);
values[slot].v.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.
* All target offsets for forward jumps we have seen (including ones whose
* target we have advanced past). We lazily add pending entries at these
* targets for the original value of variables modified before the branch
* rejoins.
*/
Vector<uint32_t> branchTargets(cx);
/*
* Subset of branchTargets which are also exception handlers. Any value of
* a variable modified before the target is reached is a potential value
* at that target, along with the lazily added original value.
* Subset of branchTargets which are exception handlers at future offsets.
* Any new value of a variable modified before the target is reached is a
* potential value at that target, along with the lazy original value.
*/
Vector<uint32_t> exceptionTargets(cx);
@ -1266,6 +1267,17 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
continue;
}
if (code->exceptionEntry) {
/* Remove from exception targets list, which reflects only future targets. */
for (size_t i = 0; i < exceptionTargets.length(); i++) {
if (exceptionTargets[i] == offset) {
exceptionTargets[i] = exceptionTargets.back();
exceptionTargets.popBack();
break;
}
}
}
if (code->stackDepth > stackDepth)
PodZero(stack + stackDepth, code->stackDepth - stackDepth);
stackDepth = code->stackDepth;
@ -1287,9 +1299,7 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
* loops), but in such cases the original value is pushed back.
*/
Vector<SlotValue> *&pending = code->pendingValues;
if (pending) {
removeBranchTarget(branchTargets, exceptionTargets, offset);
} else {
if (!pending) {
pending = cx->new_< Vector<SlotValue> >(cx);
if (!pending) {
setOOM(cx);
@ -1305,7 +1315,7 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
for (unsigned i = 0; i < pending->length(); i++) {
SlotValue &v = (*pending)[i];
if (v.slot < numSlots && liveness(v.slot).firstWrite(code->loop) != UINT32_MAX) {
if (v.value.kind() != SSAValue::PHI || v.value.phiOffset() < offset) {
if (v.value.kind() != SSAValue::PHI || v.value.phiOffset() != offset) {
SSAValue ov = v.value;
if (!makePhi(cx, v.slot, offset, &ov))
return;
@ -1314,9 +1324,9 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
}
}
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;
mergeValue(cx, offset, values[v.slot].v, &v);
mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets, offset - 1);
values[v.slot].v = v.value;
}
/*
@ -1331,7 +1341,7 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
continue;
if (liveness(slot).firstWrite(code->loop) == UINT32_MAX)
continue;
if (values[slot].kind() == SSAValue::PHI && values[slot].phiOffset() == offset) {
if (values[slot].v.kind() == SSAValue::PHI && values[slot].v.phiOffset() == offset) {
/* There is already a pending entry for this slot. */
continue;
}
@ -1339,9 +1349,9 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
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;
insertPhi(cx, ov, values[slot].v);
mergeBranchTarget(cx, values[slot], slot, branchTargets, offset - 1);
values[slot].v = ov;
if (!pending->append(SlotValue(slot, ov))) {
setOOM(cx);
return;
@ -1358,16 +1368,16 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
* that values written inside the block but not subsequently
* overwritten are picked up.
*/
bool exception = removeBranchTarget(branchTargets, exceptionTargets, offset);
bool exception = getCode(offset).exceptionEntry;
Vector<SlotValue> *pending = code->pendingValues;
for (unsigned i = 0; i < pending->length(); i++) {
SlotValue &v = (*pending)[i];
if (code->fallthrough || code->jumpFallthrough ||
(exception && values[v.slot].kind() != SSAValue::EMPTY)) {
mergeValue(cx, offset, values[v.slot], &v);
(exception && values[v.slot].v.kind() != SSAValue::EMPTY)) {
mergeValue(cx, offset, values[v.slot].v, &v);
}
mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets);
values[v.slot] = v.value;
mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets, offset);
values[v.slot].v = v.value;
}
freezeNewValues(cx, offset);
}
@ -1390,7 +1400,7 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
return;
}
for (unsigned i = 0; i < nuses; i++) {
SSAValue &v = stack[stackDepth - 1 - i];
SSAValue &v = stack[stackDepth - 1 - i].v;
code->poppedValues[i] = v;
v.clear();
}
@ -1401,7 +1411,7 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
*/
uint32_t slot = GetBytecodeSlot(script, pc);
if (trackSlot(slot))
code->poppedValues[nuses] = values[slot];
code->poppedValues[nuses] = values[slot].v;
else
code->poppedValues[nuses].clear();
}
@ -1430,7 +1440,7 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
stackDepth -= nuses;
for (unsigned i = 0; i < ndefs; i++)
stack[stackDepth + i].initPushed(offset, i);
stack[stackDepth + i].v.initPushed(offset, i);
unsigned xdefs = ExtendedDef(pc) ? ndefs + 1 : ndefs;
if (xdefs) {
@ -1447,9 +1457,9 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
if (BytecodeUpdatesSlot(op)) {
uint32_t slot = GetBytecodeSlot(script, pc);
if (trackSlot(slot)) {
mergeBranchTarget(cx, values[slot], slot, branchTargets);
mergeExceptionTarget(cx, values[slot], slot, exceptionTargets);
values[slot].initWritten(slot, offset);
mergeBranchTarget(cx, values[slot], slot, branchTargets, offset);
mergeExceptionTarget(cx, values[slot].v, slot, exceptionTargets);
values[slot].v.initWritten(slot, offset);
}
}
@ -1462,7 +1472,7 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
* 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];
stack[stackDepth - 1].v = code->poppedValues[0] = values[slot].v;
}
break;
}
@ -1470,34 +1480,34 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
/* Short circuit ops which push back one of their operands. */
case JSOP_MOREITER:
stack[stackDepth - 2] = code->poppedValues[0];
stack[stackDepth - 2].v = code->poppedValues[0];
break;
case JSOP_INITPROP:
case JSOP_INITMETHOD:
stack[stackDepth - 1] = code->poppedValues[1];
stack[stackDepth - 1].v = code->poppedValues[1];
break;
case JSOP_INITELEM:
stack[stackDepth - 1] = code->poppedValues[2];
stack[stackDepth - 1].v = code->poppedValues[2];
break;
case JSOP_DUP:
stack[stackDepth - 1] = stack[stackDepth - 2] = code->poppedValues[0];
stack[stackDepth - 1].v = stack[stackDepth - 2].v = code->poppedValues[0];
break;
case JSOP_DUP2:
stack[stackDepth - 1] = stack[stackDepth - 3] = code->poppedValues[0];
stack[stackDepth - 2] = stack[stackDepth - 4] = code->poppedValues[1];
stack[stackDepth - 1].v = stack[stackDepth - 3].v = code->poppedValues[0];
stack[stackDepth - 2].v = stack[stackDepth - 4].v = code->poppedValues[1];
break;
case JSOP_SWAP:
/* Swap is like pick 1. */
case JSOP_PICK: {
unsigned pickedDepth = (op == JSOP_SWAP ? 1 : pc[1]);
stack[stackDepth - 1] = code->poppedValues[pickedDepth];
stack[stackDepth - 1].v = code->poppedValues[pickedDepth];
for (unsigned i = 0; i < pickedDepth; i++)
stack[stackDepth - 2 - i] = code->poppedValues[i];
stack[stackDepth - 2 - i].v = code->poppedValues[i];
break;
}
@ -1514,14 +1524,19 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
jsint high = GET_JUMP_OFFSET(pc2);
pc2 += JUMP_OFFSET_LEN;
checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth);
Vector<SlotValue> *pending = NULL;
uint32_t pendingOffset = 0;
for (jsint i = low; i <= high; i++) {
unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2);
if (targetOffset != offset)
checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth);
checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth,
&pending, &pendingOffset);
pc2 += JUMP_OFFSET_LEN;
}
checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth,
&pending, &pendingOffset);
break;
}
@ -1531,15 +1546,20 @@ ScriptAnalysis::analyzeSSA(JSContext *cx)
unsigned npairs = GET_UINT16(pc2);
pc2 += UINT16_LEN;
checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth);
Vector<SlotValue> *pending = NULL;
uint32_t pendingOffset = 0;
while (npairs) {
pc2 += INDEX_LEN;
unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2);
checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth);
checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth,
&pending, &pendingOffset);
pc2 += JUMP_OFFSET_LEN;
npairs--;
}
checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth,
&pending, &pendingOffset);
break;
}
@ -1676,7 +1696,7 @@ ScriptAnalysis::mergeValue(JSContext *cx, uint32_t offset, const SSAValue &v, Sl
if (v == pv->value)
return;
if (pv->value.kind() != SSAValue::PHI || pv->value.phiOffset() < offset) {
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);
@ -1685,7 +1705,6 @@ ScriptAnalysis::mergeValue(JSContext *cx, uint32_t offset, const SSAValue &v, Sl
return;
}
JS_ASSERT(pv->value.phiOffset() == offset);
insertPhi(cx, pv->value, v);
}
@ -1707,7 +1726,8 @@ ScriptAnalysis::checkPendingValue(JSContext *cx, const SSAValue &v, uint32_t slo
void
ScriptAnalysis::checkBranchTarget(JSContext *cx, uint32_t targetOffset,
Vector<uint32_t> &branchTargets,
SSAValue *values, uint32_t stackDepth)
SSAValueInfo *values, uint32_t stackDepth,
Vector<SlotValue> **ppending, uint32_t *ppendingOffset)
{
unsigned targetDepth = getCode(targetOffset).stackDepth;
JS_ASSERT(targetDepth <= stackDepth);
@ -1721,14 +1741,28 @@ ScriptAnalysis::checkBranchTarget(JSContext *cx, uint32_t targetOffset,
if (pending) {
for (unsigned i = 0; i < pending->length(); i++) {
SlotValue &v = (*pending)[i];
mergeValue(cx, targetOffset, values[v.slot], &v);
mergeValue(cx, targetOffset, values[v.slot].v, &v);
}
} else {
pending = cx->new_< Vector<SlotValue> >(cx);
if (!pending || !branchTargets.append(targetOffset)) {
if (ppending && *ppending) {
JS_ASSERT(*ppendingOffset != targetOffset);
pending = *ppending;
getCode(Min(targetOffset, *ppendingOffset)).switchSharesPending = true;
} else {
pending = cx->new_< Vector<SlotValue> >(cx);
if (!pending) {
setOOM(cx);
return;
}
}
if (!branchTargets.append(targetOffset)) {
setOOM(cx);
return;
}
if (ppending) {
*ppending = pending;
*ppendingOffset = Max(targetOffset, *ppendingOffset);
}
}
/*
@ -1739,7 +1773,7 @@ ScriptAnalysis::checkBranchTarget(JSContext *cx, uint32_t targetOffset,
*/
for (unsigned i = 0; i < targetDepth; i++) {
uint32_t slot = StackSlot(script, i);
checkPendingValue(cx, values[slot], slot, pending);
checkPendingValue(cx, values[slot].v, slot, pending);
}
}
@ -1747,6 +1781,8 @@ void
ScriptAnalysis::checkExceptionTarget(JSContext *cx, uint32_t catchOffset,
Vector<uint32_t> &exceptionTargets)
{
JS_ASSERT(getCode(catchOffset).exceptionEntry);
/*
* The catch offset will already be in the branch targets, just check
* whether this is already a known exception target.
@ -1760,8 +1796,8 @@ ScriptAnalysis::checkExceptionTarget(JSContext *cx, uint32_t catchOffset,
}
void
ScriptAnalysis::mergeBranchTarget(JSContext *cx, const SSAValue &value, uint32_t slot,
const Vector<uint32_t> &branchTargets)
ScriptAnalysis::mergeBranchTarget(JSContext *cx, SSAValueInfo &value, uint32_t slot,
const Vector<uint32_t> &branchTargets, uint32_t currentOffset)
{
if (slot >= numSlots) {
/*
@ -1777,11 +1813,27 @@ ScriptAnalysis::mergeBranchTarget(JSContext *cx, const SSAValue &value, uint32_t
/*
* 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.
* Only look at new branch targets which have appeared since the last time
* the variable was written.
*/
for (unsigned i = 0; i < branchTargets.length(); i++) {
Vector<SlotValue> *pending = getCode(branchTargets[i]).pendingValues;
checkPendingValue(cx, value, slot, pending);
for (int i = branchTargets.length() - 1; i >= value.branchSize; i--) {
if (branchTargets[i] <= currentOffset)
continue;
const Bytecode &code = getCode(branchTargets[i]);
/*
* If the pending array for this offset is shared with a later branch
* target, it will be updated when that offset is handled.
*/
if (code.switchSharesPending)
continue;
Vector<SlotValue> *pending = code.pendingValues;
checkPendingValue(cx, value.v, slot, pending);
}
value.branchSize = branchTargets.length();
}
void
@ -1818,7 +1870,7 @@ ScriptAnalysis::mergeExceptionTarget(JSContext *cx, const SSAValue &value, uint3
}
void
ScriptAnalysis::mergeAllExceptionTargets(JSContext *cx, SSAValue *values,
ScriptAnalysis::mergeAllExceptionTargets(JSContext *cx, SSAValueInfo *values,
const Vector<uint32_t> &exceptionTargets)
{
for (unsigned i = 0; i < exceptionTargets.length(); i++) {
@ -1826,36 +1878,11 @@ ScriptAnalysis::mergeAllExceptionTargets(JSContext *cx, SSAValue *values,
for (unsigned i = 0; i < pending->length(); i++) {
const SlotValue &v = (*pending)[i];
if (trackSlot(v.slot))
mergeExceptionTarget(cx, values[v.slot], v.slot, exceptionTargets);
mergeExceptionTarget(cx, values[v.slot].v, v.slot, exceptionTargets);
}
}
}
bool
ScriptAnalysis::removeBranchTarget(Vector<uint32_t> &branchTargets,
Vector<uint32_t> &exceptionTargets,
uint32_t offset)
{
bool exception = false;
for (unsigned i = 0; i < exceptionTargets.length(); i++) {
if (exceptionTargets[i] == offset) {
exceptionTargets[i] = branchTargets.back();
exceptionTargets.popBack();
exception = true;
break;
}
}
for (unsigned i = 0; i < branchTargets.length(); i++) {
if (branchTargets[i] == offset) {
branchTargets[i] = branchTargets.back();
branchTargets.popBack();
return exception;
}
}
JS_ASSERT(OOM());
return exception;
}
void
ScriptAnalysis::freezeNewValues(JSContext *cx, uint32_t offset)
{
@ -1866,7 +1893,8 @@ ScriptAnalysis::freezeNewValues(JSContext *cx, uint32_t offset)
unsigned count = pending->length();
if (count == 0) {
cx->delete_(pending);
if (!code.switchSharesPending)
cx->delete_(pending);
return;
}
@ -1881,7 +1909,8 @@ ScriptAnalysis::freezeNewValues(JSContext *cx, uint32_t offset)
code.newValues[count].slot = 0;
code.newValues[count].value.clear();
cx->delete_(pending);
if (!code.switchSharesPending)
cx->delete_(pending);
}
CrossSSAValue

View File

@ -142,6 +142,12 @@ class Bytecode
bool getStringElement:1; /* GETELEM which has accessed string properties. */
bool accessGetter: 1; /* Property read on a shape with a getter hook. */
/*
* Switch target other than the last one, which shares its pending values
* with a later offset during SSA analysis.
*/
bool switchSharesPending : 1;
/* Stack depth before this opcode. */
uint32_t stackDepth;
@ -1190,6 +1196,19 @@ 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);
@ -1197,18 +1216,16 @@ class ScriptAnalysis
void checkPendingValue(JSContext *cx, const SSAValue &v, uint32_t slot,
Vector<SlotValue> *pending);
void checkBranchTarget(JSContext *cx, uint32_t targetOffset, Vector<uint32_t> &branchTargets,
SSAValue *values, uint32_t stackDepth);
SSAValueInfo *values, uint32_t stackDepth,
Vector<SlotValue> **ppending = NULL, uint32_t *ppendingOffset = NULL);
void checkExceptionTarget(JSContext *cx, uint32_t catchOffset,
Vector<uint32_t> &exceptionTargets);
void mergeBranchTarget(JSContext *cx, const SSAValue &value, uint32_t slot,
const Vector<uint32_t> &branchTargets);
void mergeBranchTarget(JSContext *cx, SSAValueInfo &value, uint32_t slot,
const Vector<uint32_t> &branchTargets, uint32_t currentOffset);
void mergeExceptionTarget(JSContext *cx, const SSAValue &value, uint32_t slot,
const Vector<uint32_t> &exceptionTargets);
void mergeAllExceptionTargets(JSContext *cx, SSAValue *values,
void mergeAllExceptionTargets(JSContext *cx, SSAValueInfo *values,
const Vector<uint32_t> &exceptionTargets);
bool removeBranchTarget(Vector<uint32_t> &branchTargets,
Vector<uint32_t> &exceptionTargets,
uint32_t offset);
void freezeNewValues(JSContext *cx, uint32_t offset);
struct TypeInferenceState {