From 55a4a7f1c533636771f0c5de729c83898ec77c9d Mon Sep 17 00:00:00 2001 From: "norris%netscape.com" Date: Mon, 27 Sep 1999 23:17:29 +0000 Subject: [PATCH] Changes for classfile generation. git-svn-id: svn://10.0.0.236/trunk@49158 18797224-902f-48f8-a5cc-f745e15eee43 --- .../rhino/org/mozilla/javascript/Context.java | 4 +- .../debug/util/GenerateDebugSwitch.java | 2 +- .../mozilla/javascript/optimizer/Block.java | 688 +++ .../mozilla/javascript/optimizer/Codegen.java | 4172 +++++++++++++++++ .../javascript/optimizer/DataFlowBitSet.java | 130 + .../javascript/optimizer/FatBlock.java | 92 + .../optimizer/JavaScriptClassLoader.java | 71 + .../optimizer/OptClassNameHelper.java | 142 + .../javascript/optimizer/OptFunctionNode.java | 128 + .../javascript/optimizer/OptIRFactory.java | 69 + .../optimizer/OptLocalVariable.java | 93 + .../javascript/optimizer/OptRuntime.java | 311 ++ .../javascript/optimizer/OptTransformer.java | 190 + .../optimizer/OptVariableTable.java | 74 + .../javascript/optimizer/Optimizer.java | 1080 +++++ .../optimizer/StmtNodeIterator.java | 94 + .../javascript/optimizer/TypeEvent.java | 67 + .../mozilla/javascript/tools/jsc/Main.java | 293 ++ .../mozilla/javascript/tools/shell/Main.java | 2 +- .../src/org/mozilla/javascript/Context.java | 4 +- .../mozilla/javascript/optimizer/Block.java | 688 +++ .../mozilla/javascript/optimizer/Codegen.java | 4172 +++++++++++++++++ .../javascript/optimizer/DataFlowBitSet.java | 130 + .../javascript/optimizer/FatBlock.java | 92 + .../optimizer/JavaScriptClassLoader.java | 71 + .../optimizer/OptClassNameHelper.java | 142 + .../javascript/optimizer/OptFunctionNode.java | 128 + .../javascript/optimizer/OptIRFactory.java | 69 + .../optimizer/OptLocalVariable.java | 93 + .../javascript/optimizer/OptRuntime.java | 311 ++ .../javascript/optimizer/OptTransformer.java | 190 + .../optimizer/OptVariableTable.java | 74 + .../javascript/optimizer/Optimizer.java | 1080 +++++ .../optimizer/StmtNodeIterator.java | 94 + .../javascript/optimizer/TypeEvent.java | 67 + .../mozilla/javascript/tools/jsc/Main.java | 293 ++ .../mozilla/javascript/tools/shell/Main.java | 2 +- 37 files changed, 15395 insertions(+), 7 deletions(-) create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/Block.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/Codegen.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/DataFlowBitSet.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/FatBlock.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/JavaScriptClassLoader.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/OptClassNameHelper.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/OptFunctionNode.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/OptIRFactory.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/OptLocalVariable.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/OptRuntime.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/OptTransformer.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/OptVariableTable.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/Optimizer.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/StmtNodeIterator.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/optimizer/TypeEvent.java create mode 100644 mozilla/js/rhino/org/mozilla/javascript/tools/jsc/Main.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Block.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Codegen.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/FatBlock.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/JavaScriptClassLoader.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptClassNameHelper.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptFunctionNode.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptIRFactory.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptLocalVariable.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptRuntime.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptTransformer.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptVariableTable.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Optimizer.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/StmtNodeIterator.java create mode 100644 mozilla/js/rhino/src/org/mozilla/javascript/optimizer/TypeEvent.java create mode 100644 mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/jsc/Main.java diff --git a/mozilla/js/rhino/org/mozilla/javascript/Context.java b/mozilla/js/rhino/org/mozilla/javascript/Context.java index 4b41a602f7a..2a1f45a0457 100644 --- a/mozilla/js/rhino/org/mozilla/javascript/Context.java +++ b/mozilla/js/rhino/org/mozilla/javascript/Context.java @@ -1666,9 +1666,9 @@ public final class Context { static { try { codegenClass = Class.forName( - "com.netscape.javascript.optimizer.Codegen"); + "org.mozilla.javascript.optimizer.Codegen"); Class nameHelperClass = Class.forName( - "com.netscape.javascript.optimizer.OptClassNameHelper"); + "org.mozilla.javascript.optimizer.OptClassNameHelper"); nameHelper = (ClassNameHelper)nameHelperClass.newInstance(); } catch (ClassNotFoundException x) { // ...must be running lite, that's ok diff --git a/mozilla/js/rhino/org/mozilla/javascript/debug/util/GenerateDebugSwitch.java b/mozilla/js/rhino/org/mozilla/javascript/debug/util/GenerateDebugSwitch.java index 253ee3becbb..1ff31492835 100644 --- a/mozilla/js/rhino/org/mozilla/javascript/debug/util/GenerateDebugSwitch.java +++ b/mozilla/js/rhino/org/mozilla/javascript/debug/util/GenerateDebugSwitch.java @@ -22,7 +22,7 @@ * used by other classes to turn debug code on or off (at compile time) */ -public class GenerateDebugSwitchSourceFile +public class GenerateDebugSwitch { public static void main(String[] args) { diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/Block.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/Block.java new file mode 100644 index 00000000000..af4308dcf72 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/Block.java @@ -0,0 +1,688 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import org.mozilla.classfile.*; + +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class Block { + + public Block(int startNodeIndex, int endNodeIndex, Node[] statementNodes) + { + itsStartNodeIndex = startNodeIndex; + itsEndNodeIndex = endNodeIndex; + itsStatementNodes = statementNodes; + } + + public void setBlockID(int id) { itsBlockID = id; } + public int getBlockID() { return itsBlockID; } + public Node getStartNode() { return itsStatementNodes[itsStartNodeIndex]; } + public Node getEndNode() { return itsStatementNodes[itsEndNodeIndex]; } + + public Block[] getPredecessorList() { return itsPredecessors; } + public Block[] getSuccessorList() { return itsSuccessors; } + + public static Block[] buildBlocks(Node[] statementNodes) + { + // a mapping from each target node to the block it begins + Hashtable theTargetBlocks = new Hashtable(); + Vector theBlocks = new Vector(); + + // 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 TokenStream.TARGET : + { + if (i != beginNodeIndex) { + FatBlock fb = new FatBlock(beginNodeIndex, + i - 1, statementNodes); + if (statementNodes[beginNodeIndex].getType() + == TokenStream.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.addElement(fb); + // start the next block at this node + beginNodeIndex = i; + } + } + break; + case TokenStream.IFNE : + case TokenStream.IFEQ : + case TokenStream.GOTO : + { + FatBlock fb = new FatBlock(beginNodeIndex, + i, statementNodes); + if (statementNodes[beginNodeIndex].getType() + == TokenStream.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.addElement(fb); + // start the next block at the next node + beginNodeIndex = i + 1; + } + break; + } + } + + if ((beginNodeIndex != statementNodes.length)) { + FatBlock fb = new FatBlock(beginNodeIndex, + statementNodes.length - 1, + statementNodes); + if (statementNodes[beginNodeIndex].getType() == TokenStream.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.addElement(fb); + } + + // build successor and predecessor links + + for (int i = 0; i < theBlocks.size(); i++) { + FatBlock fb = (FatBlock)(theBlocks.elementAt(i)); + + Node blockEndNode = fb.getEndNode(); + int blockEndNodeType = blockEndNode.getType(); + + if ((blockEndNodeType != TokenStream.GOTO) + && (i < (theBlocks.size() - 1))) { + FatBlock fallThruTarget = (FatBlock)(theBlocks.elementAt(i + 1)); + fb.addSuccessor(fallThruTarget); + fallThruTarget.addPredecessor(fb); + } + + + if ( (blockEndNodeType == TokenStream.IFNE) + || (blockEndNodeType == TokenStream.IFEQ) + || (blockEndNodeType == TokenStream.GOTO) ) { + Node target = (Node)(blockEndNode.getProp(Node.TARGET_PROP)); + FatBlock branchTargetBlock + = (FatBlock)(theTargetBlocks.get(target)); + target.putProp(Node.TARGETBLOCK_PROP, + branchTargetBlock.getSlimmerSelf()); + fb.addSuccessor(branchTargetBlock); + branchTargetBlock.addPredecessor(fb); + } + } + + Block[] result = new Block[theBlocks.size()]; + + for (int i = 0; i < theBlocks.size(); i++) { + FatBlock fb = (FatBlock)(theBlocks.elementAt(i)); + result[i] = fb.diet(); + result[i].setBlockID(i); + } + + return result; + } + + public static String toString(Block[] blockList, Node[] statementNodes) + { + 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].getBlockID() + " "); + 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].getBlockID() + " "); + pw.println(); + } + else + pw.println("none"); + } + return sw.toString(); + } + + /* + We maintain the liveSet as each statement executes, identifying + those variables that are live across function calls + + */ + void lookForVariablesAndCalls(Node n, boolean liveSet[], + VariableTable theVariables) + { + switch (n.getType()) { + case TokenStream.SETVAR : + { + Node lhs = n.getFirstChild(); + Node rhs = lhs.getNextSibling(); + lookForVariablesAndCalls(rhs, liveSet, theVariables); + Object theVarProp = n.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + liveSet[theVarIndex] = true; + } + } + break; + case TokenStream.CALL : { + Node child = n.getFirstChild(); + while (child != null) { + lookForVariablesAndCalls(child, liveSet, theVariables); + child = child.getNextSibling(); + } + for (int i = 0; i < liveSet.length; i++) { + if (liveSet[i]) + ((OptLocalVariable)theVariables.get(i)).markLiveAcrossCall(); + } + } + break; + case TokenStream.GETVAR : + { + Object theVarProp = n.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + if ((n.getProp(Node.LASTUSE_PROP) != null) + && !itsLiveOnExitSet.test(theVarIndex)) + liveSet[theVarIndex] = false; + } + } + break; + default : + Node child = n.getFirstChild(); + while (child != null) { + lookForVariablesAndCalls(child, liveSet, theVariables); + child = child.getNextSibling(); + } + break; + } + } + + void markVolatileVariables(VariableTable theVariables) + { + boolean liveSet[] = new boolean[theVariables.size()]; + for (int i = 0; i < liveSet.length; i++) + liveSet[i] = itsLiveOnEntrySet.test(i); + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + lookForVariablesAndCalls(n, liveSet, theVariables); + } + } + + /* + 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. + + */ + void lookForVariableAccess(Node n, Node lastUse[]) + { + switch (n.getType()) { + case TokenStream.DEC : + case TokenStream.INC : + { + Node child = n.getFirstChild(); + if (child.getType() == TokenStream.GETVAR) { + Object theVarProp = child.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + if (!itsNotDefSet.test(theVarIndex)) + itsUseBeforeDefSet.set(theVarIndex); + itsNotDefSet.set(theVarIndex); + } + } + } + break; + case TokenStream.SETVAR : + { + Node lhs = n.getFirstChild(); + Node rhs = lhs.getNextSibling(); + lookForVariableAccess(rhs, lastUse); + Object theVarProp = n.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + itsNotDefSet.set(theVarIndex); + if (lastUse[theVarIndex] != null) + lastUse[theVarIndex].putProp(Node.LASTUSE_PROP, + theVarProp); + } + } + break; + case TokenStream.GETVAR : + { + Object theVarProp = n.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + if (!itsNotDefSet.test(theVarIndex)) + itsUseBeforeDefSet.set(theVarIndex); + lastUse[theVarIndex] = n; + } + } + break; + default : + Node child = n.getFirstChild(); + while (child != null) { + lookForVariableAccess(child, lastUse); + child = child.getNextSibling(); + } + 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. + */ + public void initLiveOnEntrySets(VariableTable theVariables) + { + int listLength = theVariables.size(); + Node lastUse[] = new Node[listLength]; + itsUseBeforeDefSet = new DataFlowBitSet(listLength); + itsNotDefSet = new DataFlowBitSet(listLength); + itsLiveOnEntrySet = new DataFlowBitSet(listLength); + itsLiveOnExitSet = new DataFlowBitSet(listLength); + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + lookForVariableAccess(n, lastUse); + } + for (int i = 0; i < listLength; i++) { + if (lastUse[i] != null) + lastUse[i].putProp(Node.LASTUSE_PROP, this); + } + itsNotDefSet.not(); // truth in advertising + } + + /* + the liveOnEntry of each successor is the liveOnExit for this block. + The liveOnEntry for this block is - + liveOnEntry = liveOnExit - defsInThisBlock + useBeforeDefsInThisBlock + + */ + boolean doReachedUseDataFlow() + { + itsLiveOnExitSet.clear(); + if (itsSuccessors != null) + for (int i = 0; i < itsSuccessors.length; i++) + itsLiveOnExitSet.or(itsSuccessors[i].itsLiveOnEntrySet); + return itsLiveOnEntrySet.df2(itsLiveOnExitSet, + itsUseBeforeDefSet, itsNotDefSet); + } + + /* + the type of an expression is relatively unknown. Cases we can be sure + about are - + Literals, + Arithmetic operations - always return a Number + */ + int findExpressionType(Node n) + { + switch (n.getType()) { + case TokenStream.NUMBER : { +/* distinguish between integers & f.p.s ? + Number num = ((NumberNode)n).getNumber(); + if ((num instanceof Byte) + || (num instanceof Short) + || (num instanceof Integer)) { + } + else { + } +*/ + return TypeEvent.NumberType; + } + case TokenStream.NEW : + case TokenStream.CALL : + return TypeEvent.NoType; + + case TokenStream.GETELEM : + return TypeEvent.AnyType; + + case TokenStream.GETVAR : { + OptLocalVariable theVar = (OptLocalVariable) + (n.getProp(Node.VARIABLE_PROP)); + if (theVar != null) + return theVar.getTypeUnion(); + } + + case TokenStream.INC : + case TokenStream.DEC : + case TokenStream.DIV: + case TokenStream.MOD: + case TokenStream.BITOR: + case TokenStream.BITXOR: + case TokenStream.BITAND: + case TokenStream.LSH: + case TokenStream.RSH: + case TokenStream.URSH: + case TokenStream.SUB : { + return TypeEvent.NumberType; + } + case TokenStream.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(child); + int rType = findExpressionType(child.getNextSibling()); + return lType | rType; // we're not distinguishng strings yet + } + default : { + Node child = n.getFirstChild(); + if (child == null) + return TypeEvent.AnyType; + else { + int result = TypeEvent.NoType; + while (child != null) { + result |= findExpressionType(child); + child = child.getNextSibling(); + } + return result; + } + } + } + } + + boolean findDefPoints(Node n) + { + boolean result = false; + switch (n.getType()) { + default : { + Node child = n.getFirstChild(); + while (child != null) { + result |= findDefPoints(child); + child = child.getNextSibling(); + } + } + break; + case TokenStream.DEC : + case TokenStream.INC : { + Node firstChild = n.getFirstChild(); + OptLocalVariable theVar = (OptLocalVariable) + (firstChild.getProp(Node.VARIABLE_PROP)); + if (theVar != null) { + // theVar is a Number now + result |= theVar.assignType(TypeEvent.NumberType); + } + } + break; + + case TokenStream.SETPROP : { + Node baseChild = n.getFirstChild(); + Node nameChild = baseChild.getNextSibling(); + Node rhs = nameChild.getNextSibling(); + if (baseChild != null) { + if (baseChild.getType() == TokenStream.GETVAR) { + OptLocalVariable theVar = (OptLocalVariable) + (baseChild.getProp(Node.VARIABLE_PROP)); + if (theVar != null) + theVar.assignType(TypeEvent.AnyType); + } + result |= findDefPoints(baseChild); + } + if (nameChild != null) result |= findDefPoints(nameChild); + if (rhs != null) result |= findDefPoints(rhs); + } + break; + + case TokenStream.SETVAR : { + Node firstChild = n.getFirstChild(); + OptLocalVariable theVar = (OptLocalVariable) + (n.getProp(Node.VARIABLE_PROP)); + if (theVar != null) { + Node rValue = firstChild.getNextSibling(); + int theType = findExpressionType(rValue); + result |= theVar.assignType(theType); + } + } + break; + } + return result; + } + + // a total misnomer for now. To start with we're only trying to find + // duplicate getProp calls on 'this' that can be merged + void localCSE(Node parent, Node n, Hashtable theCSETable, OptFunctionNode theFunction) + { + switch (n.getType()) { + default : { + Node child = n.getFirstChild(); + while (child != null) { + localCSE(n, child, theCSETable, theFunction); + child = child.getNextSibling(); + } + } + break; + case TokenStream.DEC : + case TokenStream.INC : { + Node child = n.getFirstChild(); + if (child.getType() == TokenStream.GETPROP) { + Node nameChild = child.getFirstChild().getNextSibling(); + if (nameChild.getType() == TokenStream.STRING) + theCSETable.remove(nameChild.getString()); + else + theCSETable.clear(); + } + else + if (child.getType() != TokenStream.GETVAR) + theCSETable.clear(); + } + break; + case TokenStream.SETPROP : { + Node baseChild = n.getFirstChild(); + Node nameChild = baseChild.getNextSibling(); + Node rhs = nameChild.getNextSibling(); + if (baseChild != null) localCSE(n, baseChild, theCSETable, theFunction); + if (nameChild != null) localCSE(n, nameChild, theCSETable, theFunction); + if (rhs != null) localCSE(n, rhs, theCSETable, theFunction); + if (nameChild.getType() == TokenStream.STRING) { + theCSETable.remove(nameChild.getString()); +// System.out.println("clear at SETPROP " + ((StringNode)nameChild).getString()); + } + else { + theCSETable.clear(); +// System.out.println("clear all at SETPROP"); + } + } + break; + case TokenStream.GETPROP : { + Node baseChild = n.getFirstChild(); + if (baseChild != null) localCSE(n, baseChild, theCSETable, theFunction); + if ((baseChild.getType() == TokenStream.PRIMARY) + && (baseChild.getInt() == TokenStream.THIS)) { + Node nameChild = baseChild.getNextSibling(); + if (nameChild.getType() == TokenStream.STRING) { + String theName = nameChild.getString(); +// System.out.println("considering " + theName); + Object cse = theCSETable.get(theName); + if (cse == null) { + theCSETable.put(theName, new CSEHolder(parent, n)); + } + else { + if (parent != null) { +// System.out.println("Yay for " + theName); + Node theCSE; + if (cse instanceof CSEHolder) { + CSEHolder cseHolder = (CSEHolder)cse; + Node nextChild = cseHolder.getPropChild.getNextSibling(); + cseHolder.getPropParent.removeChild(cseHolder.getPropChild); + theCSE = itsIRFactory.createNewLocal(cseHolder.getPropChild); + theFunction.incrementLocalCount(); + if (nextChild == null) + cseHolder.getPropParent.addChildToBack(theCSE); + else + cseHolder.getPropParent.addChildBefore(theCSE, nextChild); + theCSETable.put(theName, theCSE); + } + else + theCSE = (Node)cse; + Node nextChild = n.getNextSibling(); + parent.removeChild(n); + Node cseUse = itsIRFactory.createUseLocal(theCSE); + if (nextChild == null) + parent.addChildToBack(cseUse); + else + parent.addChildBefore(cseUse, nextChild); + } + } + } + } + } + break; + case TokenStream.SETELEM : { + Node lhsBase = n.getFirstChild(); + Node lhsIndex = lhsBase.getNextSibling(); + Node rhs = lhsIndex.getNextSibling(); + if (lhsBase != null) localCSE(n, lhsBase, theCSETable, theFunction); + if (lhsIndex != null) localCSE(n, lhsIndex, theCSETable, theFunction); + if (rhs != null) localCSE(n, rhs, theCSETable, theFunction); + theCSETable.clear(); +//System.out.println("clear all at SETELEM"); + } + break; + case TokenStream.CALL : { + Node child = n.getFirstChild(); + while (child != null) { + localCSE(n, child, theCSETable, theFunction); + child = child.getNextSibling(); + } + theCSETable.clear(); +//System.out.println("clear all at CALL"); + } + break; + } + } + + private IRFactory itsIRFactory; + + Hashtable localCSE(Hashtable theCSETable, OptFunctionNode theFunction) + { + itsIRFactory = new IRFactory(null); + if (theCSETable == null) theCSETable = new Hashtable(5); + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + if (n != null) + localCSE(null, n, theCSETable, theFunction); + } + return theCSETable; + } + + void findDefs() + { + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + if (n != null) + findDefPoints(n); + } + } + + public boolean doTypeFlow() + { + boolean changed = false; + + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + if (n != null) + changed |= findDefPoints(n); + } + + return changed; + } + + public boolean isLiveOnEntry(int index) + { + return (itsLiveOnEntrySet != null) && (itsLiveOnEntrySet.test(index)); + } + + public void printLiveOnEntrySet(PrintWriter pw, VariableTable theVariables) + { + for (int i = 0; i < theVariables.size(); i++) { + if (itsUseBeforeDefSet.test(i)) + pw.println(theVariables.get(i).getName() + " is used before def'd"); + if (itsNotDefSet.test(i)) + pw.println(theVariables.get(i).getName() + " is not def'd"); + if (itsLiveOnEntrySet.test(i)) + pw.println(theVariables.get(i).getName() + " is live on entry"); + if (itsLiveOnExitSet.test(i)) + pw.println(theVariables.get(i).getName() + " is live on exit"); + } + } + + public void setSuccessorList(Block[] b) { itsSuccessors = b; } + public void setPredecessorList(Block[] b) { itsPredecessors = b; } + + // 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 Node itsStatementNodes[]; // the list of all statement nodes + + private int itsBlockID; // a unique index for each block + +// reaching def bit sets - + private DataFlowBitSet itsLiveOnEntrySet; + private DataFlowBitSet itsLiveOnExitSet; + private DataFlowBitSet itsUseBeforeDefSet; + private DataFlowBitSet itsNotDefSet; + +} + +class CSEHolder { + + CSEHolder(Node parent, Node child) + { + getPropParent = parent; + getPropChild = child; + } + + Node getPropParent; + Node getPropChild; + +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/Codegen.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/Codegen.java new file mode 100644 index 00000000000..8317005f9d9 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/Codegen.java @@ -0,0 +1,4172 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import org.mozilla.classfile.*; +import java.util.*; +import java.io.*; +import java.lang.reflect.*; + +/** + * This class generates code for a given IR tree. + * + * @author Norris Boyd + * @author Roger Lawrence + */ + +public class Codegen extends Interpreter { + + public Codegen() { + itsConstantList = new ConstantList(); + } + + public IRFactory createIRFactory(TokenStream ts, + ClassNameHelper nameHelper) + { + return new OptIRFactory(ts, nameHelper); + } + + public Node transform(Node tree, TokenStream ts) { + OptTransformer opt = new OptTransformer(new Hashtable(11)); + return opt.transform(tree, null, ts); + } + + public Object compile(Context cx, Scriptable scope, Node tree, + Object securityDomain, + SecuritySupport securitySupport, + ClassNameHelper nameHelper) + throws IOException + { + Hashtable classFiles = new Hashtable(23); + String generatedName = null; + + Exception e = null; + Class result = null; + + try { + if (cx.getOptimizationLevel() > 0) { + (new Optimizer()).optimize(tree, cx.getOptimizationLevel()); + } + generatedName = generateCode(tree, classFiles, nameHelper); + + Enumeration keys = classFiles.keys(); + while (keys.hasMoreElements()) { + String name = (String) keys.nextElement(); + byte[] classFile = (byte[]) classFiles.get(name); + if (nameHelper.getGeneratingDirectory() != null) { + try { + int lastDot = name.lastIndexOf('.'); + if (lastDot != -1) + name = name.substring(lastDot+1); + String filename + = nameHelper.getTargetClassFileName(name); + FileOutputStream out = new FileOutputStream(filename); + out.write(classFile); + out.close(); + } + catch (IOException iox) { + throw WrappedException.wrapException(iox); + } + } else { + try { + Class clazz; + if (securitySupport == null) { + if (Context.isSecurityDomainRequired()) + throw new SecurityException("Required " + + "security context missing"); + if (classLoader == null) + classLoader = new JavaScriptClassLoader(); + clazz = classLoader.defineClass(name, classFile); + } else { + clazz = securitySupport.defineClass(name, classFile, + securityDomain); + } + ClassLoader loader = clazz.getClassLoader(); + clazz = loader.loadClass(name); + if (name.equals(generatedName)) + result = clazz; + } catch (ClassFormatError ex) { + throw new RuntimeException(ex.toString()); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex.toString()); + } + } + } + } + catch (SecurityException x) { + e = x; + } + catch (IllegalArgumentException x) { + e = x; + } + if (e != null) + throw new RuntimeException("Malformed optimizer package " + e); + + OptClassNameHelper onh = (OptClassNameHelper) nameHelper; + if (onh.getTargetImplements() != null || + onh.getTargetExtends() != null) + { + String name = onh.getJavaScriptClassName(null, true); + ScriptableObject obj = new NativeObject(); + ShallowNodeIterator i = tree.getChildIterator(); + while (i.hasMoreElements()) { + Node n = i.nextNode(); + if (n.getType() == TokenStream.FUNCTION) + obj.put((String) n.getDatum(), obj, + n.getProp(Node.FUNCTION_PROP)); + } + try { + Class superClass = onh.getTargetExtends(); + if (superClass == null) + superClass = Object.class; + JavaAdapter.createAdapterClass(cx, obj, name, + superClass, + onh.getTargetImplements(), + generatedName, + onh); + } catch (ClassNotFoundException exn) { + // should never happen + throw new Error(exn.toString()); + } + } + if (tree instanceof OptFunctionNode) { + return ScriptRuntime.createFunctionObject(scope, result, cx); + } else { + try { + if (result == null) + return null; + NativeScript script = (NativeScript) result.newInstance(); + if (scope != null) { + script.setPrototype(script.getClassPrototype(scope, + "Script")); + script.setParentScope(scope); + } + return script; + } + catch (InstantiationException x) { + } + catch (IllegalAccessException x) { + } + throw new RuntimeException("Unable to instantiate compiled class"); + } + } + + void addByteCode(byte theOpcode) + { + classFile.add(theOpcode); + } + + void addByteCode(byte theOpcode, int theOperand) + { + classFile.add(theOpcode, theOperand); + } + + void addByteCode(byte theOpcode, String className) + { + classFile.add(theOpcode, className); + } + + void addVirtualInvoke(String className, String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKEVIRTUAL, + className, + methodName, + parameterSignature, + resultSignature); + } + + void addStaticInvoke(String className, String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKESTATIC, + className, + methodName, + parameterSignature, + resultSignature); + } + + void addScriptRuntimeInvoke(String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + methodName, + parameterSignature, + resultSignature); + } + + void addOptRuntimeInvoke(String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + methodName, + parameterSignature, + resultSignature); + } + + void addSpecialInvoke(String className, String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKESPECIAL, + className, + methodName, + parameterSignature, + resultSignature); + } + + void addDoubleConstructor() + { + classFile.add(ByteCode.INVOKESPECIAL, + "java/lang/Double", + "", "(D)", "V"); + } + + public int markLabel(int label) + { + return classFile.markLabel(label); + } + + public int markLabel(int label, short stackheight) + { + return classFile.markLabel(label, stackheight); + } + + public int acquireLabel() + { + return classFile.acquireLabel(); + } + + public void emitDirectConstructor(OptFunctionNode fnNode) + { +/* + we generate .. + Scriptable directConstruct() { + Scriptable newInstance = new NativeObject(); + newInstance.setPrototype(getClassPrototype()); + newInstance.setParentScope(getParentScope()); + + Object val = callDirect(); + // except that the incoming thisArg is discarded + // and replaced by newInstance + // + + if (val != null && val != Undefined.instance && + val instanceof Scriptable) + { + return (Scriptable) val; + } + return newInstance; + } +*/ + short flags = (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL); + classFile.startMethod("constructDirect", + fnNode.getDirectCallParameterSignature() + + "Ljava/lang/Object;", + flags); + + int argCount = fnNode.getVariableTable().getParameterCount(); + short firstLocal = (short)((4 + argCount * 3) + 1); + + addByteCode(ByteCode.NEW, "org/mozilla/javascript/NativeObject"); + addByteCode(ByteCode.DUP); + classFile.add(ByteCode.INVOKESPECIAL, + "org/mozilla/javascript/NativeObject", + "", "()", "V"); + astore(firstLocal); + + aload(firstLocal); + aload((short)0); + addVirtualInvoke("org/mozilla/javascript/NativeFunction", + "getClassPrototype", + "()", "Lorg/mozilla/javascript/Scriptable;"); + classFile.add(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "setPrototype", + "(Lorg/mozilla/javascript/Scriptable;)", + "V"); + + + aload(firstLocal); + aload((short)0); + addVirtualInvoke("org/mozilla/javascript/NativeFunction", + "getParentScope", + "()", "Lorg/mozilla/javascript/Scriptable;"); + classFile.add(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "setPrototype", + "(Lorg/mozilla/javascript/Scriptable;)", + "V"); + + + aload((short)0); + aload((short)1); + aload((short)2); + aload(firstLocal); + for (int i = 0; i < argCount; i++) { + aload((short)(4 + (i * 3))); + dload((short)(5 + (i * 3))); + } + aload((short)(4 + argCount * 3)); + addVirtualInvoke(this.name, + "callDirect", + fnNode.getDirectCallParameterSignature(), + "Ljava/lang/Object;"); + astore((short)(firstLocal + 1)); + + int exitLabel = acquireLabel(); + aload((short)(firstLocal + 1)); + addByteCode(ByteCode.IFNULL, exitLabel); + aload((short)(firstLocal + 1)); + pushUndefined(); + addByteCode(ByteCode.IF_ACMPEQ, exitLabel); + aload((short)(firstLocal + 1)); + addByteCode(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable"); + addByteCode(ByteCode.IFEQ, exitLabel); + aload((short)(firstLocal + 1)); + addByteCode(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable"); + addByteCode(ByteCode.ARETURN); + markLabel(exitLabel); + + aload(firstLocal); + addByteCode(ByteCode.ARETURN); + + classFile.stopMethod((short)(firstLocal + 2), null); + + } + + public String generateCode(Node tree, Hashtable results, + ClassNameHelper nameHelper) + { + this.itsNameHelper = (OptClassNameHelper) nameHelper; + this.results = results; + Context cx = Context.getCurrentContext(); + itsSourceFile = null; + if (cx.isGeneratingDebug()) + itsSourceFile = (String) tree.getProp(Node.SOURCENAME_PROP); + version = cx.getLanguageVersion(); + optLevel = cx.getOptimizationLevel(); + debugLevel = cx.getDebugLevel(); + inFunction = tree.getType() == TokenStream.FUNCTION; + if (debugLevel >= 3) { + superClassName = inFunction + ? debugFunctionSuperClassName + : debugScriptSuperClassName; + } else { + superClassName = inFunction + ? normalFunctionSuperClassName + : normalScriptSuperClassName; + } + superClassSlashName = superClassName.replace('.', '/'); + + Node codegenBase; + if (inFunction) { + OptFunctionNode fnNode = (OptFunctionNode) tree; + inDirectCallFunction = fnNode.isTargetOfDirectCall(); + vars = (OptVariableTable) fnNode.getVariableTable(); + this.name = fnNode.getClassName(); + classFile = new ClassFileWriter(name, superClassName, itsSourceFile); + Node args = tree.getFirstChild(); + String name = fnNode.getFunctionName(); + generateInit(cx, "", tree, name, args); + if (fnNode.isTargetOfDirectCall()) { + classFile.startMethod("call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", + (short)(ClassFileWriter.ACC_PUBLIC + + ClassFileWriter.ACC_FINAL)); + addByteCode(ByteCode.ALOAD_0); + addByteCode(ByteCode.ALOAD_1); + addByteCode(ByteCode.ALOAD_2); + addByteCode(ByteCode.ALOAD_3); + for (int i = 0; i < vars.getParameterCount(); i++) { + push(i); + addByteCode(ByteCode.ALOAD, 4); + addByteCode(ByteCode.ARRAYLENGTH); + int undefArg = acquireLabel(); + int beyond = acquireLabel(); + addByteCode(ByteCode.IF_ICMPGE, undefArg); + addByteCode(ByteCode.ALOAD, 4); + push(i); + addByteCode(ByteCode.AALOAD); + push(0.0); + addByteCode(ByteCode.GOTO, beyond); + markLabel(undefArg); + pushUndefined(); + push(0.0); + markLabel(beyond); + } + addByteCode(ByteCode.ALOAD, 4); + addVirtualInvoke(this.name, + "callDirect", + fnNode.getDirectCallParameterSignature(), + "Ljava/lang/Object;"); + addByteCode(ByteCode.ARETURN); + classFile.stopMethod((short)5, null); + // 1 for this, 1 for js this, 1 for args[] + + emitDirectConstructor(fnNode); + + startNewMethod("callDirect", + fnNode.getDirectCallParameterSignature() + + "Ljava/lang/Object;", + 1, false, true); + vars.assignParameterJRegs(); + if (!fnNode.getParameterNumberContext()) { + // make sure that all parameters are objects + itsForcedObjectParameters = true; + for (int i = 0; i < vars.getParameterCount(); i++) { + OptLocalVariable lVar = (OptLocalVariable) vars.get(i); + aload(lVar.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isObjectLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, isObjectLabel); + addByteCode(ByteCode.NEW,"java/lang/Double"); + addByteCode(ByteCode.DUP); + dload((short)(lVar.getJRegister() + 1)); + addDoubleConstructor(); + astore(lVar.getJRegister()); + markLabel(isObjectLabel); + } + } + generatePrologue(cx, tree, true, vars.getParameterCount()); + } else { + startNewMethod("call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", + 1, false, true); + generatePrologue(cx, tree, true, -1); + } + codegenBase = tree.getLastChild(); + } else { + // better be a script + if (tree.getType() != TokenStream.SCRIPT) + badTree(); + vars = (OptVariableTable) tree.getProp(Node.VARS_PROP); + boolean isPrimary = itsNameHelper.getTargetExtends() == null && + itsNameHelper.getTargetImplements() == null; + this.name = itsNameHelper.getJavaScriptClassName(null, isPrimary); + classFile = new ClassFileWriter(name, superClassName, itsSourceFile); + classFile.addInterface("org/mozilla/javascript/Script"); + generateScriptCtor(cx, tree); + generateMain(cx); + generateInit(cx, "initScript", tree, "", null); + generateExecute(cx); + startNewMethod("call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", + 1, false, true); + generatePrologue(cx, tree, false, -1); + tree.addChildToBack(new Node(TokenStream.RETURN)); + codegenBase = tree; + } + + generateCodeFromNode(codegenBase, null, -1, -1); + + generateEpilogue(); + + finishMethod(cx, debugVars); + + emitConstantDudeInitializers(); + + // this needs to be done after the primary code generation is done + if (debugLevel >= 1) + generateDebugInit(cx, tree); + + ByteArrayOutputStream out = new ByteArrayOutputStream(512); + try { + classFile.write(out); + } + catch (IOException ioe) { + throw new RuntimeException("unexpected IOException"); + } + byte[] bytes = out.toByteArray(); + + results.put(name, bytes); + classFile = null; + + return name; + } + + private void generateCodeFromNode(Node node, Node parent, int trueLabel, + int falseLabel) + { + // System.out.println("gen code for " + node.toString()); + + int type = node.getType(); + Node child = node.getFirstChild(); + switch (type) { + case TokenStream.LOOP: + case TokenStream.WITH: + case TokenStream.LABEL: + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + break; + + case TokenStream.CASE: + case TokenStream.DEFAULT: + // XXX shouldn't these be StatementNodes? + + case TokenStream.SCRIPT: + case TokenStream.BLOCK: + case TokenStream.VOID: + case TokenStream.NOP: + // no-ops. + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + break; + + case TokenStream.FUNCTION: + if (inFunction || parent.getType() != TokenStream.SCRIPT) + visitFunction(node); + break; + + case TokenStream.NAME: + visitName(node); + break; + + case TokenStream.NEW: + case TokenStream.CALL: + visitCall(node, type, child); + break; + + case TokenStream.NUMBER: + case TokenStream.STRING: + visitLiteral(node); + break; + + case TokenStream.PRIMARY: + visitPrimary(node); + break; + + case TokenStream.OBJECT: + visitObject(node); + break; + + case TokenStream.TRY: + visitTryCatchFinally(node, child); + break; + + case TokenStream.THROW: + visitThrow(node, child); + break; + + case TokenStream.RETURN: + visitReturn(node, child); + break; + + case TokenStream.SWITCH: + visitSwitch(node, child); + break; + + case TokenStream.COMMA: + generateCodeFromNode(child, node, -1, -1); + addByteCode(ByteCode.POP); + generateCodeFromNode(child.getNextSibling(), + node, trueLabel, falseLabel); + break; + + case TokenStream.NEWSCOPE: + addScriptRuntimeInvoke("newScope", "()", + "Lorg/mozilla/javascript/Scriptable;"); + break; + + case TokenStream.ENTERWITH: + visitEnterWith(node, child); + break; + + case TokenStream.LEAVEWITH: + visitLeaveWith(node, child); + break; + + case TokenStream.ENUMINIT: + visitEnumInit(node, child); + break; + + case TokenStream.ENUMNEXT: + visitEnumNext(node, child); + break; + + case TokenStream.ENUMDONE: + visitEnumDone(node, child); + break; + + case TokenStream.POP: + visitStatement(node); + if (child.getType() == TokenStream.SETVAR) { + /* special case this so as to avoid unnecessary + load's & pop's */ + visitSetVar(child, child.getFirstChild(), false); + } + else { + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + if (node.getProp(Node.ISNUMBER_PROP) != null) + addByteCode(ByteCode.POP2); + else + addByteCode(ByteCode.POP); + } + break; + + case TokenStream.POPV: + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + astore(scriptResultLocal); + break; + + case TokenStream.TARGET: + visitTarget(node); + break; + + case TokenStream.JSR: + case TokenStream.GOTO: + case TokenStream.IFEQ: + case TokenStream.IFNE: + visitGOTO(node, type, child); + break; + + case TokenStream.UNARYOP: + visitUnary(node, child, trueLabel, falseLabel); + break; + + case TokenStream.TYPEOF: + visitTypeof(node, child); + break; + + case TokenStream.INC: + visitIncDec(node, true); + break; + + case TokenStream.DEC: + visitIncDec(node, false); + break; + + case TokenStream.OR: + case TokenStream.AND: { + if (trueLabel == -1) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + addByteCode(ByteCode.DUP); + addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)", "Z"); + int falseTarget = acquireLabel(); + if (type == TokenStream.AND) + addByteCode(ByteCode.IFEQ, falseTarget); + else + addByteCode(ByteCode.IFNE, falseTarget); + addByteCode(ByteCode.POP); + generateCodeFromNode(child.getNextSibling(), node, trueLabel, falseLabel); + markLabel(falseTarget); + } + else { + int interLabel = acquireLabel(); + if (type == TokenStream.AND) { + generateCodeFromNode(child, node, interLabel, falseLabel); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, interLabel); + addByteCode(ByteCode.GOTO, falseLabel); + } + } + else { + generateCodeFromNode(child, node, trueLabel, interLabel); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, trueLabel); + addByteCode(ByteCode.GOTO, interLabel); + } + } + markLabel(interLabel); + child = child.getNextSibling(); + generateCodeFromNode(child, node, trueLabel, falseLabel); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, trueLabel); + addByteCode(ByteCode.GOTO, falseLabel); + } + } + } + break; + + case TokenStream.ADD: { + generateCodeFromNode(child, node, trueLabel, falseLabel); + generateCodeFromNode(child.getNextSibling(), + node, trueLabel, falseLabel); + Integer childNumberFlag = + (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if (childNumberFlag != null) { + if (childNumberFlag.intValue() == Node.BOTH) { + addByteCode(ByteCode.DADD); + } + else { + if (childNumberFlag.intValue() == Node.LEFT) { + addOptRuntimeInvoke("add", + "(DLjava/lang/Object;)", + "Ljava/lang/Object;"); + } + else { + addOptRuntimeInvoke("add", + "(Ljava/lang/Object;D)", + "Ljava/lang/Object;"); + } + } + } + else { + addScriptRuntimeInvoke("add", + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Object;"); + } + } + break; + + case TokenStream.MUL: + visitArithmetic(node, ByteCode.DMUL, child, parent); + break; + + case TokenStream.SUB: + visitArithmetic(node, ByteCode.DSUB, child, parent); + break; + + case TokenStream.DIV: + case TokenStream.MOD: + visitArithmetic(node, type == TokenStream.DIV + ? ByteCode.DDIV + : ByteCode.DREM, child, parent); + break; + + case TokenStream.BITOR: + case TokenStream.BITXOR: + case TokenStream.BITAND: + case TokenStream.LSH: + case TokenStream.RSH: + case TokenStream.URSH: + visitBitOp(node, type, child); + break; + + case TokenStream.CONVERT: { + Object toType = node.getProp(Node.TYPE_PROP); + if (toType == ScriptRuntime.NumberClass) { + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, trueLabel, falseLabel); + addScriptRuntimeInvoke("toNumber", + "(Ljava/lang/Object;)", "D"); + addDoubleConstructor(); + } + else { + if (toType == ScriptRuntime.DoubleClass) { + // cnvt to double + // (not Double) + generateCodeFromNode(child, node, trueLabel, falseLabel); + addScriptRuntimeInvoke("toNumber", + "(Ljava/lang/Object;)", "D"); + } + else { + if (toType == ScriptRuntime.ObjectClass) {// convert from double + if ((child.getType() == TokenStream.NUMBER) + && (child.getProp(Node.ISNUMBER_PROP) + != null)) { + Object oldProp + = child.getProp(Node.ISNUMBER_PROP); + child.putProp(Node.ISNUMBER_PROP, null); + generateCodeFromNode(child, node, trueLabel, falseLabel); + child.putProp(Node.ISNUMBER_PROP, oldProp); + } + else { + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, trueLabel, falseLabel); + addDoubleConstructor(); + } + } + else + badTree(); + } + } + } + break; + + case TokenStream.RELOP: + if (trueLabel == -1) // need a result Object + visitRelOp(node, child, parent); + else + visitGOTOingRelOp(node, child, parent, trueLabel, falseLabel); + break; + + case TokenStream.EQOP: + visitEqOp(node, child, parent, trueLabel, falseLabel); + break; + + case TokenStream.GETPROP: + visitGetProp(node, child); + break; + + case TokenStream.GETELEM: + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + if (node.getProp(Node.ISNUMBER_PROP) != null) { + addOptRuntimeInvoke("getElem", + "(Ljava/lang/Object;D" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + addScriptRuntimeInvoke("getElem", + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + break; + + case TokenStream.GETVAR: { + OptLocalVariable lVar + = (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP)); + visitGetVar(lVar, + node.getProp(Node.ISNUMBER_PROP) != null, + node.getString()); + } + break; + + case TokenStream.SETVAR: + visitSetVar(node, child, true); + break; + + case TokenStream.SETNAME: + visitSetName(node, child); + break; + + case TokenStream.SETPROP: + visitSetProp(node, child); + break; + + case TokenStream.SETELEM: + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + if (node.getProp(Node.ISNUMBER_PROP) != null) { + addOptRuntimeInvoke("setElem", + "(Ljava/lang/Object;D" + + "Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + addScriptRuntimeInvoke("setElem", + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + break; + + case TokenStream.DELPROP: + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + addScriptRuntimeInvoke("delete", + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Object;"); + break; + + case TokenStream.BINDNAME: + case TokenStream.GETBASE: + visitBind(node, type, child); + break; + + case TokenStream.GETTHIS: + generateCodeFromNode(child, node, trueLabel, falseLabel); + addScriptRuntimeInvoke("getThis", + "(Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + break; + + case TokenStream.PARENT: + generateCodeFromNode(child, node, trueLabel, falseLabel); + addScriptRuntimeInvoke("getParent", + "(Ljava/lang/Object;)", + "Lorg/mozilla/javascript/Scriptable;"); + break; + + case TokenStream.NEWTEMP: + visitNewTemp(node, child); + break; + + case TokenStream.USETEMP: + visitUseTemp(node, child); + break; + + case TokenStream.NEWLOCAL: + visitNewLocal(node, child); + break; + + case TokenStream.USELOCAL: + visitUseLocal(node, child); + break; + + default: + throw new RuntimeException("Unexpected node type " + + TokenStream.tokenToName(type)); + } + + } + + private void startNewMethod(String methodName, String methodDesc, + int parmCount, boolean isStatic, + boolean isFinal) + { + locals = new boolean[MAX_LOCALS]; + localsMax = (short) (parmCount+1); // number of parms + "this" + firstFreeLocal = 0; + contextLocal = -1; + variableObjectLocal = -1; + scriptResultLocal = -1; + argsLocal = -1; + thisObjLocal = -1; + funObjLocal = -1; + debug_pcLocal = -1; + debugStopSubRetLocal = -1; + itsZeroArgArray = -1; + itsOneArgArray = -1; + short flags = ClassFileWriter.ACC_PUBLIC; + if (isStatic) + flags |= ClassFileWriter.ACC_STATIC; + if (isFinal) + flags |= ClassFileWriter.ACC_FINAL; + epilogueLabel = -1; + classFile.startMethod(methodName, methodDesc, (short) flags); + } + + private void finishMethod(Context cx, VariableTable vars) { + classFile.stopMethod((short)(localsMax + 1), vars); + contextLocal = -1; + } + + private void generateMain(Context cx) { + startNewMethod("main", "([Ljava/lang/String;)V", 1, true, true); + + push(this.name); // load the name of this class + addByteCode(ByteCode.ALOAD_0); // load 'args' + addScriptRuntimeInvoke("main", + "(Ljava/lang/String;[Ljava/lang/String;)", + "V"); + addByteCode(ByteCode.RETURN); + finishMethod(cx, null); + } + + + private void generateExecute(Context cx) { + String signature = "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;)" + + "Ljava/lang/Object;"; + startNewMethod("exec", signature, 2, false, true); + String slashName = this.name.replace('.', '/'); + + if (!trivialInit) { + // to begin a script, call the initScript method + addByteCode(ByteCode.ALOAD_0); // load 'this' + addByteCode(ByteCode.ALOAD_2); // load 'scope' + addByteCode(ByteCode.ALOAD_1); // load 'cx' + addVirtualInvoke(slashName, + "initScript", + "(Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Context;)", + "V"); + } + + if (debugLevel >= 3) { + addByteCode(ByteCode.ALOAD_1); // load 'cx' + addByteCode(ByteCode.ALOAD_0); // load 'this' + addByteCode(ByteCode.ALOAD_2); // load 'scope' + addStaticInvoke("org/mozilla/javascript/debug/NativeDelegate", + "executeDebug", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/NativeFunction;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + addByteCode(ByteCode.ALOAD_0); // load 'this' + addByteCode(ByteCode.ALOAD_1); // load 'cx' + addByteCode(ByteCode.ALOAD_2); // load 'scope' + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.ACONST_NULL); + addVirtualInvoke(slashName, + "call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)", + "Ljava/lang/Object;"); + } + + addByteCode(ByteCode.ARETURN); + finishMethod(cx, null); + } + + private void generateScriptCtor(Context cx, Node tree) { + startNewMethod("", "()V", 1, false, false); + addByteCode(ByteCode.ALOAD_0); + addSpecialInvoke(superClassSlashName, + "", "()", "V"); + addByteCode(ByteCode.RETURN); + finishMethod(cx, null); + } + + /** + * Indicate that the init is non-trivial. + * + * For trivial inits we can omit creating the init method + * altogether. (Only applies to scripts, since function + * inits are constructors, which are required.) For trivial + * inits we also omit the call to initScript from exec(). + */ + private void setNonTrivialInit(String methodName) { + if (!trivialInit) + return; // already set + trivialInit = false; + startNewMethod(methodName, "(Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Context;)V", + 1, false, false); + reserveWordLocal(0); // reserve 0 for 'this' + variableObjectLocal = reserveWordLocal(1); // reserve 1 for 'scope' + contextLocal = reserveWordLocal(2); // reserve 2 for 'context' + } + + private void generateInit(Context cx, String methodName, + Node tree, String name, Node args) + { + trivialInit = true; + boolean inCtor = false; + VariableTable vars; + if (tree instanceof OptFunctionNode) + vars = ((OptFunctionNode)tree).getVariableTable(); + else + vars = (VariableTable) tree.getProp(Node.VARS_PROP); + + if (methodName.equals("")) { + inCtor = true; + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); + addSpecialInvoke(superClassSlashName, "", "()", "V"); + + addByteCode(ByteCode.ALOAD_0); + addByteCode(ByteCode.ALOAD_1); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/ScriptableObject", + "parent", "Lorg/mozilla/javascript/Scriptable;"); + } + + /* + * Generate code to initialize names field with a + * string array that is the names of the function, + * the parameters and the vars. Initialize argCount + * to the number of formal parameters. + */ + + if (name.length() != 0 || (vars != null && vars.size() > 0)) { + setNonTrivialInit(methodName); + if (vars == null) + vars = new OptVariableTable(); + push(vars.size() + 1); + addByteCode(ByteCode.ANEWARRAY, "java/lang/String"); + addByteCode(ByteCode.DUP); + short x = getNewWordLocal(); + astore(x); + addByteCode(ByteCode.DUP); + push(0); + classFile.addLoadConstant(name); + addByteCode(ByteCode.AASTORE); + if (vars != null) { + for (int i = 0; i < vars.size(); i++) { + aload(x); + push(i + 1); + push(vars.getName(i)); + addByteCode(ByteCode.AASTORE); + } + } + releaseWordLocal(x); + addByteCode(ByteCode.ALOAD_0); + addByteCode(ByteCode.SWAP); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "names", "[Ljava/lang/String;"); + } + + int parmCount = vars == null ? 0 : vars.getParameterCount(); + if (parmCount != 0) { + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); + push(parmCount); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "argCount", "S"); + } + + // Initialize NativeFunction.version with Context's version. + if (cx.getLanguageVersion() != 0) { + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); + push(cx.getLanguageVersion()); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "version", "S"); + } + + // add the String representing the source. + String source = (String) tree.getProp(Node.SOURCE_PROP); + if ((source != null) + && cx.isGeneratingSource() + && (source.length() < 65536)) { + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); + push(source); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "source", "Ljava/lang/String;"); + } + + // precompile all regexp literals + Vector regexps = (Vector) tree.getProp(Node.REGEXP_PROP); + if (regexps != null) { + setNonTrivialInit(methodName); + generateRegExpLiterals(regexps, inCtor); + } + + Vector fns = (Vector) tree.getProp(Node.FUNCTION_PROP); + if (fns != null) { + setNonTrivialInit(methodName); + generateFunctionInits(fns); + } + + if (tree instanceof OptFunctionNode) { + + OptFunctionNode fnNode = (OptFunctionNode)tree; + Vector directCallTargets + = ((OptFunctionNode)tree).getDirectCallTargets(); + if (directCallTargets != null) { + setNonTrivialInit(methodName); + classFile.addField("EmptyArray", + "[Ljava/lang/Object;", + (short)ClassFileWriter.ACC_PRIVATE); + addByteCode(ByteCode.ALOAD_0); + push(0); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + classFile.add(ByteCode.PUTFIELD, + classFile.fullyQualifiedForm(this.name), + "EmptyArray", + "[Ljava/lang/Object;"); + } + if (fnNode.isTargetOfDirectCall()) { + setNonTrivialInit(methodName); + String className + = classFile.fullyQualifiedForm(fnNode.getClassName()); + String fieldName = className.replace('/', '_'); + classFile.addField(fieldName, + "L" + className + ";", + (short)(ClassFileWriter.ACC_PUBLIC + + ClassFileWriter.ACC_STATIC)); + addByteCode(ByteCode.ALOAD_0); + classFile.add(ByteCode.PUTSTATIC, + className, + fieldName, + "L" + className + ";"); + } + } + + // debug stuff ... (should be last thing in method) + + if (debugLevel >= 1) { + // save debugLevel + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); // 'this' + push(debugLevel); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "debug_level", "I"); + + // generate call to "debugInit()" + String slashName = this.name.replace('.', '/'); + addByteCode(ByteCode.ALOAD_0); // load 'this' + addVirtualInvoke(slashName, + "debugInit","()", "V"); + } + + if (!trivialInit) { + addByteCode(ByteCode.RETURN); + finishMethod(cx, null); + } + } + + // this is only called if (debugLevel >= 1) + private void generateDebugInit(Context cx, Node tree) { + startNewMethod("debugInit", "()V", 0, false, true); + + // save source name + String srcName = (String) tree.getProp(Node.SOURCENAME_PROP); + addByteCode(ByteCode.ALOAD_0); // 'this' + if (srcName != null) + push(srcName); + else + addByteCode(ByteCode.ACONST_NULL); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "debug_srcName", "Ljava/lang/String;"); + + if (debugLevel >= 3) { + // at this debug level we know that our superclass + // is debug specific. Generate more debug info into it. + + // save the base and end line numbers + Integer prop; + + prop = (Integer) tree.getProp(Node.BASE_LINENO_PROP); + if (null != prop) { + addByteCode(ByteCode.ALOAD_0); // 'this' + push(prop.intValue()); + + classFile.add(ByteCode.PUTFIELD, + superClassSlashName, + "debug_baseLineno", "I"); + } + + prop = (Integer) tree.getProp(Node.END_LINENO_PROP); + if (null != prop) { + addByteCode(ByteCode.ALOAD_0); // 'this' + push(prop.intValue()); + + classFile.add(ByteCode.PUTFIELD, + superClassSlashName, + "debug_endLineno", "I"); + } + + // trap support + if (debugLevel >= 6) { + writeDebugPCEntries(); + buildDebugTrapMap(); + } + + // generate call to script loading hook + // *always do this last* + + addByteCode(ByteCode.ALOAD_0); //'this' + addVirtualInvoke(superClassSlashName, + "callLoadingScriptHook", + "()", "V"); + } + + addByteCode(ByteCode.RETURN); + finishMethod(cx, null); + } + + private void generateRegExpLiterals(Vector regexps, boolean inCtor) { + for (int i=0; i < regexps.size(); i++) { + Node regexp = (Node) regexps.elementAt(i); + StringBuffer sb = new StringBuffer("_re"); + sb.append(i); + String fieldName = sb.toString(); + short flags = ClassFileWriter.ACC_PRIVATE; + if (inCtor) + flags |= ClassFileWriter.ACC_FINAL; + classFile.addField(fieldName, + "Lorg/mozilla/javascript/regexp/NativeRegExp;", + flags); + addByteCode(ByteCode.ALOAD_0); // load 'this' + + addByteCode(ByteCode.NEW, "org/mozilla/javascript/regexp/NativeRegExp"); + addByteCode(ByteCode.DUP); + + aload(contextLocal); // load 'context' + aload(variableObjectLocal); // load 'scope' + Node left = regexp.getFirstChild(); + push(left.getString()); + Node right = regexp.getLastChild(); + if (left == right) { + addByteCode(ByteCode.ACONST_NULL); + } else { + push(right.getString()); + } + + addSpecialInvoke("org/mozilla/javascript/regexp/NativeRegExp", + "", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/String;Ljava/lang/String;)", + "V"); + + regexp.putProp(Node.REGEXP_PROP, fieldName); + classFile.add(ByteCode.PUTFIELD, + classFile.fullyQualifiedForm(this.name), + fieldName, "Lorg/mozilla/javascript/regexp/NativeRegExp;"); + } + } + + private void generateFunctionInits(Vector fns) { + // make an array to put them in, so they're available + // for decompilation. + push(fns.size()); + addByteCode(ByteCode.ANEWARRAY, "org/mozilla/javascript/NativeFunction"); + + /* note that nested function decompilation currently + * depends on the elements of the nestedFunctions array + * being defined in source order. (per function/script, + * starting from 0.) Change Parser and NativeFunction if + * changing ordering. + */ + + for (short i = 0; i < fns.size(); i++) { + addByteCode(ByteCode.DUP); // make another reference to the array + push(i); // to set up for aastore at end of loop + + Node def = (Node) fns.elementAt(i); + Codegen codegen = new Codegen(); + String fnClassName = codegen.generateCode(def, results, itsNameHelper); + // initialize a property by that name, and push the object + // as a result + if (inFunction) { + addByteCode(ByteCode.ALOAD_0); // load "this" argument + } else { + aload(variableObjectLocal); + } + push(fnClassName); + String str = def.getString(); + if (str != null) { + push(str); + } else { + addByteCode(ByteCode.ACONST_NULL); + } + addByteCode(ByteCode.ALOAD_0); // load 'this' + aload(contextLocal); // load 'cx' + addScriptRuntimeInvoke("defineFunction", + "(Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/String;" + + "Ljava/lang/String;" + + "Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Context;)", + "Lorg/mozilla/javascript/NativeFunction;"); + def.putProp(Node.FUNCTION_PROP, new Short(i)); + addByteCode(ByteCode.AASTORE); // store NativeFunction + // instance to array + } + + // add the array as the nestedFunctions field; array should + // still be on the stack. + + addByteCode(ByteCode.ALOAD_0); // load 'this' + addByteCode(ByteCode.SWAP); // swap with original array reference + + classFile.add(ByteCode.PUTFIELD, "org/mozilla/javascript/NativeFunction", + "nestedFunctions", "[Lorg/mozilla/javascript/NativeFunction;"); + } + + + /** + * Generate the prologue for a function or script. + * + * @param tree the tree to generate code for + * @param inFunction true if generating the prologue for a function + * (as opposed to a script) + * @param directParameterCount number of parameters for direct call, + * or -1 if not direct call + */ + private void generatePrologue(Context cx, Node tree, boolean inFunction, + int directParameterCount) + { + funObjLocal = reserveWordLocal(0); + contextLocal = reserveWordLocal(1); + variableObjectLocal = reserveWordLocal(2); + thisObjLocal = reserveWordLocal(3); + + if (directParameterCount > 0) { + for (int i = 0; i < (3 * directParameterCount); i++) + reserveWordLocal(i + 4); // reserve 'args' + } + // reserve 'args[]' + argsLocal = reserveWordLocal(directParameterCount <= 0 + ? 4 : (3 * directParameterCount) + 4); + + // These locals are to be pre-allocated since they need function scope. + // They are primarily used by the exception handling mechanism + Integer localCount = (Integer)(tree.getProp(Node.LOCALCOUNT_PROP)); + if (localCount != null) { + itsLocalAllocationBase = (short)(argsLocal + 1); + for (int i = 0; i < localCount.intValue(); i++) { + reserveWordLocal(itsLocalAllocationBase + i); + } + } + + hasVarsInRegs = inFunction && + !((OptFunctionNode)tree).requiresActivation(); + if (hasVarsInRegs) { + // No need to create activation. Pad arguments if need be. + int parmCount = vars.getParameterCount(); + if (inFunction && parmCount > 0 && directParameterCount < 0) { + // Set up args array + // check length of arguments, pad if need be + aload(argsLocal); + addByteCode(ByteCode.ARRAYLENGTH); + push(parmCount); + int label = acquireLabel(); + addByteCode(ByteCode.IF_ICMPGE, label); + aload(argsLocal); + push(parmCount); + addScriptRuntimeInvoke("padArguments", + "([Ljava/lang/Object;I)", + "[Ljava/lang/Object;"); + astore(argsLocal); + markLabel(label); + } + + // REMIND - only need to initialize the vars that don't get a value + // before the next call and are used in the function + short firstUndefVar = -1; + for (int i = 0; i < vars.size(); i++) { + OptLocalVariable lVar = (OptLocalVariable) vars.get(i); + if (lVar.isNumber()) { + lVar.assignJRegister(getNewWordPairLocal()); + push(0.0); + dstore(lVar.getJRegister()); + } else if (lVar.isParameter()) { + if (directParameterCount < 0) { + lVar.assignJRegister(getNewWordLocal()); + aload(argsLocal); + push(i); + addByteCode(ByteCode.AALOAD); + astore(lVar.getJRegister()); + } + } else { + lVar.assignJRegister(getNewWordLocal()); + if (firstUndefVar == -1) { + pushUndefined(); + firstUndefVar = lVar.getJRegister(); + } else { + aload(firstUndefVar); + } + astore(lVar.getJRegister()); + } + lVar.setStartPC(classFile.getCurrentCodeOffset()); + } + + // Indicate that we should generate debug information for + // the variable table. (If we're generating debug info at + // all.) + debugVars = vars; + + // Skip creating activation object. + return; + } + + if (directParameterCount > 0) { + // We're going to create an activation object, so we + // need to get an args array with all the arguments in it. + + aload(argsLocal); + push(directParameterCount); + addOptRuntimeInvoke("padStart", + "([Ljava/lang/Object;I)", + "[Ljava/lang/Object;"); + astore(argsLocal); + for (int i=0; i < directParameterCount; i++) { + aload(argsLocal); + push(i); + // "3" is 1 for Object parm and 2 for double parm, and + // "4" is to account for the context, etc. parms + aload((short) (3*i+4)); + addByteCode(ByteCode.AASTORE); + } + } + + String debugVariableName; + if (inFunction) { + aload(contextLocal); + aload(variableObjectLocal); + aload(funObjLocal); + aload(thisObjLocal); + aload(argsLocal); + addScriptRuntimeInvoke("initVarObj", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/NativeFunction;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)", + "Lorg/mozilla/javascript/Scriptable;"); + debugVariableName = "activation"; + } else { + aload(contextLocal); + aload(variableObjectLocal); + aload(funObjLocal); + aload(thisObjLocal); + addScriptRuntimeInvoke("initScript", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/NativeFunction;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + debugVariableName = "global"; + } + astore(variableObjectLocal); + + if (cx.isGeneratingDebug()) { + debugVars = new OptVariableTable(); + debugVars.addLocal(debugVariableName); + OptLocalVariable lv = (OptLocalVariable) debugVars.get(debugVariableName); + lv.assignJRegister(variableObjectLocal); + lv.setStartPC(classFile.getCurrentCodeOffset()); + } + + if (!inFunction) { + // OPT: use dataflow to prove that this assignment is dead + scriptResultLocal = getNewWordLocal(); + pushUndefined(); + astore(scriptResultLocal); + } + + if (inFunction && ((OptFunctionNode)tree).containsCalls(-1)) { + if (((OptFunctionNode)tree).containsCalls(0)) { + itsZeroArgArray = getNewWordLocal(); + classFile.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "emptyArgs", "[Ljava/lang/Object;"); + astore(itsZeroArgArray); + } + if (((OptFunctionNode)tree).containsCalls(1)) { + itsOneArgArray = getNewWordLocal(); + push(1); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + astore(itsOneArgArray); + } + } + + if (debugLevel > 0) { // XXX is 0 correct? + // setup the local for 'pc' with a default value + debug_pcLocal = getNewWordLocal(); + addByteCode(ByteCode.ICONST_0); + istore(debug_pcLocal); + if (debugLevel >= 6) { + debugStopSubLabel = acquireLabel(); + generateDebugStopSubroutine(); + } + } + } + + private void generateEpilogue() { + if (epilogueLabel != -1) { + classFile.markLabel(epilogueLabel); + } + if (!hasVarsInRegs || !inFunction || debugLevel > 0) { + // restore caller's activation + aload(contextLocal); + addScriptRuntimeInvoke("popActivation", + "(Lorg/mozilla/javascript/Context;)", + "V"); + } + addByteCode(ByteCode.ARETURN); + } + + private void generateDebugStopSubroutine() { + // local to store our subroutine return address +// int stackTop = classFile.getStackTop(); + debugStopSubRetLocal = getNewWordLocal(); + + // generate the code to jump over this subroutine + // (needed if generated anyplace other than after the last return) + int labelSkip = acquireLabel(); + addByteCode(ByteCode.GOTO, labelSkip); + + // + // the actual subroutine starts here + // + classFile.adjustStackTop(1); + markLabel(debugStopSubLabel); + astore(debugStopSubRetLocal); +// System.out.println(1+" " +stackTop+" "+classFile.getStackTop()); + int labelRetPop0 = acquireLabel(); + int labelRetPop1 = acquireLabel(); + int labelRetPop2 = acquireLabel(); + int labelCheckForStopPoint = acquireLabel(); + int labelHandleHookAnswer = acquireLabel(); + int labelNotThrow = acquireLabel(); + + // parse the flag to see what should be done + addByteCode(ByteCode.ALOAD_0); // 'this' + classFile.add(ByteCode.GETFIELD, + superClassSlashName, + "debug_stopFlags", "I"); + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.ICONST_1); + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.IFEQ, labelCheckForStopPoint); + addByteCode(ByteCode.POP); + + // if here then an immediate interrupt was requested. (stack empty) +// System.out.println(2+" " +stackTop+" "+classFile.getStackTop()); + + // call our "hook caller" helper in Debug superclass + addByteCode(ByteCode.ALOAD_0); // 'this' + aload(contextLocal); + addByteCode(ByteCode.ICONST_1); // retVal[1] array + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + addByteCode(ByteCode.DUP_X2); //retVal will be on stack after call + iload(debug_pcLocal); + aload(variableObjectLocal); + + addVirtualInvoke(superClassSlashName, + "callInterruptHook", + "(Lorg/mozilla/javascript/Context;[Ljava/lang/Object;" + + "ILorg/mozilla/javascript/Scriptable;)", + "I"); + // jump to labelHandleHookAnswer (same code for both hooks) + addByteCode(ByteCode.GOTO, labelHandleHookAnswer); + + /********************************************************/ + + // if here, check to see if this is our stoppoint + // the stack has 'debug_stopFlags' + classFile.adjustStackTop(-1); + markLabel(labelCheckForStopPoint); + + // sanity check that breakpoint flag is really set + addByteCode(ByteCode.ICONST_2); + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.IFEQ, labelRetPop0); + + // check to see if we are at a pc with a breakpoint (stack empty) +// System.out.println(3+" " +stackTop+" "+classFile.getStackTop()); + + // We have an array of 32bit ints. We use the pc to index bits in + // that array. If the bit is set then we trap, otherwise continue. + // To figure out which bit the pc represents we do bitwise work to + // calc an index and mask. Similar work is done in Java in the class + // org.mozilla.javascript.debug.NativeDelegate to set and clear these + // bits. + + // get current pc + iload(debug_pcLocal); + addByteCode(ByteCode.DUP); + + // calc the index into trapMap + addByteCode(ByteCode.ICONST_5); + addByteCode(ByteCode.ISHR); + addByteCode(ByteCode.SWAP); + + // calc the mask + push(0x1f); + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.ICONST_1); + addByteCode(ByteCode.SWAP); + addByteCode(ByteCode.ISHL); + addByteCode(ByteCode.SWAP); + + // stack has mask, index + + // get refernce to trapMap + addByteCode(ByteCode.ALOAD_0); // 'this' + classFile.add(ByteCode.GETFIELD, + superClassSlashName, + "debug_trapMap", "[I"); + + // get our entry + addByteCode(ByteCode.SWAP); + addByteCode(ByteCode.IALOAD); + + // 'and' against the map + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.IFEQ, labelRetPop0); + + // here if we've hit a trap (stack empty) +// System.out.println(4+" " +stackTop+" "+classFile.getStackTop()); + + // call our "hook caller" helper in Debug superclass + addByteCode(ByteCode.ALOAD_0); // 'this' + aload(contextLocal); + addByteCode(ByteCode.ICONST_1); // retVal[1] array + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + addByteCode(ByteCode.DUP_X2); //retVal will be on stack after call + iload(debug_pcLocal); + aload(variableObjectLocal); + + addVirtualInvoke(superClassSlashName, + "callTrappedHook", + "(Lorg/mozilla/javascript/Context;[Ljava/lang/Object;" + + "ILorg/mozilla/javascript/Scriptable;)", + "I"); + // fall through to labelHandleHookAnswer (same code for both hooks) + + // Deal with Hook Answer, stack has retval object, action code int + markLabel(labelHandleHookAnswer); + + // available actions... + // org.mozilla.javascript.DeepBytecodeHook.CONTINUE = 0; + // org.mozilla.javascript.DeepBytecodeHook.THROW = 1; + // org.mozilla.javascript.DeepBytecodeHook.RETURN_VALUE = 2; + + // is it just 'continue'? + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.IFEQ, labelRetPop2); + + // either a throw or return value -- get the value (Object[0]) + addByteCode(ByteCode.SWAP); + addByteCode(ByteCode.ICONST_0); + addByteCode(ByteCode.AALOAD); + addByteCode(ByteCode.SWAP); + + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.ICONST_1); + addByteCode(ByteCode.IF_ICMPNE, labelNotThrow); + addByteCode(ByteCode.POP); // get the object to the top + addByteCode(ByteCode.CHECKCAST, "java/lang/Throwable"); + addByteCode(ByteCode.ATHROW); + + classFile.adjustStackTop(2); + markLabel(labelNotThrow); + // sanity check + addByteCode(ByteCode.ICONST_2); + addByteCode(ByteCode.IF_ICMPNE, labelRetPop1); + addByteCode(ByteCode.ARETURN); + + classFile.adjustStackTop(2); + markLabel(labelRetPop2); + addByteCode(ByteCode.POP); + markLabel(labelRetPop1); + addByteCode(ByteCode.POP); + markLabel(labelRetPop0); + addByteCode(ByteCode.RET, debugStopSubRetLocal); + + // target for jumping over the subroutine + markLabel(labelSkip); + addByteCode(ByteCode.NOP); +// System.out.println(5+" " +stackTop+" "+classFile.getStackTop()); + } + + private short addDebugPCEntry(short lineno) { + if (0 == debugLineEntryCount) { + debugLineMap = new short[DEBUG_LINE_MAP_INITIAL_SIZE]; + } + if (debugLineMap.length == debugLineEntryCount+1) { + short[] newMap = + new short[debugLineMap.length+DEBUG_LINE_MAP_RESIZE_INCREMENT]; + for (short i = 0; i < debugLineMap.length; i++) + newMap[i] = debugLineMap[i]; + debugLineMap = newMap; + } + debugLineMap[++debugLineEntryCount] = lineno; + return debugLineEntryCount; + } + private void writeDebugPCEntries() { + String s = null; + if (null != debugLineMap && 0 != debugLineEntryCount) { + // I have a short[]. I want a char[]. Java won't let me cast. + char[] ca = new char[debugLineEntryCount+1]; + for (short i = 0; i < debugLineEntryCount+1; i++) + ca[i] = (char) debugLineMap[i]; + s = new String(ca); + } + + addByteCode(ByteCode.ALOAD_0); // 'this' + if (s == null) + addByteCode(ByteCode.ACONST_NULL); + else + push(s); + classFile.add(ByteCode.PUTFIELD, + superClassSlashName, + "debug_linenoMap", "Ljava/lang/String;"); + } + private void buildDebugTrapMap() { + if (null != debugLineMap && 0 != debugLineEntryCount) { + short count = (short)((debugLineEntryCount+1)/32); + if (0 != (debugLineEntryCount+1)%32) + count++; + + addByteCode(ByteCode.ALOAD_0); // 'this' + push(count); + addByteCode(ByteCode.NEWARRAY, 10); // array of int + classFile.add(ByteCode.PUTFIELD, + superClassSlashName, + "debug_trapMap", "[I"); + } + } + + private void visitFunction(Node node) { + addByteCode(ByteCode.NEW, "org/mozilla/javascript/NativeClosure"); + addByteCode(ByteCode.DUP); + + // Context argument to constructor + aload(contextLocal); + + // Scope argument to constructor + aload(variableObjectLocal); + + // Function argument to constructor + Node fn = (Node) node.getProp(Node.FUNCTION_PROP); + Short index = (Short) fn.getProp(Node.FUNCTION_PROP); + aload(funObjLocal); + classFile.add(ByteCode.GETFIELD, "org/mozilla/javascript/NativeFunction", + "nestedFunctions", "[Lorg/mozilla/javascript/NativeFunction;"); + push(index.shortValue()); + addByteCode(ByteCode.AALOAD); + + addSpecialInvoke("org/mozilla/javascript/NativeClosure", + "", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/NativeFunction;)", "V"); + } + + private void visitTarget(Node node) { + Object lblObect = node.getProp(Node.LABEL_PROP); + if (lblObect == null) { + int label = markLabel(acquireLabel()); + node.putProp(Node.LABEL_PROP, new Integer(label)); + } + else { + int label = ((Integer)lblObect).intValue(); + markLabel(label); + } + + } + + private void visitGOTO(Node node, int type, Node child) { + Node target = (Node)(node.getProp(Node.TARGET_PROP)); + Object lblObect = target.getProp(Node.LABEL_PROP); + int targetLabel; + if (lblObect == null) { + targetLabel = acquireLabel(); + target.putProp(Node.LABEL_PROP, new Integer(targetLabel)); + } + else + targetLabel = ((Integer)lblObect).intValue(); + int fallThruLabel = acquireLabel(); + + if ((type == TokenStream.IFEQ) || (type == TokenStream.IFNE)) { + if (child == null) { + // can have a null child from visitSwitch which + // has already generated the code for the child + // and just needs the GOTO code emitted + addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)", "Z"); + if (type == TokenStream.IFEQ) + addByteCode(ByteCode.IFNE, targetLabel); + else + addByteCode(ByteCode.IFEQ, targetLabel); + } + else { + if (type == TokenStream.IFEQ) + generateCodeFromNode(child, node, targetLabel, fallThruLabel); + else + generateCodeFromNode(child, node, fallThruLabel, targetLabel); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + if (type == TokenStream.IFEQ) + addByteCode(ByteCode.IFNE, targetLabel); + else + addByteCode(ByteCode.IFEQ, targetLabel); + } + } + } + else { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + if (type == TokenStream.JSR) + addByteCode(ByteCode.JSR, targetLabel); + else + addByteCode(ByteCode.GOTO, targetLabel); + } + markLabel(fallThruLabel); + } + + private void visitEnumInit(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + addScriptRuntimeInvoke("initEnum", + "(Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Ljava/util/Enumeration;"); + short x = getNewWordLocal(); + astore(x); + node.putProp(Node.LOCAL_PROP, new Integer(x)); + } + + private void visitEnumNext(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + Node init = (Node) node.getProp(Node.ENUM_PROP); + Integer local = (Integer) init.getProp(Node.LOCAL_PROP); + aload(local.shortValue()); + addScriptRuntimeInvoke("nextEnum", "(Ljava/util/Enumeration;)", "Ljava/lang/Object;"); + } + + private void visitEnumDone(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + Node init = (Node) node.getProp(Node.ENUM_PROP); + Integer local = (Integer) init.getProp(Node.LOCAL_PROP); + releaseWordLocal(local.shortValue()); + } + + private void visitEnterWith(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + addScriptRuntimeInvoke("enterWith", + "(Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + astore(variableObjectLocal); + } + + private void visitLeaveWith(Node node, Node child) { + aload(variableObjectLocal); + addScriptRuntimeInvoke("leaveWith", + "(Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + astore(variableObjectLocal); + } + + private void resetTargets(Node node) + { + if (node.getType() == TokenStream.TARGET) { + node.putProp(Node.LABEL_PROP, null); + } + Node child = node.getFirstChild(); + while (child != null) { + resetTargets(child); + child = child.getNextSibling(); + } + } + + private void visitCall(Node node, int type, Node child) { + /* + * Generate code for call. + */ + + Node chelsea = child; // remember the first child for later + OptFunctionNode target = (OptFunctionNode)node.getProp(Node.DIRECTCALL_PROP); + if (target != null) { + generateCodeFromNode(child, node, -1, -1); + int regularCall = acquireLabel(); + + String className = classFile.fullyQualifiedForm(target.getClassName()); + String fieldName = className.replace('/', '_'); + + classFile.add(ByteCode.GETSTATIC, + classFile.fullyQualifiedForm(className), + fieldName, + "L" + className + ";"); + short stackHeight = classFile.getStackTop(); + + addByteCode(ByteCode.DUP2); + addByteCode(ByteCode.IF_ACMPNE, regularCall); + addByteCode(ByteCode.SWAP); + addByteCode(ByteCode.POP); + + addByteCode(ByteCode.DUP); + classFile.add(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()", "Lorg/mozilla/javascript/Scriptable;"); + aload(contextLocal); + addByteCode(ByteCode.SWAP); + + if (type == TokenStream.NEW) + addByteCode(ByteCode.ACONST_NULL); + else { + child = child.getNextSibling(); + generateCodeFromNode(child, node, -1, -1); + } +/* + Remember that directCall parameters are paired in 1 aReg and 1 dReg + If the argument is an incoming arg, just pass the orginal pair thru. + Else, if the argument is known to be typed 'Number', pass Void.TYPE + in the aReg and the number is the dReg + Else pass the JS object in the aReg and 0.0 in the dReg. +*/ + child = child.getNextSibling(); + while (child != null) { + boolean handled = false; + if ((child.getType() == TokenStream.GETVAR) + && inDirectCallFunction) { + OptLocalVariable lVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + if (lVar.isParameter()) { + handled = true; + aload(lVar.getJRegister()); + dload((short)(lVar.getJRegister() + 1)); + } + } + if (!handled) { + Integer childNumberFlag + = (Integer)(child.getProp(Node.ISNUMBER_PROP)); + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.BOTH)) { + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + generateCodeFromNode(child, node, -1, -1); + } + else { + generateCodeFromNode(child, node, -1, -1); + push(0.0); + } + } + resetTargets(child); + child = child.getNextSibling(); + } + + addByteCode(ByteCode.ALOAD_0); + classFile.add(ByteCode.GETFIELD, + classFile.fullyQualifiedForm(this.name), + "EmptyArray", + "[Ljava/lang/Object;"); + + if (type == TokenStream.NEW) + addVirtualInvoke(target.getClassName(), + "constructDirect", + target.getDirectCallParameterSignature(), + "Ljava/lang/Object;"); + else + addVirtualInvoke(target.getClassName(), + "callDirect", + target.getDirectCallParameterSignature(), + "Ljava/lang/Object;"); + + int beyond = acquireLabel(); + addByteCode(ByteCode.GOTO, beyond); + markLabel(regularCall, stackHeight); + addByteCode(ByteCode.POP); + + visitRegularCall(node, type, chelsea, true); + markLabel(beyond); + } + else { + visitRegularCall(node, type, chelsea, false); + } + } + + + private String getSimpleCallName(Node callNode) + /* + Find call trees that look like this : + (they arise from simple function invocations) + + CALL <-- this is the callNode node + GETPROP + NEWTEMP [USES: 1] + GETBASE 'name' + STRING 'name' + GETTHIS + USETEMP [TEMP: NEWTEMP [USES: 1]] + <-- arguments would be here + + and return the name found. + + */ + { +if (true) { + Node callBase = callNode.getFirstChild(); + if (callBase.getType() == TokenStream.GETPROP) { + Node callBaseChild = callBase.getFirstChild(); + if (callBaseChild.getType() == TokenStream.NEWTEMP) { + Node callBaseID = callBaseChild.getNextSibling(); + Node tempChild = callBaseChild.getFirstChild(); + if (tempChild.getType() == TokenStream.GETBASE) { + String functionName = tempChild.getString(); + if ((callBaseID != null) && + (callBaseID.getType() == TokenStream.STRING)) { + if (functionName.equals(callBaseID.getString())) { + Node thisChild = callBase.getNextSibling(); + if (thisChild.getType() == TokenStream.GETTHIS) { + Node useChild = thisChild.getFirstChild(); + if (useChild.getType() == TokenStream.USETEMP) { + Node temp = (Node)useChild.getProp(Node.TEMP_PROP); + if (temp == callBaseChild) { + return functionName; + } + } + } + } + } + } + } + } +} + return null; + } + + private void constructArgArray(int argCount) + { + if (argCount == 0) { + if (itsZeroArgArray >= 0) + aload(itsZeroArgArray); + else { + push(0); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + else { + if (argCount == 1) { + if (itsOneArgArray >= 0) + aload(itsOneArgArray); + else { + push(1); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + else { + push(argCount); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + } + + private void visitRegularCall(Node node, int type, + Node child, boolean firstArgDone) + { + /* + * Generate code for call. + * + * push + * anewarray Ljava/lang/Object; + * // "initCount" instances of code from here... + * dup + * push + * + * aastore + * //...to here + * invokestatic call + */ + + OptFunctionNode target = (OptFunctionNode)node.getProp(Node.DIRECTCALL_PROP); + Node chelsea = child; + int childCount = 0; + int argSkipCount = (type == TokenStream.NEW) ? 1 : 2; + while (child != null) { + childCount++; + child = child.getNextSibling(); + } + + child = chelsea; // re-start the iterator from the first child, + // REMIND - too bad we couldn't just back-patch the count ? + + int argIndex = -argSkipCount; + if (firstArgDone && (child != null)) { + child = child.getNextSibling(); + argIndex++; + aload(contextLocal); + addByteCode(ByteCode.SWAP); + } + else + aload(contextLocal); + + if (firstArgDone && (type == TokenStream.NEW)) + constructArgArray(childCount - argSkipCount); + + boolean isSpecialCall = node.getProp(Node.SPECIALCALL_PROP) != null; + boolean isSimpleCall = false; + String simpleCallName = null; + if (type != TokenStream.NEW) { + simpleCallName = getSimpleCallName(node); + if (simpleCallName != null && !isSpecialCall && debugLevel < 3) { + isSimpleCall = true; + push(simpleCallName); + aload(variableObjectLocal); + child = child.getNextSibling().getNextSibling(); + argIndex = 0; + push(childCount - argSkipCount); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + + while (child != null) { + if (argIndex < 0) // not moving these arguments to the array + generateCodeFromNode(child, node, -1, -1); + else { + addByteCode(ByteCode.DUP); + push(argIndex); + if (target != null) { +/* + If this has also been a directCall sequence, the Number flag will + have remained set for any parameter so that the values could be + copied directly into the outgoing args. Here we want to force it + to be treated as not in a Number context, so we set the flag off. +*/ + boolean handled = false; + if ((child.getType() == TokenStream.GETVAR) + && inDirectCallFunction) { + OptLocalVariable lVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + if (lVar.isParameter()) { + child.putProp(Node.ISNUMBER_PROP, null); + generateCodeFromNode(child, node, -1, -1); + handled = true; + } + } + if (!handled) { + Integer childNumberFlag + = (Integer)(child.getProp(Node.ISNUMBER_PROP)); + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.BOTH)) { + addByteCode(ByteCode.NEW,"java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, -1, -1); + addDoubleConstructor(); + } + else + generateCodeFromNode(child, node, -1, -1); + } + } + else + generateCodeFromNode(child, node, -1, -1); + addByteCode(ByteCode.AASTORE); + } + argIndex++; + if (argIndex == 0) { + // now we need to construct the array + // (even if there are no args to load + // into it) - REMIND could pass null + // instead ? + constructArgArray(childCount - argSkipCount); + } + child = child.getNextSibling(); + } + + // set our pc into the top frame + if (debugLevel >= 1) { + aload(contextLocal); + aload(variableObjectLocal); + iload(debug_pcLocal); + addStaticInvoke("org/mozilla/javascript/debug/NativeDelegate", + "setPC", + "(Lorg/mozilla/javascript/Context;Lorg/mozilla/javascript/Scriptable;I)", + "V"); + } + + String className; + String methodNameNewObj; + String methodNameCall; + String callSignature; + + if (isSpecialCall) { + className = "org/mozilla/javascript/ScriptRuntime"; + methodNameNewObj = "newObjectSpecial"; + methodNameCall = "callSpecial"; + if (type != TokenStream.NEW) { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;" + + "[Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/String;I)"; // filename & linenumber + aload(thisObjLocal); + aload(variableObjectLocal); + push(itsSourceFile); + push(itsLineNumber); + } else { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/Object;" + + "[Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)"; + aload(variableObjectLocal); + } + } else { + methodNameNewObj = "newObject"; + if (isSimpleCall) { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)"; + methodNameCall = "callSimple"; + className = "org/mozilla/javascript/optimizer/OptRuntime"; + } else { + if (type == TokenStream.NEW) { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/Object;" + + "[Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)"; + aload(variableObjectLocal); + } else { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;" + + "[Ljava/lang/Object;)"; + } + methodNameCall = "call"; + className = "org/mozilla/javascript/ScriptRuntime"; + } + } + + /* NOTE: we assure above !(isSimpleCall && debugLevel >= 3). So + * we don't need to worry about the SimpleCall case in debug code. + */ + if (debugLevel >= 3) { + className = "org/mozilla/javascript/debug/NativeDelegate"; + if (isSpecialCall) { + methodNameNewObj = "newObjectSpecialDebug"; + methodNameCall = "callSpecialDebug"; + } else { + methodNameNewObj = "newObjectDebug"; + methodNameCall = "callDebug"; + } + } + + if (type == TokenStream.NEW) { + addStaticInvoke(className, + methodNameNewObj, + callSignature, + "Lorg/mozilla/javascript/Scriptable;"); + } else { + addStaticInvoke(className, + methodNameCall, + callSignature, + "Ljava/lang/Object;"); + } + } + + private void visitStatement(Node node) { + Object datum = node.getDatum(); + if (datum == null || !(datum instanceof Number)) + return; + itsLineNumber = ((Number) datum).shortValue(); + if (itsLineNumber == -1) + return; + classFile.addLineNumberEntry((short)itsLineNumber); + + if (debugLevel >= 1) { + if (debugLevel >= 6) { + int pc = addDebugPCEntry((short)itsLineNumber); + push(pc); + istore(debug_pcLocal); + + // see if a stop is requested anywhere in this function. + // if the flag is non-zero, then let the subroutine decide + // what to do. + + addByteCode(ByteCode.ALOAD_0); // load 'this' + classFile.add(ByteCode.GETFIELD, + superClassSlashName, + "debug_stopFlags", "I"); + int label = acquireLabel(); + addByteCode(ByteCode.IFEQ, label); + addByteCode(ByteCode.JSR, debugStopSubLabel); + classFile.adjustStackTop(-1); + markLabel(label); + } + else { + push(itsLineNumber); + istore(debug_pcLocal); + } + } + } + + + private void visitTryCatchFinally(Node node, Node child) { + /* Save the variable object, in case there are with statements + * enclosed by the try block and we catch some exception. + * We'll restore it for the catch block so that catch block + * statements get the right scope. + */ + + // OPT we only need to do this if there are enclosed WITH + // statements; could statically check and omit this if there aren't any. + + // XXX OPT Maybe instead do syntactic transforms to associate + // each 'with' with a try/finally block that does the exitwith. + + // For that matter: Why do we have leavewith? + + // XXX does Java have any kind of MOV(reg, reg)? + short savedVariableObject = getNewWordLocal(); + aload(variableObjectLocal); + astore(savedVariableObject); + + /* + * Generate the code for the tree; most of the work is done in IRFactory + * and NodeTransformer; Codegen just adds the java handlers for the + * javascript catch and finally clauses. */ + // need to set the stack top to 1 to account for the incoming exception + int startLabel = markLabel(acquireLabel(), (short)1); + + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + + Node catchTarget = (Node)node.getProp(Node.TARGET_PROP); + Node finallyTarget = (Node)node.getProp(Node.FINALLY_PROP); + + // control flow skips the handlers + int realEnd = acquireLabel(); + addByteCode(ByteCode.GOTO, realEnd); + + // javascript handler; unwrap exception and GOTO to javascript + // catch area. + if (catchTarget != null) { + + int jsHandler = classFile.markHandler(acquireLabel()); + + // MS JVM gets cranky if the exception object is left on the stack + short exceptionObject = getNewWordLocal(); + astore(exceptionObject); + + // reset the variable object local + aload(savedVariableObject); + astore(variableObjectLocal); + + aload(exceptionObject); + releaseWordLocal(exceptionObject); + + // unwrap the exception... + addScriptRuntimeInvoke("unwrapJavaScriptException", + "(Lorg/mozilla/javascript/JavaScriptException;)", + "Ljava/lang/Object;"); + + // get the label to goto + int catchLabel = + ((Integer)catchTarget.getProp(Node.LABEL_PROP)).intValue(); + addByteCode(ByteCode.GOTO, catchLabel); + + // mark the handler + classFile.addExceptionHandler + (startLabel, catchLabel, jsHandler, + "org/mozilla/javascript/JavaScriptException"); + } + + // finally handler; catch all exceptions, store to a local; JSR to + // the finally, then re-throw. + if (finallyTarget != null) { + int finallyHandler = classFile.markHandler(acquireLabel()); + + // reset the variable object local + aload(savedVariableObject); + astore(variableObjectLocal); + + short exnLocal = itsLocalAllocationBase++; + astore(exnLocal); + + // get the label to JSR to + int finallyLabel = + ((Integer)finallyTarget.getProp(Node.LABEL_PROP)).intValue(); + addByteCode(ByteCode.JSR, finallyLabel); + + // rethrow + aload(exnLocal); + addByteCode(ByteCode.ATHROW); + + // mark the handler + classFile.addExceptionHandler(startLabel, finallyLabel, + finallyHandler, null); // catch any + } + releaseWordLocal(savedVariableObject); + markLabel(realEnd); + } + + private void visitThrow(Node node, Node child) { + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + + addByteCode(ByteCode.NEW, + "org/mozilla/javascript/JavaScriptException"); + addByteCode(ByteCode.DUP_X1); + addByteCode(ByteCode.SWAP); + addSpecialInvoke("org/mozilla/javascript/JavaScriptException", + "", "(Ljava/lang/Object;)", "V"); + + addByteCode(ByteCode.ATHROW); + } + + private void visitReturn(Node node, Node child) { + visitStatement(node); + if (child != null) { + do { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } while (child != null); + } else if (inFunction) { + pushUndefined(); + } else { + aload(scriptResultLocal); + } + + if (epilogueLabel == -1) + epilogueLabel = classFile.acquireLabel(); + addByteCode(ByteCode.GOTO, epilogueLabel); + } + + private void visitSwitch(Node node, Node child) { + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + + // save selector value + short selector = getNewWordLocal(); + astore(selector); + + Vector cases = (Vector) node.getProp(Node.CASES_PROP); + for (int i=0; i < cases.size(); i++) { + Node thisCase = (Node) cases.elementAt(i); + Node first = thisCase.getFirstChild(); + generateCodeFromNode(first, thisCase, -1, -1); + aload(selector); + addScriptRuntimeInvoke("seqB", + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Boolean;"); + Node target = new Node(TokenStream.TARGET); + thisCase.replaceChild(first, target); + generateGOTO(TokenStream.IFEQ, target); + } + + Node defaultNode = (Node) node.getProp(Node.DEFAULT_PROP); + if (defaultNode != null) { + Node defaultTarget = new Node(TokenStream.TARGET); + defaultNode.getFirstChild().addChildToFront(defaultTarget); + generateGOTO(TokenStream.GOTO, defaultTarget); + } + + Node breakTarget = (Node) node.getProp(Node.BREAK_PROP); + generateGOTO(TokenStream.GOTO, breakTarget); + } + + private void generateGOTO(int type, Node target) { + Node GOTO = new Node(type); + GOTO.putProp(Node.TARGET_PROP, target); + visitGOTO(GOTO, type, null); + } + + private void visitUnary(Node node, Node child, int trueGOTO, int falseGOTO) { + int op = node.getInt(); + switch (op) { + case TokenStream.NOT: + { + if (trueGOTO != -1) { + generateCodeFromNode(child, node, falseGOTO, trueGOTO); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, falseGOTO); + addByteCode(ByteCode.GOTO, trueGOTO); + } + } + else { + int trueTarget = acquireLabel(); + int falseTarget = acquireLabel(); + int beyond = acquireLabel(); + generateCodeFromNode(child, node, trueTarget, falseTarget); + + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFEQ, falseTarget); + addByteCode(ByteCode.GOTO, trueTarget); + } + + markLabel(trueTarget); + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + addByteCode(ByteCode.GOTO, beyond); + markLabel(falseTarget); + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + markLabel(beyond); + classFile.adjustStackTop(-1); + } + break; + } + + case TokenStream.TYPEOF: + visitTypeof(node, child); + break; + + case TokenStream.VOID: + generateCodeFromNode(child, node, -1, -1); + addByteCode(ByteCode.POP); + pushUndefined(); + break; + + case TokenStream.BITNOT: + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, -1, -1); + addScriptRuntimeInvoke("toInt32", + "(Ljava/lang/Object;)", "I"); + push(-1); // implement ~a as (a ^ -1) + addByteCode(ByteCode.IXOR); + addByteCode(ByteCode.I2D); + addDoubleConstructor(); + break; + + case TokenStream.ADD: + case TokenStream.SUB: + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, -1, -1); + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D"); + if (op == TokenStream.SUB) { + addByteCode(ByteCode.DNEG); + } + addDoubleConstructor(); + break; + + default: + badTree(); + } + } + + private void visitTypeof(Node node, Node child) { + if (node.getType() == TokenStream.UNARYOP) { + generateCodeFromNode(child, node, -1, -1); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;)", "Ljava/lang/String;"); + return; + } + String name = node.getString(); + if (hasVarsInRegs) { + OptLocalVariable lVar = (OptLocalVariable) vars.get(name); + if (lVar != null) { + if (lVar.isNumber()) { + push("number"); + return; + } + visitGetVar(lVar, false, name); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;)", "Ljava/lang/String;"); + return; + } + } + aload(variableObjectLocal); + push(name); + addScriptRuntimeInvoke("typeofName", + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Ljava/lang/String;"); + } + + private void visitIncDec(Node node, boolean isInc) + { + Node child = node.getFirstChild(); + if (node.getProp(Node.ISNUMBER_PROP) != null) { + OptLocalVariable lVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + if (lVar.getJRegister() == -1) + lVar.assignJRegister(getNewWordPairLocal()); + dload(lVar.getJRegister()); + addByteCode(ByteCode.DUP2); + push(1.0); + addByteCode((isInc) ? ByteCode.DADD : ByteCode.DSUB); + dstore(lVar.getJRegister()); + } else { + OptLocalVariable lVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + String routine = (isInc) ? "postIncrement" : "postDecrement"; + if (hasVarsInRegs && child.getType() == TokenStream.GETVAR) { + if (lVar == null) + lVar = (OptLocalVariable) vars.get(child.getString()); + if (lVar.getJRegister() == -1) + lVar.assignJRegister(getNewWordLocal()); + aload(lVar.getJRegister()); + addByteCode(ByteCode.DUP); + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;)", "Ljava/lang/Object;"); + astore(lVar.getJRegister()); + } else { + if (child.getType() == TokenStream.GETPROP) { + Node getPropChild = child.getFirstChild(); + generateCodeFromNode(getPropChild, node, -1, -1); + generateCodeFromNode(getPropChild.getNextSibling(), node, -1, -1); + aload(variableObjectLocal); + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + if (child.getType() == TokenStream.GETELEM) { + routine += "Elem"; + Node getPropChild = child.getFirstChild(); + generateCodeFromNode(getPropChild, node, -1, -1); + generateCodeFromNode(getPropChild.getNextSibling(), node, -1, -1); + aload(variableObjectLocal); + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + aload(variableObjectLocal); + push(child.getString()); // push name + addScriptRuntimeInvoke(routine, + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Ljava/lang/Object;"); + } + } + } + } + } + + private boolean isArithmeticNode(Node node) { + int type = node.getType(); + return (type == TokenStream.SUB) + || (type == TokenStream.MOD) + || (type == TokenStream.DIV) + || (type == TokenStream.MUL); + } + + private void visitArithmetic(Node node, byte opCode, Node child, + Node parent) + { + Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if (childNumberFlag != null) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addByteCode(opCode); + } + else { + boolean childOfArithmetic = isArithmeticNode(parent); + if (!childOfArithmetic) { + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + } + generateCodeFromNode(child, node, -1, -1); + if (!isArithmeticNode(child)) + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D"); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + if (!isArithmeticNode(child.getNextSibling())) + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D"); + addByteCode(opCode); + if (!childOfArithmetic) { + addDoubleConstructor(); + } + } + } + + private void visitBitOp(Node node, int type, Node child) { + Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if (childNumberFlag == null) { + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + } + generateCodeFromNode(child, node, -1, -1); + + // special-case URSH; work with the target arg as a long, so + // that we can return a 32-bit unsigned value, and call + // toUint32 instead of toInt32. + if (type == TokenStream.URSH) { + addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)", "J"); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I"); + // Looks like we need to explicitly mask the shift to 5 bits - + // LUSHR takes 6 bits. + push(31); + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.LUSHR); + addByteCode(ByteCode.L2D); + addDoubleConstructor(); + return; + } + if (childNumberFlag == null) { + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I"); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I"); + } + else { + addScriptRuntimeInvoke("toInt32", "(D)", "I"); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addScriptRuntimeInvoke("toInt32", "(D)", "I"); + } + switch (type) { + case TokenStream.BITOR: + addByteCode(ByteCode.IOR); + break; + case TokenStream.BITXOR: + addByteCode(ByteCode.IXOR); + break; + case TokenStream.BITAND: + addByteCode(ByteCode.IAND); + break; + case TokenStream.RSH: + addByteCode(ByteCode.ISHR); + break; + case TokenStream.LSH: + addByteCode(ByteCode.ISHL); + break; + default: + badTree(); + } + addByteCode(ByteCode.I2D); + if (childNumberFlag == null) { + addDoubleConstructor(); + } + } + + private boolean nodeIsDirectCallParameter(Node node) { + if (node.getType() == TokenStream.GETVAR) { + OptLocalVariable lVar + = (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP)); + if (lVar != null && lVar.isParameter() && inDirectCallFunction && + !itsForcedObjectParameters) + { + return true; + } + } + return false; + } + + private void genSimpleCompare(int op, int trueGOTO, int falseGOTO) { + switch (op) { + case TokenStream.LE : + addByteCode(ByteCode.DCMPG); + addByteCode(ByteCode.IFLE, trueGOTO); + break; + case TokenStream.GE : + addByteCode(ByteCode.DCMPL); + addByteCode(ByteCode.IFGE, trueGOTO); + break; + case TokenStream.LT : + addByteCode(ByteCode.DCMPG); + addByteCode(ByteCode.IFLT, trueGOTO); + break; + case TokenStream.GT : + addByteCode(ByteCode.DCMPL); + addByteCode(ByteCode.IFGT, trueGOTO); + break; + } + if (falseGOTO != -1) + addByteCode(ByteCode.GOTO, falseGOTO); + } + + private void visitGOTOingRelOp(Node node, Node child, Node parent, + int trueGOTO, int falseGOTO) + { + int op = node.getInt(); + Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.BOTH)) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + genSimpleCompare(op, trueGOTO, falseGOTO); + } + else { + if (op == TokenStream.INSTANCEOF) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addScriptRuntimeInvoke("instanceOf", + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + } else if (op == TokenStream.IN) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + aload(variableObjectLocal); + addScriptRuntimeInvoke("in", + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)","Z"); + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + } else { + Node rChild = child.getNextSibling(); + boolean leftIsDCP = nodeIsDirectCallParameter(child); + boolean rightIsDCP = nodeIsDirectCallParameter(rChild); + if (leftIsDCP || rightIsDCP) { + if (leftIsDCP) { + if (rightIsDCP) { + OptLocalVariable lVar1 + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + aload(lVar1.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + OptLocalVariable lVar2 + = (OptLocalVariable)(rChild.getProp(Node.VARIABLE_PROP)); + aload(lVar2.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + dload((short)(lVar1.getJRegister() + 1)); + dload((short)(lVar2.getJRegister() + 1)); + genSimpleCompare(op, trueGOTO, falseGOTO); + markLabel(notNumbersLabel); + // fall thru to generic handling + } + else { // just the left child is a DCP, if the right child + // is a number it's worth testing the left + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.RIGHT)) { + OptLocalVariable lVar1 + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + aload(lVar1.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + dload((short)(lVar1.getJRegister() + 1)); + generateCodeFromNode(rChild, node, -1, -1); + genSimpleCompare(op, trueGOTO, falseGOTO); + markLabel(notNumbersLabel); + // fall thru to generic handling + } + } + } + else { // just the right child is a DCP, if the left child + // is a number it's worth testing the right + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.LEFT)) { + OptLocalVariable lVar2 + = (OptLocalVariable)(rChild.getProp(Node.VARIABLE_PROP)); + aload(lVar2.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + generateCodeFromNode(child, node, -1, -1); + dload((short)(lVar2.getJRegister() + 1)); + genSimpleCompare(op, trueGOTO, falseGOTO); + markLabel(notNumbersLabel); + // fall thru to generic handling + } + } + } + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(rChild, node, -1, -1); + if (childNumberFlag == null) { + if (op == TokenStream.GE || op == TokenStream.GT) { + addByteCode(ByteCode.SWAP); + } + String routine = ((op == TokenStream.LT) + || (op == TokenStream.GT)) ? "cmp_LT" : "cmp_LE"; + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;Ljava/lang/Object;)", "I"); + } + else { + boolean doubleThenObject + = (childNumberFlag.intValue() == Node.LEFT); + if (op == TokenStream.GE || op == TokenStream.GT) { + if (doubleThenObject) { + addByteCode(ByteCode.DUP_X2); + addByteCode(ByteCode.POP); + doubleThenObject = false; + } + else { + addByteCode(ByteCode.DUP2_X1); + addByteCode(ByteCode.POP2); + doubleThenObject = true; + } + } + String routine = ((op == TokenStream.LT) + || (op == TokenStream.GT)) ? "cmp_LT" : "cmp_LE"; + if (doubleThenObject) + addOptRuntimeInvoke(routine, + "(DLjava/lang/Object;)", "I"); + else + addOptRuntimeInvoke(routine, + "(Ljava/lang/Object;D)", "I"); + } + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + } + } + } + + private void visitRelOp(Node node, Node child, Node parent) { + /* + this is the version that returns an Object result + */ + int op = node.getInt(); + Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if (((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.BOTH)) + || (op == TokenStream.INSTANCEOF) + || (op == TokenStream.IN)) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + int trueGOTO = acquireLabel(); + int skip = acquireLabel(); + if (op == TokenStream.INSTANCEOF) { + addScriptRuntimeInvoke("instanceOf", + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, trueGOTO); + } + else { + if (op == TokenStream.IN) { + addScriptRuntimeInvoke("in", + "(Ljava/lang/Object;Ljava/lang/Object;)","Z"); + addByteCode(ByteCode.IFNE, trueGOTO); + } + else { + genSimpleCompare(op, trueGOTO, -1); + } + } + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + addByteCode(ByteCode.GOTO, skip); + markLabel(trueGOTO); + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + markLabel(skip); + classFile.adjustStackTop(-1); // only have 1 of true/false + } + else { + String routine = ((op == TokenStream.LT) + || (op == TokenStream.GT)) ? "cmp_LTB" : "cmp_LEB"; + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + if (childNumberFlag == null) { + if (op == TokenStream.GE || op == TokenStream.GT) { + addByteCode(ByteCode.SWAP); + } + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Boolean;"); + } + else { + boolean doubleThenObject + = (childNumberFlag.intValue() == Node.LEFT); + if (op == TokenStream.GE || op == TokenStream.GT) { + if (doubleThenObject) { + addByteCode(ByteCode.DUP_X2); + addByteCode(ByteCode.POP); + doubleThenObject = false; + } + else { + addByteCode(ByteCode.DUP2_X1); + addByteCode(ByteCode.POP2); + doubleThenObject = true; + } + } + if (doubleThenObject) + addOptRuntimeInvoke(routine, + "(DLjava/lang/Object;)", + "Ljava/lang/Boolean;"); + else + addOptRuntimeInvoke(routine, + "(Ljava/lang/Object;D)", + "Ljava/lang/Boolean;"); + } + } + } + + private Number nodeIsConvertToObjectOfNumber(Node node) + { + if (node.getType() == TokenStream.CONVERT) { + Object toType = node.getProp(Node.TYPE_PROP); + if (toType == ScriptRuntime.ObjectClass) { + Node convertChild = node.getFirstChild(); + if (convertChild.getType() == TokenStream.NUMBER) + return (Number)convertChild.getDatum(); + } + } + return null; + } + + private void visitEqOp(Node node, Node child, Node parent, int trueGOTO, int falseGOTO) { + int op = node.getInt(); + Node rightChild = child.getNextSibling(); + if (trueGOTO == -1) { + if (rightChild.getType() == TokenStream.PRIMARY && + rightChild.getInt() == TokenStream.NULL) + { + generateCodeFromNode(child, node, -1, -1); + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.IFNULL, 15); + pushUndefined(); + addByteCode(ByteCode.IF_ACMPEQ, 10); + if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ)) + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + else + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + addByteCode(ByteCode.GOTO, 7); + addByteCode(ByteCode.POP); + if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ)) + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + else + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + return; + } + + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + + // JavaScript 1.2 uses shallow equality for == and != + String name; + switch (op) { + case TokenStream.EQ: + name = version == Context.VERSION_1_2 ? "seqB" : "eqB"; + break; + + case TokenStream.NE: + name = version == Context.VERSION_1_2 ? "sneB" : "neB"; + break; + + case TokenStream.SHEQ: + name = "seqB"; + break; + + case TokenStream.SHNE: + name = "sneB"; + break; + + default: + name = null; + badTree(); + } + addScriptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Boolean;"); + } + else { + if (rightChild.getType() == TokenStream.PRIMARY && + rightChild.getInt() == TokenStream.NULL) + { + generateCodeFromNode(child, node, -1, -1); + /* + since we have to test for null && undefined we end up having to + push the operand twice and so have to GOTO to a pop site if the + first test passes. + We can avoid that for operands that are 'simple' i.e. don't generate + a lot of code and don't have side-effects. + For now, 'simple' means GETVAR + */ + boolean simpleChild = (child.getType() == TokenStream.GETVAR); + if (!simpleChild) addByteCode(ByteCode.DUP); + int popGOTO = acquireLabel(); + if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ)) { + addByteCode(ByteCode.IFNULL, + (simpleChild) ? trueGOTO : popGOTO); + short popStack = classFile.getStackTop(); + if (simpleChild) generateCodeFromNode(child, node, -1, -1); + pushUndefined(); + addByteCode(ByteCode.IF_ACMPEQ, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + if (!simpleChild) { + markLabel(popGOTO, popStack); + addByteCode(ByteCode.POP); + addByteCode(ByteCode.GOTO, trueGOTO); + } + } + else { + addByteCode(ByteCode.IFNULL, + (simpleChild) ? falseGOTO : popGOTO); + short popStack = classFile.getStackTop(); + if (simpleChild) generateCodeFromNode(child, node, -1, -1); + pushUndefined(); + addByteCode(ByteCode.IF_ACMPEQ, falseGOTO); + addByteCode(ByteCode.GOTO, trueGOTO); + if (!simpleChild) { + markLabel(popGOTO, popStack); + addByteCode(ByteCode.POP); + addByteCode(ByteCode.GOTO, falseGOTO); + } + } + return; + } + + Node rChild = child.getNextSibling(); + + Number numChild = nodeIsConvertToObjectOfNumber(rChild); + if (nodeIsDirectCallParameter(child) + && (numChild != null)) { + OptLocalVariable lVar1 + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + aload(lVar1.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + dload((short)(lVar1.getJRegister() + 1)); + push(numChild.doubleValue()); + addByteCode(ByteCode.DCMPL); + if (op == TokenStream.EQ) + addByteCode(ByteCode.IFEQ, trueGOTO); + else + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + markLabel(notNumbersLabel); + // fall thru into generic handling + } + + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(rChild, node, -1, -1); + + String name; + switch (op) { + case TokenStream.EQ: + name = version == Context.VERSION_1_2 ? "shallowEq" : "eq"; + addScriptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + break; + + case TokenStream.NE: + name = version == Context.VERSION_1_2 ? "shallowNeq" : "neq"; + addOptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + break; + + case TokenStream.SHEQ: + name = "shallowEq"; + addScriptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + break; + + case TokenStream.SHNE: + name = "shallowNeq"; + addOptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + break; + + default: + name = null; + badTree(); + } + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + } + } + + private void visitLiteral(Node node) { + if (node.getType() == TokenStream.STRING) { + // just load the string constant + push(node.getString()); + } else { + Number num = (Number) node.getDatum(); + if (node.getProp(Node.ISNUMBER_PROP) != null) { + push(num.doubleValue()); + } + else { + /* + * Generate code to create the new numeric constant + * + * new java/lang/ + * dup + * push + * invokestatic java/lang//(D)V + */ + String wrapperType = ""; + String signature = ""; + boolean isInteger = false; + + if (num instanceof Float) + num = new Double(num.floatValue()); + + // OPT: cache getClass() and compare == Type.class + if (num instanceof Integer) { + wrapperType = "Integer"; + signature = "I"; + isInteger = true; + /* + } else if (num instanceof Float) { + wrapperType = "Float"; + signature = "F"; + */ + } else if (num instanceof Double) { + wrapperType = "Double"; + signature = "D"; + } else if (num instanceof Byte) { + wrapperType = "Byte"; + signature = "B"; + isInteger = true; + } else if (num instanceof Short) { + wrapperType = "Short"; + signature = "S"; + isInteger = true; + } else { + throw Context.reportRuntimeError( + "NumberNode contains unsupported Number type: " + + num.getClass().getName()); + } + /* + There appears to be a limit in the JVM on either the number of + static fields in a class or the size of the class initializer. + Either way, we can't have any more than 2000 statically init'd + constants. + */ + if (itsConstantList.itsTop >= 2000) { + addByteCode(ByteCode.NEW, "java/lang/" + wrapperType); + addByteCode(ByteCode.DUP); + if (isInteger) + push(num.longValue()); + else + push(num.doubleValue()); + addSpecialInvoke("java/lang/" + + wrapperType, + "", + "(" + + signature + + ")", + "V"); + } + else { + classFile.add(ByteCode.GETSTATIC, + classFile.fullyQualifiedForm(this.name), + "jsK_" + + itsConstantList.addConstant(wrapperType, + signature, + num, + isInteger), + "Ljava/lang/" + wrapperType + ";"); + } + } + } + } + + private void emitConstantDudeInitializers() { + if (itsConstantList.itsTop == 0) + return; + classFile.startMethod("", "()V", + (short)(ClassFileWriter.ACC_STATIC + ClassFileWriter.ACC_FINAL)); + + for (int i = 0; i < itsConstantList.itsTop; i++) { + addByteCode(ByteCode.NEW, + "java/lang/" + + itsConstantList.itsList[i].itsWrapperType); + addByteCode(ByteCode.DUP); + if (itsConstantList.itsList[i].itsIsInteger) + push(itsConstantList.itsList[i].itsLValue); + else + push(itsConstantList.itsList[i].itsDValue); + addSpecialInvoke("java/lang/" + + itsConstantList.itsList[i].itsWrapperType, + "", + "(" + + itsConstantList.itsList[i].itsSignature + + ")", + "V"); + classFile.addField("jsK_" + i, + "Ljava/lang/" + + itsConstantList.itsList[i].itsWrapperType + ";", + ClassFileWriter.ACC_STATIC); + classFile.add(ByteCode.PUTSTATIC, + classFile.fullyQualifiedForm(this.name), + "jsK_" + i, + "Ljava/lang/" + + itsConstantList.itsList[i].itsWrapperType + ";"); + } + addByteCode(ByteCode.RETURN); + classFile.stopMethod((short)0, null); + } + + private void visitPrimary(Node node) { + int op = node.getInt(); + switch (op) { + + case TokenStream.THIS: + aload(thisObjLocal); + break; + + case TokenStream.NULL: + addByteCode(ByteCode.ACONST_NULL); + break; + + case TokenStream.TRUE: + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + break; + + case TokenStream.FALSE: + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + break; + + case TokenStream.UNDEFINED: + pushUndefined(); + break; + + default: + badTree(); + } + } + + private void visitObject(Node node) { + Node regexp = (Node) node.getProp(Node.REGEXP_PROP); + String fieldName = (String)(regexp.getProp(Node.REGEXP_PROP)); + aload(funObjLocal); + classFile.add(ByteCode.GETFIELD, + classFile.fullyQualifiedForm(this.name), + fieldName, "Lorg/mozilla/javascript/regexp/NativeRegExp;"); + } + + private void visitName(Node node) { + aload(variableObjectLocal); // get variable object + push(node.getString()); // push name + addScriptRuntimeInvoke("name", + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Ljava/lang/Object;"); + } + + private void visitSetName(Node node, Node child) { + String name = node.getFirstChild().getString(); + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + push(name); + addScriptRuntimeInvoke("setName", + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Ljava/lang/Object;"); + } + + private void visitGetVar(OptLocalVariable lVar, boolean isNumber, + String name) + { + // TODO: Clean up use of lVar here and in set. + if (hasVarsInRegs && lVar == null) + lVar = (OptLocalVariable) vars.get(name); + if (lVar != null) { + if (lVar.getJRegister() == -1) + if (lVar.isNumber()) + lVar.assignJRegister(getNewWordPairLocal()); + else + lVar.assignJRegister(getNewWordLocal()); + if (lVar.isParameter() && inDirectCallFunction && + !itsForcedObjectParameters) + { +/* + Remember that here the isNumber flag means that we want to + use the incoming parameter in a Number context, so test the + object type and convert the value as necessary. + +*/ + if (isNumber) { + aload(lVar.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = acquireLabel(); + int beyond = acquireLabel(); + addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel); + aload(lVar.getJRegister()); + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D"); + addByteCode(ByteCode.GOTO, beyond); + markLabel(isNumberLabel); + dload((short)(lVar.getJRegister() + 1)); + markLabel(beyond); + } else { + aload(lVar.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = acquireLabel(); + int beyond = acquireLabel(); + addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel); + aload(lVar.getJRegister()); + addByteCode(ByteCode.GOTO, beyond); + markLabel(isNumberLabel); + addByteCode(ByteCode.NEW,"java/lang/Double"); + addByteCode(ByteCode.DUP); + dload((short)(lVar.getJRegister() + 1)); + addDoubleConstructor(); + markLabel(beyond); + } + } else { + if (lVar.isNumber()) + dload(lVar.getJRegister()); + else + aload(lVar.getJRegister()); + } + return; + } + + aload(variableObjectLocal); + push(name); + aload(variableObjectLocal); + addScriptRuntimeInvoke("getProp", + "(Ljava/lang/Object;Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + + private void visitSetVar(Node node, Node child, boolean needValue) { + OptLocalVariable lVar = (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP)); + // XXX is this right? If so, clean up. + if (hasVarsInRegs && lVar == null) + lVar = (OptLocalVariable) vars.get(child.getString()); + if (lVar != null) { + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + if (lVar.getJRegister() == -1) { + if (lVar.isNumber()) + lVar.assignJRegister(getNewWordPairLocal()); + else + lVar.assignJRegister(getNewWordLocal()); + } + if (lVar.isParameter() + && inDirectCallFunction + && !itsForcedObjectParameters) { + if (node.getProp(Node.ISNUMBER_PROP) != null) { + if (needValue) addByteCode(ByteCode.DUP2); + aload(lVar.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = acquireLabel(); + int beyond = acquireLabel(); + addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel); + addByteCode(ByteCode.NEW,"java/lang/Double"); + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.DUP2_X2); + addByteCode(ByteCode.POP2); + addDoubleConstructor(); + astore(lVar.getJRegister()); + addByteCode(ByteCode.GOTO, beyond); + markLabel(isNumberLabel); + dstore((short)(lVar.getJRegister() + 1)); + markLabel(beyond); + } + else { + if (needValue) addByteCode(ByteCode.DUP); + astore(lVar.getJRegister()); + } + } + else { + if (node.getProp(Node.ISNUMBER_PROP) != null) { + dstore(lVar.getJRegister()); + if (needValue) dload(lVar.getJRegister()); + } + else { + astore(lVar.getJRegister()); + if (needValue) aload(lVar.getJRegister()); + } + } + return; + } + + // default: just treat like any other name lookup + child.setType(TokenStream.BINDNAME); + node.setType(TokenStream.SETNAME); + visitSetName(node, child); + if (!needValue) + addByteCode(ByteCode.POP); + } + + + private void visitGetProp(Node node, Node child) { + String s = (String) node.getProp(Node.SPECIAL_PROP_PROP); + if (s != null) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + String runtimeMethod = null; + if (s.equals("__proto__")) { + runtimeMethod = "getProto"; + } else if (s.equals("__parent__")) { + runtimeMethod = "getParent"; + } else { + badTree(); + } + addScriptRuntimeInvoke(runtimeMethod, + "(Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + return; + } + Node nameChild = child.getNextSibling(); + /* + for 'this.foo' we call thisGet which can skip some + casting overhead. + + */ + generateCodeFromNode(child, node, -1, -1); // the object + generateCodeFromNode(nameChild, node, -1, -1); // the name + if (nameChild.getType() == TokenStream.STRING) { + if ((child.getType() == TokenStream.PRIMARY && + child.getInt() == TokenStream.THIS) + || ((child.getType() == TokenStream.NEWTEMP) + && (child.getFirstChild().getType() == TokenStream.PRIMARY) + && (child.getFirstChild().getInt() == TokenStream.THIS)) + ) { + aload(variableObjectLocal); + addOptRuntimeInvoke("thisGet", + "(Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + aload(variableObjectLocal); + addScriptRuntimeInvoke("getProp", + "(Ljava/lang/Object;Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + } + else { + aload(variableObjectLocal); + addScriptRuntimeInvoke("getProp", + "(Ljava/lang/Object;Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + } + + private void visitSetProp(Node node, Node child) { + String s = (String) node.getProp(Node.SPECIAL_PROP_PROP); + if (s != null) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + String runtimeMethod = null; + if (s.equals("__proto__")) { + runtimeMethod = "setProto"; + } else if (s.equals("__parent__")) { + runtimeMethod = "setParent"; + } else { + badTree(); + } + addScriptRuntimeInvoke(runtimeMethod, + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + return; + } + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + addScriptRuntimeInvoke("setProp", + "(Ljava/lang/Object;Ljava/lang/String;" + + "Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + + private void visitBind(Node node, int type, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + // Generate code for "ScriptRuntime.bind(varObj, "s")" + aload(variableObjectLocal); // get variable object + push(node.getString()); // push name + addScriptRuntimeInvoke(type == TokenStream.BINDNAME ? "bind" : "getBase", + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Lorg/mozilla/javascript/Scriptable;"); + } + + private void getParentScope(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + classFile.add(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()", "Lorg/mozilla/javascript/Scriptable;"); + } + + private short getLocalFromNode(Node node) { + Integer localProp = (Integer) node.getProp(Node.LOCAL_PROP); + if (localProp == null) { + // for NEWLOCAL & USELOCAL, use the next pre-allocated + // register, otherwise for NEWTEMP & USETEMP, get the + // next available from the pool + short local = ((node.getType() == TokenStream.NEWLOCAL) + || (node.getType() == TokenStream.USELOCAL)) ? + itsLocalAllocationBase++ : getNewWordLocal(); + + node.putProp(Node.LOCAL_PROP, new Integer(local)); + return local; + } else { + return localProp.shortValue(); + } + } + + private void visitNewTemp(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + short local = getLocalFromNode(node); + addByteCode(ByteCode.DUP); + astore(local); + Integer n = (Integer) node.getProp(Node.USES_PROP); + if (n == null || n.intValue() == 0) + releaseWordLocal(local); + } + + private void visitUseTemp(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + Node temp = (Node) node.getProp(Node.TEMP_PROP); + short local = getLocalFromNode(temp); + + // if the temp node has a magic TARGET property, + // treat it as a RET to that temp. + if (node.getProp(Node.TARGET_PROP) != null) + addByteCode(ByteCode.RET, local); + else + aload(local); + Integer n = (Integer) temp.getProp(Node.USES_PROP); + if (n == null) { + releaseWordLocal(local); + } else { + if (n.intValue() < Integer.MAX_VALUE) { + int i = n.intValue() - 1; + if (i == 0) + releaseWordLocal(local); + temp.putProp(Node.USES_PROP, new Integer(i)); + } + } + } + + private void visitNewLocal(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + short local = getLocalFromNode(node); + addByteCode(ByteCode.DUP); + astore(local); + } + + private void visitUseLocal(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + Node temp = (Node) node.getProp(Node.LOCAL_PROP); + short local = getLocalFromNode(temp); + + // if the temp node has a magic TARGET property, + // treat it as a RET to that temp. + if (node.getProp(Node.TARGET_PROP) != null) + addByteCode(ByteCode.RET, local); + else + aload(local); + } + + private void dstore(short local) { + xstore(ByteCode.DSTORE_0, ByteCode.DSTORE, local); + } + + private void istore(short local) { + xstore(ByteCode.ISTORE_0, ByteCode.ISTORE, local); + } + + private void astore(short local) { + xstore(ByteCode.ASTORE_0, ByteCode.ASTORE, local); + } + + private void xstore(byte shortOp, byte op, short local) { + switch (local) { + case 0: + addByteCode(shortOp); + break; + case 1: + addByteCode((byte)(shortOp + 1)); + break; + case 2: + addByteCode((byte)(shortOp + 2)); + break; + case 3: + addByteCode((byte)(shortOp + 3)); + break; + default: + if (local < 0 || local >= Byte.MAX_VALUE) + throw new RuntimeException("bad local"); + addByteCode(op, (byte)local); + break; + } + } + + private void dload(short local) { + xload(ByteCode.DLOAD_0, ByteCode.DLOAD, local); + } + + private void iload(short local) { + xload(ByteCode.ILOAD_0, ByteCode.ILOAD, local); + } + + private void aload(short local) { + xload(ByteCode.ALOAD_0, ByteCode.ALOAD, local); + } + + private void xload(byte shortOp, byte op, short local) { + switch (local) { + case 0: + addByteCode(shortOp); + break; + case 1: + addByteCode((byte)(shortOp + 1)); + break; + case 2: + addByteCode((byte)(shortOp + 2)); + break; + case 3: + addByteCode((byte)(shortOp + 3)); + break; + default: + if (local < 0 || local >= Byte.MAX_VALUE) + throw new RuntimeException("bad local"); + addByteCode(op, (byte) local); + break; + } + } + + private short getNewWordPairLocal() { + short result = firstFreeLocal; + while (true) { + if (result >= (MAX_LOCALS - 1)) + break; + if (!locals[result] + && !locals[result + 1]) + break; + result++; + } + if (result < (MAX_LOCALS - 1)) { + locals[result] = true; + locals[result + 1] = true; + if (result == firstFreeLocal) { + for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) { + if (!locals[i]) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + } + else { + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private short reserveWordLocal(int local) { + if (getNewWordLocal() != local) + throw new RuntimeException("Local allocation error"); + return (short) local; + } + + private short getNewWordLocal() { + short result = firstFreeLocal; + locals[result] = true; + for (int i = firstFreeLocal + 1; i < MAX_LOCALS; i++) { + if (!locals[i]) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private void releaseWordpairLocal(short local) { + if (local < firstFreeLocal) + firstFreeLocal = local; + locals[local] = false; + locals[local + 1] = false; + } + + private void releaseWordLocal(short local) { + if (local < firstFreeLocal) + firstFreeLocal = local; + locals[local] = false; + } + + private void push(long l) { + if (l == -1) { + addByteCode(ByteCode.ICONST_M1); + } else if (0 <= l && l <= 5) { + addByteCode((byte) (ByteCode.ICONST_0 + l)); + } else if (Byte.MIN_VALUE <= l && l <= Byte.MAX_VALUE) { + addByteCode(ByteCode.BIPUSH, (byte) l); + } else if (Short.MIN_VALUE <= l && l <= Short.MAX_VALUE) { + addByteCode(ByteCode.SIPUSH, (short) l); + } else if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE) { + classFile.addLoadConstant((int)l); + } else { + classFile.addLoadConstant((long)l); + } + } + + private void push(double d) { + if (d == 0.0) { + addByteCode(ByteCode.DCONST_0); + } else if (d == 1.0) { + addByteCode(ByteCode.DCONST_1); + /* XXX this breaks all sorts of simple math. + } else if (Float.MIN_VALUE <= d && d <= Float.MAX_VALUE) { + loadWordConstant(classFile.addFloatConstant((float) d)); + */ + } else { + classFile.addLoadConstant((double)d); + } + } + + private void push(String s) { + classFile.addLoadConstant(s); + } + + private void pushUndefined() { + classFile.add(ByteCode.GETSTATIC, "org/mozilla/javascript/Undefined", + "instance", "Lorg/mozilla/javascript/Scriptable;"); + } + + + private void badTree() { + throw new RuntimeException("Bad tree in codegen"); + } + + private static final String normalFunctionSuperClassName = + "org.mozilla.javascript.NativeFunction"; + private static final String normalScriptSuperClassName = + "org.mozilla.javascript.NativeScript"; + private static final String debugFunctionSuperClassName = + "org.mozilla.javascript.debug.NativeFunctionDebug"; + private static final String debugScriptSuperClassName = + "org.mozilla.javascript.debug.NativeScriptDebug"; + private String superClassName; + private String superClassSlashName; + private String name; + private int ordinal; + boolean inFunction; + boolean inDirectCallFunction; + private ClassFileWriter classFile; + private Hashtable results; + private short scriptRuntimeIndex; + private int version; + private OptClassNameHelper itsNameHelper; + private static JavaScriptClassLoader classLoader; + + private String itsSourceFile; + private int itsLineNumber; + + private int stackDepth; + private int stackDepthMax; + + private static final int MAX_LOCALS = 256; + private boolean[] locals; + private short firstFreeLocal; + private short localsMax; + + private ConstantList itsConstantList; + + // special known locals. If you add a new local here, be sure + // to initialize it to -1 in startNewMethod + private short variableObjectLocal; + private short scriptResultLocal; + private short contextLocal; + private short argsLocal; + private short thisObjLocal; + private short funObjLocal; + private short debug_pcLocal; + private short debugStopSubRetLocal; + private short itsZeroArgArray; + private short itsOneArgArray; + + private boolean hasVarsInRegs; + private boolean itsForcedObjectParameters; + private boolean trivialInit; + private short itsLocalAllocationBase; + private OptVariableTable vars; + private OptVariableTable debugVars; + private int epilogueLabel; + private int optLevel; + private int debugLevel; + private int debugStopSubLabel; + + private short[] debugLineMap; + private short debugLineEntryCount; + private static final int DEBUG_LINE_MAP_INITIAL_SIZE = 100; + private static final int DEBUG_LINE_MAP_RESIZE_INCREMENT = 100; +} + +class ConstantList { + + int addConstant(String wrapperType, String signature, Number num, boolean isInteger) + { + long lValue = num.longValue(); + double dValue = num.doubleValue(); + + for (int i = 0; i < itsTop; i++) { + if (signature.equals(itsList[i].itsSignature)) { + if (itsList[i].itsIsInteger) { + if (isInteger && (lValue == itsList[i].itsLValue)) { + return i; + } + } + else { + if (!isInteger && (dValue == itsList[i].itsDValue)) { + return i; + } + } + } + } + + if (itsTop == itsList.length) { + ConstantDude[] newList = new ConstantDude[itsList.length * 2]; + System.arraycopy(itsList, 0, newList, 0, itsList.length); + itsList = newList; + } + if (isInteger) { + itsList[itsTop] = new ConstantDude(wrapperType, signature, lValue); + } + else { + itsList[itsTop] = new ConstantDude(wrapperType, signature, dValue); + } + return itsTop++; + } + + ConstantDude[] itsList = new ConstantDude[128]; + int itsTop; + +} + +class ConstantDude { + + ConstantDude(String wrapperType, String signature, long value) + { + itsWrapperType = wrapperType; + itsSignature = signature; + itsIsInteger = true; + itsLValue = value; + } + + ConstantDude(String wrapperType, String signature, double value) + { + itsWrapperType = wrapperType; + itsSignature = signature; + itsIsInteger = false; + itsDValue = value; + } + + String itsWrapperType; + String itsSignature; + long itsLValue; + double itsDValue; + boolean itsIsInteger; + +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/DataFlowBitSet.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/DataFlowBitSet.java new file mode 100644 index 00000000000..6e523a77694 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/DataFlowBitSet.java @@ -0,0 +1,130 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + + +package org.mozilla.javascript.optimizer; + +class DataFlowBitSet { + + private int itsBits[]; + int itsSize; + + DataFlowBitSet(int size) + { + itsSize = size; + itsBits = new int[(size >> 5) + 1]; + } + + int size() + { + return itsSize; + } + + void set(int n) + { + if ((n < 0) || (n >= itsSize)) + throw new RuntimeException("DataFlowBitSet bad index " + n); + itsBits[n >> 5] |= 1 << (n & 31); + } + + boolean test(int n) + { + if ((n < 0) || (n >= itsSize)) + throw new RuntimeException("DataFlowBitSet bad index " + n); + return ((itsBits[n >> 5] & (1 << (n & 31))) != 0); + } + + void not() + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] = ~itsBits[i]; + } + + void clear(int n) + { + if ((n < 0) || (n >= itsSize)) + throw new RuntimeException("DataFlowBitSet bad index " + n); + itsBits[n >> 5] &= ~(1 << (n & 31)); + } + + void clear() + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] = 0; + } + + void or(DataFlowBitSet b) + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] |= b.itsBits[i]; + } + + public String toString() + { + StringBuffer result = new StringBuffer(); + result.append("DataFlowBitSet, size = " + itsSize + "\n"); + for (int i = 0; i < itsBits.length; i++) + result.append(Integer.toHexString(itsBits[i]) + " "); + return result.toString(); + } + + boolean df(DataFlowBitSet in, DataFlowBitSet gen, DataFlowBitSet notKill) + { + int bitsLength = itsBits.length; + boolean changed = false; + for (int i = 0; i < bitsLength; i++) { + int oldBits = itsBits[i]; + itsBits[i] = (in.itsBits[i] | gen.itsBits[i]) & notKill.itsBits[i]; + changed |= (oldBits != itsBits[i]); + } + return changed; + } + + boolean df2(DataFlowBitSet in, DataFlowBitSet gen, DataFlowBitSet notKill) + { + int bitsLength = itsBits.length; + boolean changed = false; + for (int i = 0; i < bitsLength; i++) { + int oldBits = itsBits[i]; + itsBits[i] = (in.itsBits[i] & notKill.itsBits[i]) | gen.itsBits[i]; + changed |= (oldBits != itsBits[i]); + } + return changed; + } +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/FatBlock.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/FatBlock.java new file mode 100644 index 00000000000..1d062ec72b4 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/FatBlock.java @@ -0,0 +1,92 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; + +public class FatBlock { + + public FatBlock(int startNodeIndex, int endNodeIndex, Node[] statementNodes) + { + itsShadowOfFormerSelf = new Block(startNodeIndex, endNodeIndex, statementNodes); + } + + public Node getEndNode() + { return itsShadowOfFormerSelf.getEndNode(); } + + public Block getSlimmerSelf() + { return itsShadowOfFormerSelf; } + + private Block[] reduceToArray(Hashtable h) + { + Block[] result = null; + if (!h.isEmpty()) { + result = new Block[h.size()]; + Enumeration enum = h.elements(); + int i = 0; + while (enum.hasMoreElements()) { + FatBlock fb = (FatBlock)(enum.nextElement()); + result[i++] = fb.itsShadowOfFormerSelf; + } + } + return result; + } + + Block diet() + { + itsShadowOfFormerSelf.setSuccessorList(reduceToArray(itsSuccessors)); + itsShadowOfFormerSelf.setPredecessorList(reduceToArray(itsPredecessors)); + return itsShadowOfFormerSelf; + } + + public void addSuccessor(FatBlock b) { itsSuccessors.put(b, b); } + public void addPredecessor(FatBlock b) { itsPredecessors.put(b, b); } + + // all the Blocks that come immediately after this + private Hashtable itsSuccessors = new Hashtable(4); + // all the Blocks that come immediately before this + private Hashtable itsPredecessors = new Hashtable(4); + + private Block itsShadowOfFormerSelf; + + +} + diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/JavaScriptClassLoader.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/JavaScriptClassLoader.java new file mode 100644 index 00000000000..4829aa5cc72 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/JavaScriptClassLoader.java @@ -0,0 +1,71 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import java.util.*; + +/** + * Load generated classes that implement JavaScript scripts + * or functions. + * + * @author Norris Boyd + */ +final class JavaScriptClassLoader extends ClassLoader { + + JavaScriptClassLoader() { + } + + public Class defineClass(String name, byte data[]) { + return super.defineClass(name, data, 0, data.length); + } + + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + Class clazz = findLoadedClass(name); + if (clazz == null) { + // Experimental change for JShell, use outer class loader. + ClassLoader loader = JavaScriptClassLoader.class.getClassLoader(); + if (loader != null) + return loader.loadClass(name); + clazz = findSystemClass(name); + } + if (resolve) + resolveClass(clazz); + return clazz; + } +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptClassNameHelper.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptClassNameHelper.java new file mode 100644 index 00000000000..1f1b0726738 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptClassNameHelper.java @@ -0,0 +1,142 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import java.io.*; + +public class OptClassNameHelper implements ClassNameHelper { + + public String getGeneratingDirectory() { + return generatingDirectory; + } + + public synchronized String getJavaScriptClassName(String functionName, + boolean primary) + { + StringBuffer s = new StringBuffer(); + if (packageName != null && packageName.length() > 0) { + s.append(packageName); + s.append('.'); + } + s.append(initialName); + if (generatingDirectory != null) { + if (functionName != null) { + s.append('$'); + s.append(functionName); + } else if (!primary) { + s.append(++serial); + } + } else { + s.append(globalSerial++); + } + return s.toString(); + } + + public String getTargetClassFileName() { + return getTargetClassFileName(getInitialClassName()); + } + + public void setTargetClassFileName(String classFileName) { + int lastSeparator = classFileName.lastIndexOf('/'); + String initialName; + if (lastSeparator == -1) { + generatingDirectory = ""; + initialName = classFileName; + } else { + generatingDirectory = classFileName.substring(0, lastSeparator); + initialName = classFileName.substring(lastSeparator+1); + } + if (initialName.endsWith(".class")) + initialName = initialName.substring(0, initialName.length() - 6); + setInitialClassName(initialName); + } + + public String getTargetPackage() { + return packageName; + } + + public void setTargetPackage(String targetPackage) { + this.packageName = targetPackage; + } + + public String getTargetClassFileName(String className) { + if (generatingDirectory == null) + return null; + StringBuffer sb = new StringBuffer(); + if (generatingDirectory.length() > 0) { + sb.append(generatingDirectory); + sb.append(File.separator); + } + sb.append(className); + sb.append(".class"); + return sb.toString(); + } + + public Class getTargetExtends() { + return targetExtends; + } + + public void setTargetExtends(Class extendsClass) { + targetExtends = extendsClass; + } + + public Class[] getTargetImplements() { + return targetImplements; + } + + public void setTargetImplements(Class[] implementsClasses) { + targetImplements = implementsClasses; + } + + String getInitialClassName() { + return initialName; + } + + void setInitialClassName(String initialName) { + this.initialName = initialName; + serial = 0; + } + + private String generatingDirectory; + private String packageName = "org.mozilla.javascript.gen"; + private String initialName = "c"; + private static int globalSerial=1; + private int serial=1; + private Class targetExtends; + private Class[] targetImplements; +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptFunctionNode.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptFunctionNode.java new file mode 100644 index 00000000000..d80c76ee6b8 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptFunctionNode.java @@ -0,0 +1,128 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import java.util.*; + +public class OptFunctionNode extends FunctionNode { + + public OptFunctionNode(String name, Node left, Node right, + ClassNameHelper nameHelper) + { + super(name, left, right); + itsVariableTable = new OptVariableTable(); + OptClassNameHelper nh = (OptClassNameHelper) nameHelper; + itsClassName = nh.getJavaScriptClassName(name, false); + } + + public String getDirectCallParameterSignature() { + StringBuffer parameterSig = new StringBuffer( + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;"); + int pCount = itsVariableTable.getParameterCount(); + for (int i = 0; i < pCount; i++) { + parameterSig.append("Ljava/lang/Object;D"); + } + parameterSig.append("[Ljava/lang/Object;)"); + return parameterSig.toString(); + } + + public String getClassName() { + return itsClassName; + } + + public boolean isTargetOfDirectCall() { + return itsIsTargetOfDirectCall; + } + + public void addDirectCallTarget(FunctionNode target) { + if (itsDirectCallTargets == null) + itsDirectCallTargets = new Vector(); + for (int i = 0; i < itsDirectCallTargets.size(); i++) // OPT !! + if (((FunctionNode)itsDirectCallTargets.elementAt(i)) == target) + return; + itsDirectCallTargets.addElement(target); + } + + public Vector getDirectCallTargets() { + return itsDirectCallTargets; + } + + public void setIsTargetOfDirectCall() { + itsIsTargetOfDirectCall = true; + } + + public void setParameterNumberContext(boolean b) { + itsParameterNumberContext = b; + } + + public boolean getParameterNumberContext() { + return itsParameterNumberContext; + } + + public boolean containsCalls(int argCount) { + if ((argCount < itsContainsCallsCount.length) && (argCount >= 0)) + return itsContainsCallsCount[argCount]; + else + return itsContainsCalls; + } + + public void setContainsCalls(int argCount) { + if (argCount < itsContainsCallsCount.length) + itsContainsCallsCount[argCount] = true; + itsContainsCalls = true; + } + + public void incrementLocalCount() { + Integer localCount = (Integer)(getProp(Node.LOCALCOUNT_PROP)); + if (localCount == null) { + putProp(Node.LOCALCOUNT_PROP, new Integer(1)); + } else { + putProp(Node.LOCALCOUNT_PROP, + new Integer(localCount.intValue() + 1)); + } + } + + private String itsClassName; + private boolean itsIsTargetOfDirectCall; + private boolean itsContainsCalls; + private boolean[] itsContainsCallsCount = new boolean[4]; + private boolean itsParameterNumberContext; + private Vector itsDirectCallTargets; +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptIRFactory.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptIRFactory.java new file mode 100644 index 00000000000..5b02c34f505 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptIRFactory.java @@ -0,0 +1,69 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +/** + * This class allows the creation of nodes, and follows the Factory pattern. + * + * @see IRFactory + * @author Norris Boyd + */ +public class OptIRFactory extends IRFactory { + + public OptIRFactory(TokenStream ts, ClassNameHelper nameHelper) { + super(ts); + this.nameHelper = nameHelper; + } + + public Object createFunctionNode(String name, Object args, + Object statements) + { + if (name == null) + name = ""; + OptFunctionNode result = new OptFunctionNode(name, (Node) args, + (Node) statements, + nameHelper); + Context cx = Context.getCurrentContext(); + if (cx.getDebugLevel() > 0) + result.setRequiresActivation(true); + return result; + } + + private ClassNameHelper nameHelper; +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptLocalVariable.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptLocalVariable.java new file mode 100644 index 00000000000..56261cbc63c --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptLocalVariable.java @@ -0,0 +1,93 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +public class OptLocalVariable extends LocalVariable { + + public OptLocalVariable(String name, boolean isParameter) { + super(name, isParameter); + } + + public String toString() { + return "LocalVariable : '" + getName() + + "', index = " + getIndex() + + ", LiveAcrossCall = " + itsLiveAcrossCall + + ", isNumber = " + itsIsNumber + + ", isParameter = " + isParameter() + + ", JRegister = " + itsJRegister; + } + + public void setIsNumber() { itsIsNumber = true; } + public boolean isNumber() { return itsIsNumber; } + + public void markLiveAcrossCall() { itsLiveAcrossCall = true; } + public void clearLiveAcrossCall() { itsLiveAcrossCall = false; } + public boolean isLiveAcrossCall() { return itsLiveAcrossCall; } + + public void assignJRegister(short aJReg) { itsJRegister = aJReg; } + public short getJRegister() { return itsJRegister; } + + public boolean assignType(int aType) { return itsTypeUnion.add(aType); } + public int getTypeUnion() { return itsTypeUnion.getEvent(); } + + /** + * Get the offset into the bytecode where the variable becomes live. + * Used for generating the local variable table. + */ + public int getStartPC() { + return initPC; + } + + /** + * Set the offset into the bytecode where the variable becomes live. + * Used for generating the local variable table. + */ + public void setStartPC(int pc) { + initPC = pc; + } + + private short itsJRegister = -1; // unassigned + + private boolean itsLiveAcrossCall; + private boolean itsIsNumber; + + private TypeEvent itsTypeUnion = new TypeEvent(); // the union of all assigned types + + private int initPC; +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptRuntime.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptRuntime.java new file mode 100644 index 00000000000..d1207e343eb --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptRuntime.java @@ -0,0 +1,311 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +public final class OptRuntime extends ScriptRuntime { + + /** + * No instances should be created. + */ + private OptRuntime() { + } + + public static Object getElem(Object obj, double dblIndex, Scriptable scope) { + int index; + String s; + index = (int) dblIndex; + s = ((double) index) == dblIndex ? null : toString(new Double(dblIndex)); + Scriptable start = obj instanceof Scriptable + ? (Scriptable) obj + : toObject(scope, obj); + Scriptable m = start; + if (s != null) { + while (m != null) { + Object result = m.get(s, start); + if (result != Scriptable.NOT_FOUND) + return result; + m = m.getPrototype(); + } + return Undefined.instance; + } + while (m != null) { + Object result = m.get(index, start); + if (result != Scriptable.NOT_FOUND) + return result; + m = m.getPrototype(); + } + return Undefined.instance; + } + + public static Object setElem(Object obj, double dblIndex, Object value, + Scriptable scope) + { + int index; + String s; + index = (int) dblIndex; + s = ((double) index) == dblIndex ? + null : toString(new Double(dblIndex)); + + Scriptable start = obj instanceof Scriptable + ? (Scriptable) obj + : toObject(scope, obj); + Scriptable m = start; + if (s != null) { + do { + if (m.has(s, start)) { + m.put(s, start, value); + return value; + } + m = m.getPrototype(); + } while (m != null); + start.put(s, start, value); + return value; + } + + do { + if (m.has(index, start)) { + m.put(index, start, value); + return value; + } + m = m.getPrototype(); + } while (m != null); + start.put(index, start, value); + return value; + } + + public static Object add(Object val1, double val2) { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(null); + if (!(val1 instanceof String)) + return new Double(toNumber(val1) + val2); + return toString(val1) + numberToString(val2, 10); + } + + public static Object add(double val1, Object val2) { + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(null); + if (!(val2 instanceof String)) + return new Double(toNumber(val2) + val1); + return numberToString(val1, 10) + toString(val2); + } + + public static boolean neq(Object x, Object y) { + return !eq(x, y); + } + + public static boolean shallowNeq(Object x, Object y) { + return !shallowEq(x, y); + } + + public static Boolean cmp_LTB(double d1, Object val2) { + if (cmp_LT(d1, val2) == 1) + return Boolean.TRUE; + else + return Boolean.FALSE; + } + + public static int cmp_LT(double d1, Object val2) { + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(NumberClass); + if (!(val2 instanceof String)) { + if (d1 != d1) + return 0; + double d2 = toNumber(val2); + if (d2 != d2) + return 0; + return d1 < d2 ? 1 : 0; + } + return toString(new Double(d1)).compareTo(toString(val2)) < 0 ? 1 : 0; + } + + public static Boolean cmp_LTB(Object val1, double d2) { + if (cmp_LT(val1, d2) == 1) + return Boolean.TRUE; + else + return Boolean.FALSE; + } + + public static int cmp_LT(Object val1, double d2) { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(NumberClass); + if (!(val1 instanceof String)) { + double d1 = toNumber(val1); + if (d1 != d1) + return 0; + if (d2 != d2) + return 0; + return d1 < d2 ? 1 : 0; + } + return toString(val1).compareTo(toString(new Double(d2))) < 0 ? 1 : 0; + } + + public static Boolean cmp_LEB(double d1, Object val2) { + if (cmp_LE(d1, val2) == 1) + return Boolean.TRUE; + else + return Boolean.FALSE; + } + + public static int cmp_LE(double d1, Object val2) { + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(NumberClass); + if (!(val2 instanceof String)) { + if (d1 != d1) + return 0; + double d2 = toNumber(val2); + if (d2 != d2) + return 0; + return d1 <= d2 ? 1 : 0; + } + return toString(new Double(d1)).compareTo(toString(val2)) <= 0 ? 1 : 0; + } + + public static Boolean cmp_LEB(Object val1, double d2) { + if (cmp_LE(val1, d2) == 1) + return Boolean.TRUE; + else + return Boolean.FALSE; + } + + public static int cmp_LE(Object val1, double d2) { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(NumberClass); + if (!(val1 instanceof String)) { + double d1 = toNumber(val1); + if (d1 != d1) + return 0; + if (d2 != d2) + return 0; + return d1 <= d2 ? 1 : 0; + } + return toString(val1).compareTo(toString(new Double(d2))) <= 0 ? 1 : 0; + } + + public static int cmp(Object val1, Object val2) { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(NumberClass); + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(NumberClass); + if (!(val1 instanceof String) || !(val2 instanceof String)) { + double d1 = toNumber(val1); + if (d1 != d1) + return -1; + double d2 = toNumber(val2); + if (d2 != d2) + return -1; + return d1 < d2 ? 1 : 0; + } + return toString(val1).compareTo(toString(val2)) < 0 ? 1 : 0; + } + + public static Object callSimple(Context cx, String id, Scriptable scope, + Object[] args) + throws JavaScriptException + { + Scriptable obj = scope; + Object prop = null; + Scriptable thisArg = null; + search: + while (obj != null) { + Scriptable m = obj; + do { + prop = m.get(id, obj); + if (prop != Scriptable.NOT_FOUND) { + thisArg = obj; + break search; + } + m = m.getPrototype(); + } while (m != null); + obj = obj.getParentScope(); + } + if ((prop == null) || (prop == Scriptable.NOT_FOUND)) { + Object[] errorArgs = { id }; + throw Context.reportRuntimeError( + getMessage("msg.is.not.defined", errorArgs)); + } + + while (thisArg instanceof NativeWith) + thisArg = thisArg.getPrototype(); + if (thisArg instanceof NativeCall) + thisArg = ScriptableObject.getTopLevelScope(thisArg); + + Function function; + try { + function = (Function) prop; + } + catch (ClassCastException e) { + Object[] errorArgs = { toString(prop) }; + throw cx.reportRuntimeError( + getMessage("msg.isnt.function", errorArgs)); + } + + return function.call(cx, function.getParentScope(), thisArg, args); + } + + public static Object thisGet(Scriptable thisObj, String id, + Scriptable scope) + { + Object result = thisObj.get(id, thisObj); + if (result != Scriptable.NOT_FOUND) + return result; + + Scriptable start = thisObj; + if (start == null) { + throw Context.reportRuntimeError( + getMessage("msg.null.to.object", null)); + } + Scriptable m = start; + do { + result = m.get(id, start); + if (result != Scriptable.NOT_FOUND) + return result; + + m = m.getPrototype(); + } while (m != null); + return Undefined.instance; + } + + public static Object[] padStart(Object[] currentArgs, int count) { + Object[] result = new Object[currentArgs.length + count]; + System.arraycopy(currentArgs, 0, result, count, currentArgs.length); + return result; + } + + +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptTransformer.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptTransformer.java new file mode 100644 index 00000000000..36c409e0bcb --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptTransformer.java @@ -0,0 +1,190 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import java.util.Hashtable; +import java.util.Stack; +import java.util.Vector; + +/** + * This class performs node transforms to prepare for optimization. + * + * @see NodeTransformer + * @author Norris Boyd + */ + +class OptTransformer extends NodeTransformer { + private Hashtable theFnClassNameList; + + OptTransformer(Hashtable theFnClassNameList) { + this.theFnClassNameList = theFnClassNameList; + } + + public NodeTransformer newInstance() { + return new OptTransformer((Hashtable) theFnClassNameList.clone()); + } + + public IRFactory createIRFactory(TokenStream ts) { + return new IRFactory(ts); + } + + public Node transform(Node tree, Node enclosing, TokenStream ts) { + + // Collect all of the contained functions into a hashtable + // so that the call optimizer can access the class name & parameter + // count for any call it encounters + collectContainedFunctions(tree.getFirstChild()); + + return super.transform(tree, enclosing, ts); + } + + protected VariableTable createVariableTable() { + return new OptVariableTable(); + } + + private int detectDirectCall(Node node, Node tree) + { + Context cx = Context.getCurrentContext(); + int optLevel = cx.getOptimizationLevel(); + Node left = node.getFirstChild(); + + // count the arguments + int argCount = 0; + Node arg = left.getNextSibling(); + while (arg != null) { + arg = arg.getNextSibling(); + argCount++; + } + + if (tree.getType() == TokenStream.FUNCTION && optLevel > 0) { + if (left.getType() == TokenStream.NAME) { + markDirectCall(tree, node, argCount, left.getString()); + } else { + if (left.getType() == TokenStream.GETPROP) { + Node name = left.getFirstChild().getNextSibling(); + markDirectCall(tree, node, argCount, name.getString()); + } + } + } + + return argCount; + } + + protected void visitNew(Node node, Node tree) { + detectDirectCall(node, tree); + super.visitNew(node, tree); + } + + protected void visitCall(Node node, Node tree) { + int argCount = detectDirectCall(node, tree); + if (inFunction && (argCount == 0)) + ((OptFunctionNode)tree).setContainsCalls(argCount); + + super.visitCall(node, tree); + } + + /* + * Optimize a call site by converting call("a", b, c) into : + * + * FunctionObjectFor"a" <-- instance variable init'd by constructor + * + * // this is a DIRECTCALL node + * fn = GetProp(tmp = GetBase("a"), "a"); + * if (fn == FunctionObjectFor"a") + * fn.call(tmp, b, c) + * else + * ScriptRuntime.Call(fn, tmp, b, c) + */ + void markDirectCall(Node containingTree, Node callNode, int argCount, + String targetName) + { + OptFunctionNode theFunction + = (OptFunctionNode)theFnClassNameList.get(targetName); + if (theFunction != null) { + VariableTable varTable = theFunction.getVariableTable(); + // Refuse to directCall any function with more + // than 32 parameters - prevent code explosion + // for wacky test cases + if (varTable.getParameterCount() > 32) + return; + + if (argCount == varTable.getParameterCount()) { + callNode.putProp(Node.DIRECTCALL_PROP, theFunction); + ((OptFunctionNode)containingTree) + .addDirectCallTarget(theFunction); + theFunction.setIsTargetOfDirectCall(); + } + } + } + + /** + * Collect all of the contained functions into a hashtable + * so that the call optimizer can access the class name & parameter + * count for any call it encounters + */ + void collectContainedFunctions(Node node) { + for (Node tNode=node; tNode != null; tNode = tNode.getNextSibling()) { + if (tNode.getType() == TokenStream.FUNCTION) { + FunctionNode fnNode = (FunctionNode) + tNode.getProp(Node.FUNCTION_PROP); + if (fnNode.getFunctionName().length() != 0) { + String name = fnNode.getFunctionName(); + Object oldFn = theFnClassNameList.get(name); + if (oldFn == fnNode) { + // already processed this list of functions + return; + } + /* + if (oldFn != null) { + Object prop = fnNode.getProp(Node.BASE_LINENO_PROP); + Object[] errArgs = { name }; + Context.reportWarning( + Context.getMessage("msg.fn.redecl", errArgs), + (String) fnNode.getProp(Node.SOURCENAME_PROP), + prop == null ? 0 : ((Integer) prop).intValue(), + null, 0); + } + */ + theFnClassNameList.put(name, fnNode); + } + addParameters(fnNode); + } + } + } + +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptVariableTable.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptVariableTable.java new file mode 100644 index 00000000000..f7d7e362c12 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/OptVariableTable.java @@ -0,0 +1,74 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import java.io.*; +import java.util.*; + +public class OptVariableTable extends VariableTable { + + public void print(PrintWriter pw) + { + System.out.println("Variable Table, size = " + itsVariables.size()); + for (int i = 0; i < itsVariables.size(); i++) { + LocalVariable lVar = (LocalVariable)(itsVariables.elementAt(i)); + pw.println(lVar.toString()); + } + } + + public void assignParameterJRegs() + { + // 0 is reserved for function Object 'this' + // 1 is reserved for context + // 2 is reserved for parentScope + // 3 is reserved for script 'this' + short jReg = 4; + for (int i = 0; i < varStart; i++) { + OptLocalVariable lVar = (OptLocalVariable) + itsVariables.elementAt(i); + lVar.assignJRegister(jReg); + jReg += 3; // 3 is 1 for Object parm and 2 for double parm + } + } + + public LocalVariable createLocalVariable(String name, boolean isParameter) + { + return new OptLocalVariable(name, isParameter); + } + +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/Optimizer.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/Optimizer.java new file mode 100644 index 00000000000..5ddeb4d442c --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/Optimizer.java @@ -0,0 +1,1080 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +import java.io.PrintWriter; +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.File; +import java.io.IOException; + +import java.util.Vector; +import java.util.Hashtable; + +public class Optimizer { + + public Optimizer() + { + } + + static final boolean DEBUG_OPTIMIZER = false; + static final boolean DO_CONSTANT_FOLDING = true; + + static int blockCount = 0; + + boolean inDirectCallFunction; + boolean parameterUsedInNumberContext; + + void optimizeFunction(OptFunctionNode theFunction) + { + if (theFunction.requiresActivation()) return; + + inDirectCallFunction = theFunction.isTargetOfDirectCall(); + + Node[] theStatementNodes = buildStatementList(theFunction); + Block[] theBlocks = Block.buildBlocks(theStatementNodes); + PrintWriter pw = null; + try { + if (DEBUG_OPTIMIZER) { + pw = new PrintWriter( + new DataOutputStream( + new FileOutputStream( + new File("blocks" + + blockCount++ + ".txt")))); + pw.println(Block.toString(theBlocks, theStatementNodes)); + } + + OptVariableTable vars = (OptVariableTable) + theFunction.getVariableTable(); + if (vars != null) { + + vars.establishIndices(); + for (int i = 0; i < theStatementNodes.length; i++) + replaceVariableAccess(theStatementNodes[i], vars); + + if(DO_CONSTANT_FOLDING){ + foldConstants(theFunction, null); + } + + reachingDefDataFlow(vars, theBlocks); + typeFlow(vars, theBlocks); + findSinglyTypedVars(vars, theBlocks); + localCSE(theBlocks, theFunction); + if (!theFunction.requiresActivation()) { + /* + * Now that we know which local vars are in fact always + * Numbers, we re-write the tree to take advantage of + * that. Any arithmetic or assignment op involving just + * Number typed vars is marked so that the codegen will + * generate non-object code. + */ + parameterUsedInNumberContext = false; + for (int i = 0; i < theStatementNodes.length; i++) { + rewriteForNumberVariables(theStatementNodes[i]); + } + theFunction.setParameterNumberContext(parameterUsedInNumberContext); + //System.out.println("Function " + theFunction.getFunctionName() + " has parameters in number contexts : " + parameterUsedInNumberContext); + } + if (DEBUG_OPTIMIZER) { + for (int i = 0; i < theBlocks.length; i++) { + pw.println("For block " + theBlocks[i].getBlockID()); + theBlocks[i].printLiveOnEntrySet(pw, vars); + } + vars.print(pw); + } + + } + if (DEBUG_OPTIMIZER) pw.close(); + } + catch (IOException x) // for the DEBUG_OPTIMIZER i/o + { + } + finally { + if (DEBUG_OPTIMIZER) pw.close(); + } + } + + public void optimize(Node tree, int optLevel) + { + itsOptLevel = optLevel; + // run on one function at a time for now + PreorderNodeIterator iterator = tree.getPreorderIterator(); + Node node; + while ((node = iterator.nextNode()) != null) { + // should be able to do this more cheaply ? + // - run through initial block children ? + if (node.getType() == TokenStream.FUNCTION) { + OptFunctionNode theFunction = (OptFunctionNode) + node.getProp(Node.FUNCTION_PROP); + if (theFunction != null) + optimizeFunction(theFunction); + } + } + } + + void findSinglyTypedVars(VariableTable theVariables, Block theBlocks[]) + { +/* + discover the type events for each non-volatile variable (not live + across function calls). A type event is a def, which sets the target + type to the source type. +*/ + if (false) { + /* + it's enough to prove that every def point for a local variable + confers the same type on that variable. If that is the case (and + that type is 'Number') then we can assign that local variable to + a Double jReg for the life of the function. + */ + for (int i = 0; i < theBlocks.length; i++) { + theBlocks[i].findDefs(); + } + } + for (int i = 0; i < theVariables.size(); i++) { + OptLocalVariable lVar = (OptLocalVariable) theVariables.get(i); + if (!lVar.isParameter()) { + int theType = lVar.getTypeUnion(); + if (theType == TypeEvent.NumberType) { + lVar.setIsNumber(); + } + } + } + } + + void doBlockLocalCSE(Block theBlocks[], Block b, + Hashtable theCSETable, boolean beenThere[], + OptFunctionNode theFunction) + { + if (!beenThere[b.getBlockID()]) { + beenThere[b.getBlockID()] = true; + theCSETable = b.localCSE(theCSETable, theFunction); + Block succ[] = theBlocks[b.getBlockID()].getSuccessorList(); + if (succ != null) { + for (int i = 0; i < succ.length; i++) { + int index = succ[i].getBlockID(); + Block pred[] = theBlocks[index].getPredecessorList(); + if (pred.length == 1) + doBlockLocalCSE(theBlocks, succ[i], + (Hashtable)(theCSETable.clone()), + beenThere, theFunction); + } + } + } + } + + void localCSE(Block theBlocks[], OptFunctionNode theFunction) + { + boolean beenThere[] = new boolean[theBlocks.length]; + doBlockLocalCSE(theBlocks, theBlocks[0], null, beenThere, theFunction); + for (int i = 0; i < theBlocks.length; i++) { + if (!beenThere[i]) theBlocks[i].localCSE(null, theFunction); + } + } + + void typeFlow(VariableTable theVariables, Block theBlocks[]) + { + 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()) { + Block succ[] = theBlocks[vIndex].getSuccessorList(); + if (succ != null) { + for (int i = 0; i < succ.length; i++) { + int index = succ[i].getBlockID(); + visit[index] = true; + needRescan |= (index < vIndex); + } + } + } + } + if (vIndex == (theBlocks.length - 1)) { + if (needRescan) { + vIndex = 0; + needRescan = false; + } + else + break; + } + else + vIndex++; + } + } + + void reachingDefDataFlow(VariableTable theVariables, Block theBlocks[]) + { +/* + 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(theVariables); + } +/* + 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].getPredecessorList(); + if (pred != null) { + for (int i = 0; i < pred.length; i++) { + int index = pred[i].getBlockID(); + visit[index] = true; + needRescan |= (index > vIndex); + } + } + } + } + if (vIndex == 0) { + if (needRescan) { + vIndex = theBlocks.length - 1; + needRescan = false; + } + else + break; + } + else + vIndex--; + } +/* + The liveOnEntry, liveOnExit sets are now complete. Discover the variables + that are live across function calls. +*/ +/* + 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. +*/ + + for (int i = 0; i < theBlocks.length; i++) { + theBlocks[i].markVolatileVariables(theVariables); + } + } + +/* + Each directCall parameter is passed as a pair of values - an object + and a double. The value passed depends on the type of value available at + the call site. If a double is available, the object in java/lang/Void.TYPE + is passed as the object value, and if an object value is available, then + 0.0 is passed as the double value. + + The receiving routine always tests the object value before proceeding. + If the parameter is being accessed in a 'Number Context' then the code + sequence is : + if ("parameter_objectValue" == java/lang/Void.TYPE) + ...fine..., use the parameter_doubleValue + else + toNumber(parameter_objectValue) + + and if the parameter is being referenced in an Object context, the code is + if ("parameter_objectValue" == java/lang/Void.TYPE) + new Double(parameter_doubleValue) + else + ...fine..., use the parameter_objectValue + + If the receiving code never uses the doubleValue, it is converted on + entry to a Double instead. +*/ + + +/* + We're referencing a node in a Number context (i.e. we'd prefer it + was a double value). If the node is a parameter in a directCall + function, mark it as being referenced in this context. +*/ + void markDCPNumberContext(Node n) + { + if (inDirectCallFunction && (n.getType() == TokenStream.GETVAR)) + { + OptLocalVariable theVar + = (OptLocalVariable)(n.getProp(Node.VARIABLE_PROP)); + if ((theVar != null) && theVar.isParameter()) { + parameterUsedInNumberContext = true; + } + } + } + + boolean convertParameter(Node n) + { + if (inDirectCallFunction && (n.getType() == TokenStream.GETVAR)) + { + OptLocalVariable theVar + = (OptLocalVariable)(n.getProp(Node.VARIABLE_PROP)); + if ((theVar != null) && theVar.isParameter()) { + n.putProp(Node.ISNUMBER_PROP, null); + return true; + } + } + return false; + } + + int rewriteForNumberVariables(Node n) + { + switch (n.getType()) { + case TokenStream.POP : { + Node child = n.getFirstChild(); + int type = rewriteForNumberVariables(child); + if (type == TypeEvent.NumberType) + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NoType; + } + case TokenStream.NUMBER : + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NumberType; + + case TokenStream.GETVAR : { + OptLocalVariable theVar + = (OptLocalVariable)(n.getProp(Node.VARIABLE_PROP)); + if (theVar != null) { + if (inDirectCallFunction && theVar.isParameter()) { + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + else + if (theVar.isNumber()) { + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + } + return TypeEvent.NoType; + } + + case TokenStream.INC : + case TokenStream.DEC : { + Node child = n.getFirstChild(); // will be a GETVAR or GETPROP + if (child.getType() == TokenStream.GETVAR) { + OptLocalVariable theVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + if ((theVar != null) && theVar.isNumber()) { + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + markDCPNumberContext(child); + return TypeEvent.NumberType; + } + else + return TypeEvent.NoType; + } + else + return TypeEvent.NoType; + } + case TokenStream.SETVAR : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNextSibling(); + int rType = rewriteForNumberVariables(rChild); + OptLocalVariable theVar + = (OptLocalVariable)(n.getProp(Node.VARIABLE_PROP)); + if (inDirectCallFunction && theVar.isParameter()) { + if (rType == TypeEvent.NumberType) { + if (!convertParameter(rChild)) { + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + markDCPNumberContext(rChild); + return TypeEvent.NoType; + } + else + return rType; + } + else { + if ((theVar != null) && theVar.isNumber()) { + if (rType != TypeEvent.NumberType) { + n.removeChild(rChild); + Node newRChild = new Node(TokenStream.CONVERT, rChild); + newRChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToBack(newRChild); + } + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + markDCPNumberContext(rChild); + return TypeEvent.NumberType; + } + else { + if (rType == TypeEvent.NumberType) { + if (!convertParameter(rChild)) { + n.removeChild(rChild); + Node newRChild = new Node(TokenStream.CONVERT, rChild); + newRChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToBack(newRChild); + } + } + return TypeEvent.NoType; + } + } + } + case TokenStream.RELOP : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNextSibling(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + markDCPNumberContext(lChild); + markDCPNumberContext(rChild); + + if ((n.getInt() == TokenStream.INSTANCEOF) + || (n.getInt() == TokenStream.IN)) { + if (lType == TypeEvent.NumberType) { + if (!convertParameter(lChild)) { + n.removeChild(lChild); + Node nuChild = new Node(TokenStream.CONVERT, lChild); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToFront(nuChild); + } + } + if (rType == TypeEvent.NumberType) { + if (!convertParameter(rChild)) { + n.removeChild(rChild); + Node nuChild = new Node(TokenStream.CONVERT, rChild); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToBack(nuChild); + } + } + } + else { + if (convertParameter(lChild)) { + if (convertParameter(rChild)) { + return TypeEvent.NoType; + } + else { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.RIGHT)); + } + } + } + else { + if (convertParameter(rChild)) { + if (lType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.LEFT)); + } + } + else { + if (lType == TypeEvent.NumberType) { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + } + else { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.LEFT)); + } + } + else { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.RIGHT)); + } + } + } + } + } + // we actually build a boolean value + return TypeEvent.NoType; + } + + case TokenStream.ADD : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNextSibling(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + + + if (convertParameter(lChild)) { + if (convertParameter(rChild)) { + return TypeEvent.NoType; + } + else { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.RIGHT)); + } + } + } + else { + if (convertParameter(rChild)) { + if (lType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.LEFT)); + } + } + else { + if (lType == TypeEvent.NumberType) { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + else { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.LEFT)); + } + } + else { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.RIGHT)); + } + } + } + } + return TypeEvent.NoType; + } + + case TokenStream.BITXOR : + case TokenStream.BITOR : + case TokenStream.BITAND : + case TokenStream.RSH : + case TokenStream.LSH : + case TokenStream.SUB : + case TokenStream.MUL : + case TokenStream.DIV : + case TokenStream.MOD : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNextSibling(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + markDCPNumberContext(lChild); + markDCPNumberContext(rChild); + if (lType == TypeEvent.NumberType) { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + else { + if (!convertParameter(rChild)) { + n.removeChild(rChild); + Node newRChild = new Node(TokenStream.CONVERT, rChild); + newRChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToBack(newRChild); + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + } + return TypeEvent.NumberType; + } + } + else { + if (rType == TypeEvent.NumberType) { + if (!convertParameter(lChild)) { + n.removeChild(lChild); + Node newLChild = new Node(TokenStream.CONVERT, lChild); + newLChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToFront(newLChild); + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + } + return TypeEvent.NumberType; + } + else { + if (!convertParameter(lChild)) { + n.removeChild(lChild); + Node newLChild = new Node(TokenStream.CONVERT, lChild); + newLChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToFront(newLChild); + } + if (!convertParameter(rChild)) { + n.removeChild(rChild); + Node newRChild = new Node(TokenStream.CONVERT, rChild); + newRChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToBack(newRChild); + } + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + } + } + case TokenStream.SETELEM : { + Node arrayBase = n.getFirstChild(); + Node arrayIndex = arrayBase.getNextSibling(); + Node rValue = arrayIndex.getNextSibling(); + int baseType = rewriteForNumberVariables(arrayBase); + if (baseType == TypeEvent.NumberType) {// can never happen ??? + if (!convertParameter(arrayBase)) { + n.removeChild(arrayBase); + Node nuChild = new Node(TokenStream.CONVERT, arrayBase); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToFront(nuChild); + } + } + int indexType = rewriteForNumberVariables(arrayIndex); + if (indexType == TypeEvent.NumberType) { + // setting the ISNUMBER_PROP signals the codegen + // to use the scriptRuntime.setElem that takes + // a double index + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.LEFT)); + markDCPNumberContext(arrayIndex); + } + int rValueType = rewriteForNumberVariables(rValue); + if (rValueType == TypeEvent.NumberType) { + if (!convertParameter(rValue)) { + n.removeChild(rValue); + Node nuChild = new Node(TokenStream.CONVERT, rValue); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToBack(nuChild); + } + } + return TypeEvent.NoType; + } + case TokenStream.GETELEM : { + Node arrayBase = n.getFirstChild(); + Node arrayIndex = arrayBase.getNextSibling(); + int baseType = rewriteForNumberVariables(arrayBase); + if (baseType == TypeEvent.NumberType) {// can never happen ??? + if (!convertParameter(arrayBase)) { + n.removeChild(arrayBase); + Node nuChild = new Node(TokenStream.CONVERT, arrayBase); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToFront(nuChild); + } + } + int indexType = rewriteForNumberVariables(arrayIndex); + if (indexType == TypeEvent.NumberType) { + // setting the ISNUMBER_PROP signals the codegen + // to use the scriptRuntime.getElem that takes + // a double index + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.RIGHT)); + markDCPNumberContext(arrayIndex); + } + return TypeEvent.NoType; + } + case TokenStream.CALL : + { + FunctionNode target + = (FunctionNode)n.getProp(Node.DIRECTCALL_PROP); + if (target != null) { +/* + we leave each child as a Number if it can be. The codegen will + handle moving the pairs of parameters. +*/ + Node child = n.getFirstChild(); // the function + rewriteForNumberVariables(child); + child = child.getNextSibling(); // the 'this' object + rewriteForNumberVariables(child); + child = child.getNextSibling(); // the first arg + while (child != null) { + int type = rewriteForNumberVariables(child); + if (type == TypeEvent.NumberType) { + markDCPNumberContext(child); + } + child = child.getNextSibling(); + } + return TypeEvent.NoType; + } + // else fall thru... + } + default : { + Node child = n.getFirstChild(); + while (child != null) { + Node nextChild = child.getNextSibling(); + int type = rewriteForNumberVariables(child); + if (type == TypeEvent.NumberType) { + if (!convertParameter(child)) { + n.removeChild(child); + Node nuChild = new Node(TokenStream.CONVERT, child); + nuChild.putProp(Node.TYPE_PROP, Object.class); + if (nextChild == null) + n.addChildToBack(nuChild); + else + n.addChildBefore(nuChild, nextChild); + } + } + child = nextChild; + } + return TypeEvent.NoType; + } + } + } + + /* + Do constant folding, for integers, bools and strings + as well as for if() statements. + */ + void foldConstants(Node n, Node parent){ + Node lChild, rChild=null; // children + + lChild = n.getFirstChild(); + if(lChild == null){ // no children -- exit + return; + }else{ + rChild = lChild.getNextSibling(); + + if(rChild == null){ + foldConstants(lChild, n); // one child -- recurse + return; + } + } + + /* o.w. two children -- recurse on both first and proceed */ + foldConstants(lChild, n); + foldConstants(rChild, n); + + /* take care of all the other children */ + Node child = rChild.getNextSibling(); + while (child != null) { + foldConstants(child, n); + child = child.getNextSibling(); + } + + + /* children can change, so recompute them */ + lChild = n.getFirstChild(); + if(lChild == null){ // no children -- exit + return; + }else{ + rChild = lChild.getNextSibling(); + + if(rChild == null){ + return; + } + } + + /* at this point n has two children or more */ + int lt = lChild.getType(); + int rt = rChild.getType(); + + /* two or more children */ + switch(n.getType()){ + /* numerical addition and string concatenation */ + case TokenStream.ADD: + /* number addition -- both numbers */ + if(lt == TokenStream.NUMBER && rt == TokenStream.NUMBER){ + if(lChild.getDatum() instanceof Double || + rChild.getDatum() instanceof Double){ + parent.replaceChild(n, new Node(TokenStream.NUMBER, + new Double(lChild.getDouble() + rChild.getDouble()))); + }else{ + long longval = lChild.getLong() + rChild.getLong(); + + parent.replaceChild(n, new Node(TokenStream.NUMBER, + toSmallestType(longval))); + } + }else + /* string + string */ + if(lt == TokenStream.STRING && rt == TokenStream.STRING){ + parent.replaceChild(n, new Node(TokenStream.STRING, + lChild.getString() + rChild.getString())); + }else + /* string + num */ + if(lt == TokenStream.STRING && rt == TokenStream.NUMBER){ + parent.replaceChild(n, new Node(TokenStream.STRING, lChild.getString() + + ScriptRuntime.numberToString(rChild.getDouble(), 10))); + }else + /* num + string */ + if(lt == TokenStream.NUMBER && rt == TokenStream.STRING){ + parent.replaceChild(n, new Node(TokenStream.STRING, + ScriptRuntime.numberToString(lChild.getDouble(), 10) + + rChild.getString())); + } + /* can't do anything if we don't know both types - since 0 + object + is supposed to call toString on the object and do string concantenation + rather than addition + */ + break; + /* subtraction */ + case TokenStream.SUB: + /* both numbers */ + if(lt == TokenStream.NUMBER && rt == TokenStream.NUMBER){ + if(lChild.getDatum() instanceof Double || rChild.getDatum() instanceof Double){ + parent.replaceChild(n, new Node(TokenStream.NUMBER, + new Double(lChild.getDouble() - + rChild.getDouble()))); + }else{ + long longval = lChild.getLong() - rChild.getLong(); + parent.replaceChild(n, new Node(TokenStream.NUMBER, + toSmallestType(longval))); + } + }else + /* first 0: 0-x -> -x */ + if(lt == TokenStream.NUMBER && lChild.getDouble() == 0){ + parent.replaceChild(n, new Node(TokenStream.UNARYOP, rChild, + new Integer(TokenStream.SUB))); + }else + /* second 0: x-0 -> x */ + if(rt == TokenStream.NUMBER && rChild.getDouble() == 0){ + parent.replaceChild(n, lChild); + } + break; + /* handle multiplication by zero and one */ + case TokenStream.MUL: + /* both constants -- just multiply */ + if(lt == TokenStream.NUMBER && rt == TokenStream.NUMBER){ + /* at least one double */ + + if(lChild.getDatum() instanceof Double || rChild.getDatum() instanceof Double){ + parent.replaceChild(n, new Node(TokenStream.NUMBER, + new Double(lChild.getDouble() * + rChild.getDouble()))); + }else{ + long longval = + lChild.getLong() * + rChild.getLong(); + + parent.replaceChild(n, new Node(TokenStream.NUMBER, + toSmallestType(longval))); + } + /* one of them is constant -- zero or one */ + /* can't do zero, since zero * infinity has to become a NaN */ + }else{ + if(lt == TokenStream.NUMBER){ + double ld = ((Number)lChild.getDatum()).doubleValue(); + + if(ld == 1){ + parent.replaceChild(n, rChild); + } + }else + if(rt == TokenStream.NUMBER){ + double rd = ((Number)rChild.getDatum()).doubleValue(); + + if(rd == 1){ + parent.replaceChild(n, lChild); + } + } + } + break; + case TokenStream.DIV: + /* both constants -- just divide */ + if(lt == TokenStream.NUMBER && rt == TokenStream.NUMBER){ + /* at least one double */ + if((lChild.getDatum() instanceof Double || rChild.getDatum() instanceof Double)){ + double d = rChild.getDouble(); + + if(d == 0) + return; // division by zero + else + parent.replaceChild(n, new Node(TokenStream.NUMBER, + new Double(lChild.getDouble() / d))); + }else{ + int d = rChild.getInt(); + + if(d == 0) + return; // division by zero + else{ + long longval = + lChild.getLong() / + rChild.getLong(); + + parent.replaceChild(n, new Node(TokenStream.NUMBER, + toSmallestType(longval))); + } + } + /* 0/x , x!=0 */ + }else + /* x/1 */ + if((rt == TokenStream.NUMBER) && // added cast -roger + rChild.getDouble()==1){ + parent.replaceChild(n, lChild); + } + break; + case TokenStream.AND: + /* if the first one is false, replace with FALSE */ + if( + ((lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.NULL) || + (lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.UNDEFINED)) && + !IRFactory.hasSideEffects(rChild) + ){ + parent.replaceChild(n, new Node(TokenStream.PRIMARY, new Integer(TokenStream.FALSE))); + }else + /* if the second one is false, replace with FALSE */ + if( + ((rt==TokenStream.PRIMARY && rChild.getInt()==TokenStream.NULL) || + (rt==TokenStream.PRIMARY && rChild.getInt()==TokenStream.UNDEFINED)) && + !IRFactory.hasSideEffects(lChild) + ){ + parent.replaceChild(n, new Node(TokenStream.PRIMARY, new Integer(TokenStream.FALSE))); + }else + /* if first is true, set to second */ + if( + (lt == TokenStream.PRIMARY && ((Integer)lChild.getDatum()).intValue() == TokenStream.TRUE) || + (lt == TokenStream.NUMBER && ((Number)lChild.getDatum()).doubleValue() != 0) + ){ + parent.replaceChild(n, rChild); + }else + /* if second is true, set to first */ + if( + (rt == TokenStream.PRIMARY && ((Integer)rChild.getDatum()).intValue() == TokenStream.TRUE) || + (rt == TokenStream.NUMBER && ((Number)rChild.getDatum()).doubleValue() != 0) + ){ + parent.replaceChild(n, lChild); + } + break; + case TokenStream.OR: + /* if first is false, set to second */ + if( + (lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.NULL) || + (lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.UNDEFINED) || + (lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.FALSE) || + (lt == TokenStream.NUMBER && ((Number)lChild.getDatum()).doubleValue() == 0) + ){ + parent.replaceChild(n, rChild); + }else + /* if second is true, set to first */ + if( + (rt == TokenStream.PRIMARY && rChild.getInt() == TokenStream.NULL) || + (rt == TokenStream.PRIMARY && rChild.getInt() == TokenStream.UNDEFINED) || + (rt == TokenStream.PRIMARY && rChild.getInt() == TokenStream.FALSE) || + (rt == TokenStream.NUMBER && ((Number)rChild.getDatum()).doubleValue() == 0) + ){ + parent.replaceChild(n, lChild); + }else + /* if first one is true, replace with TRUE */ + if( + ((lt == TokenStream.PRIMARY && ((Integer)lChild.getDatum()).intValue() == TokenStream.TRUE) || + (lt == TokenStream.NUMBER && ((Number)lChild.getDatum()).doubleValue() != 0)) && + !IRFactory.hasSideEffects(rChild) + ){ + parent.replaceChild(n, new Node(TokenStream.PRIMARY, new Integer(TokenStream.TRUE))); + }else + /* if second one is true, replace with TRUE */ + if( + ((rt == TokenStream.PRIMARY && ((Integer)rChild.getDatum()).intValue() == TokenStream.TRUE) || + (rt == TokenStream.NUMBER && ((Number)rChild.getDatum()).doubleValue() != 0)) && + !IRFactory.hasSideEffects(lChild) + ){ + parent.replaceChild(n, new Node(TokenStream.PRIMARY, new Integer(TokenStream.TRUE))); + } + break; + case TokenStream.BLOCK: + /* if statement */ + if(lChild.getType() == TokenStream.IFNE){ + Node condition = lChild.getFirstChild(); + /* if(false) -> replace by the else clause if it exists */ + if(condition.getType() == TokenStream.PRIMARY){ + int id = condition.getInt(); + if(id == TokenStream.FALSE||id == TokenStream.NULL||id == TokenStream.UNDEFINED){ + Node elseClause = null; + try{ + elseClause = + rChild.getNextSibling().getNextSibling().getNextSibling().getFirstChild(); + }catch(Exception e){ + return; + } + + if(elseClause != null){ + parent.replaceChild(n, elseClause); + } + /* if(true) -> replace by the then clause if it exists */ + }else + if((condition.getType() == TokenStream.PRIMARY && condition.getInt() == TokenStream.TRUE) || + (condition.getType() == TokenStream.NUMBER && ((Number)condition.getDatum()).doubleValue() != 0)) + { + if(rChild.getType() == TokenStream.BLOCK){ + parent.replaceChild(n, rChild.getFirstChild()); + } + } + } + } + break; + }//switch + } + + /* + Converts a long to the smalles possible integral type. + */ + private static Number toSmallestType(long longval){ + // We generate the smallest possible type here + if (Byte.MIN_VALUE <= longval && longval <= Byte.MAX_VALUE) + return new Byte((byte)longval); + else if (Short.MIN_VALUE <= longval && + longval <= Short.MAX_VALUE) + return new Short((short)longval); + else if (Integer.MIN_VALUE <= longval && + longval <= Integer.MAX_VALUE) + return new Integer((int)longval); + else + return new Double(longval); + } + + void replaceVariableAccess(Node n, VariableTable theVariables) + { + Node child = n.getFirstChild(); + while (child != null) { + replaceVariableAccess(child, theVariables); + child = child.getNextSibling(); + } + switch (n.getType()) { + case TokenStream.SETVAR : { + String name = n.getFirstChild().getString(); + OptLocalVariable theVar = (OptLocalVariable) + theVariables.get(name); + if (theVar != null) + n.putProp(Node.VARIABLE_PROP, theVar); + } + break; + case TokenStream.GETVAR : { + String name = n.getString(); + OptLocalVariable theVar = (OptLocalVariable) + theVariables.get(name); + if (theVar != null) + n.putProp(Node.VARIABLE_PROP, theVar); + } + break; + default : + break; + } + } + + private Node[] buildStatementList(FunctionNode theFunction) + { + Vector nodeList = new Vector(); + + StmtNodeIterator iterator = new StmtNodeIterator(theFunction); + Node node = iterator.nextNode(); + while (node != null) { + nodeList.addElement(node); + node = iterator.nextNode(); + } + Node[] result = new Node[nodeList.size()]; + for (int i = 0; i < nodeList.size(); i++) { + result[i] = (Node)(nodeList.elementAt(i)); + } + return result; + } + + int itsOptLevel; +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/StmtNodeIterator.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/StmtNodeIterator.java new file mode 100644 index 00000000000..2cc60d9d9b7 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/StmtNodeIterator.java @@ -0,0 +1,94 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +import java.util.Stack; + +public class StmtNodeIterator { + + public StmtNodeIterator(Node start) + { + itsStart = start; + } + + private Node findFirstInterestingNode(Node theNode) + { + if (theNode == null) return null; + + if ((theNode.getType() == TokenStream.BLOCK) + || (theNode.getType() == TokenStream.LOOP) + || (theNode.getType() == TokenStream.FUNCTION)) { + if (theNode.getFirst() == null) { + return findFirstInterestingNode(theNode.getNext()); + } + else { + itsStack.push(theNode); + return findFirstInterestingNode(theNode.getFirst()); + } + } + else + return theNode; + } + + public Node nextNode() + { + if (itsCurrentNode == null) + return itsCurrentNode = findFirstInterestingNode(itsStart); + + itsCurrentNode = itsCurrentNode.getNext(); + if (itsCurrentNode == null) { + while ( ! itsStack.isEmpty()) { + Node n = (Node)(itsStack.pop()); + if (n.getNext() != null) { + return itsCurrentNode = findFirstInterestingNode(n.getNext()); + } + } + return null; + } + else + return itsCurrentNode = findFirstInterestingNode(itsCurrentNode); + } + + private Stack itsStack = new Stack(); + private Node itsStart; + private Node itsCurrentNode; + +} diff --git a/mozilla/js/rhino/org/mozilla/javascript/optimizer/TypeEvent.java b/mozilla/js/rhino/org/mozilla/javascript/optimizer/TypeEvent.java new file mode 100644 index 00000000000..52d9fda2766 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/optimizer/TypeEvent.java @@ -0,0 +1,67 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +public class TypeEvent { + + public static final int EventBitLength = 2; + + public static final int + AnyType = (1 << EventBitLength) - 1, + NumberType = 0x1, + NoType = 0x0; + + public TypeEvent(int theEvent) + { + itsEvent = theEvent; + } + + public TypeEvent() + { + itsEvent = NoType; + } + + public boolean add(int anOther) + { + return ((itsEvent |= anOther) != anOther); + } + + public int getEvent() { return itsEvent; } + + private int itsEvent; +} + diff --git a/mozilla/js/rhino/org/mozilla/javascript/tools/jsc/Main.java b/mozilla/js/rhino/org/mozilla/javascript/tools/jsc/Main.java new file mode 100644 index 00000000000..4e235bd6747 --- /dev/null +++ b/mozilla/js/rhino/org/mozilla/javascript/tools/jsc/Main.java @@ -0,0 +1,293 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Christine Begle + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript.tools.jsc; + +import java.io.*; +import java.util.*; +import java.lang.reflect.*; +import java.text.MessageFormat; +import org.mozilla.javascript.*; +import org.mozilla.javascript.tools.ToolErrorReporter; + +/** + * @author Norris Boyd + */ +public class Main { + + /** + * Main entry point. + * + * Process arguments as would a normal Java program. Also + * create a new Context and associate it with the current thread. + * Then set up the execution environment and begin to + * compile scripts. + */ + public static void main(String args[]) { + Context cx = Context.enter(); + + reporter = new ToolErrorReporter(true); + + cx.setErrorReporter(reporter); + + args = processOptions(cx, args); + + if ( ! reporter.hasReportedError()) + processSource(cx, args); + + cx.exit(); + } + + /** + * Parse arguments. + * + */ + public static String[] processOptions(Context cx, String args[]) { + cx.setTargetPackage(""); // default to no package + for (int i=0; i < args.length; i++) { + String arg = args[i]; + if (!arg.startsWith("-")) { + String[] result = new String[args.length - i]; + for (int j=i; j < args.length; j++) + result[j-i] = args[j]; + return result; + } + try { + if (arg.equals("-version") && ++i < args.length) { + int version = Integer.parseInt(args[i]); + cx.setLanguageVersion(version); + continue; + } + if ((arg.equals("-opt") || arg.equals("-O")) && + ++i < args.length) + { + int optLevel = Integer.parseInt(args[i]); + cx.setOptimizationLevel(optLevel); + continue; + } + if (arg.equals("-debuglevel") && ++i < args.length) { + int debugLevel = Integer.parseInt(args[i]); + cx.setDebugLevel(debugLevel); + continue; + } + } + catch (NumberFormatException e) { + cx.reportError( ToolErrorReporter.getMessage("msg.jsc.usage", + args[i] )); + continue; + } + if (arg.equals("-nosource")) { + cx.setGeneratingSource(false); + continue; + } + if (arg.equals("-debug") || arg.equals("-g")) { + cx.setGeneratingDebug(true); + continue; + } + if (arg.equals("-o") && ++i < args.length) { + String outFile = args[i]; + if (!Character.isJavaIdentifierStart(outFile.charAt(0))) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.invalid.classfile.name", + outFile)); + continue; + } + for ( int j = 1; j < outFile.length(); j++ ) { + if ( (!Character.isJavaIdentifierPart(outFile.charAt(j)) && + outFile.charAt(j) != '.') || (outFile.charAt(j) == '.' && + (!outFile.endsWith(".class") || j != outFile.length()-6 )) ) + { + cx.reportError(ToolErrorReporter.getMessage( + "msg.invalid.classfile.name", + outFile )); + break; + } + } + cx.setTargetClassFileName(outFile); + hasOutOption = true; + continue; + } + if (arg.equals("-package") && ++i < args.length) { + String targetPackage = args[i]; + for ( int j = 0; j < targetPackage.length(); j++ ) { + if ( !Character.isJavaIdentifierStart(targetPackage.charAt(j))) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.package.name", + targetPackage)); + continue; + } + for ( int k = ++j; k< targetPackage.length(); k++, j++ ) { + if (targetPackage.charAt(k) == '.' && + targetPackage.charAt(k-1) != '.' && + k != targetPackage.length()-1) + { + continue; + } + if (!Character.isJavaIdentifierPart( + targetPackage.charAt(k))) + { + cx.reportError(ToolErrorReporter.getMessage( + "msg.package.name", + targetPackage)); + continue; + } + continue; + } + } + cx.setTargetPackage(targetPackage); + continue; + } + if (arg.equals("-extends") && ++i < args.length) { + String targetExtends = args[i]; + try { + cx.setTargetExtends(Class.forName(targetExtends)); + } catch (ClassNotFoundException e) { + throw new Error(e.toString()); // TODO: better error + } + continue; + } + if (arg.equals("-implements") && ++i < args.length) { + // TODO: allow for multiple comma-separated interfaces. + String targetImplements = args[i]; + try { + Class[] implementsClasses = { Class.forName(targetImplements) }; + cx.setTargetImplements(implementsClasses); + } catch (ClassNotFoundException e) { + throw new Error(e.toString()); // TODO: better error + } + continue; + } + usage(arg); + } + // no file name + p(ToolErrorReporter.getMessage("msg.no.file")); + System.exit(1); + return null; + } + /** + * Print a usage message. + */ + public static void usage(String s) { + p(ToolErrorReporter.getMessage("msg.jsc.usage", s)); + System.exit(1); + } + + /** + * Compile JavaScript source. + * + */ + public static void processSource(Context cx, String[] filenames) { + if (hasOutOption && filenames.length > 1) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.multiple.js.to.file", + cx.getTargetClassFileName())); + } + for (int i=0; i < filenames.length; i++) { + String filename = filenames[i]; + File f = new File(filename); + + if (!f.exists()) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.jsfile.not.found", + filename)); + return; + } + if (!filename.endsWith(".js")) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.extension.not.js", + filename)); + return; + } + if (!hasOutOption) { + String name = f.getName(); + String nojs = name.substring(0, name.length() - 3); + String className = getClassName(nojs) + ".class"; + String out = f.getParent() == null ? className : f.getParent() + + File.separator + className; + cx.setTargetClassFileName(out); + } + if (cx.getTargetClassFileName() == null) { + cx.reportError(ToolErrorReporter.getMessage("msg.no-opt")); + } + try { + Reader in = new FileReader(filename); + cx.compileReader(null, in, filename, 1, null); + } + catch (FileNotFoundException ex) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.couldnt.open", + filename)); + return; + } + catch (IOException ioe) { + cx.reportError(ioe.toString()); + } + } + } + + /** + * Verify that class file names are legal Java identifiers. Substitute + * illegal characters with underscores, and prepend the name with an + * underscore if the file name does not begin with a JavaLetter. + */ + + static String getClassName(String name) { + char[] s = new char[name.length()+1]; + char c; + int j = 0; + + if (!Character.isJavaIdentifierStart(name.charAt(0))) { + s[j++] = '_'; + } + for (int i=0; i < name.length(); i++, j++) { + c = name.charAt(i); + if ( Character.isJavaIdentifierPart(c) ) { + s[j] = c; + } else { + s[j] = '_'; + } + } + return (new String(s)).trim(); + } + + private static void p(String s) { + System.out.println(s); + } + + private static boolean hasOutOption = false; + private static ToolErrorReporter reporter; +} + diff --git a/mozilla/js/rhino/org/mozilla/javascript/tools/shell/Main.java b/mozilla/js/rhino/org/mozilla/javascript/tools/shell/Main.java index 8d762c9f8c6..689167ae336 100644 --- a/mozilla/js/rhino/org/mozilla/javascript/tools/shell/Main.java +++ b/mozilla/js/rhino/org/mozilla/javascript/tools/shell/Main.java @@ -108,7 +108,7 @@ public class Main extends ScriptableObject { try { Class clazz = Class.forName( - "com.netscape.jsdebugging.ifcui.launcher.rhino.LaunchNetscapeJavaScriptDebugger"); + "org.mozilla.jsdebugging.ifcui.launcher.rhino.LaunchNetscapeJavaScriptDebugger"); ILaunchableDebugger debugger = (ILaunchableDebugger) clazz.newInstance(); debugger.launch(global.debug_dm, global.debug_stm, false); diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/Context.java b/mozilla/js/rhino/src/org/mozilla/javascript/Context.java index 4b41a602f7a..2a1f45a0457 100644 --- a/mozilla/js/rhino/src/org/mozilla/javascript/Context.java +++ b/mozilla/js/rhino/src/org/mozilla/javascript/Context.java @@ -1666,9 +1666,9 @@ public final class Context { static { try { codegenClass = Class.forName( - "com.netscape.javascript.optimizer.Codegen"); + "org.mozilla.javascript.optimizer.Codegen"); Class nameHelperClass = Class.forName( - "com.netscape.javascript.optimizer.OptClassNameHelper"); + "org.mozilla.javascript.optimizer.OptClassNameHelper"); nameHelper = (ClassNameHelper)nameHelperClass.newInstance(); } catch (ClassNotFoundException x) { // ...must be running lite, that's ok diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Block.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Block.java new file mode 100644 index 00000000000..af4308dcf72 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Block.java @@ -0,0 +1,688 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import org.mozilla.classfile.*; + +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class Block { + + public Block(int startNodeIndex, int endNodeIndex, Node[] statementNodes) + { + itsStartNodeIndex = startNodeIndex; + itsEndNodeIndex = endNodeIndex; + itsStatementNodes = statementNodes; + } + + public void setBlockID(int id) { itsBlockID = id; } + public int getBlockID() { return itsBlockID; } + public Node getStartNode() { return itsStatementNodes[itsStartNodeIndex]; } + public Node getEndNode() { return itsStatementNodes[itsEndNodeIndex]; } + + public Block[] getPredecessorList() { return itsPredecessors; } + public Block[] getSuccessorList() { return itsSuccessors; } + + public static Block[] buildBlocks(Node[] statementNodes) + { + // a mapping from each target node to the block it begins + Hashtable theTargetBlocks = new Hashtable(); + Vector theBlocks = new Vector(); + + // 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 TokenStream.TARGET : + { + if (i != beginNodeIndex) { + FatBlock fb = new FatBlock(beginNodeIndex, + i - 1, statementNodes); + if (statementNodes[beginNodeIndex].getType() + == TokenStream.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.addElement(fb); + // start the next block at this node + beginNodeIndex = i; + } + } + break; + case TokenStream.IFNE : + case TokenStream.IFEQ : + case TokenStream.GOTO : + { + FatBlock fb = new FatBlock(beginNodeIndex, + i, statementNodes); + if (statementNodes[beginNodeIndex].getType() + == TokenStream.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.addElement(fb); + // start the next block at the next node + beginNodeIndex = i + 1; + } + break; + } + } + + if ((beginNodeIndex != statementNodes.length)) { + FatBlock fb = new FatBlock(beginNodeIndex, + statementNodes.length - 1, + statementNodes); + if (statementNodes[beginNodeIndex].getType() == TokenStream.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.addElement(fb); + } + + // build successor and predecessor links + + for (int i = 0; i < theBlocks.size(); i++) { + FatBlock fb = (FatBlock)(theBlocks.elementAt(i)); + + Node blockEndNode = fb.getEndNode(); + int blockEndNodeType = blockEndNode.getType(); + + if ((blockEndNodeType != TokenStream.GOTO) + && (i < (theBlocks.size() - 1))) { + FatBlock fallThruTarget = (FatBlock)(theBlocks.elementAt(i + 1)); + fb.addSuccessor(fallThruTarget); + fallThruTarget.addPredecessor(fb); + } + + + if ( (blockEndNodeType == TokenStream.IFNE) + || (blockEndNodeType == TokenStream.IFEQ) + || (blockEndNodeType == TokenStream.GOTO) ) { + Node target = (Node)(blockEndNode.getProp(Node.TARGET_PROP)); + FatBlock branchTargetBlock + = (FatBlock)(theTargetBlocks.get(target)); + target.putProp(Node.TARGETBLOCK_PROP, + branchTargetBlock.getSlimmerSelf()); + fb.addSuccessor(branchTargetBlock); + branchTargetBlock.addPredecessor(fb); + } + } + + Block[] result = new Block[theBlocks.size()]; + + for (int i = 0; i < theBlocks.size(); i++) { + FatBlock fb = (FatBlock)(theBlocks.elementAt(i)); + result[i] = fb.diet(); + result[i].setBlockID(i); + } + + return result; + } + + public static String toString(Block[] blockList, Node[] statementNodes) + { + 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].getBlockID() + " "); + 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].getBlockID() + " "); + pw.println(); + } + else + pw.println("none"); + } + return sw.toString(); + } + + /* + We maintain the liveSet as each statement executes, identifying + those variables that are live across function calls + + */ + void lookForVariablesAndCalls(Node n, boolean liveSet[], + VariableTable theVariables) + { + switch (n.getType()) { + case TokenStream.SETVAR : + { + Node lhs = n.getFirstChild(); + Node rhs = lhs.getNextSibling(); + lookForVariablesAndCalls(rhs, liveSet, theVariables); + Object theVarProp = n.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + liveSet[theVarIndex] = true; + } + } + break; + case TokenStream.CALL : { + Node child = n.getFirstChild(); + while (child != null) { + lookForVariablesAndCalls(child, liveSet, theVariables); + child = child.getNextSibling(); + } + for (int i = 0; i < liveSet.length; i++) { + if (liveSet[i]) + ((OptLocalVariable)theVariables.get(i)).markLiveAcrossCall(); + } + } + break; + case TokenStream.GETVAR : + { + Object theVarProp = n.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + if ((n.getProp(Node.LASTUSE_PROP) != null) + && !itsLiveOnExitSet.test(theVarIndex)) + liveSet[theVarIndex] = false; + } + } + break; + default : + Node child = n.getFirstChild(); + while (child != null) { + lookForVariablesAndCalls(child, liveSet, theVariables); + child = child.getNextSibling(); + } + break; + } + } + + void markVolatileVariables(VariableTable theVariables) + { + boolean liveSet[] = new boolean[theVariables.size()]; + for (int i = 0; i < liveSet.length; i++) + liveSet[i] = itsLiveOnEntrySet.test(i); + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + lookForVariablesAndCalls(n, liveSet, theVariables); + } + } + + /* + 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. + + */ + void lookForVariableAccess(Node n, Node lastUse[]) + { + switch (n.getType()) { + case TokenStream.DEC : + case TokenStream.INC : + { + Node child = n.getFirstChild(); + if (child.getType() == TokenStream.GETVAR) { + Object theVarProp = child.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + if (!itsNotDefSet.test(theVarIndex)) + itsUseBeforeDefSet.set(theVarIndex); + itsNotDefSet.set(theVarIndex); + } + } + } + break; + case TokenStream.SETVAR : + { + Node lhs = n.getFirstChild(); + Node rhs = lhs.getNextSibling(); + lookForVariableAccess(rhs, lastUse); + Object theVarProp = n.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + itsNotDefSet.set(theVarIndex); + if (lastUse[theVarIndex] != null) + lastUse[theVarIndex].putProp(Node.LASTUSE_PROP, + theVarProp); + } + } + break; + case TokenStream.GETVAR : + { + Object theVarProp = n.getProp(Node.VARIABLE_PROP); + if (theVarProp != null) { + int theVarIndex = ((OptLocalVariable)theVarProp).getIndex(); + if (!itsNotDefSet.test(theVarIndex)) + itsUseBeforeDefSet.set(theVarIndex); + lastUse[theVarIndex] = n; + } + } + break; + default : + Node child = n.getFirstChild(); + while (child != null) { + lookForVariableAccess(child, lastUse); + child = child.getNextSibling(); + } + 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. + */ + public void initLiveOnEntrySets(VariableTable theVariables) + { + int listLength = theVariables.size(); + Node lastUse[] = new Node[listLength]; + itsUseBeforeDefSet = new DataFlowBitSet(listLength); + itsNotDefSet = new DataFlowBitSet(listLength); + itsLiveOnEntrySet = new DataFlowBitSet(listLength); + itsLiveOnExitSet = new DataFlowBitSet(listLength); + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + lookForVariableAccess(n, lastUse); + } + for (int i = 0; i < listLength; i++) { + if (lastUse[i] != null) + lastUse[i].putProp(Node.LASTUSE_PROP, this); + } + itsNotDefSet.not(); // truth in advertising + } + + /* + the liveOnEntry of each successor is the liveOnExit for this block. + The liveOnEntry for this block is - + liveOnEntry = liveOnExit - defsInThisBlock + useBeforeDefsInThisBlock + + */ + boolean doReachedUseDataFlow() + { + itsLiveOnExitSet.clear(); + if (itsSuccessors != null) + for (int i = 0; i < itsSuccessors.length; i++) + itsLiveOnExitSet.or(itsSuccessors[i].itsLiveOnEntrySet); + return itsLiveOnEntrySet.df2(itsLiveOnExitSet, + itsUseBeforeDefSet, itsNotDefSet); + } + + /* + the type of an expression is relatively unknown. Cases we can be sure + about are - + Literals, + Arithmetic operations - always return a Number + */ + int findExpressionType(Node n) + { + switch (n.getType()) { + case TokenStream.NUMBER : { +/* distinguish between integers & f.p.s ? + Number num = ((NumberNode)n).getNumber(); + if ((num instanceof Byte) + || (num instanceof Short) + || (num instanceof Integer)) { + } + else { + } +*/ + return TypeEvent.NumberType; + } + case TokenStream.NEW : + case TokenStream.CALL : + return TypeEvent.NoType; + + case TokenStream.GETELEM : + return TypeEvent.AnyType; + + case TokenStream.GETVAR : { + OptLocalVariable theVar = (OptLocalVariable) + (n.getProp(Node.VARIABLE_PROP)); + if (theVar != null) + return theVar.getTypeUnion(); + } + + case TokenStream.INC : + case TokenStream.DEC : + case TokenStream.DIV: + case TokenStream.MOD: + case TokenStream.BITOR: + case TokenStream.BITXOR: + case TokenStream.BITAND: + case TokenStream.LSH: + case TokenStream.RSH: + case TokenStream.URSH: + case TokenStream.SUB : { + return TypeEvent.NumberType; + } + case TokenStream.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(child); + int rType = findExpressionType(child.getNextSibling()); + return lType | rType; // we're not distinguishng strings yet + } + default : { + Node child = n.getFirstChild(); + if (child == null) + return TypeEvent.AnyType; + else { + int result = TypeEvent.NoType; + while (child != null) { + result |= findExpressionType(child); + child = child.getNextSibling(); + } + return result; + } + } + } + } + + boolean findDefPoints(Node n) + { + boolean result = false; + switch (n.getType()) { + default : { + Node child = n.getFirstChild(); + while (child != null) { + result |= findDefPoints(child); + child = child.getNextSibling(); + } + } + break; + case TokenStream.DEC : + case TokenStream.INC : { + Node firstChild = n.getFirstChild(); + OptLocalVariable theVar = (OptLocalVariable) + (firstChild.getProp(Node.VARIABLE_PROP)); + if (theVar != null) { + // theVar is a Number now + result |= theVar.assignType(TypeEvent.NumberType); + } + } + break; + + case TokenStream.SETPROP : { + Node baseChild = n.getFirstChild(); + Node nameChild = baseChild.getNextSibling(); + Node rhs = nameChild.getNextSibling(); + if (baseChild != null) { + if (baseChild.getType() == TokenStream.GETVAR) { + OptLocalVariable theVar = (OptLocalVariable) + (baseChild.getProp(Node.VARIABLE_PROP)); + if (theVar != null) + theVar.assignType(TypeEvent.AnyType); + } + result |= findDefPoints(baseChild); + } + if (nameChild != null) result |= findDefPoints(nameChild); + if (rhs != null) result |= findDefPoints(rhs); + } + break; + + case TokenStream.SETVAR : { + Node firstChild = n.getFirstChild(); + OptLocalVariable theVar = (OptLocalVariable) + (n.getProp(Node.VARIABLE_PROP)); + if (theVar != null) { + Node rValue = firstChild.getNextSibling(); + int theType = findExpressionType(rValue); + result |= theVar.assignType(theType); + } + } + break; + } + return result; + } + + // a total misnomer for now. To start with we're only trying to find + // duplicate getProp calls on 'this' that can be merged + void localCSE(Node parent, Node n, Hashtable theCSETable, OptFunctionNode theFunction) + { + switch (n.getType()) { + default : { + Node child = n.getFirstChild(); + while (child != null) { + localCSE(n, child, theCSETable, theFunction); + child = child.getNextSibling(); + } + } + break; + case TokenStream.DEC : + case TokenStream.INC : { + Node child = n.getFirstChild(); + if (child.getType() == TokenStream.GETPROP) { + Node nameChild = child.getFirstChild().getNextSibling(); + if (nameChild.getType() == TokenStream.STRING) + theCSETable.remove(nameChild.getString()); + else + theCSETable.clear(); + } + else + if (child.getType() != TokenStream.GETVAR) + theCSETable.clear(); + } + break; + case TokenStream.SETPROP : { + Node baseChild = n.getFirstChild(); + Node nameChild = baseChild.getNextSibling(); + Node rhs = nameChild.getNextSibling(); + if (baseChild != null) localCSE(n, baseChild, theCSETable, theFunction); + if (nameChild != null) localCSE(n, nameChild, theCSETable, theFunction); + if (rhs != null) localCSE(n, rhs, theCSETable, theFunction); + if (nameChild.getType() == TokenStream.STRING) { + theCSETable.remove(nameChild.getString()); +// System.out.println("clear at SETPROP " + ((StringNode)nameChild).getString()); + } + else { + theCSETable.clear(); +// System.out.println("clear all at SETPROP"); + } + } + break; + case TokenStream.GETPROP : { + Node baseChild = n.getFirstChild(); + if (baseChild != null) localCSE(n, baseChild, theCSETable, theFunction); + if ((baseChild.getType() == TokenStream.PRIMARY) + && (baseChild.getInt() == TokenStream.THIS)) { + Node nameChild = baseChild.getNextSibling(); + if (nameChild.getType() == TokenStream.STRING) { + String theName = nameChild.getString(); +// System.out.println("considering " + theName); + Object cse = theCSETable.get(theName); + if (cse == null) { + theCSETable.put(theName, new CSEHolder(parent, n)); + } + else { + if (parent != null) { +// System.out.println("Yay for " + theName); + Node theCSE; + if (cse instanceof CSEHolder) { + CSEHolder cseHolder = (CSEHolder)cse; + Node nextChild = cseHolder.getPropChild.getNextSibling(); + cseHolder.getPropParent.removeChild(cseHolder.getPropChild); + theCSE = itsIRFactory.createNewLocal(cseHolder.getPropChild); + theFunction.incrementLocalCount(); + if (nextChild == null) + cseHolder.getPropParent.addChildToBack(theCSE); + else + cseHolder.getPropParent.addChildBefore(theCSE, nextChild); + theCSETable.put(theName, theCSE); + } + else + theCSE = (Node)cse; + Node nextChild = n.getNextSibling(); + parent.removeChild(n); + Node cseUse = itsIRFactory.createUseLocal(theCSE); + if (nextChild == null) + parent.addChildToBack(cseUse); + else + parent.addChildBefore(cseUse, nextChild); + } + } + } + } + } + break; + case TokenStream.SETELEM : { + Node lhsBase = n.getFirstChild(); + Node lhsIndex = lhsBase.getNextSibling(); + Node rhs = lhsIndex.getNextSibling(); + if (lhsBase != null) localCSE(n, lhsBase, theCSETable, theFunction); + if (lhsIndex != null) localCSE(n, lhsIndex, theCSETable, theFunction); + if (rhs != null) localCSE(n, rhs, theCSETable, theFunction); + theCSETable.clear(); +//System.out.println("clear all at SETELEM"); + } + break; + case TokenStream.CALL : { + Node child = n.getFirstChild(); + while (child != null) { + localCSE(n, child, theCSETable, theFunction); + child = child.getNextSibling(); + } + theCSETable.clear(); +//System.out.println("clear all at CALL"); + } + break; + } + } + + private IRFactory itsIRFactory; + + Hashtable localCSE(Hashtable theCSETable, OptFunctionNode theFunction) + { + itsIRFactory = new IRFactory(null); + if (theCSETable == null) theCSETable = new Hashtable(5); + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + if (n != null) + localCSE(null, n, theCSETable, theFunction); + } + return theCSETable; + } + + void findDefs() + { + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + if (n != null) + findDefPoints(n); + } + } + + public boolean doTypeFlow() + { + boolean changed = false; + + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = itsStatementNodes[i]; + if (n != null) + changed |= findDefPoints(n); + } + + return changed; + } + + public boolean isLiveOnEntry(int index) + { + return (itsLiveOnEntrySet != null) && (itsLiveOnEntrySet.test(index)); + } + + public void printLiveOnEntrySet(PrintWriter pw, VariableTable theVariables) + { + for (int i = 0; i < theVariables.size(); i++) { + if (itsUseBeforeDefSet.test(i)) + pw.println(theVariables.get(i).getName() + " is used before def'd"); + if (itsNotDefSet.test(i)) + pw.println(theVariables.get(i).getName() + " is not def'd"); + if (itsLiveOnEntrySet.test(i)) + pw.println(theVariables.get(i).getName() + " is live on entry"); + if (itsLiveOnExitSet.test(i)) + pw.println(theVariables.get(i).getName() + " is live on exit"); + } + } + + public void setSuccessorList(Block[] b) { itsSuccessors = b; } + public void setPredecessorList(Block[] b) { itsPredecessors = b; } + + // 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 Node itsStatementNodes[]; // the list of all statement nodes + + private int itsBlockID; // a unique index for each block + +// reaching def bit sets - + private DataFlowBitSet itsLiveOnEntrySet; + private DataFlowBitSet itsLiveOnExitSet; + private DataFlowBitSet itsUseBeforeDefSet; + private DataFlowBitSet itsNotDefSet; + +} + +class CSEHolder { + + CSEHolder(Node parent, Node child) + { + getPropParent = parent; + getPropChild = child; + } + + Node getPropParent; + Node getPropChild; + +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Codegen.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Codegen.java new file mode 100644 index 00000000000..8317005f9d9 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Codegen.java @@ -0,0 +1,4172 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import org.mozilla.classfile.*; +import java.util.*; +import java.io.*; +import java.lang.reflect.*; + +/** + * This class generates code for a given IR tree. + * + * @author Norris Boyd + * @author Roger Lawrence + */ + +public class Codegen extends Interpreter { + + public Codegen() { + itsConstantList = new ConstantList(); + } + + public IRFactory createIRFactory(TokenStream ts, + ClassNameHelper nameHelper) + { + return new OptIRFactory(ts, nameHelper); + } + + public Node transform(Node tree, TokenStream ts) { + OptTransformer opt = new OptTransformer(new Hashtable(11)); + return opt.transform(tree, null, ts); + } + + public Object compile(Context cx, Scriptable scope, Node tree, + Object securityDomain, + SecuritySupport securitySupport, + ClassNameHelper nameHelper) + throws IOException + { + Hashtable classFiles = new Hashtable(23); + String generatedName = null; + + Exception e = null; + Class result = null; + + try { + if (cx.getOptimizationLevel() > 0) { + (new Optimizer()).optimize(tree, cx.getOptimizationLevel()); + } + generatedName = generateCode(tree, classFiles, nameHelper); + + Enumeration keys = classFiles.keys(); + while (keys.hasMoreElements()) { + String name = (String) keys.nextElement(); + byte[] classFile = (byte[]) classFiles.get(name); + if (nameHelper.getGeneratingDirectory() != null) { + try { + int lastDot = name.lastIndexOf('.'); + if (lastDot != -1) + name = name.substring(lastDot+1); + String filename + = nameHelper.getTargetClassFileName(name); + FileOutputStream out = new FileOutputStream(filename); + out.write(classFile); + out.close(); + } + catch (IOException iox) { + throw WrappedException.wrapException(iox); + } + } else { + try { + Class clazz; + if (securitySupport == null) { + if (Context.isSecurityDomainRequired()) + throw new SecurityException("Required " + + "security context missing"); + if (classLoader == null) + classLoader = new JavaScriptClassLoader(); + clazz = classLoader.defineClass(name, classFile); + } else { + clazz = securitySupport.defineClass(name, classFile, + securityDomain); + } + ClassLoader loader = clazz.getClassLoader(); + clazz = loader.loadClass(name); + if (name.equals(generatedName)) + result = clazz; + } catch (ClassFormatError ex) { + throw new RuntimeException(ex.toString()); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex.toString()); + } + } + } + } + catch (SecurityException x) { + e = x; + } + catch (IllegalArgumentException x) { + e = x; + } + if (e != null) + throw new RuntimeException("Malformed optimizer package " + e); + + OptClassNameHelper onh = (OptClassNameHelper) nameHelper; + if (onh.getTargetImplements() != null || + onh.getTargetExtends() != null) + { + String name = onh.getJavaScriptClassName(null, true); + ScriptableObject obj = new NativeObject(); + ShallowNodeIterator i = tree.getChildIterator(); + while (i.hasMoreElements()) { + Node n = i.nextNode(); + if (n.getType() == TokenStream.FUNCTION) + obj.put((String) n.getDatum(), obj, + n.getProp(Node.FUNCTION_PROP)); + } + try { + Class superClass = onh.getTargetExtends(); + if (superClass == null) + superClass = Object.class; + JavaAdapter.createAdapterClass(cx, obj, name, + superClass, + onh.getTargetImplements(), + generatedName, + onh); + } catch (ClassNotFoundException exn) { + // should never happen + throw new Error(exn.toString()); + } + } + if (tree instanceof OptFunctionNode) { + return ScriptRuntime.createFunctionObject(scope, result, cx); + } else { + try { + if (result == null) + return null; + NativeScript script = (NativeScript) result.newInstance(); + if (scope != null) { + script.setPrototype(script.getClassPrototype(scope, + "Script")); + script.setParentScope(scope); + } + return script; + } + catch (InstantiationException x) { + } + catch (IllegalAccessException x) { + } + throw new RuntimeException("Unable to instantiate compiled class"); + } + } + + void addByteCode(byte theOpcode) + { + classFile.add(theOpcode); + } + + void addByteCode(byte theOpcode, int theOperand) + { + classFile.add(theOpcode, theOperand); + } + + void addByteCode(byte theOpcode, String className) + { + classFile.add(theOpcode, className); + } + + void addVirtualInvoke(String className, String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKEVIRTUAL, + className, + methodName, + parameterSignature, + resultSignature); + } + + void addStaticInvoke(String className, String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKESTATIC, + className, + methodName, + parameterSignature, + resultSignature); + } + + void addScriptRuntimeInvoke(String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + methodName, + parameterSignature, + resultSignature); + } + + void addOptRuntimeInvoke(String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + methodName, + parameterSignature, + resultSignature); + } + + void addSpecialInvoke(String className, String methodName, String parameterSignature, String resultSignature) + { + classFile.add(ByteCode.INVOKESPECIAL, + className, + methodName, + parameterSignature, + resultSignature); + } + + void addDoubleConstructor() + { + classFile.add(ByteCode.INVOKESPECIAL, + "java/lang/Double", + "", "(D)", "V"); + } + + public int markLabel(int label) + { + return classFile.markLabel(label); + } + + public int markLabel(int label, short stackheight) + { + return classFile.markLabel(label, stackheight); + } + + public int acquireLabel() + { + return classFile.acquireLabel(); + } + + public void emitDirectConstructor(OptFunctionNode fnNode) + { +/* + we generate .. + Scriptable directConstruct() { + Scriptable newInstance = new NativeObject(); + newInstance.setPrototype(getClassPrototype()); + newInstance.setParentScope(getParentScope()); + + Object val = callDirect(); + // except that the incoming thisArg is discarded + // and replaced by newInstance + // + + if (val != null && val != Undefined.instance && + val instanceof Scriptable) + { + return (Scriptable) val; + } + return newInstance; + } +*/ + short flags = (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL); + classFile.startMethod("constructDirect", + fnNode.getDirectCallParameterSignature() + + "Ljava/lang/Object;", + flags); + + int argCount = fnNode.getVariableTable().getParameterCount(); + short firstLocal = (short)((4 + argCount * 3) + 1); + + addByteCode(ByteCode.NEW, "org/mozilla/javascript/NativeObject"); + addByteCode(ByteCode.DUP); + classFile.add(ByteCode.INVOKESPECIAL, + "org/mozilla/javascript/NativeObject", + "", "()", "V"); + astore(firstLocal); + + aload(firstLocal); + aload((short)0); + addVirtualInvoke("org/mozilla/javascript/NativeFunction", + "getClassPrototype", + "()", "Lorg/mozilla/javascript/Scriptable;"); + classFile.add(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "setPrototype", + "(Lorg/mozilla/javascript/Scriptable;)", + "V"); + + + aload(firstLocal); + aload((short)0); + addVirtualInvoke("org/mozilla/javascript/NativeFunction", + "getParentScope", + "()", "Lorg/mozilla/javascript/Scriptable;"); + classFile.add(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "setPrototype", + "(Lorg/mozilla/javascript/Scriptable;)", + "V"); + + + aload((short)0); + aload((short)1); + aload((short)2); + aload(firstLocal); + for (int i = 0; i < argCount; i++) { + aload((short)(4 + (i * 3))); + dload((short)(5 + (i * 3))); + } + aload((short)(4 + argCount * 3)); + addVirtualInvoke(this.name, + "callDirect", + fnNode.getDirectCallParameterSignature(), + "Ljava/lang/Object;"); + astore((short)(firstLocal + 1)); + + int exitLabel = acquireLabel(); + aload((short)(firstLocal + 1)); + addByteCode(ByteCode.IFNULL, exitLabel); + aload((short)(firstLocal + 1)); + pushUndefined(); + addByteCode(ByteCode.IF_ACMPEQ, exitLabel); + aload((short)(firstLocal + 1)); + addByteCode(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable"); + addByteCode(ByteCode.IFEQ, exitLabel); + aload((short)(firstLocal + 1)); + addByteCode(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable"); + addByteCode(ByteCode.ARETURN); + markLabel(exitLabel); + + aload(firstLocal); + addByteCode(ByteCode.ARETURN); + + classFile.stopMethod((short)(firstLocal + 2), null); + + } + + public String generateCode(Node tree, Hashtable results, + ClassNameHelper nameHelper) + { + this.itsNameHelper = (OptClassNameHelper) nameHelper; + this.results = results; + Context cx = Context.getCurrentContext(); + itsSourceFile = null; + if (cx.isGeneratingDebug()) + itsSourceFile = (String) tree.getProp(Node.SOURCENAME_PROP); + version = cx.getLanguageVersion(); + optLevel = cx.getOptimizationLevel(); + debugLevel = cx.getDebugLevel(); + inFunction = tree.getType() == TokenStream.FUNCTION; + if (debugLevel >= 3) { + superClassName = inFunction + ? debugFunctionSuperClassName + : debugScriptSuperClassName; + } else { + superClassName = inFunction + ? normalFunctionSuperClassName + : normalScriptSuperClassName; + } + superClassSlashName = superClassName.replace('.', '/'); + + Node codegenBase; + if (inFunction) { + OptFunctionNode fnNode = (OptFunctionNode) tree; + inDirectCallFunction = fnNode.isTargetOfDirectCall(); + vars = (OptVariableTable) fnNode.getVariableTable(); + this.name = fnNode.getClassName(); + classFile = new ClassFileWriter(name, superClassName, itsSourceFile); + Node args = tree.getFirstChild(); + String name = fnNode.getFunctionName(); + generateInit(cx, "", tree, name, args); + if (fnNode.isTargetOfDirectCall()) { + classFile.startMethod("call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", + (short)(ClassFileWriter.ACC_PUBLIC + + ClassFileWriter.ACC_FINAL)); + addByteCode(ByteCode.ALOAD_0); + addByteCode(ByteCode.ALOAD_1); + addByteCode(ByteCode.ALOAD_2); + addByteCode(ByteCode.ALOAD_3); + for (int i = 0; i < vars.getParameterCount(); i++) { + push(i); + addByteCode(ByteCode.ALOAD, 4); + addByteCode(ByteCode.ARRAYLENGTH); + int undefArg = acquireLabel(); + int beyond = acquireLabel(); + addByteCode(ByteCode.IF_ICMPGE, undefArg); + addByteCode(ByteCode.ALOAD, 4); + push(i); + addByteCode(ByteCode.AALOAD); + push(0.0); + addByteCode(ByteCode.GOTO, beyond); + markLabel(undefArg); + pushUndefined(); + push(0.0); + markLabel(beyond); + } + addByteCode(ByteCode.ALOAD, 4); + addVirtualInvoke(this.name, + "callDirect", + fnNode.getDirectCallParameterSignature(), + "Ljava/lang/Object;"); + addByteCode(ByteCode.ARETURN); + classFile.stopMethod((short)5, null); + // 1 for this, 1 for js this, 1 for args[] + + emitDirectConstructor(fnNode); + + startNewMethod("callDirect", + fnNode.getDirectCallParameterSignature() + + "Ljava/lang/Object;", + 1, false, true); + vars.assignParameterJRegs(); + if (!fnNode.getParameterNumberContext()) { + // make sure that all parameters are objects + itsForcedObjectParameters = true; + for (int i = 0; i < vars.getParameterCount(); i++) { + OptLocalVariable lVar = (OptLocalVariable) vars.get(i); + aload(lVar.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isObjectLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, isObjectLabel); + addByteCode(ByteCode.NEW,"java/lang/Double"); + addByteCode(ByteCode.DUP); + dload((short)(lVar.getJRegister() + 1)); + addDoubleConstructor(); + astore(lVar.getJRegister()); + markLabel(isObjectLabel); + } + } + generatePrologue(cx, tree, true, vars.getParameterCount()); + } else { + startNewMethod("call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", + 1, false, true); + generatePrologue(cx, tree, true, -1); + } + codegenBase = tree.getLastChild(); + } else { + // better be a script + if (tree.getType() != TokenStream.SCRIPT) + badTree(); + vars = (OptVariableTable) tree.getProp(Node.VARS_PROP); + boolean isPrimary = itsNameHelper.getTargetExtends() == null && + itsNameHelper.getTargetImplements() == null; + this.name = itsNameHelper.getJavaScriptClassName(null, isPrimary); + classFile = new ClassFileWriter(name, superClassName, itsSourceFile); + classFile.addInterface("org/mozilla/javascript/Script"); + generateScriptCtor(cx, tree); + generateMain(cx); + generateInit(cx, "initScript", tree, "", null); + generateExecute(cx); + startNewMethod("call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", + 1, false, true); + generatePrologue(cx, tree, false, -1); + tree.addChildToBack(new Node(TokenStream.RETURN)); + codegenBase = tree; + } + + generateCodeFromNode(codegenBase, null, -1, -1); + + generateEpilogue(); + + finishMethod(cx, debugVars); + + emitConstantDudeInitializers(); + + // this needs to be done after the primary code generation is done + if (debugLevel >= 1) + generateDebugInit(cx, tree); + + ByteArrayOutputStream out = new ByteArrayOutputStream(512); + try { + classFile.write(out); + } + catch (IOException ioe) { + throw new RuntimeException("unexpected IOException"); + } + byte[] bytes = out.toByteArray(); + + results.put(name, bytes); + classFile = null; + + return name; + } + + private void generateCodeFromNode(Node node, Node parent, int trueLabel, + int falseLabel) + { + // System.out.println("gen code for " + node.toString()); + + int type = node.getType(); + Node child = node.getFirstChild(); + switch (type) { + case TokenStream.LOOP: + case TokenStream.WITH: + case TokenStream.LABEL: + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + break; + + case TokenStream.CASE: + case TokenStream.DEFAULT: + // XXX shouldn't these be StatementNodes? + + case TokenStream.SCRIPT: + case TokenStream.BLOCK: + case TokenStream.VOID: + case TokenStream.NOP: + // no-ops. + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + break; + + case TokenStream.FUNCTION: + if (inFunction || parent.getType() != TokenStream.SCRIPT) + visitFunction(node); + break; + + case TokenStream.NAME: + visitName(node); + break; + + case TokenStream.NEW: + case TokenStream.CALL: + visitCall(node, type, child); + break; + + case TokenStream.NUMBER: + case TokenStream.STRING: + visitLiteral(node); + break; + + case TokenStream.PRIMARY: + visitPrimary(node); + break; + + case TokenStream.OBJECT: + visitObject(node); + break; + + case TokenStream.TRY: + visitTryCatchFinally(node, child); + break; + + case TokenStream.THROW: + visitThrow(node, child); + break; + + case TokenStream.RETURN: + visitReturn(node, child); + break; + + case TokenStream.SWITCH: + visitSwitch(node, child); + break; + + case TokenStream.COMMA: + generateCodeFromNode(child, node, -1, -1); + addByteCode(ByteCode.POP); + generateCodeFromNode(child.getNextSibling(), + node, trueLabel, falseLabel); + break; + + case TokenStream.NEWSCOPE: + addScriptRuntimeInvoke("newScope", "()", + "Lorg/mozilla/javascript/Scriptable;"); + break; + + case TokenStream.ENTERWITH: + visitEnterWith(node, child); + break; + + case TokenStream.LEAVEWITH: + visitLeaveWith(node, child); + break; + + case TokenStream.ENUMINIT: + visitEnumInit(node, child); + break; + + case TokenStream.ENUMNEXT: + visitEnumNext(node, child); + break; + + case TokenStream.ENUMDONE: + visitEnumDone(node, child); + break; + + case TokenStream.POP: + visitStatement(node); + if (child.getType() == TokenStream.SETVAR) { + /* special case this so as to avoid unnecessary + load's & pop's */ + visitSetVar(child, child.getFirstChild(), false); + } + else { + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + if (node.getProp(Node.ISNUMBER_PROP) != null) + addByteCode(ByteCode.POP2); + else + addByteCode(ByteCode.POP); + } + break; + + case TokenStream.POPV: + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + astore(scriptResultLocal); + break; + + case TokenStream.TARGET: + visitTarget(node); + break; + + case TokenStream.JSR: + case TokenStream.GOTO: + case TokenStream.IFEQ: + case TokenStream.IFNE: + visitGOTO(node, type, child); + break; + + case TokenStream.UNARYOP: + visitUnary(node, child, trueLabel, falseLabel); + break; + + case TokenStream.TYPEOF: + visitTypeof(node, child); + break; + + case TokenStream.INC: + visitIncDec(node, true); + break; + + case TokenStream.DEC: + visitIncDec(node, false); + break; + + case TokenStream.OR: + case TokenStream.AND: { + if (trueLabel == -1) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + addByteCode(ByteCode.DUP); + addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)", "Z"); + int falseTarget = acquireLabel(); + if (type == TokenStream.AND) + addByteCode(ByteCode.IFEQ, falseTarget); + else + addByteCode(ByteCode.IFNE, falseTarget); + addByteCode(ByteCode.POP); + generateCodeFromNode(child.getNextSibling(), node, trueLabel, falseLabel); + markLabel(falseTarget); + } + else { + int interLabel = acquireLabel(); + if (type == TokenStream.AND) { + generateCodeFromNode(child, node, interLabel, falseLabel); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, interLabel); + addByteCode(ByteCode.GOTO, falseLabel); + } + } + else { + generateCodeFromNode(child, node, trueLabel, interLabel); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, trueLabel); + addByteCode(ByteCode.GOTO, interLabel); + } + } + markLabel(interLabel); + child = child.getNextSibling(); + generateCodeFromNode(child, node, trueLabel, falseLabel); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, trueLabel); + addByteCode(ByteCode.GOTO, falseLabel); + } + } + } + break; + + case TokenStream.ADD: { + generateCodeFromNode(child, node, trueLabel, falseLabel); + generateCodeFromNode(child.getNextSibling(), + node, trueLabel, falseLabel); + Integer childNumberFlag = + (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if (childNumberFlag != null) { + if (childNumberFlag.intValue() == Node.BOTH) { + addByteCode(ByteCode.DADD); + } + else { + if (childNumberFlag.intValue() == Node.LEFT) { + addOptRuntimeInvoke("add", + "(DLjava/lang/Object;)", + "Ljava/lang/Object;"); + } + else { + addOptRuntimeInvoke("add", + "(Ljava/lang/Object;D)", + "Ljava/lang/Object;"); + } + } + } + else { + addScriptRuntimeInvoke("add", + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Object;"); + } + } + break; + + case TokenStream.MUL: + visitArithmetic(node, ByteCode.DMUL, child, parent); + break; + + case TokenStream.SUB: + visitArithmetic(node, ByteCode.DSUB, child, parent); + break; + + case TokenStream.DIV: + case TokenStream.MOD: + visitArithmetic(node, type == TokenStream.DIV + ? ByteCode.DDIV + : ByteCode.DREM, child, parent); + break; + + case TokenStream.BITOR: + case TokenStream.BITXOR: + case TokenStream.BITAND: + case TokenStream.LSH: + case TokenStream.RSH: + case TokenStream.URSH: + visitBitOp(node, type, child); + break; + + case TokenStream.CONVERT: { + Object toType = node.getProp(Node.TYPE_PROP); + if (toType == ScriptRuntime.NumberClass) { + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, trueLabel, falseLabel); + addScriptRuntimeInvoke("toNumber", + "(Ljava/lang/Object;)", "D"); + addDoubleConstructor(); + } + else { + if (toType == ScriptRuntime.DoubleClass) { + // cnvt to double + // (not Double) + generateCodeFromNode(child, node, trueLabel, falseLabel); + addScriptRuntimeInvoke("toNumber", + "(Ljava/lang/Object;)", "D"); + } + else { + if (toType == ScriptRuntime.ObjectClass) {// convert from double + if ((child.getType() == TokenStream.NUMBER) + && (child.getProp(Node.ISNUMBER_PROP) + != null)) { + Object oldProp + = child.getProp(Node.ISNUMBER_PROP); + child.putProp(Node.ISNUMBER_PROP, null); + generateCodeFromNode(child, node, trueLabel, falseLabel); + child.putProp(Node.ISNUMBER_PROP, oldProp); + } + else { + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, trueLabel, falseLabel); + addDoubleConstructor(); + } + } + else + badTree(); + } + } + } + break; + + case TokenStream.RELOP: + if (trueLabel == -1) // need a result Object + visitRelOp(node, child, parent); + else + visitGOTOingRelOp(node, child, parent, trueLabel, falseLabel); + break; + + case TokenStream.EQOP: + visitEqOp(node, child, parent, trueLabel, falseLabel); + break; + + case TokenStream.GETPROP: + visitGetProp(node, child); + break; + + case TokenStream.GETELEM: + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + if (node.getProp(Node.ISNUMBER_PROP) != null) { + addOptRuntimeInvoke("getElem", + "(Ljava/lang/Object;D" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + addScriptRuntimeInvoke("getElem", + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + break; + + case TokenStream.GETVAR: { + OptLocalVariable lVar + = (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP)); + visitGetVar(lVar, + node.getProp(Node.ISNUMBER_PROP) != null, + node.getString()); + } + break; + + case TokenStream.SETVAR: + visitSetVar(node, child, true); + break; + + case TokenStream.SETNAME: + visitSetName(node, child); + break; + + case TokenStream.SETPROP: + visitSetProp(node, child); + break; + + case TokenStream.SETELEM: + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + if (node.getProp(Node.ISNUMBER_PROP) != null) { + addOptRuntimeInvoke("setElem", + "(Ljava/lang/Object;D" + + "Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + addScriptRuntimeInvoke("setElem", + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + break; + + case TokenStream.DELPROP: + while (child != null) { + generateCodeFromNode(child, node, trueLabel, falseLabel); + child = child.getNextSibling(); + } + addScriptRuntimeInvoke("delete", + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Object;"); + break; + + case TokenStream.BINDNAME: + case TokenStream.GETBASE: + visitBind(node, type, child); + break; + + case TokenStream.GETTHIS: + generateCodeFromNode(child, node, trueLabel, falseLabel); + addScriptRuntimeInvoke("getThis", + "(Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + break; + + case TokenStream.PARENT: + generateCodeFromNode(child, node, trueLabel, falseLabel); + addScriptRuntimeInvoke("getParent", + "(Ljava/lang/Object;)", + "Lorg/mozilla/javascript/Scriptable;"); + break; + + case TokenStream.NEWTEMP: + visitNewTemp(node, child); + break; + + case TokenStream.USETEMP: + visitUseTemp(node, child); + break; + + case TokenStream.NEWLOCAL: + visitNewLocal(node, child); + break; + + case TokenStream.USELOCAL: + visitUseLocal(node, child); + break; + + default: + throw new RuntimeException("Unexpected node type " + + TokenStream.tokenToName(type)); + } + + } + + private void startNewMethod(String methodName, String methodDesc, + int parmCount, boolean isStatic, + boolean isFinal) + { + locals = new boolean[MAX_LOCALS]; + localsMax = (short) (parmCount+1); // number of parms + "this" + firstFreeLocal = 0; + contextLocal = -1; + variableObjectLocal = -1; + scriptResultLocal = -1; + argsLocal = -1; + thisObjLocal = -1; + funObjLocal = -1; + debug_pcLocal = -1; + debugStopSubRetLocal = -1; + itsZeroArgArray = -1; + itsOneArgArray = -1; + short flags = ClassFileWriter.ACC_PUBLIC; + if (isStatic) + flags |= ClassFileWriter.ACC_STATIC; + if (isFinal) + flags |= ClassFileWriter.ACC_FINAL; + epilogueLabel = -1; + classFile.startMethod(methodName, methodDesc, (short) flags); + } + + private void finishMethod(Context cx, VariableTable vars) { + classFile.stopMethod((short)(localsMax + 1), vars); + contextLocal = -1; + } + + private void generateMain(Context cx) { + startNewMethod("main", "([Ljava/lang/String;)V", 1, true, true); + + push(this.name); // load the name of this class + addByteCode(ByteCode.ALOAD_0); // load 'args' + addScriptRuntimeInvoke("main", + "(Ljava/lang/String;[Ljava/lang/String;)", + "V"); + addByteCode(ByteCode.RETURN); + finishMethod(cx, null); + } + + + private void generateExecute(Context cx) { + String signature = "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;)" + + "Ljava/lang/Object;"; + startNewMethod("exec", signature, 2, false, true); + String slashName = this.name.replace('.', '/'); + + if (!trivialInit) { + // to begin a script, call the initScript method + addByteCode(ByteCode.ALOAD_0); // load 'this' + addByteCode(ByteCode.ALOAD_2); // load 'scope' + addByteCode(ByteCode.ALOAD_1); // load 'cx' + addVirtualInvoke(slashName, + "initScript", + "(Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Context;)", + "V"); + } + + if (debugLevel >= 3) { + addByteCode(ByteCode.ALOAD_1); // load 'cx' + addByteCode(ByteCode.ALOAD_0); // load 'this' + addByteCode(ByteCode.ALOAD_2); // load 'scope' + addStaticInvoke("org/mozilla/javascript/debug/NativeDelegate", + "executeDebug", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/NativeFunction;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + addByteCode(ByteCode.ALOAD_0); // load 'this' + addByteCode(ByteCode.ALOAD_1); // load 'cx' + addByteCode(ByteCode.ALOAD_2); // load 'scope' + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.ACONST_NULL); + addVirtualInvoke(slashName, + "call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)", + "Ljava/lang/Object;"); + } + + addByteCode(ByteCode.ARETURN); + finishMethod(cx, null); + } + + private void generateScriptCtor(Context cx, Node tree) { + startNewMethod("", "()V", 1, false, false); + addByteCode(ByteCode.ALOAD_0); + addSpecialInvoke(superClassSlashName, + "", "()", "V"); + addByteCode(ByteCode.RETURN); + finishMethod(cx, null); + } + + /** + * Indicate that the init is non-trivial. + * + * For trivial inits we can omit creating the init method + * altogether. (Only applies to scripts, since function + * inits are constructors, which are required.) For trivial + * inits we also omit the call to initScript from exec(). + */ + private void setNonTrivialInit(String methodName) { + if (!trivialInit) + return; // already set + trivialInit = false; + startNewMethod(methodName, "(Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Context;)V", + 1, false, false); + reserveWordLocal(0); // reserve 0 for 'this' + variableObjectLocal = reserveWordLocal(1); // reserve 1 for 'scope' + contextLocal = reserveWordLocal(2); // reserve 2 for 'context' + } + + private void generateInit(Context cx, String methodName, + Node tree, String name, Node args) + { + trivialInit = true; + boolean inCtor = false; + VariableTable vars; + if (tree instanceof OptFunctionNode) + vars = ((OptFunctionNode)tree).getVariableTable(); + else + vars = (VariableTable) tree.getProp(Node.VARS_PROP); + + if (methodName.equals("")) { + inCtor = true; + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); + addSpecialInvoke(superClassSlashName, "", "()", "V"); + + addByteCode(ByteCode.ALOAD_0); + addByteCode(ByteCode.ALOAD_1); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/ScriptableObject", + "parent", "Lorg/mozilla/javascript/Scriptable;"); + } + + /* + * Generate code to initialize names field with a + * string array that is the names of the function, + * the parameters and the vars. Initialize argCount + * to the number of formal parameters. + */ + + if (name.length() != 0 || (vars != null && vars.size() > 0)) { + setNonTrivialInit(methodName); + if (vars == null) + vars = new OptVariableTable(); + push(vars.size() + 1); + addByteCode(ByteCode.ANEWARRAY, "java/lang/String"); + addByteCode(ByteCode.DUP); + short x = getNewWordLocal(); + astore(x); + addByteCode(ByteCode.DUP); + push(0); + classFile.addLoadConstant(name); + addByteCode(ByteCode.AASTORE); + if (vars != null) { + for (int i = 0; i < vars.size(); i++) { + aload(x); + push(i + 1); + push(vars.getName(i)); + addByteCode(ByteCode.AASTORE); + } + } + releaseWordLocal(x); + addByteCode(ByteCode.ALOAD_0); + addByteCode(ByteCode.SWAP); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "names", "[Ljava/lang/String;"); + } + + int parmCount = vars == null ? 0 : vars.getParameterCount(); + if (parmCount != 0) { + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); + push(parmCount); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "argCount", "S"); + } + + // Initialize NativeFunction.version with Context's version. + if (cx.getLanguageVersion() != 0) { + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); + push(cx.getLanguageVersion()); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "version", "S"); + } + + // add the String representing the source. + String source = (String) tree.getProp(Node.SOURCE_PROP); + if ((source != null) + && cx.isGeneratingSource() + && (source.length() < 65536)) { + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); + push(source); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "source", "Ljava/lang/String;"); + } + + // precompile all regexp literals + Vector regexps = (Vector) tree.getProp(Node.REGEXP_PROP); + if (regexps != null) { + setNonTrivialInit(methodName); + generateRegExpLiterals(regexps, inCtor); + } + + Vector fns = (Vector) tree.getProp(Node.FUNCTION_PROP); + if (fns != null) { + setNonTrivialInit(methodName); + generateFunctionInits(fns); + } + + if (tree instanceof OptFunctionNode) { + + OptFunctionNode fnNode = (OptFunctionNode)tree; + Vector directCallTargets + = ((OptFunctionNode)tree).getDirectCallTargets(); + if (directCallTargets != null) { + setNonTrivialInit(methodName); + classFile.addField("EmptyArray", + "[Ljava/lang/Object;", + (short)ClassFileWriter.ACC_PRIVATE); + addByteCode(ByteCode.ALOAD_0); + push(0); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + classFile.add(ByteCode.PUTFIELD, + classFile.fullyQualifiedForm(this.name), + "EmptyArray", + "[Ljava/lang/Object;"); + } + if (fnNode.isTargetOfDirectCall()) { + setNonTrivialInit(methodName); + String className + = classFile.fullyQualifiedForm(fnNode.getClassName()); + String fieldName = className.replace('/', '_'); + classFile.addField(fieldName, + "L" + className + ";", + (short)(ClassFileWriter.ACC_PUBLIC + + ClassFileWriter.ACC_STATIC)); + addByteCode(ByteCode.ALOAD_0); + classFile.add(ByteCode.PUTSTATIC, + className, + fieldName, + "L" + className + ";"); + } + } + + // debug stuff ... (should be last thing in method) + + if (debugLevel >= 1) { + // save debugLevel + setNonTrivialInit(methodName); + addByteCode(ByteCode.ALOAD_0); // 'this' + push(debugLevel); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "debug_level", "I"); + + // generate call to "debugInit()" + String slashName = this.name.replace('.', '/'); + addByteCode(ByteCode.ALOAD_0); // load 'this' + addVirtualInvoke(slashName, + "debugInit","()", "V"); + } + + if (!trivialInit) { + addByteCode(ByteCode.RETURN); + finishMethod(cx, null); + } + } + + // this is only called if (debugLevel >= 1) + private void generateDebugInit(Context cx, Node tree) { + startNewMethod("debugInit", "()V", 0, false, true); + + // save source name + String srcName = (String) tree.getProp(Node.SOURCENAME_PROP); + addByteCode(ByteCode.ALOAD_0); // 'this' + if (srcName != null) + push(srcName); + else + addByteCode(ByteCode.ACONST_NULL); + classFile.add(ByteCode.PUTFIELD, + "org/mozilla/javascript/NativeFunction", + "debug_srcName", "Ljava/lang/String;"); + + if (debugLevel >= 3) { + // at this debug level we know that our superclass + // is debug specific. Generate more debug info into it. + + // save the base and end line numbers + Integer prop; + + prop = (Integer) tree.getProp(Node.BASE_LINENO_PROP); + if (null != prop) { + addByteCode(ByteCode.ALOAD_0); // 'this' + push(prop.intValue()); + + classFile.add(ByteCode.PUTFIELD, + superClassSlashName, + "debug_baseLineno", "I"); + } + + prop = (Integer) tree.getProp(Node.END_LINENO_PROP); + if (null != prop) { + addByteCode(ByteCode.ALOAD_0); // 'this' + push(prop.intValue()); + + classFile.add(ByteCode.PUTFIELD, + superClassSlashName, + "debug_endLineno", "I"); + } + + // trap support + if (debugLevel >= 6) { + writeDebugPCEntries(); + buildDebugTrapMap(); + } + + // generate call to script loading hook + // *always do this last* + + addByteCode(ByteCode.ALOAD_0); //'this' + addVirtualInvoke(superClassSlashName, + "callLoadingScriptHook", + "()", "V"); + } + + addByteCode(ByteCode.RETURN); + finishMethod(cx, null); + } + + private void generateRegExpLiterals(Vector regexps, boolean inCtor) { + for (int i=0; i < regexps.size(); i++) { + Node regexp = (Node) regexps.elementAt(i); + StringBuffer sb = new StringBuffer("_re"); + sb.append(i); + String fieldName = sb.toString(); + short flags = ClassFileWriter.ACC_PRIVATE; + if (inCtor) + flags |= ClassFileWriter.ACC_FINAL; + classFile.addField(fieldName, + "Lorg/mozilla/javascript/regexp/NativeRegExp;", + flags); + addByteCode(ByteCode.ALOAD_0); // load 'this' + + addByteCode(ByteCode.NEW, "org/mozilla/javascript/regexp/NativeRegExp"); + addByteCode(ByteCode.DUP); + + aload(contextLocal); // load 'context' + aload(variableObjectLocal); // load 'scope' + Node left = regexp.getFirstChild(); + push(left.getString()); + Node right = regexp.getLastChild(); + if (left == right) { + addByteCode(ByteCode.ACONST_NULL); + } else { + push(right.getString()); + } + + addSpecialInvoke("org/mozilla/javascript/regexp/NativeRegExp", + "", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/String;Ljava/lang/String;)", + "V"); + + regexp.putProp(Node.REGEXP_PROP, fieldName); + classFile.add(ByteCode.PUTFIELD, + classFile.fullyQualifiedForm(this.name), + fieldName, "Lorg/mozilla/javascript/regexp/NativeRegExp;"); + } + } + + private void generateFunctionInits(Vector fns) { + // make an array to put them in, so they're available + // for decompilation. + push(fns.size()); + addByteCode(ByteCode.ANEWARRAY, "org/mozilla/javascript/NativeFunction"); + + /* note that nested function decompilation currently + * depends on the elements of the nestedFunctions array + * being defined in source order. (per function/script, + * starting from 0.) Change Parser and NativeFunction if + * changing ordering. + */ + + for (short i = 0; i < fns.size(); i++) { + addByteCode(ByteCode.DUP); // make another reference to the array + push(i); // to set up for aastore at end of loop + + Node def = (Node) fns.elementAt(i); + Codegen codegen = new Codegen(); + String fnClassName = codegen.generateCode(def, results, itsNameHelper); + // initialize a property by that name, and push the object + // as a result + if (inFunction) { + addByteCode(ByteCode.ALOAD_0); // load "this" argument + } else { + aload(variableObjectLocal); + } + push(fnClassName); + String str = def.getString(); + if (str != null) { + push(str); + } else { + addByteCode(ByteCode.ACONST_NULL); + } + addByteCode(ByteCode.ALOAD_0); // load 'this' + aload(contextLocal); // load 'cx' + addScriptRuntimeInvoke("defineFunction", + "(Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/String;" + + "Ljava/lang/String;" + + "Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Context;)", + "Lorg/mozilla/javascript/NativeFunction;"); + def.putProp(Node.FUNCTION_PROP, new Short(i)); + addByteCode(ByteCode.AASTORE); // store NativeFunction + // instance to array + } + + // add the array as the nestedFunctions field; array should + // still be on the stack. + + addByteCode(ByteCode.ALOAD_0); // load 'this' + addByteCode(ByteCode.SWAP); // swap with original array reference + + classFile.add(ByteCode.PUTFIELD, "org/mozilla/javascript/NativeFunction", + "nestedFunctions", "[Lorg/mozilla/javascript/NativeFunction;"); + } + + + /** + * Generate the prologue for a function or script. + * + * @param tree the tree to generate code for + * @param inFunction true if generating the prologue for a function + * (as opposed to a script) + * @param directParameterCount number of parameters for direct call, + * or -1 if not direct call + */ + private void generatePrologue(Context cx, Node tree, boolean inFunction, + int directParameterCount) + { + funObjLocal = reserveWordLocal(0); + contextLocal = reserveWordLocal(1); + variableObjectLocal = reserveWordLocal(2); + thisObjLocal = reserveWordLocal(3); + + if (directParameterCount > 0) { + for (int i = 0; i < (3 * directParameterCount); i++) + reserveWordLocal(i + 4); // reserve 'args' + } + // reserve 'args[]' + argsLocal = reserveWordLocal(directParameterCount <= 0 + ? 4 : (3 * directParameterCount) + 4); + + // These locals are to be pre-allocated since they need function scope. + // They are primarily used by the exception handling mechanism + Integer localCount = (Integer)(tree.getProp(Node.LOCALCOUNT_PROP)); + if (localCount != null) { + itsLocalAllocationBase = (short)(argsLocal + 1); + for (int i = 0; i < localCount.intValue(); i++) { + reserveWordLocal(itsLocalAllocationBase + i); + } + } + + hasVarsInRegs = inFunction && + !((OptFunctionNode)tree).requiresActivation(); + if (hasVarsInRegs) { + // No need to create activation. Pad arguments if need be. + int parmCount = vars.getParameterCount(); + if (inFunction && parmCount > 0 && directParameterCount < 0) { + // Set up args array + // check length of arguments, pad if need be + aload(argsLocal); + addByteCode(ByteCode.ARRAYLENGTH); + push(parmCount); + int label = acquireLabel(); + addByteCode(ByteCode.IF_ICMPGE, label); + aload(argsLocal); + push(parmCount); + addScriptRuntimeInvoke("padArguments", + "([Ljava/lang/Object;I)", + "[Ljava/lang/Object;"); + astore(argsLocal); + markLabel(label); + } + + // REMIND - only need to initialize the vars that don't get a value + // before the next call and are used in the function + short firstUndefVar = -1; + for (int i = 0; i < vars.size(); i++) { + OptLocalVariable lVar = (OptLocalVariable) vars.get(i); + if (lVar.isNumber()) { + lVar.assignJRegister(getNewWordPairLocal()); + push(0.0); + dstore(lVar.getJRegister()); + } else if (lVar.isParameter()) { + if (directParameterCount < 0) { + lVar.assignJRegister(getNewWordLocal()); + aload(argsLocal); + push(i); + addByteCode(ByteCode.AALOAD); + astore(lVar.getJRegister()); + } + } else { + lVar.assignJRegister(getNewWordLocal()); + if (firstUndefVar == -1) { + pushUndefined(); + firstUndefVar = lVar.getJRegister(); + } else { + aload(firstUndefVar); + } + astore(lVar.getJRegister()); + } + lVar.setStartPC(classFile.getCurrentCodeOffset()); + } + + // Indicate that we should generate debug information for + // the variable table. (If we're generating debug info at + // all.) + debugVars = vars; + + // Skip creating activation object. + return; + } + + if (directParameterCount > 0) { + // We're going to create an activation object, so we + // need to get an args array with all the arguments in it. + + aload(argsLocal); + push(directParameterCount); + addOptRuntimeInvoke("padStart", + "([Ljava/lang/Object;I)", + "[Ljava/lang/Object;"); + astore(argsLocal); + for (int i=0; i < directParameterCount; i++) { + aload(argsLocal); + push(i); + // "3" is 1 for Object parm and 2 for double parm, and + // "4" is to account for the context, etc. parms + aload((short) (3*i+4)); + addByteCode(ByteCode.AASTORE); + } + } + + String debugVariableName; + if (inFunction) { + aload(contextLocal); + aload(variableObjectLocal); + aload(funObjLocal); + aload(thisObjLocal); + aload(argsLocal); + addScriptRuntimeInvoke("initVarObj", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/NativeFunction;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)", + "Lorg/mozilla/javascript/Scriptable;"); + debugVariableName = "activation"; + } else { + aload(contextLocal); + aload(variableObjectLocal); + aload(funObjLocal); + aload(thisObjLocal); + addScriptRuntimeInvoke("initScript", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/NativeFunction;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + debugVariableName = "global"; + } + astore(variableObjectLocal); + + if (cx.isGeneratingDebug()) { + debugVars = new OptVariableTable(); + debugVars.addLocal(debugVariableName); + OptLocalVariable lv = (OptLocalVariable) debugVars.get(debugVariableName); + lv.assignJRegister(variableObjectLocal); + lv.setStartPC(classFile.getCurrentCodeOffset()); + } + + if (!inFunction) { + // OPT: use dataflow to prove that this assignment is dead + scriptResultLocal = getNewWordLocal(); + pushUndefined(); + astore(scriptResultLocal); + } + + if (inFunction && ((OptFunctionNode)tree).containsCalls(-1)) { + if (((OptFunctionNode)tree).containsCalls(0)) { + itsZeroArgArray = getNewWordLocal(); + classFile.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "emptyArgs", "[Ljava/lang/Object;"); + astore(itsZeroArgArray); + } + if (((OptFunctionNode)tree).containsCalls(1)) { + itsOneArgArray = getNewWordLocal(); + push(1); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + astore(itsOneArgArray); + } + } + + if (debugLevel > 0) { // XXX is 0 correct? + // setup the local for 'pc' with a default value + debug_pcLocal = getNewWordLocal(); + addByteCode(ByteCode.ICONST_0); + istore(debug_pcLocal); + if (debugLevel >= 6) { + debugStopSubLabel = acquireLabel(); + generateDebugStopSubroutine(); + } + } + } + + private void generateEpilogue() { + if (epilogueLabel != -1) { + classFile.markLabel(epilogueLabel); + } + if (!hasVarsInRegs || !inFunction || debugLevel > 0) { + // restore caller's activation + aload(contextLocal); + addScriptRuntimeInvoke("popActivation", + "(Lorg/mozilla/javascript/Context;)", + "V"); + } + addByteCode(ByteCode.ARETURN); + } + + private void generateDebugStopSubroutine() { + // local to store our subroutine return address +// int stackTop = classFile.getStackTop(); + debugStopSubRetLocal = getNewWordLocal(); + + // generate the code to jump over this subroutine + // (needed if generated anyplace other than after the last return) + int labelSkip = acquireLabel(); + addByteCode(ByteCode.GOTO, labelSkip); + + // + // the actual subroutine starts here + // + classFile.adjustStackTop(1); + markLabel(debugStopSubLabel); + astore(debugStopSubRetLocal); +// System.out.println(1+" " +stackTop+" "+classFile.getStackTop()); + int labelRetPop0 = acquireLabel(); + int labelRetPop1 = acquireLabel(); + int labelRetPop2 = acquireLabel(); + int labelCheckForStopPoint = acquireLabel(); + int labelHandleHookAnswer = acquireLabel(); + int labelNotThrow = acquireLabel(); + + // parse the flag to see what should be done + addByteCode(ByteCode.ALOAD_0); // 'this' + classFile.add(ByteCode.GETFIELD, + superClassSlashName, + "debug_stopFlags", "I"); + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.ICONST_1); + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.IFEQ, labelCheckForStopPoint); + addByteCode(ByteCode.POP); + + // if here then an immediate interrupt was requested. (stack empty) +// System.out.println(2+" " +stackTop+" "+classFile.getStackTop()); + + // call our "hook caller" helper in Debug superclass + addByteCode(ByteCode.ALOAD_0); // 'this' + aload(contextLocal); + addByteCode(ByteCode.ICONST_1); // retVal[1] array + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + addByteCode(ByteCode.DUP_X2); //retVal will be on stack after call + iload(debug_pcLocal); + aload(variableObjectLocal); + + addVirtualInvoke(superClassSlashName, + "callInterruptHook", + "(Lorg/mozilla/javascript/Context;[Ljava/lang/Object;" + + "ILorg/mozilla/javascript/Scriptable;)", + "I"); + // jump to labelHandleHookAnswer (same code for both hooks) + addByteCode(ByteCode.GOTO, labelHandleHookAnswer); + + /********************************************************/ + + // if here, check to see if this is our stoppoint + // the stack has 'debug_stopFlags' + classFile.adjustStackTop(-1); + markLabel(labelCheckForStopPoint); + + // sanity check that breakpoint flag is really set + addByteCode(ByteCode.ICONST_2); + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.IFEQ, labelRetPop0); + + // check to see if we are at a pc with a breakpoint (stack empty) +// System.out.println(3+" " +stackTop+" "+classFile.getStackTop()); + + // We have an array of 32bit ints. We use the pc to index bits in + // that array. If the bit is set then we trap, otherwise continue. + // To figure out which bit the pc represents we do bitwise work to + // calc an index and mask. Similar work is done in Java in the class + // org.mozilla.javascript.debug.NativeDelegate to set and clear these + // bits. + + // get current pc + iload(debug_pcLocal); + addByteCode(ByteCode.DUP); + + // calc the index into trapMap + addByteCode(ByteCode.ICONST_5); + addByteCode(ByteCode.ISHR); + addByteCode(ByteCode.SWAP); + + // calc the mask + push(0x1f); + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.ICONST_1); + addByteCode(ByteCode.SWAP); + addByteCode(ByteCode.ISHL); + addByteCode(ByteCode.SWAP); + + // stack has mask, index + + // get refernce to trapMap + addByteCode(ByteCode.ALOAD_0); // 'this' + classFile.add(ByteCode.GETFIELD, + superClassSlashName, + "debug_trapMap", "[I"); + + // get our entry + addByteCode(ByteCode.SWAP); + addByteCode(ByteCode.IALOAD); + + // 'and' against the map + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.IFEQ, labelRetPop0); + + // here if we've hit a trap (stack empty) +// System.out.println(4+" " +stackTop+" "+classFile.getStackTop()); + + // call our "hook caller" helper in Debug superclass + addByteCode(ByteCode.ALOAD_0); // 'this' + aload(contextLocal); + addByteCode(ByteCode.ICONST_1); // retVal[1] array + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + addByteCode(ByteCode.DUP_X2); //retVal will be on stack after call + iload(debug_pcLocal); + aload(variableObjectLocal); + + addVirtualInvoke(superClassSlashName, + "callTrappedHook", + "(Lorg/mozilla/javascript/Context;[Ljava/lang/Object;" + + "ILorg/mozilla/javascript/Scriptable;)", + "I"); + // fall through to labelHandleHookAnswer (same code for both hooks) + + // Deal with Hook Answer, stack has retval object, action code int + markLabel(labelHandleHookAnswer); + + // available actions... + // org.mozilla.javascript.DeepBytecodeHook.CONTINUE = 0; + // org.mozilla.javascript.DeepBytecodeHook.THROW = 1; + // org.mozilla.javascript.DeepBytecodeHook.RETURN_VALUE = 2; + + // is it just 'continue'? + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.IFEQ, labelRetPop2); + + // either a throw or return value -- get the value (Object[0]) + addByteCode(ByteCode.SWAP); + addByteCode(ByteCode.ICONST_0); + addByteCode(ByteCode.AALOAD); + addByteCode(ByteCode.SWAP); + + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.ICONST_1); + addByteCode(ByteCode.IF_ICMPNE, labelNotThrow); + addByteCode(ByteCode.POP); // get the object to the top + addByteCode(ByteCode.CHECKCAST, "java/lang/Throwable"); + addByteCode(ByteCode.ATHROW); + + classFile.adjustStackTop(2); + markLabel(labelNotThrow); + // sanity check + addByteCode(ByteCode.ICONST_2); + addByteCode(ByteCode.IF_ICMPNE, labelRetPop1); + addByteCode(ByteCode.ARETURN); + + classFile.adjustStackTop(2); + markLabel(labelRetPop2); + addByteCode(ByteCode.POP); + markLabel(labelRetPop1); + addByteCode(ByteCode.POP); + markLabel(labelRetPop0); + addByteCode(ByteCode.RET, debugStopSubRetLocal); + + // target for jumping over the subroutine + markLabel(labelSkip); + addByteCode(ByteCode.NOP); +// System.out.println(5+" " +stackTop+" "+classFile.getStackTop()); + } + + private short addDebugPCEntry(short lineno) { + if (0 == debugLineEntryCount) { + debugLineMap = new short[DEBUG_LINE_MAP_INITIAL_SIZE]; + } + if (debugLineMap.length == debugLineEntryCount+1) { + short[] newMap = + new short[debugLineMap.length+DEBUG_LINE_MAP_RESIZE_INCREMENT]; + for (short i = 0; i < debugLineMap.length; i++) + newMap[i] = debugLineMap[i]; + debugLineMap = newMap; + } + debugLineMap[++debugLineEntryCount] = lineno; + return debugLineEntryCount; + } + private void writeDebugPCEntries() { + String s = null; + if (null != debugLineMap && 0 != debugLineEntryCount) { + // I have a short[]. I want a char[]. Java won't let me cast. + char[] ca = new char[debugLineEntryCount+1]; + for (short i = 0; i < debugLineEntryCount+1; i++) + ca[i] = (char) debugLineMap[i]; + s = new String(ca); + } + + addByteCode(ByteCode.ALOAD_0); // 'this' + if (s == null) + addByteCode(ByteCode.ACONST_NULL); + else + push(s); + classFile.add(ByteCode.PUTFIELD, + superClassSlashName, + "debug_linenoMap", "Ljava/lang/String;"); + } + private void buildDebugTrapMap() { + if (null != debugLineMap && 0 != debugLineEntryCount) { + short count = (short)((debugLineEntryCount+1)/32); + if (0 != (debugLineEntryCount+1)%32) + count++; + + addByteCode(ByteCode.ALOAD_0); // 'this' + push(count); + addByteCode(ByteCode.NEWARRAY, 10); // array of int + classFile.add(ByteCode.PUTFIELD, + superClassSlashName, + "debug_trapMap", "[I"); + } + } + + private void visitFunction(Node node) { + addByteCode(ByteCode.NEW, "org/mozilla/javascript/NativeClosure"); + addByteCode(ByteCode.DUP); + + // Context argument to constructor + aload(contextLocal); + + // Scope argument to constructor + aload(variableObjectLocal); + + // Function argument to constructor + Node fn = (Node) node.getProp(Node.FUNCTION_PROP); + Short index = (Short) fn.getProp(Node.FUNCTION_PROP); + aload(funObjLocal); + classFile.add(ByteCode.GETFIELD, "org/mozilla/javascript/NativeFunction", + "nestedFunctions", "[Lorg/mozilla/javascript/NativeFunction;"); + push(index.shortValue()); + addByteCode(ByteCode.AALOAD); + + addSpecialInvoke("org/mozilla/javascript/NativeClosure", + "", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/NativeFunction;)", "V"); + } + + private void visitTarget(Node node) { + Object lblObect = node.getProp(Node.LABEL_PROP); + if (lblObect == null) { + int label = markLabel(acquireLabel()); + node.putProp(Node.LABEL_PROP, new Integer(label)); + } + else { + int label = ((Integer)lblObect).intValue(); + markLabel(label); + } + + } + + private void visitGOTO(Node node, int type, Node child) { + Node target = (Node)(node.getProp(Node.TARGET_PROP)); + Object lblObect = target.getProp(Node.LABEL_PROP); + int targetLabel; + if (lblObect == null) { + targetLabel = acquireLabel(); + target.putProp(Node.LABEL_PROP, new Integer(targetLabel)); + } + else + targetLabel = ((Integer)lblObect).intValue(); + int fallThruLabel = acquireLabel(); + + if ((type == TokenStream.IFEQ) || (type == TokenStream.IFNE)) { + if (child == null) { + // can have a null child from visitSwitch which + // has already generated the code for the child + // and just needs the GOTO code emitted + addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)", "Z"); + if (type == TokenStream.IFEQ) + addByteCode(ByteCode.IFNE, targetLabel); + else + addByteCode(ByteCode.IFEQ, targetLabel); + } + else { + if (type == TokenStream.IFEQ) + generateCodeFromNode(child, node, targetLabel, fallThruLabel); + else + generateCodeFromNode(child, node, fallThruLabel, targetLabel); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + if (type == TokenStream.IFEQ) + addByteCode(ByteCode.IFNE, targetLabel); + else + addByteCode(ByteCode.IFEQ, targetLabel); + } + } + } + else { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + if (type == TokenStream.JSR) + addByteCode(ByteCode.JSR, targetLabel); + else + addByteCode(ByteCode.GOTO, targetLabel); + } + markLabel(fallThruLabel); + } + + private void visitEnumInit(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + addScriptRuntimeInvoke("initEnum", + "(Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Ljava/util/Enumeration;"); + short x = getNewWordLocal(); + astore(x); + node.putProp(Node.LOCAL_PROP, new Integer(x)); + } + + private void visitEnumNext(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + Node init = (Node) node.getProp(Node.ENUM_PROP); + Integer local = (Integer) init.getProp(Node.LOCAL_PROP); + aload(local.shortValue()); + addScriptRuntimeInvoke("nextEnum", "(Ljava/util/Enumeration;)", "Ljava/lang/Object;"); + } + + private void visitEnumDone(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + Node init = (Node) node.getProp(Node.ENUM_PROP); + Integer local = (Integer) init.getProp(Node.LOCAL_PROP); + releaseWordLocal(local.shortValue()); + } + + private void visitEnterWith(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + addScriptRuntimeInvoke("enterWith", + "(Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + astore(variableObjectLocal); + } + + private void visitLeaveWith(Node node, Node child) { + aload(variableObjectLocal); + addScriptRuntimeInvoke("leaveWith", + "(Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + astore(variableObjectLocal); + } + + private void resetTargets(Node node) + { + if (node.getType() == TokenStream.TARGET) { + node.putProp(Node.LABEL_PROP, null); + } + Node child = node.getFirstChild(); + while (child != null) { + resetTargets(child); + child = child.getNextSibling(); + } + } + + private void visitCall(Node node, int type, Node child) { + /* + * Generate code for call. + */ + + Node chelsea = child; // remember the first child for later + OptFunctionNode target = (OptFunctionNode)node.getProp(Node.DIRECTCALL_PROP); + if (target != null) { + generateCodeFromNode(child, node, -1, -1); + int regularCall = acquireLabel(); + + String className = classFile.fullyQualifiedForm(target.getClassName()); + String fieldName = className.replace('/', '_'); + + classFile.add(ByteCode.GETSTATIC, + classFile.fullyQualifiedForm(className), + fieldName, + "L" + className + ";"); + short stackHeight = classFile.getStackTop(); + + addByteCode(ByteCode.DUP2); + addByteCode(ByteCode.IF_ACMPNE, regularCall); + addByteCode(ByteCode.SWAP); + addByteCode(ByteCode.POP); + + addByteCode(ByteCode.DUP); + classFile.add(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()", "Lorg/mozilla/javascript/Scriptable;"); + aload(contextLocal); + addByteCode(ByteCode.SWAP); + + if (type == TokenStream.NEW) + addByteCode(ByteCode.ACONST_NULL); + else { + child = child.getNextSibling(); + generateCodeFromNode(child, node, -1, -1); + } +/* + Remember that directCall parameters are paired in 1 aReg and 1 dReg + If the argument is an incoming arg, just pass the orginal pair thru. + Else, if the argument is known to be typed 'Number', pass Void.TYPE + in the aReg and the number is the dReg + Else pass the JS object in the aReg and 0.0 in the dReg. +*/ + child = child.getNextSibling(); + while (child != null) { + boolean handled = false; + if ((child.getType() == TokenStream.GETVAR) + && inDirectCallFunction) { + OptLocalVariable lVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + if (lVar.isParameter()) { + handled = true; + aload(lVar.getJRegister()); + dload((short)(lVar.getJRegister() + 1)); + } + } + if (!handled) { + Integer childNumberFlag + = (Integer)(child.getProp(Node.ISNUMBER_PROP)); + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.BOTH)) { + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + generateCodeFromNode(child, node, -1, -1); + } + else { + generateCodeFromNode(child, node, -1, -1); + push(0.0); + } + } + resetTargets(child); + child = child.getNextSibling(); + } + + addByteCode(ByteCode.ALOAD_0); + classFile.add(ByteCode.GETFIELD, + classFile.fullyQualifiedForm(this.name), + "EmptyArray", + "[Ljava/lang/Object;"); + + if (type == TokenStream.NEW) + addVirtualInvoke(target.getClassName(), + "constructDirect", + target.getDirectCallParameterSignature(), + "Ljava/lang/Object;"); + else + addVirtualInvoke(target.getClassName(), + "callDirect", + target.getDirectCallParameterSignature(), + "Ljava/lang/Object;"); + + int beyond = acquireLabel(); + addByteCode(ByteCode.GOTO, beyond); + markLabel(regularCall, stackHeight); + addByteCode(ByteCode.POP); + + visitRegularCall(node, type, chelsea, true); + markLabel(beyond); + } + else { + visitRegularCall(node, type, chelsea, false); + } + } + + + private String getSimpleCallName(Node callNode) + /* + Find call trees that look like this : + (they arise from simple function invocations) + + CALL <-- this is the callNode node + GETPROP + NEWTEMP [USES: 1] + GETBASE 'name' + STRING 'name' + GETTHIS + USETEMP [TEMP: NEWTEMP [USES: 1]] + <-- arguments would be here + + and return the name found. + + */ + { +if (true) { + Node callBase = callNode.getFirstChild(); + if (callBase.getType() == TokenStream.GETPROP) { + Node callBaseChild = callBase.getFirstChild(); + if (callBaseChild.getType() == TokenStream.NEWTEMP) { + Node callBaseID = callBaseChild.getNextSibling(); + Node tempChild = callBaseChild.getFirstChild(); + if (tempChild.getType() == TokenStream.GETBASE) { + String functionName = tempChild.getString(); + if ((callBaseID != null) && + (callBaseID.getType() == TokenStream.STRING)) { + if (functionName.equals(callBaseID.getString())) { + Node thisChild = callBase.getNextSibling(); + if (thisChild.getType() == TokenStream.GETTHIS) { + Node useChild = thisChild.getFirstChild(); + if (useChild.getType() == TokenStream.USETEMP) { + Node temp = (Node)useChild.getProp(Node.TEMP_PROP); + if (temp == callBaseChild) { + return functionName; + } + } + } + } + } + } + } + } +} + return null; + } + + private void constructArgArray(int argCount) + { + if (argCount == 0) { + if (itsZeroArgArray >= 0) + aload(itsZeroArgArray); + else { + push(0); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + else { + if (argCount == 1) { + if (itsOneArgArray >= 0) + aload(itsOneArgArray); + else { + push(1); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + else { + push(argCount); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + } + + private void visitRegularCall(Node node, int type, + Node child, boolean firstArgDone) + { + /* + * Generate code for call. + * + * push + * anewarray Ljava/lang/Object; + * // "initCount" instances of code from here... + * dup + * push + * + * aastore + * //...to here + * invokestatic call + */ + + OptFunctionNode target = (OptFunctionNode)node.getProp(Node.DIRECTCALL_PROP); + Node chelsea = child; + int childCount = 0; + int argSkipCount = (type == TokenStream.NEW) ? 1 : 2; + while (child != null) { + childCount++; + child = child.getNextSibling(); + } + + child = chelsea; // re-start the iterator from the first child, + // REMIND - too bad we couldn't just back-patch the count ? + + int argIndex = -argSkipCount; + if (firstArgDone && (child != null)) { + child = child.getNextSibling(); + argIndex++; + aload(contextLocal); + addByteCode(ByteCode.SWAP); + } + else + aload(contextLocal); + + if (firstArgDone && (type == TokenStream.NEW)) + constructArgArray(childCount - argSkipCount); + + boolean isSpecialCall = node.getProp(Node.SPECIALCALL_PROP) != null; + boolean isSimpleCall = false; + String simpleCallName = null; + if (type != TokenStream.NEW) { + simpleCallName = getSimpleCallName(node); + if (simpleCallName != null && !isSpecialCall && debugLevel < 3) { + isSimpleCall = true; + push(simpleCallName); + aload(variableObjectLocal); + child = child.getNextSibling().getNextSibling(); + argIndex = 0; + push(childCount - argSkipCount); + addByteCode(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + + while (child != null) { + if (argIndex < 0) // not moving these arguments to the array + generateCodeFromNode(child, node, -1, -1); + else { + addByteCode(ByteCode.DUP); + push(argIndex); + if (target != null) { +/* + If this has also been a directCall sequence, the Number flag will + have remained set for any parameter so that the values could be + copied directly into the outgoing args. Here we want to force it + to be treated as not in a Number context, so we set the flag off. +*/ + boolean handled = false; + if ((child.getType() == TokenStream.GETVAR) + && inDirectCallFunction) { + OptLocalVariable lVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + if (lVar.isParameter()) { + child.putProp(Node.ISNUMBER_PROP, null); + generateCodeFromNode(child, node, -1, -1); + handled = true; + } + } + if (!handled) { + Integer childNumberFlag + = (Integer)(child.getProp(Node.ISNUMBER_PROP)); + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.BOTH)) { + addByteCode(ByteCode.NEW,"java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, -1, -1); + addDoubleConstructor(); + } + else + generateCodeFromNode(child, node, -1, -1); + } + } + else + generateCodeFromNode(child, node, -1, -1); + addByteCode(ByteCode.AASTORE); + } + argIndex++; + if (argIndex == 0) { + // now we need to construct the array + // (even if there are no args to load + // into it) - REMIND could pass null + // instead ? + constructArgArray(childCount - argSkipCount); + } + child = child.getNextSibling(); + } + + // set our pc into the top frame + if (debugLevel >= 1) { + aload(contextLocal); + aload(variableObjectLocal); + iload(debug_pcLocal); + addStaticInvoke("org/mozilla/javascript/debug/NativeDelegate", + "setPC", + "(Lorg/mozilla/javascript/Context;Lorg/mozilla/javascript/Scriptable;I)", + "V"); + } + + String className; + String methodNameNewObj; + String methodNameCall; + String callSignature; + + if (isSpecialCall) { + className = "org/mozilla/javascript/ScriptRuntime"; + methodNameNewObj = "newObjectSpecial"; + methodNameCall = "callSpecial"; + if (type != TokenStream.NEW) { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;" + + "[Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/String;I)"; // filename & linenumber + aload(thisObjLocal); + aload(variableObjectLocal); + push(itsSourceFile); + push(itsLineNumber); + } else { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/Object;" + + "[Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)"; + aload(variableObjectLocal); + } + } else { + methodNameNewObj = "newObject"; + if (isSimpleCall) { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)"; + methodNameCall = "callSimple"; + className = "org/mozilla/javascript/optimizer/OptRuntime"; + } else { + if (type == TokenStream.NEW) { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/Object;" + + "[Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)"; + aload(variableObjectLocal); + } else { + callSignature = "(Lorg/mozilla/javascript/Context;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;" + + "[Ljava/lang/Object;)"; + } + methodNameCall = "call"; + className = "org/mozilla/javascript/ScriptRuntime"; + } + } + + /* NOTE: we assure above !(isSimpleCall && debugLevel >= 3). So + * we don't need to worry about the SimpleCall case in debug code. + */ + if (debugLevel >= 3) { + className = "org/mozilla/javascript/debug/NativeDelegate"; + if (isSpecialCall) { + methodNameNewObj = "newObjectSpecialDebug"; + methodNameCall = "callSpecialDebug"; + } else { + methodNameNewObj = "newObjectDebug"; + methodNameCall = "callDebug"; + } + } + + if (type == TokenStream.NEW) { + addStaticInvoke(className, + methodNameNewObj, + callSignature, + "Lorg/mozilla/javascript/Scriptable;"); + } else { + addStaticInvoke(className, + methodNameCall, + callSignature, + "Ljava/lang/Object;"); + } + } + + private void visitStatement(Node node) { + Object datum = node.getDatum(); + if (datum == null || !(datum instanceof Number)) + return; + itsLineNumber = ((Number) datum).shortValue(); + if (itsLineNumber == -1) + return; + classFile.addLineNumberEntry((short)itsLineNumber); + + if (debugLevel >= 1) { + if (debugLevel >= 6) { + int pc = addDebugPCEntry((short)itsLineNumber); + push(pc); + istore(debug_pcLocal); + + // see if a stop is requested anywhere in this function. + // if the flag is non-zero, then let the subroutine decide + // what to do. + + addByteCode(ByteCode.ALOAD_0); // load 'this' + classFile.add(ByteCode.GETFIELD, + superClassSlashName, + "debug_stopFlags", "I"); + int label = acquireLabel(); + addByteCode(ByteCode.IFEQ, label); + addByteCode(ByteCode.JSR, debugStopSubLabel); + classFile.adjustStackTop(-1); + markLabel(label); + } + else { + push(itsLineNumber); + istore(debug_pcLocal); + } + } + } + + + private void visitTryCatchFinally(Node node, Node child) { + /* Save the variable object, in case there are with statements + * enclosed by the try block and we catch some exception. + * We'll restore it for the catch block so that catch block + * statements get the right scope. + */ + + // OPT we only need to do this if there are enclosed WITH + // statements; could statically check and omit this if there aren't any. + + // XXX OPT Maybe instead do syntactic transforms to associate + // each 'with' with a try/finally block that does the exitwith. + + // For that matter: Why do we have leavewith? + + // XXX does Java have any kind of MOV(reg, reg)? + short savedVariableObject = getNewWordLocal(); + aload(variableObjectLocal); + astore(savedVariableObject); + + /* + * Generate the code for the tree; most of the work is done in IRFactory + * and NodeTransformer; Codegen just adds the java handlers for the + * javascript catch and finally clauses. */ + // need to set the stack top to 1 to account for the incoming exception + int startLabel = markLabel(acquireLabel(), (short)1); + + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + + Node catchTarget = (Node)node.getProp(Node.TARGET_PROP); + Node finallyTarget = (Node)node.getProp(Node.FINALLY_PROP); + + // control flow skips the handlers + int realEnd = acquireLabel(); + addByteCode(ByteCode.GOTO, realEnd); + + // javascript handler; unwrap exception and GOTO to javascript + // catch area. + if (catchTarget != null) { + + int jsHandler = classFile.markHandler(acquireLabel()); + + // MS JVM gets cranky if the exception object is left on the stack + short exceptionObject = getNewWordLocal(); + astore(exceptionObject); + + // reset the variable object local + aload(savedVariableObject); + astore(variableObjectLocal); + + aload(exceptionObject); + releaseWordLocal(exceptionObject); + + // unwrap the exception... + addScriptRuntimeInvoke("unwrapJavaScriptException", + "(Lorg/mozilla/javascript/JavaScriptException;)", + "Ljava/lang/Object;"); + + // get the label to goto + int catchLabel = + ((Integer)catchTarget.getProp(Node.LABEL_PROP)).intValue(); + addByteCode(ByteCode.GOTO, catchLabel); + + // mark the handler + classFile.addExceptionHandler + (startLabel, catchLabel, jsHandler, + "org/mozilla/javascript/JavaScriptException"); + } + + // finally handler; catch all exceptions, store to a local; JSR to + // the finally, then re-throw. + if (finallyTarget != null) { + int finallyHandler = classFile.markHandler(acquireLabel()); + + // reset the variable object local + aload(savedVariableObject); + astore(variableObjectLocal); + + short exnLocal = itsLocalAllocationBase++; + astore(exnLocal); + + // get the label to JSR to + int finallyLabel = + ((Integer)finallyTarget.getProp(Node.LABEL_PROP)).intValue(); + addByteCode(ByteCode.JSR, finallyLabel); + + // rethrow + aload(exnLocal); + addByteCode(ByteCode.ATHROW); + + // mark the handler + classFile.addExceptionHandler(startLabel, finallyLabel, + finallyHandler, null); // catch any + } + releaseWordLocal(savedVariableObject); + markLabel(realEnd); + } + + private void visitThrow(Node node, Node child) { + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + + addByteCode(ByteCode.NEW, + "org/mozilla/javascript/JavaScriptException"); + addByteCode(ByteCode.DUP_X1); + addByteCode(ByteCode.SWAP); + addSpecialInvoke("org/mozilla/javascript/JavaScriptException", + "", "(Ljava/lang/Object;)", "V"); + + addByteCode(ByteCode.ATHROW); + } + + private void visitReturn(Node node, Node child) { + visitStatement(node); + if (child != null) { + do { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } while (child != null); + } else if (inFunction) { + pushUndefined(); + } else { + aload(scriptResultLocal); + } + + if (epilogueLabel == -1) + epilogueLabel = classFile.acquireLabel(); + addByteCode(ByteCode.GOTO, epilogueLabel); + } + + private void visitSwitch(Node node, Node child) { + visitStatement(node); + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + + // save selector value + short selector = getNewWordLocal(); + astore(selector); + + Vector cases = (Vector) node.getProp(Node.CASES_PROP); + for (int i=0; i < cases.size(); i++) { + Node thisCase = (Node) cases.elementAt(i); + Node first = thisCase.getFirstChild(); + generateCodeFromNode(first, thisCase, -1, -1); + aload(selector); + addScriptRuntimeInvoke("seqB", + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Boolean;"); + Node target = new Node(TokenStream.TARGET); + thisCase.replaceChild(first, target); + generateGOTO(TokenStream.IFEQ, target); + } + + Node defaultNode = (Node) node.getProp(Node.DEFAULT_PROP); + if (defaultNode != null) { + Node defaultTarget = new Node(TokenStream.TARGET); + defaultNode.getFirstChild().addChildToFront(defaultTarget); + generateGOTO(TokenStream.GOTO, defaultTarget); + } + + Node breakTarget = (Node) node.getProp(Node.BREAK_PROP); + generateGOTO(TokenStream.GOTO, breakTarget); + } + + private void generateGOTO(int type, Node target) { + Node GOTO = new Node(type); + GOTO.putProp(Node.TARGET_PROP, target); + visitGOTO(GOTO, type, null); + } + + private void visitUnary(Node node, Node child, int trueGOTO, int falseGOTO) { + int op = node.getInt(); + switch (op) { + case TokenStream.NOT: + { + if (trueGOTO != -1) { + generateCodeFromNode(child, node, falseGOTO, trueGOTO); + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, falseGOTO); + addByteCode(ByteCode.GOTO, trueGOTO); + } + } + else { + int trueTarget = acquireLabel(); + int falseTarget = acquireLabel(); + int beyond = acquireLabel(); + generateCodeFromNode(child, node, trueTarget, falseTarget); + + if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT)) + || (child.getType() == TokenStream.AND) + || (child.getType() == TokenStream.OR) + || (child.getType() == TokenStream.RELOP) + || (child.getType() == TokenStream.EQOP)) { + } + else { + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFEQ, falseTarget); + addByteCode(ByteCode.GOTO, trueTarget); + } + + markLabel(trueTarget); + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + addByteCode(ByteCode.GOTO, beyond); + markLabel(falseTarget); + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + markLabel(beyond); + classFile.adjustStackTop(-1); + } + break; + } + + case TokenStream.TYPEOF: + visitTypeof(node, child); + break; + + case TokenStream.VOID: + generateCodeFromNode(child, node, -1, -1); + addByteCode(ByteCode.POP); + pushUndefined(); + break; + + case TokenStream.BITNOT: + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, -1, -1); + addScriptRuntimeInvoke("toInt32", + "(Ljava/lang/Object;)", "I"); + push(-1); // implement ~a as (a ^ -1) + addByteCode(ByteCode.IXOR); + addByteCode(ByteCode.I2D); + addDoubleConstructor(); + break; + + case TokenStream.ADD: + case TokenStream.SUB: + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + generateCodeFromNode(child, node, -1, -1); + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D"); + if (op == TokenStream.SUB) { + addByteCode(ByteCode.DNEG); + } + addDoubleConstructor(); + break; + + default: + badTree(); + } + } + + private void visitTypeof(Node node, Node child) { + if (node.getType() == TokenStream.UNARYOP) { + generateCodeFromNode(child, node, -1, -1); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;)", "Ljava/lang/String;"); + return; + } + String name = node.getString(); + if (hasVarsInRegs) { + OptLocalVariable lVar = (OptLocalVariable) vars.get(name); + if (lVar != null) { + if (lVar.isNumber()) { + push("number"); + return; + } + visitGetVar(lVar, false, name); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;)", "Ljava/lang/String;"); + return; + } + } + aload(variableObjectLocal); + push(name); + addScriptRuntimeInvoke("typeofName", + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Ljava/lang/String;"); + } + + private void visitIncDec(Node node, boolean isInc) + { + Node child = node.getFirstChild(); + if (node.getProp(Node.ISNUMBER_PROP) != null) { + OptLocalVariable lVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + if (lVar.getJRegister() == -1) + lVar.assignJRegister(getNewWordPairLocal()); + dload(lVar.getJRegister()); + addByteCode(ByteCode.DUP2); + push(1.0); + addByteCode((isInc) ? ByteCode.DADD : ByteCode.DSUB); + dstore(lVar.getJRegister()); + } else { + OptLocalVariable lVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + String routine = (isInc) ? "postIncrement" : "postDecrement"; + if (hasVarsInRegs && child.getType() == TokenStream.GETVAR) { + if (lVar == null) + lVar = (OptLocalVariable) vars.get(child.getString()); + if (lVar.getJRegister() == -1) + lVar.assignJRegister(getNewWordLocal()); + aload(lVar.getJRegister()); + addByteCode(ByteCode.DUP); + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;)", "Ljava/lang/Object;"); + astore(lVar.getJRegister()); + } else { + if (child.getType() == TokenStream.GETPROP) { + Node getPropChild = child.getFirstChild(); + generateCodeFromNode(getPropChild, node, -1, -1); + generateCodeFromNode(getPropChild.getNextSibling(), node, -1, -1); + aload(variableObjectLocal); + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + if (child.getType() == TokenStream.GETELEM) { + routine += "Elem"; + Node getPropChild = child.getFirstChild(); + generateCodeFromNode(getPropChild, node, -1, -1); + generateCodeFromNode(getPropChild.getNextSibling(), node, -1, -1); + aload(variableObjectLocal); + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + aload(variableObjectLocal); + push(child.getString()); // push name + addScriptRuntimeInvoke(routine, + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Ljava/lang/Object;"); + } + } + } + } + } + + private boolean isArithmeticNode(Node node) { + int type = node.getType(); + return (type == TokenStream.SUB) + || (type == TokenStream.MOD) + || (type == TokenStream.DIV) + || (type == TokenStream.MUL); + } + + private void visitArithmetic(Node node, byte opCode, Node child, + Node parent) + { + Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if (childNumberFlag != null) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addByteCode(opCode); + } + else { + boolean childOfArithmetic = isArithmeticNode(parent); + if (!childOfArithmetic) { + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + } + generateCodeFromNode(child, node, -1, -1); + if (!isArithmeticNode(child)) + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D"); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + if (!isArithmeticNode(child.getNextSibling())) + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D"); + addByteCode(opCode); + if (!childOfArithmetic) { + addDoubleConstructor(); + } + } + } + + private void visitBitOp(Node node, int type, Node child) { + Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if (childNumberFlag == null) { + addByteCode(ByteCode.NEW, "java/lang/Double"); + addByteCode(ByteCode.DUP); + } + generateCodeFromNode(child, node, -1, -1); + + // special-case URSH; work with the target arg as a long, so + // that we can return a 32-bit unsigned value, and call + // toUint32 instead of toInt32. + if (type == TokenStream.URSH) { + addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)", "J"); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I"); + // Looks like we need to explicitly mask the shift to 5 bits - + // LUSHR takes 6 bits. + push(31); + addByteCode(ByteCode.IAND); + addByteCode(ByteCode.LUSHR); + addByteCode(ByteCode.L2D); + addDoubleConstructor(); + return; + } + if (childNumberFlag == null) { + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I"); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I"); + } + else { + addScriptRuntimeInvoke("toInt32", "(D)", "I"); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addScriptRuntimeInvoke("toInt32", "(D)", "I"); + } + switch (type) { + case TokenStream.BITOR: + addByteCode(ByteCode.IOR); + break; + case TokenStream.BITXOR: + addByteCode(ByteCode.IXOR); + break; + case TokenStream.BITAND: + addByteCode(ByteCode.IAND); + break; + case TokenStream.RSH: + addByteCode(ByteCode.ISHR); + break; + case TokenStream.LSH: + addByteCode(ByteCode.ISHL); + break; + default: + badTree(); + } + addByteCode(ByteCode.I2D); + if (childNumberFlag == null) { + addDoubleConstructor(); + } + } + + private boolean nodeIsDirectCallParameter(Node node) { + if (node.getType() == TokenStream.GETVAR) { + OptLocalVariable lVar + = (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP)); + if (lVar != null && lVar.isParameter() && inDirectCallFunction && + !itsForcedObjectParameters) + { + return true; + } + } + return false; + } + + private void genSimpleCompare(int op, int trueGOTO, int falseGOTO) { + switch (op) { + case TokenStream.LE : + addByteCode(ByteCode.DCMPG); + addByteCode(ByteCode.IFLE, trueGOTO); + break; + case TokenStream.GE : + addByteCode(ByteCode.DCMPL); + addByteCode(ByteCode.IFGE, trueGOTO); + break; + case TokenStream.LT : + addByteCode(ByteCode.DCMPG); + addByteCode(ByteCode.IFLT, trueGOTO); + break; + case TokenStream.GT : + addByteCode(ByteCode.DCMPL); + addByteCode(ByteCode.IFGT, trueGOTO); + break; + } + if (falseGOTO != -1) + addByteCode(ByteCode.GOTO, falseGOTO); + } + + private void visitGOTOingRelOp(Node node, Node child, Node parent, + int trueGOTO, int falseGOTO) + { + int op = node.getInt(); + Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.BOTH)) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + genSimpleCompare(op, trueGOTO, falseGOTO); + } + else { + if (op == TokenStream.INSTANCEOF) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + addScriptRuntimeInvoke("instanceOf", + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + } else if (op == TokenStream.IN) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + aload(variableObjectLocal); + addScriptRuntimeInvoke("in", + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)","Z"); + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + } else { + Node rChild = child.getNextSibling(); + boolean leftIsDCP = nodeIsDirectCallParameter(child); + boolean rightIsDCP = nodeIsDirectCallParameter(rChild); + if (leftIsDCP || rightIsDCP) { + if (leftIsDCP) { + if (rightIsDCP) { + OptLocalVariable lVar1 + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + aload(lVar1.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + OptLocalVariable lVar2 + = (OptLocalVariable)(rChild.getProp(Node.VARIABLE_PROP)); + aload(lVar2.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + dload((short)(lVar1.getJRegister() + 1)); + dload((short)(lVar2.getJRegister() + 1)); + genSimpleCompare(op, trueGOTO, falseGOTO); + markLabel(notNumbersLabel); + // fall thru to generic handling + } + else { // just the left child is a DCP, if the right child + // is a number it's worth testing the left + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.RIGHT)) { + OptLocalVariable lVar1 + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + aload(lVar1.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + dload((short)(lVar1.getJRegister() + 1)); + generateCodeFromNode(rChild, node, -1, -1); + genSimpleCompare(op, trueGOTO, falseGOTO); + markLabel(notNumbersLabel); + // fall thru to generic handling + } + } + } + else { // just the right child is a DCP, if the left child + // is a number it's worth testing the right + if ((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.LEFT)) { + OptLocalVariable lVar2 + = (OptLocalVariable)(rChild.getProp(Node.VARIABLE_PROP)); + aload(lVar2.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + generateCodeFromNode(child, node, -1, -1); + dload((short)(lVar2.getJRegister() + 1)); + genSimpleCompare(op, trueGOTO, falseGOTO); + markLabel(notNumbersLabel); + // fall thru to generic handling + } + } + } + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(rChild, node, -1, -1); + if (childNumberFlag == null) { + if (op == TokenStream.GE || op == TokenStream.GT) { + addByteCode(ByteCode.SWAP); + } + String routine = ((op == TokenStream.LT) + || (op == TokenStream.GT)) ? "cmp_LT" : "cmp_LE"; + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;Ljava/lang/Object;)", "I"); + } + else { + boolean doubleThenObject + = (childNumberFlag.intValue() == Node.LEFT); + if (op == TokenStream.GE || op == TokenStream.GT) { + if (doubleThenObject) { + addByteCode(ByteCode.DUP_X2); + addByteCode(ByteCode.POP); + doubleThenObject = false; + } + else { + addByteCode(ByteCode.DUP2_X1); + addByteCode(ByteCode.POP2); + doubleThenObject = true; + } + } + String routine = ((op == TokenStream.LT) + || (op == TokenStream.GT)) ? "cmp_LT" : "cmp_LE"; + if (doubleThenObject) + addOptRuntimeInvoke(routine, + "(DLjava/lang/Object;)", "I"); + else + addOptRuntimeInvoke(routine, + "(Ljava/lang/Object;D)", "I"); + } + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + } + } + } + + private void visitRelOp(Node node, Node child, Node parent) { + /* + this is the version that returns an Object result + */ + int op = node.getInt(); + Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP)); + if (((childNumberFlag != null) + && (childNumberFlag.intValue() == Node.BOTH)) + || (op == TokenStream.INSTANCEOF) + || (op == TokenStream.IN)) { + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + int trueGOTO = acquireLabel(); + int skip = acquireLabel(); + if (op == TokenStream.INSTANCEOF) { + addScriptRuntimeInvoke("instanceOf", + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + addByteCode(ByteCode.IFNE, trueGOTO); + } + else { + if (op == TokenStream.IN) { + addScriptRuntimeInvoke("in", + "(Ljava/lang/Object;Ljava/lang/Object;)","Z"); + addByteCode(ByteCode.IFNE, trueGOTO); + } + else { + genSimpleCompare(op, trueGOTO, -1); + } + } + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + addByteCode(ByteCode.GOTO, skip); + markLabel(trueGOTO); + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + markLabel(skip); + classFile.adjustStackTop(-1); // only have 1 of true/false + } + else { + String routine = ((op == TokenStream.LT) + || (op == TokenStream.GT)) ? "cmp_LTB" : "cmp_LEB"; + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + if (childNumberFlag == null) { + if (op == TokenStream.GE || op == TokenStream.GT) { + addByteCode(ByteCode.SWAP); + } + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Boolean;"); + } + else { + boolean doubleThenObject + = (childNumberFlag.intValue() == Node.LEFT); + if (op == TokenStream.GE || op == TokenStream.GT) { + if (doubleThenObject) { + addByteCode(ByteCode.DUP_X2); + addByteCode(ByteCode.POP); + doubleThenObject = false; + } + else { + addByteCode(ByteCode.DUP2_X1); + addByteCode(ByteCode.POP2); + doubleThenObject = true; + } + } + if (doubleThenObject) + addOptRuntimeInvoke(routine, + "(DLjava/lang/Object;)", + "Ljava/lang/Boolean;"); + else + addOptRuntimeInvoke(routine, + "(Ljava/lang/Object;D)", + "Ljava/lang/Boolean;"); + } + } + } + + private Number nodeIsConvertToObjectOfNumber(Node node) + { + if (node.getType() == TokenStream.CONVERT) { + Object toType = node.getProp(Node.TYPE_PROP); + if (toType == ScriptRuntime.ObjectClass) { + Node convertChild = node.getFirstChild(); + if (convertChild.getType() == TokenStream.NUMBER) + return (Number)convertChild.getDatum(); + } + } + return null; + } + + private void visitEqOp(Node node, Node child, Node parent, int trueGOTO, int falseGOTO) { + int op = node.getInt(); + Node rightChild = child.getNextSibling(); + if (trueGOTO == -1) { + if (rightChild.getType() == TokenStream.PRIMARY && + rightChild.getInt() == TokenStream.NULL) + { + generateCodeFromNode(child, node, -1, -1); + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.IFNULL, 15); + pushUndefined(); + addByteCode(ByteCode.IF_ACMPEQ, 10); + if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ)) + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + else + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + addByteCode(ByteCode.GOTO, 7); + addByteCode(ByteCode.POP); + if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ)) + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + else + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + return; + } + + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + + // JavaScript 1.2 uses shallow equality for == and != + String name; + switch (op) { + case TokenStream.EQ: + name = version == Context.VERSION_1_2 ? "seqB" : "eqB"; + break; + + case TokenStream.NE: + name = version == Context.VERSION_1_2 ? "sneB" : "neB"; + break; + + case TokenStream.SHEQ: + name = "seqB"; + break; + + case TokenStream.SHNE: + name = "sneB"; + break; + + default: + name = null; + badTree(); + } + addScriptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", + "Ljava/lang/Boolean;"); + } + else { + if (rightChild.getType() == TokenStream.PRIMARY && + rightChild.getInt() == TokenStream.NULL) + { + generateCodeFromNode(child, node, -1, -1); + /* + since we have to test for null && undefined we end up having to + push the operand twice and so have to GOTO to a pop site if the + first test passes. + We can avoid that for operands that are 'simple' i.e. don't generate + a lot of code and don't have side-effects. + For now, 'simple' means GETVAR + */ + boolean simpleChild = (child.getType() == TokenStream.GETVAR); + if (!simpleChild) addByteCode(ByteCode.DUP); + int popGOTO = acquireLabel(); + if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ)) { + addByteCode(ByteCode.IFNULL, + (simpleChild) ? trueGOTO : popGOTO); + short popStack = classFile.getStackTop(); + if (simpleChild) generateCodeFromNode(child, node, -1, -1); + pushUndefined(); + addByteCode(ByteCode.IF_ACMPEQ, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + if (!simpleChild) { + markLabel(popGOTO, popStack); + addByteCode(ByteCode.POP); + addByteCode(ByteCode.GOTO, trueGOTO); + } + } + else { + addByteCode(ByteCode.IFNULL, + (simpleChild) ? falseGOTO : popGOTO); + short popStack = classFile.getStackTop(); + if (simpleChild) generateCodeFromNode(child, node, -1, -1); + pushUndefined(); + addByteCode(ByteCode.IF_ACMPEQ, falseGOTO); + addByteCode(ByteCode.GOTO, trueGOTO); + if (!simpleChild) { + markLabel(popGOTO, popStack); + addByteCode(ByteCode.POP); + addByteCode(ByteCode.GOTO, falseGOTO); + } + } + return; + } + + Node rChild = child.getNextSibling(); + + Number numChild = nodeIsConvertToObjectOfNumber(rChild); + if (nodeIsDirectCallParameter(child) + && (numChild != null)) { + OptLocalVariable lVar1 + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + aload(lVar1.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = acquireLabel(); + addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel); + dload((short)(lVar1.getJRegister() + 1)); + push(numChild.doubleValue()); + addByteCode(ByteCode.DCMPL); + if (op == TokenStream.EQ) + addByteCode(ByteCode.IFEQ, trueGOTO); + else + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + markLabel(notNumbersLabel); + // fall thru into generic handling + } + + generateCodeFromNode(child, node, -1, -1); + generateCodeFromNode(rChild, node, -1, -1); + + String name; + switch (op) { + case TokenStream.EQ: + name = version == Context.VERSION_1_2 ? "shallowEq" : "eq"; + addScriptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + break; + + case TokenStream.NE: + name = version == Context.VERSION_1_2 ? "shallowNeq" : "neq"; + addOptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + break; + + case TokenStream.SHEQ: + name = "shallowEq"; + addScriptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + break; + + case TokenStream.SHNE: + name = "shallowNeq"; + addOptRuntimeInvoke(name, + "(Ljava/lang/Object;Ljava/lang/Object;)", "Z"); + break; + + default: + name = null; + badTree(); + } + addByteCode(ByteCode.IFNE, trueGOTO); + addByteCode(ByteCode.GOTO, falseGOTO); + } + } + + private void visitLiteral(Node node) { + if (node.getType() == TokenStream.STRING) { + // just load the string constant + push(node.getString()); + } else { + Number num = (Number) node.getDatum(); + if (node.getProp(Node.ISNUMBER_PROP) != null) { + push(num.doubleValue()); + } + else { + /* + * Generate code to create the new numeric constant + * + * new java/lang/ + * dup + * push + * invokestatic java/lang//(D)V + */ + String wrapperType = ""; + String signature = ""; + boolean isInteger = false; + + if (num instanceof Float) + num = new Double(num.floatValue()); + + // OPT: cache getClass() and compare == Type.class + if (num instanceof Integer) { + wrapperType = "Integer"; + signature = "I"; + isInteger = true; + /* + } else if (num instanceof Float) { + wrapperType = "Float"; + signature = "F"; + */ + } else if (num instanceof Double) { + wrapperType = "Double"; + signature = "D"; + } else if (num instanceof Byte) { + wrapperType = "Byte"; + signature = "B"; + isInteger = true; + } else if (num instanceof Short) { + wrapperType = "Short"; + signature = "S"; + isInteger = true; + } else { + throw Context.reportRuntimeError( + "NumberNode contains unsupported Number type: " + + num.getClass().getName()); + } + /* + There appears to be a limit in the JVM on either the number of + static fields in a class or the size of the class initializer. + Either way, we can't have any more than 2000 statically init'd + constants. + */ + if (itsConstantList.itsTop >= 2000) { + addByteCode(ByteCode.NEW, "java/lang/" + wrapperType); + addByteCode(ByteCode.DUP); + if (isInteger) + push(num.longValue()); + else + push(num.doubleValue()); + addSpecialInvoke("java/lang/" + + wrapperType, + "", + "(" + + signature + + ")", + "V"); + } + else { + classFile.add(ByteCode.GETSTATIC, + classFile.fullyQualifiedForm(this.name), + "jsK_" + + itsConstantList.addConstant(wrapperType, + signature, + num, + isInteger), + "Ljava/lang/" + wrapperType + ";"); + } + } + } + } + + private void emitConstantDudeInitializers() { + if (itsConstantList.itsTop == 0) + return; + classFile.startMethod("", "()V", + (short)(ClassFileWriter.ACC_STATIC + ClassFileWriter.ACC_FINAL)); + + for (int i = 0; i < itsConstantList.itsTop; i++) { + addByteCode(ByteCode.NEW, + "java/lang/" + + itsConstantList.itsList[i].itsWrapperType); + addByteCode(ByteCode.DUP); + if (itsConstantList.itsList[i].itsIsInteger) + push(itsConstantList.itsList[i].itsLValue); + else + push(itsConstantList.itsList[i].itsDValue); + addSpecialInvoke("java/lang/" + + itsConstantList.itsList[i].itsWrapperType, + "", + "(" + + itsConstantList.itsList[i].itsSignature + + ")", + "V"); + classFile.addField("jsK_" + i, + "Ljava/lang/" + + itsConstantList.itsList[i].itsWrapperType + ";", + ClassFileWriter.ACC_STATIC); + classFile.add(ByteCode.PUTSTATIC, + classFile.fullyQualifiedForm(this.name), + "jsK_" + i, + "Ljava/lang/" + + itsConstantList.itsList[i].itsWrapperType + ";"); + } + addByteCode(ByteCode.RETURN); + classFile.stopMethod((short)0, null); + } + + private void visitPrimary(Node node) { + int op = node.getInt(); + switch (op) { + + case TokenStream.THIS: + aload(thisObjLocal); + break; + + case TokenStream.NULL: + addByteCode(ByteCode.ACONST_NULL); + break; + + case TokenStream.TRUE: + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + break; + + case TokenStream.FALSE: + classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + break; + + case TokenStream.UNDEFINED: + pushUndefined(); + break; + + default: + badTree(); + } + } + + private void visitObject(Node node) { + Node regexp = (Node) node.getProp(Node.REGEXP_PROP); + String fieldName = (String)(regexp.getProp(Node.REGEXP_PROP)); + aload(funObjLocal); + classFile.add(ByteCode.GETFIELD, + classFile.fullyQualifiedForm(this.name), + fieldName, "Lorg/mozilla/javascript/regexp/NativeRegExp;"); + } + + private void visitName(Node node) { + aload(variableObjectLocal); // get variable object + push(node.getString()); // push name + addScriptRuntimeInvoke("name", + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Ljava/lang/Object;"); + } + + private void visitSetName(Node node, Node child) { + String name = node.getFirstChild().getString(); + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + push(name); + addScriptRuntimeInvoke("setName", + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Ljava/lang/Object;"); + } + + private void visitGetVar(OptLocalVariable lVar, boolean isNumber, + String name) + { + // TODO: Clean up use of lVar here and in set. + if (hasVarsInRegs && lVar == null) + lVar = (OptLocalVariable) vars.get(name); + if (lVar != null) { + if (lVar.getJRegister() == -1) + if (lVar.isNumber()) + lVar.assignJRegister(getNewWordPairLocal()); + else + lVar.assignJRegister(getNewWordLocal()); + if (lVar.isParameter() && inDirectCallFunction && + !itsForcedObjectParameters) + { +/* + Remember that here the isNumber flag means that we want to + use the incoming parameter in a Number context, so test the + object type and convert the value as necessary. + +*/ + if (isNumber) { + aload(lVar.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = acquireLabel(); + int beyond = acquireLabel(); + addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel); + aload(lVar.getJRegister()); + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D"); + addByteCode(ByteCode.GOTO, beyond); + markLabel(isNumberLabel); + dload((short)(lVar.getJRegister() + 1)); + markLabel(beyond); + } else { + aload(lVar.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = acquireLabel(); + int beyond = acquireLabel(); + addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel); + aload(lVar.getJRegister()); + addByteCode(ByteCode.GOTO, beyond); + markLabel(isNumberLabel); + addByteCode(ByteCode.NEW,"java/lang/Double"); + addByteCode(ByteCode.DUP); + dload((short)(lVar.getJRegister() + 1)); + addDoubleConstructor(); + markLabel(beyond); + } + } else { + if (lVar.isNumber()) + dload(lVar.getJRegister()); + else + aload(lVar.getJRegister()); + } + return; + } + + aload(variableObjectLocal); + push(name); + aload(variableObjectLocal); + addScriptRuntimeInvoke("getProp", + "(Ljava/lang/Object;Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + + private void visitSetVar(Node node, Node child, boolean needValue) { + OptLocalVariable lVar = (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP)); + // XXX is this right? If so, clean up. + if (hasVarsInRegs && lVar == null) + lVar = (OptLocalVariable) vars.get(child.getString()); + if (lVar != null) { + generateCodeFromNode(child.getNextSibling(), node, -1, -1); + if (lVar.getJRegister() == -1) { + if (lVar.isNumber()) + lVar.assignJRegister(getNewWordPairLocal()); + else + lVar.assignJRegister(getNewWordLocal()); + } + if (lVar.isParameter() + && inDirectCallFunction + && !itsForcedObjectParameters) { + if (node.getProp(Node.ISNUMBER_PROP) != null) { + if (needValue) addByteCode(ByteCode.DUP2); + aload(lVar.getJRegister()); + classFile.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = acquireLabel(); + int beyond = acquireLabel(); + addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel); + addByteCode(ByteCode.NEW,"java/lang/Double"); + addByteCode(ByteCode.DUP); + addByteCode(ByteCode.DUP2_X2); + addByteCode(ByteCode.POP2); + addDoubleConstructor(); + astore(lVar.getJRegister()); + addByteCode(ByteCode.GOTO, beyond); + markLabel(isNumberLabel); + dstore((short)(lVar.getJRegister() + 1)); + markLabel(beyond); + } + else { + if (needValue) addByteCode(ByteCode.DUP); + astore(lVar.getJRegister()); + } + } + else { + if (node.getProp(Node.ISNUMBER_PROP) != null) { + dstore(lVar.getJRegister()); + if (needValue) dload(lVar.getJRegister()); + } + else { + astore(lVar.getJRegister()); + if (needValue) aload(lVar.getJRegister()); + } + } + return; + } + + // default: just treat like any other name lookup + child.setType(TokenStream.BINDNAME); + node.setType(TokenStream.SETNAME); + visitSetName(node, child); + if (!needValue) + addByteCode(ByteCode.POP); + } + + + private void visitGetProp(Node node, Node child) { + String s = (String) node.getProp(Node.SPECIAL_PROP_PROP); + if (s != null) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + String runtimeMethod = null; + if (s.equals("__proto__")) { + runtimeMethod = "getProto"; + } else if (s.equals("__parent__")) { + runtimeMethod = "getParent"; + } else { + badTree(); + } + addScriptRuntimeInvoke(runtimeMethod, + "(Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Lorg/mozilla/javascript/Scriptable;"); + return; + } + Node nameChild = child.getNextSibling(); + /* + for 'this.foo' we call thisGet which can skip some + casting overhead. + + */ + generateCodeFromNode(child, node, -1, -1); // the object + generateCodeFromNode(nameChild, node, -1, -1); // the name + if (nameChild.getType() == TokenStream.STRING) { + if ((child.getType() == TokenStream.PRIMARY && + child.getInt() == TokenStream.THIS) + || ((child.getType() == TokenStream.NEWTEMP) + && (child.getFirstChild().getType() == TokenStream.PRIMARY) + && (child.getFirstChild().getInt() == TokenStream.THIS)) + ) { + aload(variableObjectLocal); + addOptRuntimeInvoke("thisGet", + "(Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + else { + aload(variableObjectLocal); + addScriptRuntimeInvoke("getProp", + "(Ljava/lang/Object;Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + } + else { + aload(variableObjectLocal); + addScriptRuntimeInvoke("getProp", + "(Ljava/lang/Object;Ljava/lang/String;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + } + + private void visitSetProp(Node node, Node child) { + String s = (String) node.getProp(Node.SPECIAL_PROP_PROP); + if (s != null) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + String runtimeMethod = null; + if (s.equals("__proto__")) { + runtimeMethod = "setProto"; + } else if (s.equals("__parent__")) { + runtimeMethod = "setParent"; + } else { + badTree(); + } + addScriptRuntimeInvoke(runtimeMethod, + "(Ljava/lang/Object;Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + return; + } + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + aload(variableObjectLocal); + addScriptRuntimeInvoke("setProp", + "(Ljava/lang/Object;Ljava/lang/String;" + + "Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)", + "Ljava/lang/Object;"); + } + + private void visitBind(Node node, int type, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + // Generate code for "ScriptRuntime.bind(varObj, "s")" + aload(variableObjectLocal); // get variable object + push(node.getString()); // push name + addScriptRuntimeInvoke(type == TokenStream.BINDNAME ? "bind" : "getBase", + "(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)", + "Lorg/mozilla/javascript/Scriptable;"); + } + + private void getParentScope(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + classFile.add(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()", "Lorg/mozilla/javascript/Scriptable;"); + } + + private short getLocalFromNode(Node node) { + Integer localProp = (Integer) node.getProp(Node.LOCAL_PROP); + if (localProp == null) { + // for NEWLOCAL & USELOCAL, use the next pre-allocated + // register, otherwise for NEWTEMP & USETEMP, get the + // next available from the pool + short local = ((node.getType() == TokenStream.NEWLOCAL) + || (node.getType() == TokenStream.USELOCAL)) ? + itsLocalAllocationBase++ : getNewWordLocal(); + + node.putProp(Node.LOCAL_PROP, new Integer(local)); + return local; + } else { + return localProp.shortValue(); + } + } + + private void visitNewTemp(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + short local = getLocalFromNode(node); + addByteCode(ByteCode.DUP); + astore(local); + Integer n = (Integer) node.getProp(Node.USES_PROP); + if (n == null || n.intValue() == 0) + releaseWordLocal(local); + } + + private void visitUseTemp(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + Node temp = (Node) node.getProp(Node.TEMP_PROP); + short local = getLocalFromNode(temp); + + // if the temp node has a magic TARGET property, + // treat it as a RET to that temp. + if (node.getProp(Node.TARGET_PROP) != null) + addByteCode(ByteCode.RET, local); + else + aload(local); + Integer n = (Integer) temp.getProp(Node.USES_PROP); + if (n == null) { + releaseWordLocal(local); + } else { + if (n.intValue() < Integer.MAX_VALUE) { + int i = n.intValue() - 1; + if (i == 0) + releaseWordLocal(local); + temp.putProp(Node.USES_PROP, new Integer(i)); + } + } + } + + private void visitNewLocal(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + short local = getLocalFromNode(node); + addByteCode(ByteCode.DUP); + astore(local); + } + + private void visitUseLocal(Node node, Node child) { + while (child != null) { + generateCodeFromNode(child, node, -1, -1); + child = child.getNextSibling(); + } + Node temp = (Node) node.getProp(Node.LOCAL_PROP); + short local = getLocalFromNode(temp); + + // if the temp node has a magic TARGET property, + // treat it as a RET to that temp. + if (node.getProp(Node.TARGET_PROP) != null) + addByteCode(ByteCode.RET, local); + else + aload(local); + } + + private void dstore(short local) { + xstore(ByteCode.DSTORE_0, ByteCode.DSTORE, local); + } + + private void istore(short local) { + xstore(ByteCode.ISTORE_0, ByteCode.ISTORE, local); + } + + private void astore(short local) { + xstore(ByteCode.ASTORE_0, ByteCode.ASTORE, local); + } + + private void xstore(byte shortOp, byte op, short local) { + switch (local) { + case 0: + addByteCode(shortOp); + break; + case 1: + addByteCode((byte)(shortOp + 1)); + break; + case 2: + addByteCode((byte)(shortOp + 2)); + break; + case 3: + addByteCode((byte)(shortOp + 3)); + break; + default: + if (local < 0 || local >= Byte.MAX_VALUE) + throw new RuntimeException("bad local"); + addByteCode(op, (byte)local); + break; + } + } + + private void dload(short local) { + xload(ByteCode.DLOAD_0, ByteCode.DLOAD, local); + } + + private void iload(short local) { + xload(ByteCode.ILOAD_0, ByteCode.ILOAD, local); + } + + private void aload(short local) { + xload(ByteCode.ALOAD_0, ByteCode.ALOAD, local); + } + + private void xload(byte shortOp, byte op, short local) { + switch (local) { + case 0: + addByteCode(shortOp); + break; + case 1: + addByteCode((byte)(shortOp + 1)); + break; + case 2: + addByteCode((byte)(shortOp + 2)); + break; + case 3: + addByteCode((byte)(shortOp + 3)); + break; + default: + if (local < 0 || local >= Byte.MAX_VALUE) + throw new RuntimeException("bad local"); + addByteCode(op, (byte) local); + break; + } + } + + private short getNewWordPairLocal() { + short result = firstFreeLocal; + while (true) { + if (result >= (MAX_LOCALS - 1)) + break; + if (!locals[result] + && !locals[result + 1]) + break; + result++; + } + if (result < (MAX_LOCALS - 1)) { + locals[result] = true; + locals[result + 1] = true; + if (result == firstFreeLocal) { + for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) { + if (!locals[i]) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + } + else { + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private short reserveWordLocal(int local) { + if (getNewWordLocal() != local) + throw new RuntimeException("Local allocation error"); + return (short) local; + } + + private short getNewWordLocal() { + short result = firstFreeLocal; + locals[result] = true; + for (int i = firstFreeLocal + 1; i < MAX_LOCALS; i++) { + if (!locals[i]) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private void releaseWordpairLocal(short local) { + if (local < firstFreeLocal) + firstFreeLocal = local; + locals[local] = false; + locals[local + 1] = false; + } + + private void releaseWordLocal(short local) { + if (local < firstFreeLocal) + firstFreeLocal = local; + locals[local] = false; + } + + private void push(long l) { + if (l == -1) { + addByteCode(ByteCode.ICONST_M1); + } else if (0 <= l && l <= 5) { + addByteCode((byte) (ByteCode.ICONST_0 + l)); + } else if (Byte.MIN_VALUE <= l && l <= Byte.MAX_VALUE) { + addByteCode(ByteCode.BIPUSH, (byte) l); + } else if (Short.MIN_VALUE <= l && l <= Short.MAX_VALUE) { + addByteCode(ByteCode.SIPUSH, (short) l); + } else if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE) { + classFile.addLoadConstant((int)l); + } else { + classFile.addLoadConstant((long)l); + } + } + + private void push(double d) { + if (d == 0.0) { + addByteCode(ByteCode.DCONST_0); + } else if (d == 1.0) { + addByteCode(ByteCode.DCONST_1); + /* XXX this breaks all sorts of simple math. + } else if (Float.MIN_VALUE <= d && d <= Float.MAX_VALUE) { + loadWordConstant(classFile.addFloatConstant((float) d)); + */ + } else { + classFile.addLoadConstant((double)d); + } + } + + private void push(String s) { + classFile.addLoadConstant(s); + } + + private void pushUndefined() { + classFile.add(ByteCode.GETSTATIC, "org/mozilla/javascript/Undefined", + "instance", "Lorg/mozilla/javascript/Scriptable;"); + } + + + private void badTree() { + throw new RuntimeException("Bad tree in codegen"); + } + + private static final String normalFunctionSuperClassName = + "org.mozilla.javascript.NativeFunction"; + private static final String normalScriptSuperClassName = + "org.mozilla.javascript.NativeScript"; + private static final String debugFunctionSuperClassName = + "org.mozilla.javascript.debug.NativeFunctionDebug"; + private static final String debugScriptSuperClassName = + "org.mozilla.javascript.debug.NativeScriptDebug"; + private String superClassName; + private String superClassSlashName; + private String name; + private int ordinal; + boolean inFunction; + boolean inDirectCallFunction; + private ClassFileWriter classFile; + private Hashtable results; + private short scriptRuntimeIndex; + private int version; + private OptClassNameHelper itsNameHelper; + private static JavaScriptClassLoader classLoader; + + private String itsSourceFile; + private int itsLineNumber; + + private int stackDepth; + private int stackDepthMax; + + private static final int MAX_LOCALS = 256; + private boolean[] locals; + private short firstFreeLocal; + private short localsMax; + + private ConstantList itsConstantList; + + // special known locals. If you add a new local here, be sure + // to initialize it to -1 in startNewMethod + private short variableObjectLocal; + private short scriptResultLocal; + private short contextLocal; + private short argsLocal; + private short thisObjLocal; + private short funObjLocal; + private short debug_pcLocal; + private short debugStopSubRetLocal; + private short itsZeroArgArray; + private short itsOneArgArray; + + private boolean hasVarsInRegs; + private boolean itsForcedObjectParameters; + private boolean trivialInit; + private short itsLocalAllocationBase; + private OptVariableTable vars; + private OptVariableTable debugVars; + private int epilogueLabel; + private int optLevel; + private int debugLevel; + private int debugStopSubLabel; + + private short[] debugLineMap; + private short debugLineEntryCount; + private static final int DEBUG_LINE_MAP_INITIAL_SIZE = 100; + private static final int DEBUG_LINE_MAP_RESIZE_INCREMENT = 100; +} + +class ConstantList { + + int addConstant(String wrapperType, String signature, Number num, boolean isInteger) + { + long lValue = num.longValue(); + double dValue = num.doubleValue(); + + for (int i = 0; i < itsTop; i++) { + if (signature.equals(itsList[i].itsSignature)) { + if (itsList[i].itsIsInteger) { + if (isInteger && (lValue == itsList[i].itsLValue)) { + return i; + } + } + else { + if (!isInteger && (dValue == itsList[i].itsDValue)) { + return i; + } + } + } + } + + if (itsTop == itsList.length) { + ConstantDude[] newList = new ConstantDude[itsList.length * 2]; + System.arraycopy(itsList, 0, newList, 0, itsList.length); + itsList = newList; + } + if (isInteger) { + itsList[itsTop] = new ConstantDude(wrapperType, signature, lValue); + } + else { + itsList[itsTop] = new ConstantDude(wrapperType, signature, dValue); + } + return itsTop++; + } + + ConstantDude[] itsList = new ConstantDude[128]; + int itsTop; + +} + +class ConstantDude { + + ConstantDude(String wrapperType, String signature, long value) + { + itsWrapperType = wrapperType; + itsSignature = signature; + itsIsInteger = true; + itsLValue = value; + } + + ConstantDude(String wrapperType, String signature, double value) + { + itsWrapperType = wrapperType; + itsSignature = signature; + itsIsInteger = false; + itsDValue = value; + } + + String itsWrapperType; + String itsSignature; + long itsLValue; + double itsDValue; + boolean itsIsInteger; + +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java new file mode 100644 index 00000000000..6e523a77694 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java @@ -0,0 +1,130 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + + +package org.mozilla.javascript.optimizer; + +class DataFlowBitSet { + + private int itsBits[]; + int itsSize; + + DataFlowBitSet(int size) + { + itsSize = size; + itsBits = new int[(size >> 5) + 1]; + } + + int size() + { + return itsSize; + } + + void set(int n) + { + if ((n < 0) || (n >= itsSize)) + throw new RuntimeException("DataFlowBitSet bad index " + n); + itsBits[n >> 5] |= 1 << (n & 31); + } + + boolean test(int n) + { + if ((n < 0) || (n >= itsSize)) + throw new RuntimeException("DataFlowBitSet bad index " + n); + return ((itsBits[n >> 5] & (1 << (n & 31))) != 0); + } + + void not() + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] = ~itsBits[i]; + } + + void clear(int n) + { + if ((n < 0) || (n >= itsSize)) + throw new RuntimeException("DataFlowBitSet bad index " + n); + itsBits[n >> 5] &= ~(1 << (n & 31)); + } + + void clear() + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] = 0; + } + + void or(DataFlowBitSet b) + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] |= b.itsBits[i]; + } + + public String toString() + { + StringBuffer result = new StringBuffer(); + result.append("DataFlowBitSet, size = " + itsSize + "\n"); + for (int i = 0; i < itsBits.length; i++) + result.append(Integer.toHexString(itsBits[i]) + " "); + return result.toString(); + } + + boolean df(DataFlowBitSet in, DataFlowBitSet gen, DataFlowBitSet notKill) + { + int bitsLength = itsBits.length; + boolean changed = false; + for (int i = 0; i < bitsLength; i++) { + int oldBits = itsBits[i]; + itsBits[i] = (in.itsBits[i] | gen.itsBits[i]) & notKill.itsBits[i]; + changed |= (oldBits != itsBits[i]); + } + return changed; + } + + boolean df2(DataFlowBitSet in, DataFlowBitSet gen, DataFlowBitSet notKill) + { + int bitsLength = itsBits.length; + boolean changed = false; + for (int i = 0; i < bitsLength; i++) { + int oldBits = itsBits[i]; + itsBits[i] = (in.itsBits[i] & notKill.itsBits[i]) | gen.itsBits[i]; + changed |= (oldBits != itsBits[i]); + } + return changed; + } +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/FatBlock.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/FatBlock.java new file mode 100644 index 00000000000..1d062ec72b4 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/FatBlock.java @@ -0,0 +1,92 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; + +public class FatBlock { + + public FatBlock(int startNodeIndex, int endNodeIndex, Node[] statementNodes) + { + itsShadowOfFormerSelf = new Block(startNodeIndex, endNodeIndex, statementNodes); + } + + public Node getEndNode() + { return itsShadowOfFormerSelf.getEndNode(); } + + public Block getSlimmerSelf() + { return itsShadowOfFormerSelf; } + + private Block[] reduceToArray(Hashtable h) + { + Block[] result = null; + if (!h.isEmpty()) { + result = new Block[h.size()]; + Enumeration enum = h.elements(); + int i = 0; + while (enum.hasMoreElements()) { + FatBlock fb = (FatBlock)(enum.nextElement()); + result[i++] = fb.itsShadowOfFormerSelf; + } + } + return result; + } + + Block diet() + { + itsShadowOfFormerSelf.setSuccessorList(reduceToArray(itsSuccessors)); + itsShadowOfFormerSelf.setPredecessorList(reduceToArray(itsPredecessors)); + return itsShadowOfFormerSelf; + } + + public void addSuccessor(FatBlock b) { itsSuccessors.put(b, b); } + public void addPredecessor(FatBlock b) { itsPredecessors.put(b, b); } + + // all the Blocks that come immediately after this + private Hashtable itsSuccessors = new Hashtable(4); + // all the Blocks that come immediately before this + private Hashtable itsPredecessors = new Hashtable(4); + + private Block itsShadowOfFormerSelf; + + +} + diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/JavaScriptClassLoader.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/JavaScriptClassLoader.java new file mode 100644 index 00000000000..4829aa5cc72 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/JavaScriptClassLoader.java @@ -0,0 +1,71 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import java.util.*; + +/** + * Load generated classes that implement JavaScript scripts + * or functions. + * + * @author Norris Boyd + */ +final class JavaScriptClassLoader extends ClassLoader { + + JavaScriptClassLoader() { + } + + public Class defineClass(String name, byte data[]) { + return super.defineClass(name, data, 0, data.length); + } + + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + Class clazz = findLoadedClass(name); + if (clazz == null) { + // Experimental change for JShell, use outer class loader. + ClassLoader loader = JavaScriptClassLoader.class.getClassLoader(); + if (loader != null) + return loader.loadClass(name); + clazz = findSystemClass(name); + } + if (resolve) + resolveClass(clazz); + return clazz; + } +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptClassNameHelper.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptClassNameHelper.java new file mode 100644 index 00000000000..1f1b0726738 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptClassNameHelper.java @@ -0,0 +1,142 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import java.io.*; + +public class OptClassNameHelper implements ClassNameHelper { + + public String getGeneratingDirectory() { + return generatingDirectory; + } + + public synchronized String getJavaScriptClassName(String functionName, + boolean primary) + { + StringBuffer s = new StringBuffer(); + if (packageName != null && packageName.length() > 0) { + s.append(packageName); + s.append('.'); + } + s.append(initialName); + if (generatingDirectory != null) { + if (functionName != null) { + s.append('$'); + s.append(functionName); + } else if (!primary) { + s.append(++serial); + } + } else { + s.append(globalSerial++); + } + return s.toString(); + } + + public String getTargetClassFileName() { + return getTargetClassFileName(getInitialClassName()); + } + + public void setTargetClassFileName(String classFileName) { + int lastSeparator = classFileName.lastIndexOf('/'); + String initialName; + if (lastSeparator == -1) { + generatingDirectory = ""; + initialName = classFileName; + } else { + generatingDirectory = classFileName.substring(0, lastSeparator); + initialName = classFileName.substring(lastSeparator+1); + } + if (initialName.endsWith(".class")) + initialName = initialName.substring(0, initialName.length() - 6); + setInitialClassName(initialName); + } + + public String getTargetPackage() { + return packageName; + } + + public void setTargetPackage(String targetPackage) { + this.packageName = targetPackage; + } + + public String getTargetClassFileName(String className) { + if (generatingDirectory == null) + return null; + StringBuffer sb = new StringBuffer(); + if (generatingDirectory.length() > 0) { + sb.append(generatingDirectory); + sb.append(File.separator); + } + sb.append(className); + sb.append(".class"); + return sb.toString(); + } + + public Class getTargetExtends() { + return targetExtends; + } + + public void setTargetExtends(Class extendsClass) { + targetExtends = extendsClass; + } + + public Class[] getTargetImplements() { + return targetImplements; + } + + public void setTargetImplements(Class[] implementsClasses) { + targetImplements = implementsClasses; + } + + String getInitialClassName() { + return initialName; + } + + void setInitialClassName(String initialName) { + this.initialName = initialName; + serial = 0; + } + + private String generatingDirectory; + private String packageName = "org.mozilla.javascript.gen"; + private String initialName = "c"; + private static int globalSerial=1; + private int serial=1; + private Class targetExtends; + private Class[] targetImplements; +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptFunctionNode.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptFunctionNode.java new file mode 100644 index 00000000000..d80c76ee6b8 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptFunctionNode.java @@ -0,0 +1,128 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import java.util.*; + +public class OptFunctionNode extends FunctionNode { + + public OptFunctionNode(String name, Node left, Node right, + ClassNameHelper nameHelper) + { + super(name, left, right); + itsVariableTable = new OptVariableTable(); + OptClassNameHelper nh = (OptClassNameHelper) nameHelper; + itsClassName = nh.getJavaScriptClassName(name, false); + } + + public String getDirectCallParameterSignature() { + StringBuffer parameterSig = new StringBuffer( + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;"); + int pCount = itsVariableTable.getParameterCount(); + for (int i = 0; i < pCount; i++) { + parameterSig.append("Ljava/lang/Object;D"); + } + parameterSig.append("[Ljava/lang/Object;)"); + return parameterSig.toString(); + } + + public String getClassName() { + return itsClassName; + } + + public boolean isTargetOfDirectCall() { + return itsIsTargetOfDirectCall; + } + + public void addDirectCallTarget(FunctionNode target) { + if (itsDirectCallTargets == null) + itsDirectCallTargets = new Vector(); + for (int i = 0; i < itsDirectCallTargets.size(); i++) // OPT !! + if (((FunctionNode)itsDirectCallTargets.elementAt(i)) == target) + return; + itsDirectCallTargets.addElement(target); + } + + public Vector getDirectCallTargets() { + return itsDirectCallTargets; + } + + public void setIsTargetOfDirectCall() { + itsIsTargetOfDirectCall = true; + } + + public void setParameterNumberContext(boolean b) { + itsParameterNumberContext = b; + } + + public boolean getParameterNumberContext() { + return itsParameterNumberContext; + } + + public boolean containsCalls(int argCount) { + if ((argCount < itsContainsCallsCount.length) && (argCount >= 0)) + return itsContainsCallsCount[argCount]; + else + return itsContainsCalls; + } + + public void setContainsCalls(int argCount) { + if (argCount < itsContainsCallsCount.length) + itsContainsCallsCount[argCount] = true; + itsContainsCalls = true; + } + + public void incrementLocalCount() { + Integer localCount = (Integer)(getProp(Node.LOCALCOUNT_PROP)); + if (localCount == null) { + putProp(Node.LOCALCOUNT_PROP, new Integer(1)); + } else { + putProp(Node.LOCALCOUNT_PROP, + new Integer(localCount.intValue() + 1)); + } + } + + private String itsClassName; + private boolean itsIsTargetOfDirectCall; + private boolean itsContainsCalls; + private boolean[] itsContainsCallsCount = new boolean[4]; + private boolean itsParameterNumberContext; + private Vector itsDirectCallTargets; +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptIRFactory.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptIRFactory.java new file mode 100644 index 00000000000..5b02c34f505 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptIRFactory.java @@ -0,0 +1,69 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +/** + * This class allows the creation of nodes, and follows the Factory pattern. + * + * @see IRFactory + * @author Norris Boyd + */ +public class OptIRFactory extends IRFactory { + + public OptIRFactory(TokenStream ts, ClassNameHelper nameHelper) { + super(ts); + this.nameHelper = nameHelper; + } + + public Object createFunctionNode(String name, Object args, + Object statements) + { + if (name == null) + name = ""; + OptFunctionNode result = new OptFunctionNode(name, (Node) args, + (Node) statements, + nameHelper); + Context cx = Context.getCurrentContext(); + if (cx.getDebugLevel() > 0) + result.setRequiresActivation(true); + return result; + } + + private ClassNameHelper nameHelper; +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptLocalVariable.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptLocalVariable.java new file mode 100644 index 00000000000..56261cbc63c --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptLocalVariable.java @@ -0,0 +1,93 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +public class OptLocalVariable extends LocalVariable { + + public OptLocalVariable(String name, boolean isParameter) { + super(name, isParameter); + } + + public String toString() { + return "LocalVariable : '" + getName() + + "', index = " + getIndex() + + ", LiveAcrossCall = " + itsLiveAcrossCall + + ", isNumber = " + itsIsNumber + + ", isParameter = " + isParameter() + + ", JRegister = " + itsJRegister; + } + + public void setIsNumber() { itsIsNumber = true; } + public boolean isNumber() { return itsIsNumber; } + + public void markLiveAcrossCall() { itsLiveAcrossCall = true; } + public void clearLiveAcrossCall() { itsLiveAcrossCall = false; } + public boolean isLiveAcrossCall() { return itsLiveAcrossCall; } + + public void assignJRegister(short aJReg) { itsJRegister = aJReg; } + public short getJRegister() { return itsJRegister; } + + public boolean assignType(int aType) { return itsTypeUnion.add(aType); } + public int getTypeUnion() { return itsTypeUnion.getEvent(); } + + /** + * Get the offset into the bytecode where the variable becomes live. + * Used for generating the local variable table. + */ + public int getStartPC() { + return initPC; + } + + /** + * Set the offset into the bytecode where the variable becomes live. + * Used for generating the local variable table. + */ + public void setStartPC(int pc) { + initPC = pc; + } + + private short itsJRegister = -1; // unassigned + + private boolean itsLiveAcrossCall; + private boolean itsIsNumber; + + private TypeEvent itsTypeUnion = new TypeEvent(); // the union of all assigned types + + private int initPC; +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptRuntime.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptRuntime.java new file mode 100644 index 00000000000..d1207e343eb --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptRuntime.java @@ -0,0 +1,311 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +public final class OptRuntime extends ScriptRuntime { + + /** + * No instances should be created. + */ + private OptRuntime() { + } + + public static Object getElem(Object obj, double dblIndex, Scriptable scope) { + int index; + String s; + index = (int) dblIndex; + s = ((double) index) == dblIndex ? null : toString(new Double(dblIndex)); + Scriptable start = obj instanceof Scriptable + ? (Scriptable) obj + : toObject(scope, obj); + Scriptable m = start; + if (s != null) { + while (m != null) { + Object result = m.get(s, start); + if (result != Scriptable.NOT_FOUND) + return result; + m = m.getPrototype(); + } + return Undefined.instance; + } + while (m != null) { + Object result = m.get(index, start); + if (result != Scriptable.NOT_FOUND) + return result; + m = m.getPrototype(); + } + return Undefined.instance; + } + + public static Object setElem(Object obj, double dblIndex, Object value, + Scriptable scope) + { + int index; + String s; + index = (int) dblIndex; + s = ((double) index) == dblIndex ? + null : toString(new Double(dblIndex)); + + Scriptable start = obj instanceof Scriptable + ? (Scriptable) obj + : toObject(scope, obj); + Scriptable m = start; + if (s != null) { + do { + if (m.has(s, start)) { + m.put(s, start, value); + return value; + } + m = m.getPrototype(); + } while (m != null); + start.put(s, start, value); + return value; + } + + do { + if (m.has(index, start)) { + m.put(index, start, value); + return value; + } + m = m.getPrototype(); + } while (m != null); + start.put(index, start, value); + return value; + } + + public static Object add(Object val1, double val2) { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(null); + if (!(val1 instanceof String)) + return new Double(toNumber(val1) + val2); + return toString(val1) + numberToString(val2, 10); + } + + public static Object add(double val1, Object val2) { + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(null); + if (!(val2 instanceof String)) + return new Double(toNumber(val2) + val1); + return numberToString(val1, 10) + toString(val2); + } + + public static boolean neq(Object x, Object y) { + return !eq(x, y); + } + + public static boolean shallowNeq(Object x, Object y) { + return !shallowEq(x, y); + } + + public static Boolean cmp_LTB(double d1, Object val2) { + if (cmp_LT(d1, val2) == 1) + return Boolean.TRUE; + else + return Boolean.FALSE; + } + + public static int cmp_LT(double d1, Object val2) { + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(NumberClass); + if (!(val2 instanceof String)) { + if (d1 != d1) + return 0; + double d2 = toNumber(val2); + if (d2 != d2) + return 0; + return d1 < d2 ? 1 : 0; + } + return toString(new Double(d1)).compareTo(toString(val2)) < 0 ? 1 : 0; + } + + public static Boolean cmp_LTB(Object val1, double d2) { + if (cmp_LT(val1, d2) == 1) + return Boolean.TRUE; + else + return Boolean.FALSE; + } + + public static int cmp_LT(Object val1, double d2) { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(NumberClass); + if (!(val1 instanceof String)) { + double d1 = toNumber(val1); + if (d1 != d1) + return 0; + if (d2 != d2) + return 0; + return d1 < d2 ? 1 : 0; + } + return toString(val1).compareTo(toString(new Double(d2))) < 0 ? 1 : 0; + } + + public static Boolean cmp_LEB(double d1, Object val2) { + if (cmp_LE(d1, val2) == 1) + return Boolean.TRUE; + else + return Boolean.FALSE; + } + + public static int cmp_LE(double d1, Object val2) { + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(NumberClass); + if (!(val2 instanceof String)) { + if (d1 != d1) + return 0; + double d2 = toNumber(val2); + if (d2 != d2) + return 0; + return d1 <= d2 ? 1 : 0; + } + return toString(new Double(d1)).compareTo(toString(val2)) <= 0 ? 1 : 0; + } + + public static Boolean cmp_LEB(Object val1, double d2) { + if (cmp_LE(val1, d2) == 1) + return Boolean.TRUE; + else + return Boolean.FALSE; + } + + public static int cmp_LE(Object val1, double d2) { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(NumberClass); + if (!(val1 instanceof String)) { + double d1 = toNumber(val1); + if (d1 != d1) + return 0; + if (d2 != d2) + return 0; + return d1 <= d2 ? 1 : 0; + } + return toString(val1).compareTo(toString(new Double(d2))) <= 0 ? 1 : 0; + } + + public static int cmp(Object val1, Object val2) { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(NumberClass); + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(NumberClass); + if (!(val1 instanceof String) || !(val2 instanceof String)) { + double d1 = toNumber(val1); + if (d1 != d1) + return -1; + double d2 = toNumber(val2); + if (d2 != d2) + return -1; + return d1 < d2 ? 1 : 0; + } + return toString(val1).compareTo(toString(val2)) < 0 ? 1 : 0; + } + + public static Object callSimple(Context cx, String id, Scriptable scope, + Object[] args) + throws JavaScriptException + { + Scriptable obj = scope; + Object prop = null; + Scriptable thisArg = null; + search: + while (obj != null) { + Scriptable m = obj; + do { + prop = m.get(id, obj); + if (prop != Scriptable.NOT_FOUND) { + thisArg = obj; + break search; + } + m = m.getPrototype(); + } while (m != null); + obj = obj.getParentScope(); + } + if ((prop == null) || (prop == Scriptable.NOT_FOUND)) { + Object[] errorArgs = { id }; + throw Context.reportRuntimeError( + getMessage("msg.is.not.defined", errorArgs)); + } + + while (thisArg instanceof NativeWith) + thisArg = thisArg.getPrototype(); + if (thisArg instanceof NativeCall) + thisArg = ScriptableObject.getTopLevelScope(thisArg); + + Function function; + try { + function = (Function) prop; + } + catch (ClassCastException e) { + Object[] errorArgs = { toString(prop) }; + throw cx.reportRuntimeError( + getMessage("msg.isnt.function", errorArgs)); + } + + return function.call(cx, function.getParentScope(), thisArg, args); + } + + public static Object thisGet(Scriptable thisObj, String id, + Scriptable scope) + { + Object result = thisObj.get(id, thisObj); + if (result != Scriptable.NOT_FOUND) + return result; + + Scriptable start = thisObj; + if (start == null) { + throw Context.reportRuntimeError( + getMessage("msg.null.to.object", null)); + } + Scriptable m = start; + do { + result = m.get(id, start); + if (result != Scriptable.NOT_FOUND) + return result; + + m = m.getPrototype(); + } while (m != null); + return Undefined.instance; + } + + public static Object[] padStart(Object[] currentArgs, int count) { + Object[] result = new Object[currentArgs.length + count]; + System.arraycopy(currentArgs, 0, result, count, currentArgs.length); + return result; + } + + +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptTransformer.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptTransformer.java new file mode 100644 index 00000000000..36c409e0bcb --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptTransformer.java @@ -0,0 +1,190 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import java.util.Hashtable; +import java.util.Stack; +import java.util.Vector; + +/** + * This class performs node transforms to prepare for optimization. + * + * @see NodeTransformer + * @author Norris Boyd + */ + +class OptTransformer extends NodeTransformer { + private Hashtable theFnClassNameList; + + OptTransformer(Hashtable theFnClassNameList) { + this.theFnClassNameList = theFnClassNameList; + } + + public NodeTransformer newInstance() { + return new OptTransformer((Hashtable) theFnClassNameList.clone()); + } + + public IRFactory createIRFactory(TokenStream ts) { + return new IRFactory(ts); + } + + public Node transform(Node tree, Node enclosing, TokenStream ts) { + + // Collect all of the contained functions into a hashtable + // so that the call optimizer can access the class name & parameter + // count for any call it encounters + collectContainedFunctions(tree.getFirstChild()); + + return super.transform(tree, enclosing, ts); + } + + protected VariableTable createVariableTable() { + return new OptVariableTable(); + } + + private int detectDirectCall(Node node, Node tree) + { + Context cx = Context.getCurrentContext(); + int optLevel = cx.getOptimizationLevel(); + Node left = node.getFirstChild(); + + // count the arguments + int argCount = 0; + Node arg = left.getNextSibling(); + while (arg != null) { + arg = arg.getNextSibling(); + argCount++; + } + + if (tree.getType() == TokenStream.FUNCTION && optLevel > 0) { + if (left.getType() == TokenStream.NAME) { + markDirectCall(tree, node, argCount, left.getString()); + } else { + if (left.getType() == TokenStream.GETPROP) { + Node name = left.getFirstChild().getNextSibling(); + markDirectCall(tree, node, argCount, name.getString()); + } + } + } + + return argCount; + } + + protected void visitNew(Node node, Node tree) { + detectDirectCall(node, tree); + super.visitNew(node, tree); + } + + protected void visitCall(Node node, Node tree) { + int argCount = detectDirectCall(node, tree); + if (inFunction && (argCount == 0)) + ((OptFunctionNode)tree).setContainsCalls(argCount); + + super.visitCall(node, tree); + } + + /* + * Optimize a call site by converting call("a", b, c) into : + * + * FunctionObjectFor"a" <-- instance variable init'd by constructor + * + * // this is a DIRECTCALL node + * fn = GetProp(tmp = GetBase("a"), "a"); + * if (fn == FunctionObjectFor"a") + * fn.call(tmp, b, c) + * else + * ScriptRuntime.Call(fn, tmp, b, c) + */ + void markDirectCall(Node containingTree, Node callNode, int argCount, + String targetName) + { + OptFunctionNode theFunction + = (OptFunctionNode)theFnClassNameList.get(targetName); + if (theFunction != null) { + VariableTable varTable = theFunction.getVariableTable(); + // Refuse to directCall any function with more + // than 32 parameters - prevent code explosion + // for wacky test cases + if (varTable.getParameterCount() > 32) + return; + + if (argCount == varTable.getParameterCount()) { + callNode.putProp(Node.DIRECTCALL_PROP, theFunction); + ((OptFunctionNode)containingTree) + .addDirectCallTarget(theFunction); + theFunction.setIsTargetOfDirectCall(); + } + } + } + + /** + * Collect all of the contained functions into a hashtable + * so that the call optimizer can access the class name & parameter + * count for any call it encounters + */ + void collectContainedFunctions(Node node) { + for (Node tNode=node; tNode != null; tNode = tNode.getNextSibling()) { + if (tNode.getType() == TokenStream.FUNCTION) { + FunctionNode fnNode = (FunctionNode) + tNode.getProp(Node.FUNCTION_PROP); + if (fnNode.getFunctionName().length() != 0) { + String name = fnNode.getFunctionName(); + Object oldFn = theFnClassNameList.get(name); + if (oldFn == fnNode) { + // already processed this list of functions + return; + } + /* + if (oldFn != null) { + Object prop = fnNode.getProp(Node.BASE_LINENO_PROP); + Object[] errArgs = { name }; + Context.reportWarning( + Context.getMessage("msg.fn.redecl", errArgs), + (String) fnNode.getProp(Node.SOURCENAME_PROP), + prop == null ? 0 : ((Integer) prop).intValue(), + null, 0); + } + */ + theFnClassNameList.put(name, fnNode); + } + addParameters(fnNode); + } + } + } + +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptVariableTable.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptVariableTable.java new file mode 100644 index 00000000000..f7d7e362c12 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/OptVariableTable.java @@ -0,0 +1,74 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import java.io.*; +import java.util.*; + +public class OptVariableTable extends VariableTable { + + public void print(PrintWriter pw) + { + System.out.println("Variable Table, size = " + itsVariables.size()); + for (int i = 0; i < itsVariables.size(); i++) { + LocalVariable lVar = (LocalVariable)(itsVariables.elementAt(i)); + pw.println(lVar.toString()); + } + } + + public void assignParameterJRegs() + { + // 0 is reserved for function Object 'this' + // 1 is reserved for context + // 2 is reserved for parentScope + // 3 is reserved for script 'this' + short jReg = 4; + for (int i = 0; i < varStart; i++) { + OptLocalVariable lVar = (OptLocalVariable) + itsVariables.elementAt(i); + lVar.assignJRegister(jReg); + jReg += 3; // 3 is 1 for Object parm and 2 for double parm + } + } + + public LocalVariable createLocalVariable(String name, boolean isParameter) + { + return new OptLocalVariable(name, isParameter); + } + +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Optimizer.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Optimizer.java new file mode 100644 index 00000000000..5ddeb4d442c --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/Optimizer.java @@ -0,0 +1,1080 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +import java.io.PrintWriter; +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.File; +import java.io.IOException; + +import java.util.Vector; +import java.util.Hashtable; + +public class Optimizer { + + public Optimizer() + { + } + + static final boolean DEBUG_OPTIMIZER = false; + static final boolean DO_CONSTANT_FOLDING = true; + + static int blockCount = 0; + + boolean inDirectCallFunction; + boolean parameterUsedInNumberContext; + + void optimizeFunction(OptFunctionNode theFunction) + { + if (theFunction.requiresActivation()) return; + + inDirectCallFunction = theFunction.isTargetOfDirectCall(); + + Node[] theStatementNodes = buildStatementList(theFunction); + Block[] theBlocks = Block.buildBlocks(theStatementNodes); + PrintWriter pw = null; + try { + if (DEBUG_OPTIMIZER) { + pw = new PrintWriter( + new DataOutputStream( + new FileOutputStream( + new File("blocks" + + blockCount++ + ".txt")))); + pw.println(Block.toString(theBlocks, theStatementNodes)); + } + + OptVariableTable vars = (OptVariableTable) + theFunction.getVariableTable(); + if (vars != null) { + + vars.establishIndices(); + for (int i = 0; i < theStatementNodes.length; i++) + replaceVariableAccess(theStatementNodes[i], vars); + + if(DO_CONSTANT_FOLDING){ + foldConstants(theFunction, null); + } + + reachingDefDataFlow(vars, theBlocks); + typeFlow(vars, theBlocks); + findSinglyTypedVars(vars, theBlocks); + localCSE(theBlocks, theFunction); + if (!theFunction.requiresActivation()) { + /* + * Now that we know which local vars are in fact always + * Numbers, we re-write the tree to take advantage of + * that. Any arithmetic or assignment op involving just + * Number typed vars is marked so that the codegen will + * generate non-object code. + */ + parameterUsedInNumberContext = false; + for (int i = 0; i < theStatementNodes.length; i++) { + rewriteForNumberVariables(theStatementNodes[i]); + } + theFunction.setParameterNumberContext(parameterUsedInNumberContext); + //System.out.println("Function " + theFunction.getFunctionName() + " has parameters in number contexts : " + parameterUsedInNumberContext); + } + if (DEBUG_OPTIMIZER) { + for (int i = 0; i < theBlocks.length; i++) { + pw.println("For block " + theBlocks[i].getBlockID()); + theBlocks[i].printLiveOnEntrySet(pw, vars); + } + vars.print(pw); + } + + } + if (DEBUG_OPTIMIZER) pw.close(); + } + catch (IOException x) // for the DEBUG_OPTIMIZER i/o + { + } + finally { + if (DEBUG_OPTIMIZER) pw.close(); + } + } + + public void optimize(Node tree, int optLevel) + { + itsOptLevel = optLevel; + // run on one function at a time for now + PreorderNodeIterator iterator = tree.getPreorderIterator(); + Node node; + while ((node = iterator.nextNode()) != null) { + // should be able to do this more cheaply ? + // - run through initial block children ? + if (node.getType() == TokenStream.FUNCTION) { + OptFunctionNode theFunction = (OptFunctionNode) + node.getProp(Node.FUNCTION_PROP); + if (theFunction != null) + optimizeFunction(theFunction); + } + } + } + + void findSinglyTypedVars(VariableTable theVariables, Block theBlocks[]) + { +/* + discover the type events for each non-volatile variable (not live + across function calls). A type event is a def, which sets the target + type to the source type. +*/ + if (false) { + /* + it's enough to prove that every def point for a local variable + confers the same type on that variable. If that is the case (and + that type is 'Number') then we can assign that local variable to + a Double jReg for the life of the function. + */ + for (int i = 0; i < theBlocks.length; i++) { + theBlocks[i].findDefs(); + } + } + for (int i = 0; i < theVariables.size(); i++) { + OptLocalVariable lVar = (OptLocalVariable) theVariables.get(i); + if (!lVar.isParameter()) { + int theType = lVar.getTypeUnion(); + if (theType == TypeEvent.NumberType) { + lVar.setIsNumber(); + } + } + } + } + + void doBlockLocalCSE(Block theBlocks[], Block b, + Hashtable theCSETable, boolean beenThere[], + OptFunctionNode theFunction) + { + if (!beenThere[b.getBlockID()]) { + beenThere[b.getBlockID()] = true; + theCSETable = b.localCSE(theCSETable, theFunction); + Block succ[] = theBlocks[b.getBlockID()].getSuccessorList(); + if (succ != null) { + for (int i = 0; i < succ.length; i++) { + int index = succ[i].getBlockID(); + Block pred[] = theBlocks[index].getPredecessorList(); + if (pred.length == 1) + doBlockLocalCSE(theBlocks, succ[i], + (Hashtable)(theCSETable.clone()), + beenThere, theFunction); + } + } + } + } + + void localCSE(Block theBlocks[], OptFunctionNode theFunction) + { + boolean beenThere[] = new boolean[theBlocks.length]; + doBlockLocalCSE(theBlocks, theBlocks[0], null, beenThere, theFunction); + for (int i = 0; i < theBlocks.length; i++) { + if (!beenThere[i]) theBlocks[i].localCSE(null, theFunction); + } + } + + void typeFlow(VariableTable theVariables, Block theBlocks[]) + { + 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()) { + Block succ[] = theBlocks[vIndex].getSuccessorList(); + if (succ != null) { + for (int i = 0; i < succ.length; i++) { + int index = succ[i].getBlockID(); + visit[index] = true; + needRescan |= (index < vIndex); + } + } + } + } + if (vIndex == (theBlocks.length - 1)) { + if (needRescan) { + vIndex = 0; + needRescan = false; + } + else + break; + } + else + vIndex++; + } + } + + void reachingDefDataFlow(VariableTable theVariables, Block theBlocks[]) + { +/* + 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(theVariables); + } +/* + 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].getPredecessorList(); + if (pred != null) { + for (int i = 0; i < pred.length; i++) { + int index = pred[i].getBlockID(); + visit[index] = true; + needRescan |= (index > vIndex); + } + } + } + } + if (vIndex == 0) { + if (needRescan) { + vIndex = theBlocks.length - 1; + needRescan = false; + } + else + break; + } + else + vIndex--; + } +/* + The liveOnEntry, liveOnExit sets are now complete. Discover the variables + that are live across function calls. +*/ +/* + 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. +*/ + + for (int i = 0; i < theBlocks.length; i++) { + theBlocks[i].markVolatileVariables(theVariables); + } + } + +/* + Each directCall parameter is passed as a pair of values - an object + and a double. The value passed depends on the type of value available at + the call site. If a double is available, the object in java/lang/Void.TYPE + is passed as the object value, and if an object value is available, then + 0.0 is passed as the double value. + + The receiving routine always tests the object value before proceeding. + If the parameter is being accessed in a 'Number Context' then the code + sequence is : + if ("parameter_objectValue" == java/lang/Void.TYPE) + ...fine..., use the parameter_doubleValue + else + toNumber(parameter_objectValue) + + and if the parameter is being referenced in an Object context, the code is + if ("parameter_objectValue" == java/lang/Void.TYPE) + new Double(parameter_doubleValue) + else + ...fine..., use the parameter_objectValue + + If the receiving code never uses the doubleValue, it is converted on + entry to a Double instead. +*/ + + +/* + We're referencing a node in a Number context (i.e. we'd prefer it + was a double value). If the node is a parameter in a directCall + function, mark it as being referenced in this context. +*/ + void markDCPNumberContext(Node n) + { + if (inDirectCallFunction && (n.getType() == TokenStream.GETVAR)) + { + OptLocalVariable theVar + = (OptLocalVariable)(n.getProp(Node.VARIABLE_PROP)); + if ((theVar != null) && theVar.isParameter()) { + parameterUsedInNumberContext = true; + } + } + } + + boolean convertParameter(Node n) + { + if (inDirectCallFunction && (n.getType() == TokenStream.GETVAR)) + { + OptLocalVariable theVar + = (OptLocalVariable)(n.getProp(Node.VARIABLE_PROP)); + if ((theVar != null) && theVar.isParameter()) { + n.putProp(Node.ISNUMBER_PROP, null); + return true; + } + } + return false; + } + + int rewriteForNumberVariables(Node n) + { + switch (n.getType()) { + case TokenStream.POP : { + Node child = n.getFirstChild(); + int type = rewriteForNumberVariables(child); + if (type == TypeEvent.NumberType) + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NoType; + } + case TokenStream.NUMBER : + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NumberType; + + case TokenStream.GETVAR : { + OptLocalVariable theVar + = (OptLocalVariable)(n.getProp(Node.VARIABLE_PROP)); + if (theVar != null) { + if (inDirectCallFunction && theVar.isParameter()) { + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + else + if (theVar.isNumber()) { + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + } + return TypeEvent.NoType; + } + + case TokenStream.INC : + case TokenStream.DEC : { + Node child = n.getFirstChild(); // will be a GETVAR or GETPROP + if (child.getType() == TokenStream.GETVAR) { + OptLocalVariable theVar + = (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP)); + if ((theVar != null) && theVar.isNumber()) { + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + markDCPNumberContext(child); + return TypeEvent.NumberType; + } + else + return TypeEvent.NoType; + } + else + return TypeEvent.NoType; + } + case TokenStream.SETVAR : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNextSibling(); + int rType = rewriteForNumberVariables(rChild); + OptLocalVariable theVar + = (OptLocalVariable)(n.getProp(Node.VARIABLE_PROP)); + if (inDirectCallFunction && theVar.isParameter()) { + if (rType == TypeEvent.NumberType) { + if (!convertParameter(rChild)) { + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + markDCPNumberContext(rChild); + return TypeEvent.NoType; + } + else + return rType; + } + else { + if ((theVar != null) && theVar.isNumber()) { + if (rType != TypeEvent.NumberType) { + n.removeChild(rChild); + Node newRChild = new Node(TokenStream.CONVERT, rChild); + newRChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToBack(newRChild); + } + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.BOTH)); + markDCPNumberContext(rChild); + return TypeEvent.NumberType; + } + else { + if (rType == TypeEvent.NumberType) { + if (!convertParameter(rChild)) { + n.removeChild(rChild); + Node newRChild = new Node(TokenStream.CONVERT, rChild); + newRChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToBack(newRChild); + } + } + return TypeEvent.NoType; + } + } + } + case TokenStream.RELOP : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNextSibling(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + markDCPNumberContext(lChild); + markDCPNumberContext(rChild); + + if ((n.getInt() == TokenStream.INSTANCEOF) + || (n.getInt() == TokenStream.IN)) { + if (lType == TypeEvent.NumberType) { + if (!convertParameter(lChild)) { + n.removeChild(lChild); + Node nuChild = new Node(TokenStream.CONVERT, lChild); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToFront(nuChild); + } + } + if (rType == TypeEvent.NumberType) { + if (!convertParameter(rChild)) { + n.removeChild(rChild); + Node nuChild = new Node(TokenStream.CONVERT, rChild); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToBack(nuChild); + } + } + } + else { + if (convertParameter(lChild)) { + if (convertParameter(rChild)) { + return TypeEvent.NoType; + } + else { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.RIGHT)); + } + } + } + else { + if (convertParameter(rChild)) { + if (lType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.LEFT)); + } + } + else { + if (lType == TypeEvent.NumberType) { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + } + else { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.LEFT)); + } + } + else { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.RIGHT)); + } + } + } + } + } + // we actually build a boolean value + return TypeEvent.NoType; + } + + case TokenStream.ADD : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNextSibling(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + + + if (convertParameter(lChild)) { + if (convertParameter(rChild)) { + return TypeEvent.NoType; + } + else { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.RIGHT)); + } + } + } + else { + if (convertParameter(rChild)) { + if (lType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.LEFT)); + } + } + else { + if (lType == TypeEvent.NumberType) { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + else { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.LEFT)); + } + } + else { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP,new Integer(Node.RIGHT)); + } + } + } + } + return TypeEvent.NoType; + } + + case TokenStream.BITXOR : + case TokenStream.BITOR : + case TokenStream.BITAND : + case TokenStream.RSH : + case TokenStream.LSH : + case TokenStream.SUB : + case TokenStream.MUL : + case TokenStream.DIV : + case TokenStream.MOD : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNextSibling(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + markDCPNumberContext(lChild); + markDCPNumberContext(rChild); + if (lType == TypeEvent.NumberType) { + if (rType == TypeEvent.NumberType) { + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + else { + if (!convertParameter(rChild)) { + n.removeChild(rChild); + Node newRChild = new Node(TokenStream.CONVERT, rChild); + newRChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToBack(newRChild); + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + } + return TypeEvent.NumberType; + } + } + else { + if (rType == TypeEvent.NumberType) { + if (!convertParameter(lChild)) { + n.removeChild(lChild); + Node newLChild = new Node(TokenStream.CONVERT, lChild); + newLChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToFront(newLChild); + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + } + return TypeEvent.NumberType; + } + else { + if (!convertParameter(lChild)) { + n.removeChild(lChild); + Node newLChild = new Node(TokenStream.CONVERT, lChild); + newLChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToFront(newLChild); + } + if (!convertParameter(rChild)) { + n.removeChild(rChild); + Node newRChild = new Node(TokenStream.CONVERT, rChild); + newRChild.putProp(Node.TYPE_PROP, Double.class); + n.addChildToBack(newRChild); + } + n.putProp(Node.ISNUMBER_PROP, + new Integer(Node.BOTH)); + return TypeEvent.NumberType; + } + } + } + case TokenStream.SETELEM : { + Node arrayBase = n.getFirstChild(); + Node arrayIndex = arrayBase.getNextSibling(); + Node rValue = arrayIndex.getNextSibling(); + int baseType = rewriteForNumberVariables(arrayBase); + if (baseType == TypeEvent.NumberType) {// can never happen ??? + if (!convertParameter(arrayBase)) { + n.removeChild(arrayBase); + Node nuChild = new Node(TokenStream.CONVERT, arrayBase); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToFront(nuChild); + } + } + int indexType = rewriteForNumberVariables(arrayIndex); + if (indexType == TypeEvent.NumberType) { + // setting the ISNUMBER_PROP signals the codegen + // to use the scriptRuntime.setElem that takes + // a double index + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.LEFT)); + markDCPNumberContext(arrayIndex); + } + int rValueType = rewriteForNumberVariables(rValue); + if (rValueType == TypeEvent.NumberType) { + if (!convertParameter(rValue)) { + n.removeChild(rValue); + Node nuChild = new Node(TokenStream.CONVERT, rValue); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToBack(nuChild); + } + } + return TypeEvent.NoType; + } + case TokenStream.GETELEM : { + Node arrayBase = n.getFirstChild(); + Node arrayIndex = arrayBase.getNextSibling(); + int baseType = rewriteForNumberVariables(arrayBase); + if (baseType == TypeEvent.NumberType) {// can never happen ??? + if (!convertParameter(arrayBase)) { + n.removeChild(arrayBase); + Node nuChild = new Node(TokenStream.CONVERT, arrayBase); + nuChild.putProp(Node.TYPE_PROP, Object.class); + n.addChildToFront(nuChild); + } + } + int indexType = rewriteForNumberVariables(arrayIndex); + if (indexType == TypeEvent.NumberType) { + // setting the ISNUMBER_PROP signals the codegen + // to use the scriptRuntime.getElem that takes + // a double index + n.putProp(Node.ISNUMBER_PROP, new Integer(Node.RIGHT)); + markDCPNumberContext(arrayIndex); + } + return TypeEvent.NoType; + } + case TokenStream.CALL : + { + FunctionNode target + = (FunctionNode)n.getProp(Node.DIRECTCALL_PROP); + if (target != null) { +/* + we leave each child as a Number if it can be. The codegen will + handle moving the pairs of parameters. +*/ + Node child = n.getFirstChild(); // the function + rewriteForNumberVariables(child); + child = child.getNextSibling(); // the 'this' object + rewriteForNumberVariables(child); + child = child.getNextSibling(); // the first arg + while (child != null) { + int type = rewriteForNumberVariables(child); + if (type == TypeEvent.NumberType) { + markDCPNumberContext(child); + } + child = child.getNextSibling(); + } + return TypeEvent.NoType; + } + // else fall thru... + } + default : { + Node child = n.getFirstChild(); + while (child != null) { + Node nextChild = child.getNextSibling(); + int type = rewriteForNumberVariables(child); + if (type == TypeEvent.NumberType) { + if (!convertParameter(child)) { + n.removeChild(child); + Node nuChild = new Node(TokenStream.CONVERT, child); + nuChild.putProp(Node.TYPE_PROP, Object.class); + if (nextChild == null) + n.addChildToBack(nuChild); + else + n.addChildBefore(nuChild, nextChild); + } + } + child = nextChild; + } + return TypeEvent.NoType; + } + } + } + + /* + Do constant folding, for integers, bools and strings + as well as for if() statements. + */ + void foldConstants(Node n, Node parent){ + Node lChild, rChild=null; // children + + lChild = n.getFirstChild(); + if(lChild == null){ // no children -- exit + return; + }else{ + rChild = lChild.getNextSibling(); + + if(rChild == null){ + foldConstants(lChild, n); // one child -- recurse + return; + } + } + + /* o.w. two children -- recurse on both first and proceed */ + foldConstants(lChild, n); + foldConstants(rChild, n); + + /* take care of all the other children */ + Node child = rChild.getNextSibling(); + while (child != null) { + foldConstants(child, n); + child = child.getNextSibling(); + } + + + /* children can change, so recompute them */ + lChild = n.getFirstChild(); + if(lChild == null){ // no children -- exit + return; + }else{ + rChild = lChild.getNextSibling(); + + if(rChild == null){ + return; + } + } + + /* at this point n has two children or more */ + int lt = lChild.getType(); + int rt = rChild.getType(); + + /* two or more children */ + switch(n.getType()){ + /* numerical addition and string concatenation */ + case TokenStream.ADD: + /* number addition -- both numbers */ + if(lt == TokenStream.NUMBER && rt == TokenStream.NUMBER){ + if(lChild.getDatum() instanceof Double || + rChild.getDatum() instanceof Double){ + parent.replaceChild(n, new Node(TokenStream.NUMBER, + new Double(lChild.getDouble() + rChild.getDouble()))); + }else{ + long longval = lChild.getLong() + rChild.getLong(); + + parent.replaceChild(n, new Node(TokenStream.NUMBER, + toSmallestType(longval))); + } + }else + /* string + string */ + if(lt == TokenStream.STRING && rt == TokenStream.STRING){ + parent.replaceChild(n, new Node(TokenStream.STRING, + lChild.getString() + rChild.getString())); + }else + /* string + num */ + if(lt == TokenStream.STRING && rt == TokenStream.NUMBER){ + parent.replaceChild(n, new Node(TokenStream.STRING, lChild.getString() + + ScriptRuntime.numberToString(rChild.getDouble(), 10))); + }else + /* num + string */ + if(lt == TokenStream.NUMBER && rt == TokenStream.STRING){ + parent.replaceChild(n, new Node(TokenStream.STRING, + ScriptRuntime.numberToString(lChild.getDouble(), 10) + + rChild.getString())); + } + /* can't do anything if we don't know both types - since 0 + object + is supposed to call toString on the object and do string concantenation + rather than addition + */ + break; + /* subtraction */ + case TokenStream.SUB: + /* both numbers */ + if(lt == TokenStream.NUMBER && rt == TokenStream.NUMBER){ + if(lChild.getDatum() instanceof Double || rChild.getDatum() instanceof Double){ + parent.replaceChild(n, new Node(TokenStream.NUMBER, + new Double(lChild.getDouble() - + rChild.getDouble()))); + }else{ + long longval = lChild.getLong() - rChild.getLong(); + parent.replaceChild(n, new Node(TokenStream.NUMBER, + toSmallestType(longval))); + } + }else + /* first 0: 0-x -> -x */ + if(lt == TokenStream.NUMBER && lChild.getDouble() == 0){ + parent.replaceChild(n, new Node(TokenStream.UNARYOP, rChild, + new Integer(TokenStream.SUB))); + }else + /* second 0: x-0 -> x */ + if(rt == TokenStream.NUMBER && rChild.getDouble() == 0){ + parent.replaceChild(n, lChild); + } + break; + /* handle multiplication by zero and one */ + case TokenStream.MUL: + /* both constants -- just multiply */ + if(lt == TokenStream.NUMBER && rt == TokenStream.NUMBER){ + /* at least one double */ + + if(lChild.getDatum() instanceof Double || rChild.getDatum() instanceof Double){ + parent.replaceChild(n, new Node(TokenStream.NUMBER, + new Double(lChild.getDouble() * + rChild.getDouble()))); + }else{ + long longval = + lChild.getLong() * + rChild.getLong(); + + parent.replaceChild(n, new Node(TokenStream.NUMBER, + toSmallestType(longval))); + } + /* one of them is constant -- zero or one */ + /* can't do zero, since zero * infinity has to become a NaN */ + }else{ + if(lt == TokenStream.NUMBER){ + double ld = ((Number)lChild.getDatum()).doubleValue(); + + if(ld == 1){ + parent.replaceChild(n, rChild); + } + }else + if(rt == TokenStream.NUMBER){ + double rd = ((Number)rChild.getDatum()).doubleValue(); + + if(rd == 1){ + parent.replaceChild(n, lChild); + } + } + } + break; + case TokenStream.DIV: + /* both constants -- just divide */ + if(lt == TokenStream.NUMBER && rt == TokenStream.NUMBER){ + /* at least one double */ + if((lChild.getDatum() instanceof Double || rChild.getDatum() instanceof Double)){ + double d = rChild.getDouble(); + + if(d == 0) + return; // division by zero + else + parent.replaceChild(n, new Node(TokenStream.NUMBER, + new Double(lChild.getDouble() / d))); + }else{ + int d = rChild.getInt(); + + if(d == 0) + return; // division by zero + else{ + long longval = + lChild.getLong() / + rChild.getLong(); + + parent.replaceChild(n, new Node(TokenStream.NUMBER, + toSmallestType(longval))); + } + } + /* 0/x , x!=0 */ + }else + /* x/1 */ + if((rt == TokenStream.NUMBER) && // added cast -roger + rChild.getDouble()==1){ + parent.replaceChild(n, lChild); + } + break; + case TokenStream.AND: + /* if the first one is false, replace with FALSE */ + if( + ((lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.NULL) || + (lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.UNDEFINED)) && + !IRFactory.hasSideEffects(rChild) + ){ + parent.replaceChild(n, new Node(TokenStream.PRIMARY, new Integer(TokenStream.FALSE))); + }else + /* if the second one is false, replace with FALSE */ + if( + ((rt==TokenStream.PRIMARY && rChild.getInt()==TokenStream.NULL) || + (rt==TokenStream.PRIMARY && rChild.getInt()==TokenStream.UNDEFINED)) && + !IRFactory.hasSideEffects(lChild) + ){ + parent.replaceChild(n, new Node(TokenStream.PRIMARY, new Integer(TokenStream.FALSE))); + }else + /* if first is true, set to second */ + if( + (lt == TokenStream.PRIMARY && ((Integer)lChild.getDatum()).intValue() == TokenStream.TRUE) || + (lt == TokenStream.NUMBER && ((Number)lChild.getDatum()).doubleValue() != 0) + ){ + parent.replaceChild(n, rChild); + }else + /* if second is true, set to first */ + if( + (rt == TokenStream.PRIMARY && ((Integer)rChild.getDatum()).intValue() == TokenStream.TRUE) || + (rt == TokenStream.NUMBER && ((Number)rChild.getDatum()).doubleValue() != 0) + ){ + parent.replaceChild(n, lChild); + } + break; + case TokenStream.OR: + /* if first is false, set to second */ + if( + (lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.NULL) || + (lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.UNDEFINED) || + (lt == TokenStream.PRIMARY && lChild.getInt() == TokenStream.FALSE) || + (lt == TokenStream.NUMBER && ((Number)lChild.getDatum()).doubleValue() == 0) + ){ + parent.replaceChild(n, rChild); + }else + /* if second is true, set to first */ + if( + (rt == TokenStream.PRIMARY && rChild.getInt() == TokenStream.NULL) || + (rt == TokenStream.PRIMARY && rChild.getInt() == TokenStream.UNDEFINED) || + (rt == TokenStream.PRIMARY && rChild.getInt() == TokenStream.FALSE) || + (rt == TokenStream.NUMBER && ((Number)rChild.getDatum()).doubleValue() == 0) + ){ + parent.replaceChild(n, lChild); + }else + /* if first one is true, replace with TRUE */ + if( + ((lt == TokenStream.PRIMARY && ((Integer)lChild.getDatum()).intValue() == TokenStream.TRUE) || + (lt == TokenStream.NUMBER && ((Number)lChild.getDatum()).doubleValue() != 0)) && + !IRFactory.hasSideEffects(rChild) + ){ + parent.replaceChild(n, new Node(TokenStream.PRIMARY, new Integer(TokenStream.TRUE))); + }else + /* if second one is true, replace with TRUE */ + if( + ((rt == TokenStream.PRIMARY && ((Integer)rChild.getDatum()).intValue() == TokenStream.TRUE) || + (rt == TokenStream.NUMBER && ((Number)rChild.getDatum()).doubleValue() != 0)) && + !IRFactory.hasSideEffects(lChild) + ){ + parent.replaceChild(n, new Node(TokenStream.PRIMARY, new Integer(TokenStream.TRUE))); + } + break; + case TokenStream.BLOCK: + /* if statement */ + if(lChild.getType() == TokenStream.IFNE){ + Node condition = lChild.getFirstChild(); + /* if(false) -> replace by the else clause if it exists */ + if(condition.getType() == TokenStream.PRIMARY){ + int id = condition.getInt(); + if(id == TokenStream.FALSE||id == TokenStream.NULL||id == TokenStream.UNDEFINED){ + Node elseClause = null; + try{ + elseClause = + rChild.getNextSibling().getNextSibling().getNextSibling().getFirstChild(); + }catch(Exception e){ + return; + } + + if(elseClause != null){ + parent.replaceChild(n, elseClause); + } + /* if(true) -> replace by the then clause if it exists */ + }else + if((condition.getType() == TokenStream.PRIMARY && condition.getInt() == TokenStream.TRUE) || + (condition.getType() == TokenStream.NUMBER && ((Number)condition.getDatum()).doubleValue() != 0)) + { + if(rChild.getType() == TokenStream.BLOCK){ + parent.replaceChild(n, rChild.getFirstChild()); + } + } + } + } + break; + }//switch + } + + /* + Converts a long to the smalles possible integral type. + */ + private static Number toSmallestType(long longval){ + // We generate the smallest possible type here + if (Byte.MIN_VALUE <= longval && longval <= Byte.MAX_VALUE) + return new Byte((byte)longval); + else if (Short.MIN_VALUE <= longval && + longval <= Short.MAX_VALUE) + return new Short((short)longval); + else if (Integer.MIN_VALUE <= longval && + longval <= Integer.MAX_VALUE) + return new Integer((int)longval); + else + return new Double(longval); + } + + void replaceVariableAccess(Node n, VariableTable theVariables) + { + Node child = n.getFirstChild(); + while (child != null) { + replaceVariableAccess(child, theVariables); + child = child.getNextSibling(); + } + switch (n.getType()) { + case TokenStream.SETVAR : { + String name = n.getFirstChild().getString(); + OptLocalVariable theVar = (OptLocalVariable) + theVariables.get(name); + if (theVar != null) + n.putProp(Node.VARIABLE_PROP, theVar); + } + break; + case TokenStream.GETVAR : { + String name = n.getString(); + OptLocalVariable theVar = (OptLocalVariable) + theVariables.get(name); + if (theVar != null) + n.putProp(Node.VARIABLE_PROP, theVar); + } + break; + default : + break; + } + } + + private Node[] buildStatementList(FunctionNode theFunction) + { + Vector nodeList = new Vector(); + + StmtNodeIterator iterator = new StmtNodeIterator(theFunction); + Node node = iterator.nextNode(); + while (node != null) { + nodeList.addElement(node); + node = iterator.nextNode(); + } + Node[] result = new Node[nodeList.size()]; + for (int i = 0; i < nodeList.size(); i++) { + result[i] = (Node)(nodeList.elementAt(i)); + } + return result; + } + + int itsOptLevel; +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/StmtNodeIterator.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/StmtNodeIterator.java new file mode 100644 index 00000000000..2cc60d9d9b7 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/StmtNodeIterator.java @@ -0,0 +1,94 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; + +import java.util.Stack; + +public class StmtNodeIterator { + + public StmtNodeIterator(Node start) + { + itsStart = start; + } + + private Node findFirstInterestingNode(Node theNode) + { + if (theNode == null) return null; + + if ((theNode.getType() == TokenStream.BLOCK) + || (theNode.getType() == TokenStream.LOOP) + || (theNode.getType() == TokenStream.FUNCTION)) { + if (theNode.getFirst() == null) { + return findFirstInterestingNode(theNode.getNext()); + } + else { + itsStack.push(theNode); + return findFirstInterestingNode(theNode.getFirst()); + } + } + else + return theNode; + } + + public Node nextNode() + { + if (itsCurrentNode == null) + return itsCurrentNode = findFirstInterestingNode(itsStart); + + itsCurrentNode = itsCurrentNode.getNext(); + if (itsCurrentNode == null) { + while ( ! itsStack.isEmpty()) { + Node n = (Node)(itsStack.pop()); + if (n.getNext() != null) { + return itsCurrentNode = findFirstInterestingNode(n.getNext()); + } + } + return null; + } + else + return itsCurrentNode = findFirstInterestingNode(itsCurrentNode); + } + + private Stack itsStack = new Stack(); + private Node itsStart; + private Node itsCurrentNode; + +} diff --git a/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/TypeEvent.java b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/TypeEvent.java new file mode 100644 index 00000000000..52d9fda2766 --- /dev/null +++ b/mozilla/js/rhino/src/org/mozilla/javascript/optimizer/TypeEvent.java @@ -0,0 +1,67 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript.optimizer; + +public class TypeEvent { + + public static final int EventBitLength = 2; + + public static final int + AnyType = (1 << EventBitLength) - 1, + NumberType = 0x1, + NoType = 0x0; + + public TypeEvent(int theEvent) + { + itsEvent = theEvent; + } + + public TypeEvent() + { + itsEvent = NoType; + } + + public boolean add(int anOther) + { + return ((itsEvent |= anOther) != anOther); + } + + public int getEvent() { return itsEvent; } + + private int itsEvent; +} + diff --git a/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/jsc/Main.java b/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/jsc/Main.java new file mode 100644 index 00000000000..4e235bd6747 --- /dev/null +++ b/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/jsc/Main.java @@ -0,0 +1,293 @@ +/* + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Christine Begle + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript.tools.jsc; + +import java.io.*; +import java.util.*; +import java.lang.reflect.*; +import java.text.MessageFormat; +import org.mozilla.javascript.*; +import org.mozilla.javascript.tools.ToolErrorReporter; + +/** + * @author Norris Boyd + */ +public class Main { + + /** + * Main entry point. + * + * Process arguments as would a normal Java program. Also + * create a new Context and associate it with the current thread. + * Then set up the execution environment and begin to + * compile scripts. + */ + public static void main(String args[]) { + Context cx = Context.enter(); + + reporter = new ToolErrorReporter(true); + + cx.setErrorReporter(reporter); + + args = processOptions(cx, args); + + if ( ! reporter.hasReportedError()) + processSource(cx, args); + + cx.exit(); + } + + /** + * Parse arguments. + * + */ + public static String[] processOptions(Context cx, String args[]) { + cx.setTargetPackage(""); // default to no package + for (int i=0; i < args.length; i++) { + String arg = args[i]; + if (!arg.startsWith("-")) { + String[] result = new String[args.length - i]; + for (int j=i; j < args.length; j++) + result[j-i] = args[j]; + return result; + } + try { + if (arg.equals("-version") && ++i < args.length) { + int version = Integer.parseInt(args[i]); + cx.setLanguageVersion(version); + continue; + } + if ((arg.equals("-opt") || arg.equals("-O")) && + ++i < args.length) + { + int optLevel = Integer.parseInt(args[i]); + cx.setOptimizationLevel(optLevel); + continue; + } + if (arg.equals("-debuglevel") && ++i < args.length) { + int debugLevel = Integer.parseInt(args[i]); + cx.setDebugLevel(debugLevel); + continue; + } + } + catch (NumberFormatException e) { + cx.reportError( ToolErrorReporter.getMessage("msg.jsc.usage", + args[i] )); + continue; + } + if (arg.equals("-nosource")) { + cx.setGeneratingSource(false); + continue; + } + if (arg.equals("-debug") || arg.equals("-g")) { + cx.setGeneratingDebug(true); + continue; + } + if (arg.equals("-o") && ++i < args.length) { + String outFile = args[i]; + if (!Character.isJavaIdentifierStart(outFile.charAt(0))) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.invalid.classfile.name", + outFile)); + continue; + } + for ( int j = 1; j < outFile.length(); j++ ) { + if ( (!Character.isJavaIdentifierPart(outFile.charAt(j)) && + outFile.charAt(j) != '.') || (outFile.charAt(j) == '.' && + (!outFile.endsWith(".class") || j != outFile.length()-6 )) ) + { + cx.reportError(ToolErrorReporter.getMessage( + "msg.invalid.classfile.name", + outFile )); + break; + } + } + cx.setTargetClassFileName(outFile); + hasOutOption = true; + continue; + } + if (arg.equals("-package") && ++i < args.length) { + String targetPackage = args[i]; + for ( int j = 0; j < targetPackage.length(); j++ ) { + if ( !Character.isJavaIdentifierStart(targetPackage.charAt(j))) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.package.name", + targetPackage)); + continue; + } + for ( int k = ++j; k< targetPackage.length(); k++, j++ ) { + if (targetPackage.charAt(k) == '.' && + targetPackage.charAt(k-1) != '.' && + k != targetPackage.length()-1) + { + continue; + } + if (!Character.isJavaIdentifierPart( + targetPackage.charAt(k))) + { + cx.reportError(ToolErrorReporter.getMessage( + "msg.package.name", + targetPackage)); + continue; + } + continue; + } + } + cx.setTargetPackage(targetPackage); + continue; + } + if (arg.equals("-extends") && ++i < args.length) { + String targetExtends = args[i]; + try { + cx.setTargetExtends(Class.forName(targetExtends)); + } catch (ClassNotFoundException e) { + throw new Error(e.toString()); // TODO: better error + } + continue; + } + if (arg.equals("-implements") && ++i < args.length) { + // TODO: allow for multiple comma-separated interfaces. + String targetImplements = args[i]; + try { + Class[] implementsClasses = { Class.forName(targetImplements) }; + cx.setTargetImplements(implementsClasses); + } catch (ClassNotFoundException e) { + throw new Error(e.toString()); // TODO: better error + } + continue; + } + usage(arg); + } + // no file name + p(ToolErrorReporter.getMessage("msg.no.file")); + System.exit(1); + return null; + } + /** + * Print a usage message. + */ + public static void usage(String s) { + p(ToolErrorReporter.getMessage("msg.jsc.usage", s)); + System.exit(1); + } + + /** + * Compile JavaScript source. + * + */ + public static void processSource(Context cx, String[] filenames) { + if (hasOutOption && filenames.length > 1) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.multiple.js.to.file", + cx.getTargetClassFileName())); + } + for (int i=0; i < filenames.length; i++) { + String filename = filenames[i]; + File f = new File(filename); + + if (!f.exists()) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.jsfile.not.found", + filename)); + return; + } + if (!filename.endsWith(".js")) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.extension.not.js", + filename)); + return; + } + if (!hasOutOption) { + String name = f.getName(); + String nojs = name.substring(0, name.length() - 3); + String className = getClassName(nojs) + ".class"; + String out = f.getParent() == null ? className : f.getParent() + + File.separator + className; + cx.setTargetClassFileName(out); + } + if (cx.getTargetClassFileName() == null) { + cx.reportError(ToolErrorReporter.getMessage("msg.no-opt")); + } + try { + Reader in = new FileReader(filename); + cx.compileReader(null, in, filename, 1, null); + } + catch (FileNotFoundException ex) { + cx.reportError(ToolErrorReporter.getMessage( + "msg.couldnt.open", + filename)); + return; + } + catch (IOException ioe) { + cx.reportError(ioe.toString()); + } + } + } + + /** + * Verify that class file names are legal Java identifiers. Substitute + * illegal characters with underscores, and prepend the name with an + * underscore if the file name does not begin with a JavaLetter. + */ + + static String getClassName(String name) { + char[] s = new char[name.length()+1]; + char c; + int j = 0; + + if (!Character.isJavaIdentifierStart(name.charAt(0))) { + s[j++] = '_'; + } + for (int i=0; i < name.length(); i++, j++) { + c = name.charAt(i); + if ( Character.isJavaIdentifierPart(c) ) { + s[j] = c; + } else { + s[j] = '_'; + } + } + return (new String(s)).trim(); + } + + private static void p(String s) { + System.out.println(s); + } + + private static boolean hasOutOption = false; + private static ToolErrorReporter reporter; +} + diff --git a/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/Main.java b/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/Main.java index 8d762c9f8c6..689167ae336 100644 --- a/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/Main.java +++ b/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/Main.java @@ -108,7 +108,7 @@ public class Main extends ScriptableObject { try { Class clazz = Class.forName( - "com.netscape.jsdebugging.ifcui.launcher.rhino.LaunchNetscapeJavaScriptDebugger"); + "org.mozilla.jsdebugging.ifcui.launcher.rhino.LaunchNetscapeJavaScriptDebugger"); ILaunchableDebugger debugger = (ILaunchableDebugger) clazz.newInstance(); debugger.launch(global.debug_dm, global.debug_stm, false);