org.mozilla.javascript.optimizer.Block Maven / Gradle / Ivy
The newest version!
/* 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/. */
package org.mozilla.javascript.optimizer;
import org.mozilla.javascript.*;
import org.mozilla.javascript.ast.Jump;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;
import java.io.PrintWriter;
import java.io.StringWriter;
class Block
{
private static class FatBlock
{
private static Block[] reduceToArray(ObjToIntMap map)
{
Block[] result = null;
if (!map.isEmpty()) {
result = new Block[map.size()];
int i = 0;
ObjToIntMap.Iterator iter = map.newIterator();
for (iter.start(); !iter.done(); iter.next()) {
FatBlock fb = (FatBlock)(iter.getKey());
result[i++] = fb.realBlock;
}
}
return result;
}
void addSuccessor(FatBlock b) { successors.put(b, 0); }
void addPredecessor(FatBlock b) { predecessors.put(b, 0); }
Block[] getSuccessors() { return reduceToArray(successors); }
Block[] getPredecessors() { return reduceToArray(predecessors); }
// all the Blocks that come immediately after this
private ObjToIntMap successors = new ObjToIntMap();
// all the Blocks that come immediately before this
private ObjToIntMap predecessors = new ObjToIntMap();
Block realBlock;
}
Block(int startNodeIndex, int endNodeIndex)
{
itsStartNodeIndex = startNodeIndex;
itsEndNodeIndex = endNodeIndex;
}
static void runFlowAnalyzes(OptFunctionNode fn, Node[] statementNodes)
{
int paramCount = fn.fnode.getParamCount();
int varCount = fn.fnode.getParamAndVarCount();
int[] varTypes = new int[varCount];
// If the variable is a parameter, it could have any type.
for (int i = 0; i != paramCount; ++i) {
varTypes[i] = Optimizer.AnyType;
}
// If the variable is from a "var" statement, its typeEvent will be set
// when we see the setVar node.
for (int i = paramCount; i != varCount; ++i) {
varTypes[i] = Optimizer.NoType;
}
Block[] theBlocks = buildBlocks(statementNodes);
if (DEBUG) {
++debug_blockCount;
System.out.println("-------------------"+fn.fnode.getFunctionName()+" "+debug_blockCount+"--------");
System.out.println(fn.fnode.toStringTree(fn.fnode));
System.out.println(toString(theBlocks, statementNodes));
}
reachingDefDataFlow(fn, statementNodes, theBlocks, varTypes);
typeFlow(fn, statementNodes, theBlocks, varTypes);
if (DEBUG) {
for (int i = 0; i < theBlocks.length; i++) {
System.out.println("For block " + theBlocks[i].itsBlockID);
theBlocks[i].printLiveOnEntrySet(fn);
}
System.out.println("Variable Table, size = " + varCount);
for (int i = 0; i != varCount; i++) {
System.out.println("["+i+"] type: "+varTypes[i]);
}
}
for (int i = paramCount; i != varCount; i++) {
if (varTypes[i] == Optimizer.NumberType) {
fn.setIsNumberVar(i);
}
}
}
private static Block[] buildBlocks(Node[] statementNodes)
{
// a mapping from each target node to the block it begins
Map theTargetBlocks = new HashMap();
ObjArray theBlocks = new ObjArray();
// there's a block that starts at index 0
int beginNodeIndex = 0;
for (int i = 0; i < statementNodes.length; i++) {
switch (statementNodes[i].getType()) {
case Token.TARGET :
{
if (i != beginNodeIndex) {
FatBlock fb = newFatBlock(beginNodeIndex, i - 1);
if (statementNodes[beginNodeIndex].getType() == Token.TARGET) {
theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
}
theBlocks.add(fb);
// start the next block at this node
beginNodeIndex = i;
}
}
break;
case Token.IFNE :
case Token.IFEQ :
case Token.GOTO :
{
FatBlock fb = newFatBlock(beginNodeIndex, i);
if (statementNodes[beginNodeIndex].getType() == Token.TARGET) {
theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
}
theBlocks.add(fb);
// start the next block at the next node
beginNodeIndex = i + 1;
}
break;
}
}
if (beginNodeIndex != statementNodes.length) {
FatBlock fb = newFatBlock(beginNodeIndex, statementNodes.length - 1);
if (statementNodes[beginNodeIndex].getType() == Token.TARGET) {
theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
}
theBlocks.add(fb);
}
// build successor and predecessor links
for (int i = 0; i < theBlocks.size(); i++) {
FatBlock fb = (FatBlock)(theBlocks.get(i));
Node blockEndNode = statementNodes[fb.realBlock.itsEndNodeIndex];
int blockEndNodeType = blockEndNode.getType();
if ((blockEndNodeType != Token.GOTO) && (i < (theBlocks.size() - 1))) {
FatBlock fallThruTarget = (FatBlock)(theBlocks.get(i + 1));
fb.addSuccessor(fallThruTarget);
fallThruTarget.addPredecessor(fb);
}
if ( (blockEndNodeType == Token.IFNE)
|| (blockEndNodeType == Token.IFEQ)
|| (blockEndNodeType == Token.GOTO) ) {
Node target = ((Jump)blockEndNode).target;
FatBlock branchTargetBlock = theTargetBlocks.get(target);
target.putProp(Node.TARGETBLOCK_PROP, branchTargetBlock.realBlock);
fb.addSuccessor(branchTargetBlock);
branchTargetBlock.addPredecessor(fb);
}
}
Block[] result = new Block[theBlocks.size()];
for (int i = 0; i < theBlocks.size(); i++) {
FatBlock fb = (FatBlock)(theBlocks.get(i));
Block b = fb.realBlock;
b.itsSuccessors = fb.getSuccessors();
b.itsPredecessors = fb.getPredecessors();
b.itsBlockID = i;
result[i] = b;
}
return result;
}
private static FatBlock newFatBlock(int startNodeIndex, int endNodeIndex)
{
FatBlock fb = new FatBlock();
fb.realBlock = new Block(startNodeIndex, endNodeIndex);
return fb;
}
private static String toString(Block[] blockList, Node[] statementNodes)
{
if (!DEBUG) return null;
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println(blockList.length + " Blocks");
for (int i = 0; i < blockList.length; i++) {
Block b = blockList[i];
pw.println("#" + b.itsBlockID);
pw.println("from " + b.itsStartNodeIndex
+ " "
+ statementNodes[b.itsStartNodeIndex].toString());
pw.println("thru " + b.itsEndNodeIndex
+ " "
+ statementNodes[b.itsEndNodeIndex].toString());
pw.print("Predecessors ");
if (b.itsPredecessors != null) {
for (int j = 0; j < b.itsPredecessors.length; j++) {
pw.print(b.itsPredecessors[j].itsBlockID + " ");
}
pw.println();
} else {
pw.println("none");
}
pw.print("Successors ");
if (b.itsSuccessors != null) {
for (int j = 0; j < b.itsSuccessors.length; j++) {
pw.print(b.itsSuccessors[j].itsBlockID + " ");
}
pw.println();
} else {
pw.println("none");
}
}
return sw.toString();
}
private static void reachingDefDataFlow(OptFunctionNode fn, Node[] statementNodes,
Block theBlocks[], int[] varTypes)
{
/*
initialize the liveOnEntry and liveOnExit sets, then discover the variables
that are def'd by each function, and those that are used before being def'd
(hence liveOnEntry)
*/
for (int i = 0; i < theBlocks.length; i++) {
theBlocks[i].initLiveOnEntrySets(fn, statementNodes);
}
/*
this visits every block starting at the last, re-adding the predecessors of
any block whose inputs change as a result of the dataflow.
REMIND, better would be to visit in CFG postorder
*/
boolean visit[] = new boolean[theBlocks.length];
boolean doneOnce[] = new boolean[theBlocks.length];
int vIndex = theBlocks.length - 1;
boolean needRescan = false;
visit[vIndex] = true;
while (true) {
if (visit[vIndex] || !doneOnce[vIndex]) {
doneOnce[vIndex] = true;
visit[vIndex] = false;
if (theBlocks[vIndex].doReachedUseDataFlow()) {
Block pred[] = theBlocks[vIndex].itsPredecessors;
if (pred != null) {
for (int i = 0; i < pred.length; i++) {
int index = pred[i].itsBlockID;
visit[index] = true;
needRescan |= (index > vIndex);
}
}
}
}
if (vIndex == 0) {
if (needRescan) {
vIndex = theBlocks.length - 1;
needRescan = false;
} else {
break;
}
} else {
vIndex--;
}
}
/*
if any variable is live on entry to block 0, we have to mark it as
not jRegable - since it means that someone is trying to access the
'undefined'-ness of that variable.
*/
theBlocks[0].markAnyTypeVariables(varTypes);
}
private static void typeFlow(OptFunctionNode fn, Node[] statementNodes,
Block theBlocks[], int[] varTypes)
{
boolean visit[] = new boolean[theBlocks.length];
boolean doneOnce[] = new boolean[theBlocks.length];
int vIndex = 0;
boolean needRescan = false;
visit[vIndex] = true;
while (true) {
if (visit[vIndex] || !doneOnce[vIndex]) {
doneOnce[vIndex] = true;
visit[vIndex] = false;
if (theBlocks[vIndex].doTypeFlow(fn, statementNodes, varTypes))
{
Block succ[] = theBlocks[vIndex].itsSuccessors;
if (succ != null) {
for (int i = 0; i < succ.length; i++) {
int index = succ[i].itsBlockID;
visit[index] = true;
needRescan |= (index < vIndex);
}
}
}
}
if (vIndex == (theBlocks.length - 1)) {
if (needRescan) {
vIndex = 0;
needRescan = false;
} else {
break;
}
} else {
vIndex++;
}
}
}
private static boolean assignType(int[] varTypes, int index, int type)
{
int prev = varTypes[index];
return prev != (varTypes[index] |= type);
}
private void markAnyTypeVariables(int[] varTypes)
{
for (int i = 0; i != varTypes.length; i++) {
if (itsLiveOnEntrySet.get(i)) {
assignType(varTypes, i, Optimizer.AnyType);
}
}
}
/*
We're tracking uses and defs - in order to
build the def set and to identify the last use
nodes.
The itsNotDefSet is built reversed then flipped later.
*/
private void lookForVariableAccess(OptFunctionNode fn, Node n)
{
switch (n.getType()) {
case Token.TYPEOFNAME:
{
// TYPEOFNAME may be used with undefined names, which is why
// this is handled separately from GETVAR above.
int varIndex = fn.fnode.getIndexForNameNode(n);
if (varIndex > -1 && !itsNotDefSet.get(varIndex))
itsUseBeforeDefSet.set(varIndex);
}
break;
case Token.DEC :
case Token.INC :
{
Node child = n.getFirstChild();
if (child.getType() == Token.GETVAR) {
int varIndex = fn.getVarIndex(child);
if (!itsNotDefSet.get(varIndex))
itsUseBeforeDefSet.set(varIndex);
itsNotDefSet.set(varIndex);
} else {
lookForVariableAccess(fn, child);
}
}
break;
case Token.SETVAR :
case Token.SETCONSTVAR :
{
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
lookForVariableAccess(fn, rhs);
itsNotDefSet.set(fn.getVarIndex(n));
}
break;
case Token.GETVAR :
{
int varIndex = fn.getVarIndex(n);
if (!itsNotDefSet.get(varIndex))
itsUseBeforeDefSet.set(varIndex);
}
break;
default :
Node child = n.getFirstChild();
while (child != null) {
lookForVariableAccess(fn, child);
child = child.getNext();
}
break;
}
}
/*
build the live on entry/exit sets.
Then walk the trees looking for defs/uses of variables
and build the def and useBeforeDef sets.
*/
private void initLiveOnEntrySets(OptFunctionNode fn, Node[] statementNodes)
{
int listLength = fn.getVarCount();
itsUseBeforeDefSet = new BitSet(listLength);
itsNotDefSet = new BitSet(listLength);
itsLiveOnEntrySet = new BitSet(listLength);
itsLiveOnExitSet = new BitSet(listLength);
for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) {
Node n = statementNodes[i];
lookForVariableAccess(fn, n);
}
itsNotDefSet.flip(0, listLength); // truth in advertising
}
/*
the liveOnEntry of each successor is the liveOnExit for this block.
The liveOnEntry for this block is -
liveOnEntry = liveOnExit - defsInThisBlock + useBeforeDefsInThisBlock
*/
private boolean doReachedUseDataFlow()
{
itsLiveOnExitSet.clear();
if (itsSuccessors != null) {
for (int i = 0; i < itsSuccessors.length; i++) {
itsLiveOnExitSet.or(itsSuccessors[i].itsLiveOnEntrySet);
}
}
return updateEntrySet(itsLiveOnEntrySet, itsLiveOnExitSet,
itsUseBeforeDefSet, itsNotDefSet);
}
private boolean updateEntrySet(BitSet entrySet, BitSet exitSet,
BitSet useBeforeDef, BitSet notDef) {
int card = entrySet.cardinality();
entrySet.or(exitSet);
entrySet.and(notDef);
entrySet.or(useBeforeDef);
return entrySet.cardinality() != card;
}
/*
the type of an expression is relatively unknown. Cases we can be sure
about are -
Literals,
Arithmetic operations - always return a Number
*/
private static int findExpressionType(OptFunctionNode fn, Node n,
int[] varTypes)
{
switch (n.getType()) {
case Token.NUMBER:
return Optimizer.NumberType;
case Token.CALL:
case Token.NEW:
case Token.REF_CALL:
return Optimizer.AnyType;
case Token.GETELEM:
case Token.GETPROP:
case Token.NAME:
case Token.THIS:
return Optimizer.AnyType;
case Token.GETVAR:
return varTypes[fn.getVarIndex(n)];
case Token.INC:
case Token.DEC:
case Token.MUL:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.BITNOT:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.POS:
case Token.NEG:
return Optimizer.NumberType;
case Token.VOID:
// NYI: undefined type
return Optimizer.AnyType;
case Token.FALSE:
case Token.TRUE:
case Token.EQ:
case Token.NE:
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
case Token.SHEQ:
case Token.SHNE:
case Token.NOT:
case Token.INSTANCEOF:
case Token.IN:
case Token.DEL_REF:
case Token.DELPROP:
// NYI: boolean type
return Optimizer.AnyType;
case Token.STRING:
case Token.TYPEOF:
case Token.TYPEOFNAME:
// NYI: string type
return Optimizer.AnyType;
case Token.NULL:
case Token.REGEXP:
case Token.ARRAYCOMP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
return Optimizer.AnyType; // XXX: actually, we know it's not
// number, but no type yet for that
case Token.ADD: {
// if the lhs & rhs are known to be numbers, we can be sure that's
// the result, otherwise it could be a string.
Node child = n.getFirstChild();
int lType = findExpressionType(fn, child, varTypes);
int rType = findExpressionType(fn, child.getNext(), varTypes);
return lType | rType; // we're not distinguishing strings yet
}
case Token.HOOK: {
Node ifTrue = n.getFirstChild().getNext();
Node ifFalse = ifTrue.getNext();
int ifTrueType = findExpressionType(fn, ifTrue, varTypes);
int ifFalseType = findExpressionType(fn, ifFalse, varTypes);
return ifTrueType | ifFalseType;
}
case Token.COMMA:
case Token.SETVAR:
case Token.SETCONSTVAR:
case Token.SETNAME:
case Token.SETPROP:
case Token.SETELEM:
return findExpressionType(fn, n.getLastChild(), varTypes);
case Token.AND:
case Token.OR: {
Node child = n.getFirstChild();
int lType = findExpressionType(fn, child, varTypes);
int rType = findExpressionType(fn, child.getNext(), varTypes);
return lType | rType;
}
}
return Optimizer.AnyType;
}
private static boolean findDefPoints(OptFunctionNode fn, Node n,
int[] varTypes)
{
boolean result = false;
Node first = n.getFirstChild();
for (Node next = first; next != null; next = next.getNext()) {
result |= findDefPoints(fn, next, varTypes);
}
switch (n.getType()) {
case Token.DEC :
case Token.INC :
if (first.getType() == Token.GETVAR) {
// theVar is a Number now
int i = fn.getVarIndex(first);
if (!fn.fnode.getParamAndVarConst()[i]) {
result |= assignType(varTypes, i, Optimizer.NumberType);
}
}
break;
case Token.SETVAR :
case Token.SETCONSTVAR : {
Node rValue = first.getNext();
int theType = findExpressionType(fn, rValue, varTypes);
int i = fn.getVarIndex(n);
if (!(n.getType() == Token.SETVAR
&& fn.fnode.getParamAndVarConst()[i])) {
result |= assignType(varTypes, i, theType);
}
break;
}
}
return result;
}
private boolean doTypeFlow(OptFunctionNode fn, Node[] statementNodes,
int[] varTypes)
{
boolean changed = false;
for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) {
Node n = statementNodes[i];
if (n != null) {
changed |= findDefPoints(fn, n, varTypes);
}
}
return changed;
}
private void printLiveOnEntrySet(OptFunctionNode fn)
{
if (DEBUG) {
for (int i = 0; i < fn.getVarCount(); i++) {
String name = fn.fnode.getParamOrVarName(i);
if (itsUseBeforeDefSet.get(i))
System.out.println(name + " is used before def'd");
if (itsNotDefSet.get(i))
System.out.println(name + " is not def'd");
if (itsLiveOnEntrySet.get(i))
System.out.println(name + " is live on entry");
if (itsLiveOnExitSet.get(i))
System.out.println(name + " is live on exit");
}
}
}
// all the Blocks that come immediately after this
private Block[] itsSuccessors;
// all the Blocks that come immediately before this
private Block[] itsPredecessors;
private int itsStartNodeIndex; // the Node at the start of the block
private int itsEndNodeIndex; // the Node at the end of the block
private int itsBlockID; // a unique index for each block
// reaching def bit sets -
private BitSet itsLiveOnEntrySet;
private BitSet itsLiveOnExitSet;
private BitSet itsUseBeforeDefSet;
private BitSet itsNotDefSet;
static final boolean DEBUG = false;
private static int debug_blockCount;
}