/* 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<Node,FatBlock> theTargetBlocks = new HashMap<Node,FatBlock>(); 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; }