summaryrefslogtreecommitdiffstats
path: root/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer')
-rw-r--r--trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Block.java615
-rw-r--r--trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/ClassCompiler.java214
-rw-r--r--trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java5031
-rw-r--r--trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java134
-rw-r--r--trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptFunctionNode.java149
-rw-r--r--trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptRuntime.java311
-rw-r--r--trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptTransformer.java133
-rw-r--r--trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Optimizer.java510
8 files changed, 7097 insertions, 0 deletions
diff --git a/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Block.java b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Block.java
new file mode 100644
index 0000000..bd56714
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Block.java
@@ -0,0 +1,615 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla 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/MPL/
+ *
+ * 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 Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (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
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * 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 MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+import java.util.Hashtable;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+class Block
+{
+
+ private static class FatBlock
+ {
+
+ private static Block[] reduceToArray(ObjToIntMap map)
+ {
+ Block[] result = null;
+ if (!map.isEmpty()) {
+ result = new Block[map.size()];
+ int i = 0;
+ ObjToIntMap.Iterator iter = map.newIterator();
+ for (iter.start(); !iter.done(); iter.next()) {
+ FatBlock fb = (FatBlock)(iter.getKey());
+ result[i++] = fb.realBlock;
+ }
+ }
+ return result;
+ }
+
+ void addSuccessor(FatBlock b) { successors.put(b, 0); }
+ void addPredecessor(FatBlock b) { predecessors.put(b, 0); }
+
+ Block[] getSuccessors() { return reduceToArray(successors); }
+ Block[] getPredecessors() { return reduceToArray(predecessors); }
+
+ // all the Blocks that come immediately after this
+ private ObjToIntMap successors = new ObjToIntMap();
+ // all the Blocks that come immediately before this
+ private ObjToIntMap predecessors = new ObjToIntMap();
+
+ Block realBlock;
+ }
+
+ Block(int startNodeIndex, int endNodeIndex)
+ {
+ itsStartNodeIndex = startNodeIndex;
+ itsEndNodeIndex = endNodeIndex;
+ }
+
+ static void runFlowAnalyzes(OptFunctionNode fn, Node[] statementNodes)
+ {
+ int paramCount = fn.fnode.getParamCount();
+ int varCount = fn.fnode.getParamAndVarCount();
+ int[] varTypes = new int[varCount];
+ // If the variable is a parameter, it could have any type.
+ for (int i = 0; i != paramCount; ++i) {
+ varTypes[i] = Optimizer.AnyType;
+ }
+ // If the variable is from a "var" statement, its typeEvent will be set
+ // when we see the setVar node.
+ for (int i = paramCount; i != varCount; ++i) {
+ varTypes[i] = Optimizer.NoType;
+ }
+
+ Block[] theBlocks = buildBlocks(statementNodes);
+
+ if (DEBUG) {
+ ++debug_blockCount;
+ System.out.println("-------------------"+fn.fnode.getFunctionName()+" "+debug_blockCount+"--------");
+ System.out.println(toString(theBlocks, statementNodes));
+ }
+
+ reachingDefDataFlow(fn, statementNodes, theBlocks, varTypes);
+ typeFlow(fn, statementNodes, theBlocks, varTypes);
+
+ if (DEBUG) {
+ for (int i = 0; i < theBlocks.length; i++) {
+ System.out.println("For block " + theBlocks[i].itsBlockID);
+ theBlocks[i].printLiveOnEntrySet(fn);
+ }
+ System.out.println("Variable Table, size = " + varCount);
+ for (int i = 0; i != varCount; i++) {
+ System.out.println("["+i+"] type: "+varTypes[i]);
+ }
+ }
+
+ for (int i = paramCount; i != varCount; i++) {
+ if (varTypes[i] == Optimizer.NumberType) {
+ fn.setIsNumberVar(i);
+ }
+ }
+
+ }
+
+ private static Block[] buildBlocks(Node[] statementNodes)
+ {
+ // a mapping from each target node to the block it begins
+ Hashtable theTargetBlocks = new Hashtable();
+ ObjArray theBlocks = new ObjArray();
+
+ // there's a block that starts at index 0
+ int beginNodeIndex = 0;
+
+ for (int i = 0; i < statementNodes.length; i++) {
+ switch (statementNodes[i].getType()) {
+ case Token.TARGET :
+ {
+ if (i != beginNodeIndex) {
+ FatBlock fb = newFatBlock(beginNodeIndex, i - 1);
+ if (statementNodes[beginNodeIndex].getType()
+ == Token.TARGET)
+ theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
+ theBlocks.add(fb);
+ // start the next block at this node
+ beginNodeIndex = i;
+ }
+ }
+ break;
+ case Token.IFNE :
+ case Token.IFEQ :
+ case Token.GOTO :
+ {
+ FatBlock fb = newFatBlock(beginNodeIndex, i);
+ if (statementNodes[beginNodeIndex].getType()
+ == Token.TARGET)
+ theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
+ theBlocks.add(fb);
+ // start the next block at the next node
+ beginNodeIndex = i + 1;
+ }
+ break;
+ }
+ }
+
+ if (beginNodeIndex != statementNodes.length) {
+ FatBlock fb = newFatBlock(beginNodeIndex, statementNodes.length - 1);
+ if (statementNodes[beginNodeIndex].getType() == Token.TARGET)
+ theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
+ theBlocks.add(fb);
+ }
+
+ // build successor and predecessor links
+
+ for (int i = 0; i < theBlocks.size(); i++) {
+ FatBlock fb = (FatBlock)(theBlocks.get(i));
+
+ Node blockEndNode = statementNodes[fb.realBlock.itsEndNodeIndex];
+ int blockEndNodeType = blockEndNode.getType();
+
+ if ((blockEndNodeType != Token.GOTO)
+ && (i < (theBlocks.size() - 1))) {
+ FatBlock fallThruTarget = (FatBlock)(theBlocks.get(i + 1));
+ fb.addSuccessor(fallThruTarget);
+ fallThruTarget.addPredecessor(fb);
+ }
+
+
+ if ( (blockEndNodeType == Token.IFNE)
+ || (blockEndNodeType == Token.IFEQ)
+ || (blockEndNodeType == Token.GOTO) ) {
+ Node target = ((Node.Jump)blockEndNode).target;
+ FatBlock branchTargetBlock
+ = (FatBlock)(theTargetBlocks.get(target));
+ target.putProp(Node.TARGETBLOCK_PROP,
+ branchTargetBlock.realBlock);
+ fb.addSuccessor(branchTargetBlock);
+ branchTargetBlock.addPredecessor(fb);
+ }
+ }
+
+ Block[] result = new Block[theBlocks.size()];
+
+ for (int i = 0; i < theBlocks.size(); i++) {
+ FatBlock fb = (FatBlock)(theBlocks.get(i));
+ Block b = fb.realBlock;
+ b.itsSuccessors = fb.getSuccessors();
+ b.itsPredecessors = fb.getPredecessors();
+ b.itsBlockID = i;
+ result[i] = b;
+ }
+
+ return result;
+ }
+
+ private static FatBlock newFatBlock(int startNodeIndex, int endNodeIndex)
+ {
+ FatBlock fb = new FatBlock();
+ fb.realBlock = new Block(startNodeIndex, endNodeIndex);
+ return fb;
+ }
+
+ private static String toString(Block[] blockList, Node[] statementNodes)
+ {
+ if (!DEBUG) return null;
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ pw.println(blockList.length + " Blocks");
+ for (int i = 0; i < blockList.length; i++) {
+ Block b = blockList[i];
+ pw.println("#" + b.itsBlockID);
+ pw.println("from " + b.itsStartNodeIndex
+ + " "
+ + statementNodes[b.itsStartNodeIndex].toString());
+ pw.println("thru " + b.itsEndNodeIndex
+ + " "
+ + statementNodes[b.itsEndNodeIndex].toString());
+ pw.print("Predecessors ");
+ if (b.itsPredecessors != null) {
+ for (int j = 0; j < b.itsPredecessors.length; j++)
+ pw.print(b.itsPredecessors[j].itsBlockID + " ");
+ pw.println();
+ }
+ else
+ pw.println("none");
+ pw.print("Successors ");
+ if (b.itsSuccessors != null) {
+ for (int j = 0; j < b.itsSuccessors.length; j++)
+ pw.print(b.itsSuccessors[j].itsBlockID + " ");
+ pw.println();
+ }
+ else
+ pw.println("none");
+ }
+ return sw.toString();
+ }
+
+ private static void reachingDefDataFlow(OptFunctionNode fn, Node[] statementNodes, Block theBlocks[], int[] varTypes)
+ {
+/*
+ initialize the liveOnEntry and liveOnExit sets, then discover the variables
+ that are def'd by each function, and those that are used before being def'd
+ (hence liveOnEntry)
+*/
+ for (int i = 0; i < theBlocks.length; i++) {
+ theBlocks[i].initLiveOnEntrySets(fn, statementNodes);
+ }
+/*
+ this visits every block starting at the last, re-adding the predecessors of
+ any block whose inputs change as a result of the dataflow.
+ REMIND, better would be to visit in CFG postorder
+*/
+ boolean visit[] = new boolean[theBlocks.length];
+ boolean doneOnce[] = new boolean[theBlocks.length];
+ int vIndex = theBlocks.length - 1;
+ boolean needRescan = false;
+ visit[vIndex] = true;
+ while (true) {
+ if (visit[vIndex] || !doneOnce[vIndex]) {
+ doneOnce[vIndex] = true;
+ visit[vIndex] = false;
+ if (theBlocks[vIndex].doReachedUseDataFlow()) {
+ Block pred[] = theBlocks[vIndex].itsPredecessors;
+ if (pred != null) {
+ for (int i = 0; i < pred.length; i++) {
+ int index = pred[i].itsBlockID;
+ visit[index] = true;
+ needRescan |= (index > vIndex);
+ }
+ }
+ }
+ }
+ if (vIndex == 0) {
+ if (needRescan) {
+ vIndex = theBlocks.length - 1;
+ needRescan = false;
+ }
+ else
+ break;
+ }
+ else
+ vIndex--;
+ }
+/*
+ if any variable is live on entry to block 0, we have to mark it as
+ not jRegable - since it means that someone is trying to access the
+ 'undefined'-ness of that variable.
+*/
+
+ theBlocks[0].markAnyTypeVariables(varTypes);
+ }
+
+ private static void typeFlow(OptFunctionNode fn, Node[] statementNodes, Block theBlocks[], int[] varTypes)
+ {
+ boolean visit[] = new boolean[theBlocks.length];
+ boolean doneOnce[] = new boolean[theBlocks.length];
+ int vIndex = 0;
+ boolean needRescan = false;
+ visit[vIndex] = true;
+ while (true) {
+ if (visit[vIndex] || !doneOnce[vIndex]) {
+ doneOnce[vIndex] = true;
+ visit[vIndex] = false;
+ if (theBlocks[vIndex].doTypeFlow(fn, statementNodes, varTypes))
+ {
+ Block succ[] = theBlocks[vIndex].itsSuccessors;
+ if (succ != null) {
+ for (int i = 0; i < succ.length; i++) {
+ int index = succ[i].itsBlockID;
+ visit[index] = true;
+ needRescan |= (index < vIndex);
+ }
+ }
+ }
+ }
+ if (vIndex == (theBlocks.length - 1)) {
+ if (needRescan) {
+ vIndex = 0;
+ needRescan = false;
+ }
+ else
+ break;
+ }
+ else
+ vIndex++;
+ }
+ }
+
+ private static boolean assignType(int[] varTypes, int index, int type)
+ {
+ return type != (varTypes[index] |= type);
+ }
+
+ private void markAnyTypeVariables(int[] varTypes)
+ {
+ for (int i = 0; i != varTypes.length; i++) {
+ if (itsLiveOnEntrySet.test(i)) {
+ assignType(varTypes, i, Optimizer.AnyType);
+ }
+ }
+
+ }
+
+ /*
+ We're tracking uses and defs - in order to
+ build the def set and to identify the last use
+ nodes.
+
+ The itsNotDefSet is built reversed then flipped later.
+
+ */
+ private void lookForVariableAccess(OptFunctionNode fn, Node n)
+ {
+ switch (n.getType()) {
+ case Token.DEC :
+ case Token.INC :
+ {
+ Node child = n.getFirstChild();
+ if (child.getType() == Token.GETVAR) {
+ int varIndex = fn.getVarIndex(child);
+ if (!itsNotDefSet.test(varIndex))
+ itsUseBeforeDefSet.set(varIndex);
+ itsNotDefSet.set(varIndex);
+ }
+ }
+ break;
+ case Token.SETVAR :
+ {
+ Node lhs = n.getFirstChild();
+ Node rhs = lhs.getNext();
+ lookForVariableAccess(fn, rhs);
+ itsNotDefSet.set(fn.getVarIndex(n));
+ }
+ break;
+ case Token.GETVAR :
+ {
+ int varIndex = fn.getVarIndex(n);
+ if (!itsNotDefSet.test(varIndex))
+ itsUseBeforeDefSet.set(varIndex);
+ }
+ break;
+ default :
+ Node child = n.getFirstChild();
+ while (child != null) {
+ lookForVariableAccess(fn, child);
+ child = child.getNext();
+ }
+ break;
+ }
+ }
+
+ /*
+ build the live on entry/exit sets.
+ Then walk the trees looking for defs/uses of variables
+ and build the def and useBeforeDef sets.
+ */
+ private void initLiveOnEntrySets(OptFunctionNode fn, Node[] statementNodes)
+ {
+ int listLength = fn.getVarCount();
+ itsUseBeforeDefSet = new DataFlowBitSet(listLength);
+ itsNotDefSet = new DataFlowBitSet(listLength);
+ itsLiveOnEntrySet = new DataFlowBitSet(listLength);
+ itsLiveOnExitSet = new DataFlowBitSet(listLength);
+ for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) {
+ Node n = statementNodes[i];
+ lookForVariableAccess(fn, n);
+ }
+ 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
+
+ */
+ private 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
+ */
+ private static int findExpressionType(OptFunctionNode fn, Node n,
+ int[] varTypes)
+ {
+ switch (n.getType()) {
+ case Token.NUMBER :
+ return Optimizer.NumberType;
+
+ case Token.CALL :
+ case Token.NEW :
+ case Token.REF_CALL :
+ return Optimizer.AnyType;
+
+ case Token.GETELEM :
+ return Optimizer.AnyType;
+
+ case Token.GETVAR :
+ return varTypes[fn.getVarIndex(n)];
+
+ case Token.INC :
+ case Token.DEC :
+ case Token.DIV:
+ case Token.MOD:
+ case Token.BITOR:
+ case Token.BITXOR:
+ case Token.BITAND:
+ case Token.LSH:
+ case Token.RSH:
+ case Token.URSH:
+ case Token.SUB :
+ return Optimizer.NumberType;
+
+ case Token.ARRAYLIT:
+ case Token.OBJECTLIT:
+ return Optimizer.AnyType; // XXX: actually, we know it's not
+ // number, but no type yet for that
+
+ case Token.ADD : {
+ // if the lhs & rhs are known to be numbers, we can be sure that's
+ // the result, otherwise it could be a string.
+ Node child = n.getFirstChild();
+ int lType = findExpressionType(fn, child, varTypes);
+ int rType = findExpressionType(fn, child.getNext(), varTypes);
+ return lType | rType; // we're not distinguishing strings yet
+ }
+ }
+
+ Node child = n.getFirstChild();
+ if (child == null) {
+ return Optimizer.AnyType;
+ } else {
+ int result = Optimizer.NoType;
+ while (child != null) {
+ result |= findExpressionType(fn, child, varTypes);
+ child = child.getNext();
+ }
+ return result;
+ }
+ }
+
+ private static boolean findDefPoints(OptFunctionNode fn, Node n,
+ int[] varTypes)
+ {
+ boolean result = false;
+ Node child = n.getFirstChild();
+ switch (n.getType()) {
+ default :
+ while (child != null) {
+ result |= findDefPoints(fn, child, varTypes);
+ child = child.getNext();
+ }
+ break;
+ case Token.DEC :
+ case Token.INC :
+ if (child.getType() == Token.GETVAR) {
+ // theVar is a Number now
+ int i = fn.getVarIndex(child);
+ result |= assignType(varTypes, i, Optimizer.NumberType);
+ }
+ break;
+ case Token.SETPROP :
+ case Token.SETPROP_OP :
+ if (child.getType() == Token.GETVAR) {
+ int i = fn.getVarIndex(child);
+ assignType(varTypes, i, Optimizer.AnyType);
+ }
+ while (child != null) {
+ result |= findDefPoints(fn, child, varTypes);
+ child = child.getNext();
+ }
+ break;
+ case Token.SETVAR : {
+ Node rValue = child.getNext();
+ int theType = findExpressionType(fn, rValue, varTypes);
+ int i = fn.getVarIndex(n);
+ result |= assignType(varTypes, i, theType);
+ break;
+ }
+ }
+ return result;
+ }
+
+ private boolean doTypeFlow(OptFunctionNode fn, Node[] statementNodes,
+ int[] varTypes)
+ {
+ boolean changed = false;
+
+ for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) {
+ Node n = statementNodes[i];
+ if (n != null)
+ changed |= findDefPoints(fn, n, varTypes);
+ }
+
+ return changed;
+ }
+
+ private void printLiveOnEntrySet(OptFunctionNode fn)
+ {
+ if (DEBUG) {
+ for (int i = 0; i < fn.getVarCount(); i++) {
+ String name = fn.fnode.getParamOrVarName(i);
+ if (itsUseBeforeDefSet.test(i))
+ System.out.println(name + " is used before def'd");
+ if (itsNotDefSet.test(i))
+ System.out.println(name + " is not def'd");
+ if (itsLiveOnEntrySet.test(i))
+ System.out.println(name + " is live on entry");
+ if (itsLiveOnExitSet.test(i))
+ System.out.println(name + " is live on exit");
+ }
+ }
+ }
+
+ // all the Blocks that come immediately after this
+ private Block[] itsSuccessors;
+ // all the Blocks that come immediately before this
+ private Block[] itsPredecessors;
+
+ private int itsStartNodeIndex; // the Node at the start of the block
+ private int itsEndNodeIndex; // the Node at the end of the block
+
+ private int itsBlockID; // a unique index for each block
+
+// reaching def bit sets -
+ private DataFlowBitSet itsLiveOnEntrySet;
+ private DataFlowBitSet itsLiveOnExitSet;
+ private DataFlowBitSet itsUseBeforeDefSet;
+ private DataFlowBitSet itsNotDefSet;
+
+ static final boolean DEBUG = false;
+ private static int debug_blockCount;
+
+}
+
diff --git a/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/ClassCompiler.java b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/ClassCompiler.java
new file mode 100644
index 0000000..4a69c6a
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/ClassCompiler.java
@@ -0,0 +1,214 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla 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/MPL/
+ *
+ * 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 Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (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
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * 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 MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+/**
+ * Generates class files from script sources.
+ *
+ * since 1.5 Release 5
+ * @author Igor Bukanov
+ */
+
+public class ClassCompiler
+{
+ /**
+ * Construct ClassCompiler that uses the specified compiler environment
+ * when generating classes.
+ */
+ public ClassCompiler(CompilerEnvirons compilerEnv)
+ {
+ if (compilerEnv == null) throw new IllegalArgumentException();
+ this.compilerEnv = compilerEnv;
+ this.mainMethodClassName = Codegen.DEFAULT_MAIN_METHOD_CLASS;
+ }
+
+ /**
+ * Set the class name to use for main method implementation.
+ * The class must have a method matching
+ * <tt>public static void main(Script sc, String[] args)</tt>, it will be
+ * called when <tt>main(String[] args)</tt> is called in the generated
+ * class. The class name should be fully qulified name and include the
+ * package name like in <tt>org.foo.Bar<tt>.
+ */
+ public void setMainMethodClass(String className)
+ {
+ // XXX Should this check for a valid class name?
+ mainMethodClassName = className;
+ }
+
+ /**
+ * Get the name of the class for main method implementation.
+ * @see #setMainMethodClass(String)
+ */
+ public String getMainMethodClass()
+ {
+ return mainMethodClassName;
+ }
+
+ /**
+ * Get the compiler environment the compiler uses.
+ */
+ public CompilerEnvirons getCompilerEnv()
+ {
+ return compilerEnv;
+ }
+
+ /**
+ * Get the class that the generated target will extend.
+ */
+ public Class getTargetExtends()
+ {
+ return targetExtends;
+ }
+
+ /**
+ * Set the class that the generated target will extend.
+ *
+ * @param extendsClass the class it extends
+ */
+ public void setTargetExtends(Class extendsClass)
+ {
+ targetExtends = extendsClass;
+ }
+
+ /**
+ * Get the interfaces that the generated target will implement.
+ */
+ public Class[] getTargetImplements()
+ {
+ return targetImplements == null ? null : (Class[])targetImplements.clone();
+ }
+
+ /**
+ * Set the interfaces that the generated target will implement.
+ *
+ * @param implementsClasses an array of Class objects, one for each
+ * interface the target will extend
+ */
+ public void setTargetImplements(Class[] implementsClasses)
+ {
+ targetImplements = implementsClasses == null ? null : (Class[])implementsClasses.clone();
+ }
+
+ /**
+ * Build class name for a auxiliary class generated by compiler.
+ * If the compiler needs to generate extra classes beyond the main class,
+ * it will call this function to build the auxiliary class name.
+ * The default implementation simply appends auxMarker to mainClassName
+ * but this can be overridden.
+ */
+ protected String makeAuxiliaryClassName(String mainClassName,
+ String auxMarker)
+ {
+ return mainClassName+auxMarker;
+ }
+
+ /**
+ * Compile JavaScript source into one or more Java class files.
+ * The first compiled class will have name mainClassName.
+ * If the results of {@link #getTargetExtends()} or
+ * {@link #getTargetImplements()} are not null, then the first compiled
+ * class will extend the specified super class and implement
+ * specified interfaces.
+ *
+ * @return array where elements with even indexes specifies class name
+ * and the following odd index gives class file body as byte[]
+ * array. The initial element of the array always holds
+ * mainClassName and array[1] holds its byte code.
+ */
+ public Object[] compileToClassFiles(String source,
+ String sourceLocation,
+ int lineno,
+ String mainClassName)
+ {
+ /*APPJET*/
+ Parser p =
+ InformativeParser.makeParser(compilerEnv,
+ compilerEnv.getErrorReporter());
+ ScriptOrFnNode tree = p.parse(source, sourceLocation, lineno);
+ String encodedSource = p.getEncodedSource();
+
+ Class superClass = getTargetExtends();
+ Class[] interfaces = getTargetImplements();
+ String scriptClassName;
+ boolean isPrimary = (interfaces == null && superClass == null);
+ if (isPrimary) {
+ scriptClassName = mainClassName;
+ } else {
+ scriptClassName = makeAuxiliaryClassName(mainClassName, "1");
+ }
+
+ Codegen codegen = new Codegen();
+ codegen.setMainMethodClass(mainMethodClassName);
+ byte[] scriptClassBytes
+ = codegen.compileToClassFile(compilerEnv, scriptClassName,
+ tree, encodedSource,
+ false);
+
+ if (isPrimary) {
+ return new Object[] { scriptClassName, scriptClassBytes };
+ }
+ int functionCount = tree.getFunctionCount();
+ ObjToIntMap functionNames = new ObjToIntMap(functionCount);
+ for (int i = 0; i != functionCount; ++i) {
+ FunctionNode ofn = tree.getFunctionNode(i);
+ String name = ofn.getFunctionName();
+ if (name != null && name.length() != 0) {
+ functionNames.put(name, ofn.getParamCount());
+ }
+ }
+ if (superClass == null) {
+ superClass = ScriptRuntime.ObjectClass;
+ }
+ byte[] mainClassBytes
+ = JavaAdapter.createAdapterCode(
+ functionNames, mainClassName,
+ superClass, interfaces, scriptClassName);
+
+ return new Object[] { mainClassName, mainClassBytes,
+ scriptClassName, scriptClassBytes };
+ }
+
+ private String mainMethodClassName;
+ private CompilerEnvirons compilerEnv;
+ private Class targetExtends;
+ private Class[] targetImplements;
+
+}
+
diff --git a/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java
new file mode 100644
index 0000000..64952bf
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java
@@ -0,0 +1,5031 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla 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/MPL/
+ *
+ * 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 Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Kemal Bayram
+ * Igor Bukanov
+ * Bob Jervis
+ * Roger Lawrence
+ * Andi Vajda
+ * Hannes Wallnoefer
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (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
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * 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 MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+import org.mozilla.classfile.*;
+import java.util.*;
+import java.lang.reflect.Constructor;
+import java.util.Hashtable;
+
+/**
+ * This class generates code for a given IR tree.
+ *
+ * @author Norris Boyd
+ * @author Roger Lawrence
+ */
+
+public class Codegen implements Evaluator
+{
+ public void captureStackInfo(RhinoException ex) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getSourcePositionFromStack(Context cx, int[] linep) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getPatchedStack(RhinoException ex, String nativeStackTrace) {
+ throw new UnsupportedOperationException();
+ }
+
+ public List getScriptStack(RhinoException ex) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setEvalScriptFlag(Script script) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object compile(CompilerEnvirons compilerEnv,
+ ScriptOrFnNode tree,
+ String encodedSource,
+ boolean returnFunction)
+ {
+ int serial;
+ synchronized (globalLock) {
+ serial = ++globalSerialClassCounter;
+ }
+ String mainClassName = "org.mozilla.javascript.gen.c"+serial;
+
+ byte[] mainClassBytes = compileToClassFile(compilerEnv, mainClassName,
+ tree, encodedSource,
+ returnFunction);
+
+ return new Object[] { mainClassName, mainClassBytes };
+ }
+
+ public Script createScriptObject(Object bytecode,
+ Object staticSecurityDomain)
+ {
+ Class cl = defineClass(bytecode, staticSecurityDomain);
+
+ Script script;
+ try {
+ script = (Script)cl.newInstance();
+ } catch (Exception ex) {
+ throw new RuntimeException
+ ("Unable to instantiate compiled class:"+ex.toString());
+ }
+ return script;
+ }
+
+ public Function createFunctionObject(Context cx, Scriptable scope,
+ Object bytecode,
+ Object staticSecurityDomain)
+ {
+ Class cl = defineClass(bytecode, staticSecurityDomain);
+
+ NativeFunction f;
+ try {
+ Constructor ctor = cl.getConstructors()[0];
+ Object[] initArgs = { scope, cx, new Integer(0) };
+ f = (NativeFunction)ctor.newInstance(initArgs);
+ } catch (Exception ex) {
+ throw new RuntimeException
+ ("Unable to instantiate compiled class:"+ex.toString());
+ }
+ return f;
+ }
+
+ private Class defineClass(Object bytecode,
+ Object staticSecurityDomain)
+ {
+ Object[] nameBytesPair = (Object[])bytecode;
+ String className = (String)nameBytesPair[0];
+ byte[] classBytes = (byte[])nameBytesPair[1];
+
+ // The generated classes in this case refer only to Rhino classes
+ // which must be accessible through this class loader
+ ClassLoader rhinoLoader = getClass().getClassLoader();
+ GeneratedClassLoader loader;
+ loader = SecurityController.createLoader(rhinoLoader,
+ staticSecurityDomain);
+ Exception e;
+ try {
+ Class cl = loader.defineClass(className, classBytes);
+ loader.linkClass(cl);
+ return cl;
+ } catch (SecurityException x) {
+ e = x;
+ } catch (IllegalArgumentException x) {
+ e = x;
+ }
+ throw new RuntimeException("Malformed optimizer package " + e);
+ }
+
+ byte[] compileToClassFile(CompilerEnvirons compilerEnv,
+ String mainClassName,
+ ScriptOrFnNode scriptOrFn,
+ String encodedSource,
+ boolean returnFunction)
+ {
+ this.compilerEnv = compilerEnv;
+
+ transform(scriptOrFn);
+
+ if (Token.printTrees) {
+ /*APPJET*///System.out.println(scriptOrFn.toStringTree(scriptOrFn));
+ }
+
+ if (returnFunction) {
+ scriptOrFn = scriptOrFn.getFunctionNode(0);
+ }
+
+ initScriptOrFnNodesData(scriptOrFn);
+
+ this.mainClassName = mainClassName;
+ this.mainClassSignature
+ = ClassFileWriter.classNameToSignature(mainClassName);
+
+ try {
+ return generateCode(encodedSource);
+ } catch (ClassFileWriter.ClassFileFormatException e) {
+ throw reportClassFileFormatException(scriptOrFn, e.getMessage());
+ }
+ }
+
+ private RuntimeException reportClassFileFormatException(
+ ScriptOrFnNode scriptOrFn,
+ String message)
+ {
+ String msg = scriptOrFn instanceof FunctionNode
+ ? ScriptRuntime.getMessage2("msg.while.compiling.fn",
+ ((FunctionNode)scriptOrFn).getFunctionName(), message)
+ : ScriptRuntime.getMessage1("msg.while.compiling.script", message);
+ return Context.reportRuntimeError(msg, scriptOrFn.getSourceName(),
+ scriptOrFn.getLineno(), null, 0);
+ }
+
+ private void transform(ScriptOrFnNode tree)
+ {
+ initOptFunctions_r(tree);
+
+ int optLevel = compilerEnv.getOptimizationLevel();
+
+ Hashtable possibleDirectCalls = null;
+ if (optLevel > 0) {
+ /*
+ * 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
+ */
+ if (tree.getType() == Token.SCRIPT) {
+ int functionCount = tree.getFunctionCount();
+ for (int i = 0; i != functionCount; ++i) {
+ OptFunctionNode ofn = OptFunctionNode.get(tree, i);
+ if (ofn.fnode.getFunctionType()
+ == FunctionNode.FUNCTION_STATEMENT)
+ {
+ String name = ofn.fnode.getFunctionName();
+ if (name.length() != 0) {
+ if (possibleDirectCalls == null) {
+ possibleDirectCalls = new Hashtable();
+ }
+ possibleDirectCalls.put(name, ofn);
+ }
+ }
+ }
+ }
+ }
+
+ if (possibleDirectCalls != null) {
+ directCallTargets = new ObjArray();
+ }
+
+ OptTransformer ot = new OptTransformer(possibleDirectCalls,
+ directCallTargets);
+ ot.transform(tree);
+
+ if (optLevel > 0) {
+ (new Optimizer()).optimize(tree);
+ }
+ }
+
+ private static void initOptFunctions_r(ScriptOrFnNode scriptOrFn)
+ {
+ for (int i = 0, N = scriptOrFn.getFunctionCount(); i != N; ++i) {
+ FunctionNode fn = scriptOrFn.getFunctionNode(i);
+ new OptFunctionNode(fn);
+ initOptFunctions_r(fn);
+ }
+ }
+
+ private void initScriptOrFnNodesData(ScriptOrFnNode scriptOrFn)
+ {
+ ObjArray x = new ObjArray();
+ collectScriptOrFnNodes_r(scriptOrFn, x);
+
+ int count = x.size();
+ scriptOrFnNodes = new ScriptOrFnNode[count];
+ x.toArray(scriptOrFnNodes);
+
+ scriptOrFnIndexes = new ObjToIntMap(count);
+ for (int i = 0; i != count; ++i) {
+ scriptOrFnIndexes.put(scriptOrFnNodes[i], i);
+ }
+ }
+
+ private static void collectScriptOrFnNodes_r(ScriptOrFnNode n,
+ ObjArray x)
+ {
+ x.add(n);
+ int nestedCount = n.getFunctionCount();
+ for (int i = 0; i != nestedCount; ++i) {
+ collectScriptOrFnNodes_r(n.getFunctionNode(i), x);
+ }
+ }
+
+ private byte[] generateCode(String encodedSource)
+ {
+ boolean hasScript = (scriptOrFnNodes[0].getType() == Token.SCRIPT);
+ boolean hasFunctions = (scriptOrFnNodes.length > 1 || !hasScript);
+
+ String sourceFile = null;
+ if (compilerEnv.isGenerateDebugInfo()) {
+ sourceFile = scriptOrFnNodes[0].getSourceName();
+ }
+
+ ClassFileWriter cfw = new ClassFileWriter(mainClassName,
+ SUPER_CLASS_NAME,
+ sourceFile);
+ cfw.addField(ID_FIELD_NAME, "I",
+ ClassFileWriter.ACC_PRIVATE);
+ cfw.addField(DIRECT_CALL_PARENT_FIELD, mainClassSignature,
+ ClassFileWriter.ACC_PRIVATE);
+ cfw.addField(REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE,
+ ClassFileWriter.ACC_PRIVATE);
+
+ if (hasFunctions) {
+ generateFunctionConstructor(cfw);
+ }
+
+ if (hasScript) {
+ cfw.addInterface("org/mozilla/javascript/Script");
+ generateScriptCtor(cfw);
+ generateMain(cfw);
+ generateExecute(cfw);
+ }
+
+ generateCallMethod(cfw);
+ generateResumeGenerator(cfw);
+
+ generateNativeFunctionOverrides(cfw, encodedSource);
+
+ int count = scriptOrFnNodes.length;
+ for (int i = 0; i != count; ++i) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+
+ BodyCodegen bodygen = new BodyCodegen();
+ bodygen.cfw = cfw;
+ bodygen.codegen = this;
+ bodygen.compilerEnv = compilerEnv;
+ bodygen.scriptOrFn = n;
+ bodygen.scriptOrFnIndex = i;
+
+ try {
+ bodygen.generateBodyCode();
+ } catch (ClassFileWriter.ClassFileFormatException e) {
+ throw reportClassFileFormatException(n, e.getMessage());
+ }
+
+ if (n.getType() == Token.FUNCTION) {
+ OptFunctionNode ofn = OptFunctionNode.get(n);
+ generateFunctionInit(cfw, ofn);
+ if (ofn.isTargetOfDirectCall()) {
+ emitDirectConstructor(cfw, ofn);
+ }
+ }
+ }
+
+ if (directCallTargets != null) {
+ int N = directCallTargets.size();
+ for (int j = 0; j != N; ++j) {
+ cfw.addField(getDirectTargetFieldName(j),
+ mainClassSignature,
+ ClassFileWriter.ACC_PRIVATE);
+ }
+ }
+
+ emitRegExpInit(cfw);
+ emitConstantDudeInitializers(cfw);
+
+ return cfw.toByteArray();
+ }
+
+ private void emitDirectConstructor(ClassFileWriter cfw,
+ OptFunctionNode ofn)
+ {
+/*
+ we generate ..
+ Scriptable directConstruct(<directCallArgs>) {
+ Scriptable newInstance = createObject(cx, scope);
+ Object val = <body-name>(cx, scope, newInstance, <directCallArgs>);
+ if (val instanceof Scriptable) {
+ return (Scriptable) val;
+ }
+ return newInstance;
+ }
+*/
+ cfw.startMethod(getDirectCtorName(ofn.fnode),
+ getBodyMethodSignature(ofn.fnode),
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+
+ int argCount = ofn.fnode.getParamCount();
+ int firstLocal = (4 + argCount * 3) + 1;
+
+ cfw.addALoad(0); // this
+ cfw.addALoad(1); // cx
+ cfw.addALoad(2); // scope
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ "org/mozilla/javascript/BaseFunction",
+ "createObject",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(firstLocal);
+
+ cfw.addALoad(0);
+ cfw.addALoad(1);
+ cfw.addALoad(2);
+ cfw.addALoad(firstLocal);
+ for (int i = 0; i < argCount; i++) {
+ cfw.addALoad(4 + (i * 3));
+ cfw.addDLoad(5 + (i * 3));
+ }
+ cfw.addALoad(4 + argCount * 3);
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ mainClassName,
+ getBodyMethodName(ofn.fnode),
+ getBodyMethodSignature(ofn.fnode));
+ int exitLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.DUP); // make a copy of direct call result
+ cfw.add(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable");
+ cfw.add(ByteCode.IFEQ, exitLabel);
+ // cast direct call result
+ cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable");
+ cfw.add(ByteCode.ARETURN);
+ cfw.markLabel(exitLabel);
+
+ cfw.addALoad(firstLocal);
+ cfw.add(ByteCode.ARETURN);
+
+ cfw.stopMethod((short)(firstLocal + 1));
+ }
+
+ static boolean isGenerator(ScriptOrFnNode node)
+ {
+ return (node.getType() == Token.FUNCTION ) &&
+ ((FunctionNode)node).isGenerator();
+ }
+
+ // How dispatch to generators works:
+ // Two methods are generated corresponding to a user-written generator.
+ // One of these creates a generator object (NativeGenerator), which is
+ // returned to the user. The other method contains all of the body code
+ // of the generator.
+ // When a user calls a generator, the call() method dispatches control to
+ // to the method that creates the NativeGenerator object. Subsequently when
+ // the user invokes .next(), .send() or any such method on the generator
+ // object, the resumeGenerator() below dispatches the call to the
+ // method corresponding to the generator body. As a matter of convention
+ // the generator body is given the name of the generator activation function
+ // appended by "_gen".
+ private void generateResumeGenerator(ClassFileWriter cfw)
+ {
+ boolean hasGenerators = false;
+ for (int i=0; i < scriptOrFnNodes.length; i++) {
+ if (isGenerator(scriptOrFnNodes[i]))
+ hasGenerators = true;
+ }
+
+ // if there are no generators defined, we don't implement a
+ // resumeGenerator(). The base class provides a default implementation.
+ if (!hasGenerators)
+ return;
+
+ cfw.startMethod("resumeGenerator",
+ "(Lorg/mozilla/javascript/Context;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "ILjava/lang/Object;" +
+ "Ljava/lang/Object;)Ljava/lang/Object;",
+ (short)(ClassFileWriter.ACC_PUBLIC
+ | ClassFileWriter.ACC_FINAL));
+
+ // load arguments for dispatch to the corresponding *_gen method
+ cfw.addALoad(0);
+ cfw.addALoad(1);
+ cfw.addALoad(2);
+ cfw.addALoad(4);
+ cfw.addALoad(5);
+ cfw.addILoad(3);
+
+ cfw.addLoadThis();
+ cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
+
+ int startSwitch = cfw.addTableSwitch(0, scriptOrFnNodes.length - 1);
+ cfw.markTableSwitchDefault(startSwitch);
+ int endlabel = cfw.acquireLabel();
+
+ for (int i = 0; i < scriptOrFnNodes.length; i++) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+ cfw.markTableSwitchCase(startSwitch, i, (short)6);
+ if (isGenerator(n)) {
+ String type = "(" +
+ mainClassSignature +
+ "Lorg/mozilla/javascript/Context;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "Ljava/lang/Object;" +
+ "Ljava/lang/Object;I)Ljava/lang/Object;";
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ mainClassName,
+ getBodyMethodName(n) + "_gen",
+ type);
+ cfw.add(ByteCode.ARETURN);
+ } else {
+ cfw.add(ByteCode.GOTO, endlabel);
+ }
+ }
+
+ cfw.markLabel(endlabel);
+ pushUndefined(cfw);
+ cfw.add(ByteCode.ARETURN);
+
+
+ // this method uses as many locals as there are arguments (hence 6)
+ cfw.stopMethod((short)6);
+ }
+
+ private void generateCallMethod(ClassFileWriter cfw)
+ {
+ cfw.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));
+
+ // Generate code for:
+ // if (!ScriptRuntime.hasTopCall(cx)) {
+ // return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args);
+ // }
+
+ int nonTopCallLabel = cfw.acquireLabel();
+ cfw.addALoad(1); //cx
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "hasTopCall",
+ "(Lorg/mozilla/javascript/Context;"
+ +")Z");
+ cfw.add(ByteCode.IFNE, nonTopCallLabel);
+ cfw.addALoad(0);
+ cfw.addALoad(1);
+ cfw.addALoad(2);
+ cfw.addALoad(3);
+ cfw.addALoad(4);
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "doTopCall",
+ "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Ljava/lang/Object;");
+ cfw.add(ByteCode.ARETURN);
+ cfw.markLabel(nonTopCallLabel);
+
+ // Now generate switch to call the real methods
+ cfw.addALoad(0);
+ cfw.addALoad(1);
+ cfw.addALoad(2);
+ cfw.addALoad(3);
+ cfw.addALoad(4);
+
+ int end = scriptOrFnNodes.length;
+ boolean generateSwitch = (2 <= end);
+
+ int switchStart = 0;
+ int switchStackTop = 0;
+ if (generateSwitch) {
+ cfw.addLoadThis();
+ cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
+ // do switch from (1, end - 1) mapping 0 to
+ // the default case
+ switchStart = cfw.addTableSwitch(1, end - 1);
+ }
+
+ for (int i = 0; i != end; ++i) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+ if (generateSwitch) {
+ if (i == 0) {
+ cfw.markTableSwitchDefault(switchStart);
+ switchStackTop = cfw.getStackTop();
+ } else {
+ cfw.markTableSwitchCase(switchStart, i - 1,
+ switchStackTop);
+ }
+ }
+ if (n.getType() == Token.FUNCTION) {
+ OptFunctionNode ofn = OptFunctionNode.get(n);
+ if (ofn.isTargetOfDirectCall()) {
+ int pcount = ofn.fnode.getParamCount();
+ if (pcount != 0) {
+ // loop invariant:
+ // stack top == arguments array from addALoad4()
+ for (int p = 0; p != pcount; ++p) {
+ cfw.add(ByteCode.ARRAYLENGTH);
+ cfw.addPush(p);
+ int undefArg = cfw.acquireLabel();
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ICMPLE, undefArg);
+ // get array[p]
+ cfw.addALoad(4);
+ cfw.addPush(p);
+ cfw.add(ByteCode.AALOAD);
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(undefArg);
+ pushUndefined(cfw);
+ cfw.markLabel(beyond);
+ // Only one push
+ cfw.adjustStackTop(-1);
+ cfw.addPush(0.0);
+ // restore invariant
+ cfw.addALoad(4);
+ }
+ }
+ }
+ }
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ mainClassName,
+ getBodyMethodName(n),
+ getBodyMethodSignature(n));
+ cfw.add(ByteCode.ARETURN);
+ }
+ cfw.stopMethod((short)5);
+ // 5: this, cx, scope, js this, args[]
+ }
+
+ private void generateMain(ClassFileWriter cfw)
+ {
+ cfw.startMethod("main", "([Ljava/lang/String;)V",
+ (short)(ClassFileWriter.ACC_PUBLIC
+ | ClassFileWriter.ACC_STATIC));
+
+ // load new ScriptImpl()
+ cfw.add(ByteCode.NEW, cfw.getClassName());
+ cfw.add(ByteCode.DUP);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, cfw.getClassName(),
+ "<init>", "()V");
+ // load 'args'
+ cfw.add(ByteCode.ALOAD_0);
+ // Call mainMethodClass.main(Script script, String[] args)
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ mainMethodClass,
+ "main",
+ "(Lorg/mozilla/javascript/Script;[Ljava/lang/String;)V");
+ cfw.add(ByteCode.RETURN);
+ // 1 = String[] args
+ cfw.stopMethod((short)1);
+ }
+
+ private void generateExecute(ClassFileWriter cfw)
+ {
+ cfw.startMethod("exec",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;",
+ (short)(ClassFileWriter.ACC_PUBLIC
+ | ClassFileWriter.ACC_FINAL));
+
+ final int CONTEXT_ARG = 1;
+ final int SCOPE_ARG = 2;
+
+ cfw.addLoadThis();
+ cfw.addALoad(CONTEXT_ARG);
+ cfw.addALoad(SCOPE_ARG);
+ cfw.add(ByteCode.DUP);
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ cfw.getClassName(),
+ "call",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Ljava/lang/Object;");
+
+ cfw.add(ByteCode.ARETURN);
+ // 3 = this + context + scope
+ cfw.stopMethod((short)3);
+ }
+
+ private void generateScriptCtor(ClassFileWriter cfw)
+ {
+ cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
+
+ cfw.addLoadThis();
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME,
+ "<init>", "()V");
+ // set id to 0
+ cfw.addLoadThis();
+ cfw.addPush(0);
+ cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
+
+ cfw.add(ByteCode.RETURN);
+ // 1 parameter = this
+ cfw.stopMethod((short)1);
+ }
+
+ private void generateFunctionConstructor(ClassFileWriter cfw)
+ {
+ final int SCOPE_ARG = 1;
+ final int CONTEXT_ARG = 2;
+ final int ID_ARG = 3;
+
+ cfw.startMethod("<init>", FUNCTION_CONSTRUCTOR_SIGNATURE,
+ ClassFileWriter.ACC_PUBLIC);
+ cfw.addALoad(0);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME,
+ "<init>", "()V");
+
+ cfw.addLoadThis();
+ cfw.addILoad(ID_ARG);
+ cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
+
+ cfw.addLoadThis();
+ cfw.addALoad(CONTEXT_ARG);
+ cfw.addALoad(SCOPE_ARG);
+
+ int start = (scriptOrFnNodes[0].getType() == Token.SCRIPT) ? 1 : 0;
+ int end = scriptOrFnNodes.length;
+ if (start == end) throw badTree();
+ boolean generateSwitch = (2 <= end - start);
+
+ int switchStart = 0;
+ int switchStackTop = 0;
+ if (generateSwitch) {
+ cfw.addILoad(ID_ARG);
+ // do switch from (start + 1, end - 1) mapping start to
+ // the default case
+ switchStart = cfw.addTableSwitch(start + 1, end - 1);
+ }
+
+ for (int i = start; i != end; ++i) {
+ if (generateSwitch) {
+ if (i == start) {
+ cfw.markTableSwitchDefault(switchStart);
+ switchStackTop = cfw.getStackTop();
+ } else {
+ cfw.markTableSwitchCase(switchStart, i - 1 - start,
+ switchStackTop);
+ }
+ }
+ OptFunctionNode ofn = OptFunctionNode.get(scriptOrFnNodes[i]);
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ mainClassName,
+ getFunctionInitMethodName(ofn),
+ FUNCTION_INIT_SIGNATURE);
+ cfw.add(ByteCode.RETURN);
+ }
+
+ // 4 = this + scope + context + id
+ cfw.stopMethod((short)4);
+ }
+
+ private void generateFunctionInit(ClassFileWriter cfw,
+ OptFunctionNode ofn)
+ {
+ final int CONTEXT_ARG = 1;
+ final int SCOPE_ARG = 2;
+ cfw.startMethod(getFunctionInitMethodName(ofn),
+ FUNCTION_INIT_SIGNATURE,
+ (short)(ClassFileWriter.ACC_PRIVATE
+ | ClassFileWriter.ACC_FINAL));
+
+ // Call NativeFunction.initScriptFunction
+ cfw.addLoadThis();
+ cfw.addALoad(CONTEXT_ARG);
+ cfw.addALoad(SCOPE_ARG);
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ "org/mozilla/javascript/NativeFunction",
+ "initScriptFunction",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")V");
+
+ // precompile all regexp literals
+ int regexpCount = ofn.fnode.getRegexpCount();
+ if (regexpCount != 0) {
+ cfw.addLoadThis();
+ pushRegExpArray(cfw, ofn.fnode, CONTEXT_ARG, SCOPE_ARG);
+ cfw.add(ByteCode.PUTFIELD, mainClassName,
+ REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE);
+ }
+
+ cfw.add(ByteCode.RETURN);
+ // 3 = (scriptThis/functionRef) + scope + context
+ cfw.stopMethod((short)3);
+ }
+
+ private void generateNativeFunctionOverrides(ClassFileWriter cfw,
+ String encodedSource)
+ {
+ // Override NativeFunction.getLanguageVersion() with
+ // public int getLanguageVersion() { return <version-constant>; }
+
+ cfw.startMethod("getLanguageVersion", "()I",
+ ClassFileWriter.ACC_PUBLIC);
+
+ cfw.addPush(compilerEnv.getLanguageVersion());
+ cfw.add(ByteCode.IRETURN);
+
+ // 1: this and no argument or locals
+ cfw.stopMethod((short)1);
+
+ // The rest of NativeFunction overrides require specific code for each
+ // script/function id
+
+ final int Do_getFunctionName = 0;
+ final int Do_getParamCount = 1;
+ final int Do_getParamAndVarCount = 2;
+ final int Do_getParamOrVarName = 3;
+ final int Do_getEncodedSource = 4;
+ final int Do_getParamOrVarConst = 5;
+ final int SWITCH_COUNT = 6;
+
+ for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) {
+ if (methodIndex == Do_getEncodedSource && encodedSource == null) {
+ continue;
+ }
+
+ // Generate:
+ // prologue;
+ // switch over function id to implement function-specific action
+ // epilogue
+
+ short methodLocals;
+ switch (methodIndex) {
+ case Do_getFunctionName:
+ methodLocals = 1; // Only this
+ cfw.startMethod("getFunctionName", "()Ljava/lang/String;",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getParamCount:
+ methodLocals = 1; // Only this
+ cfw.startMethod("getParamCount", "()I",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getParamAndVarCount:
+ methodLocals = 1; // Only this
+ cfw.startMethod("getParamAndVarCount", "()I",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getParamOrVarName:
+ methodLocals = 1 + 1; // this + paramOrVarIndex
+ cfw.startMethod("getParamOrVarName", "(I)Ljava/lang/String;",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getParamOrVarConst:
+ methodLocals = 1 + 1 + 1; // this + paramOrVarName
+ cfw.startMethod("getParamOrVarConst", "(I)Z",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getEncodedSource:
+ methodLocals = 1; // Only this
+ cfw.startMethod("getEncodedSource", "()Ljava/lang/String;",
+ ClassFileWriter.ACC_PUBLIC);
+ cfw.addPush(encodedSource);
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+
+ int count = scriptOrFnNodes.length;
+
+ int switchStart = 0;
+ int switchStackTop = 0;
+ if (count > 1) {
+ // Generate switch but only if there is more then one
+ // script/function
+ cfw.addLoadThis();
+ cfw.add(ByteCode.GETFIELD, cfw.getClassName(),
+ ID_FIELD_NAME, "I");
+
+ // do switch from 1 .. count - 1 mapping 0 to the default case
+ switchStart = cfw.addTableSwitch(1, count - 1);
+ }
+
+ for (int i = 0; i != count; ++i) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+ if (i == 0) {
+ if (count > 1) {
+ cfw.markTableSwitchDefault(switchStart);
+ switchStackTop = cfw.getStackTop();
+ }
+ } else {
+ cfw.markTableSwitchCase(switchStart, i - 1,
+ switchStackTop);
+ }
+
+ // Impelemnet method-specific switch code
+ switch (methodIndex) {
+ case Do_getFunctionName:
+ // Push function name
+ if (n.getType() == Token.SCRIPT) {
+ cfw.addPush("");
+ } else {
+ String name = ((FunctionNode)n).getFunctionName();
+ cfw.addPush(name);
+ }
+ cfw.add(ByteCode.ARETURN);
+ break;
+
+ case Do_getParamCount:
+ // Push number of defined parameters
+ cfw.addPush(n.getParamCount());
+ cfw.add(ByteCode.IRETURN);
+ break;
+
+ case Do_getParamAndVarCount:
+ // Push number of defined parameters and declared variables
+ cfw.addPush(n.getParamAndVarCount());
+ cfw.add(ByteCode.IRETURN);
+ break;
+
+ case Do_getParamOrVarName:
+ // Push name of parameter using another switch
+ // over paramAndVarCount
+ int paramAndVarCount = n.getParamAndVarCount();
+ if (paramAndVarCount == 0) {
+ // The runtime should never call the method in this
+ // case but to make bytecode verifier happy return null
+ // as throwing execption takes more code
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.add(ByteCode.ARETURN);
+ } else if (paramAndVarCount == 1) {
+ // As above do not check for valid index but always
+ // return the name of the first param
+ cfw.addPush(n.getParamOrVarName(0));
+ cfw.add(ByteCode.ARETURN);
+ } else {
+ // Do switch over getParamOrVarName
+ cfw.addILoad(1); // param or var index
+ // do switch from 1 .. paramAndVarCount - 1 mapping 0
+ // to the default case
+ int paramSwitchStart = cfw.addTableSwitch(
+ 1, paramAndVarCount - 1);
+ for (int j = 0; j != paramAndVarCount; ++j) {
+ if (cfw.getStackTop() != 0) Kit.codeBug();
+ String s = n.getParamOrVarName(j);
+ if (j == 0) {
+ cfw.markTableSwitchDefault(paramSwitchStart);
+ } else {
+ cfw.markTableSwitchCase(paramSwitchStart, j - 1,
+ 0);
+ }
+ cfw.addPush(s);
+ cfw.add(ByteCode.ARETURN);
+ }
+ }
+ break;
+
+ case Do_getParamOrVarConst:
+ // Push name of parameter using another switch
+ // over paramAndVarCount
+ paramAndVarCount = n.getParamAndVarCount();
+ boolean [] constness = n.getParamAndVarConst();
+ if (paramAndVarCount == 0) {
+ // The runtime should never call the method in this
+ // case but to make bytecode verifier happy return null
+ // as throwing execption takes more code
+ cfw.add(ByteCode.ICONST_0);
+ cfw.add(ByteCode.IRETURN);
+ } else if (paramAndVarCount == 1) {
+ // As above do not check for valid index but always
+ // return the name of the first param
+ cfw.addPush(constness[0]);
+ cfw.add(ByteCode.IRETURN);
+ } else {
+ // Do switch over getParamOrVarName
+ cfw.addILoad(1); // param or var index
+ // do switch from 1 .. paramAndVarCount - 1 mapping 0
+ // to the default case
+ int paramSwitchStart = cfw.addTableSwitch(
+ 1, paramAndVarCount - 1);
+ for (int j = 0; j != paramAndVarCount; ++j) {
+ if (cfw.getStackTop() != 0) Kit.codeBug();
+ if (j == 0) {
+ cfw.markTableSwitchDefault(paramSwitchStart);
+ } else {
+ cfw.markTableSwitchCase(paramSwitchStart, j - 1,
+ 0);
+ }
+ cfw.addPush(constness[j]);
+ cfw.add(ByteCode.IRETURN);
+ }
+ }
+ break;
+
+ case Do_getEncodedSource:
+ // Push number encoded source start and end
+ // to prepare for encodedSource.substring(start, end)
+ cfw.addPush(n.getEncodedSourceStart());
+ cfw.addPush(n.getEncodedSourceEnd());
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ "java/lang/String",
+ "substring",
+ "(II)Ljava/lang/String;");
+ cfw.add(ByteCode.ARETURN);
+ break;
+
+ default:
+ throw Kit.codeBug();
+ }
+ }
+
+ cfw.stopMethod(methodLocals);
+ }
+ }
+
+ private void emitRegExpInit(ClassFileWriter cfw)
+ {
+ // precompile all regexp literals
+
+ int totalRegCount = 0;
+ for (int i = 0; i != scriptOrFnNodes.length; ++i) {
+ totalRegCount += scriptOrFnNodes[i].getRegexpCount();
+ }
+ if (totalRegCount == 0) {
+ return;
+ }
+
+ cfw.startMethod(REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE,
+ (short)(ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE
+ | ClassFileWriter.ACC_SYNCHRONIZED));
+ cfw.addField("_reInitDone", "Z",
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ cfw.add(ByteCode.GETSTATIC, mainClassName, "_reInitDone", "Z");
+ int doInit = cfw.acquireLabel();
+ cfw.add(ByteCode.IFEQ, doInit);
+ cfw.add(ByteCode.RETURN);
+ cfw.markLabel(doInit);
+
+ for (int i = 0; i != scriptOrFnNodes.length; ++i) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+ int regCount = n.getRegexpCount();
+ for (int j = 0; j != regCount; ++j) {
+ String reFieldName = getCompiledRegexpName(n, j);
+ String reFieldType = "Ljava/lang/Object;";
+ String reString = n.getRegexpString(j);
+ String reFlags = n.getRegexpFlags(j);
+ cfw.addField(reFieldName, reFieldType,
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ cfw.addALoad(0); // proxy
+ cfw.addALoad(1); // context
+ cfw.addPush(reString);
+ if (reFlags == null) {
+ cfw.add(ByteCode.ACONST_NULL);
+ } else {
+ cfw.addPush(reFlags);
+ }
+ /*APPJET*/cfw.addLineNumberEntry((short)n.getRegexpLineno(j));
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/RegExpProxy",
+ "compileRegExp",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Ljava/lang/String;Ljava/lang/String;"
+ +")Ljava/lang/Object;");
+ cfw.add(ByteCode.PUTSTATIC, mainClassName,
+ reFieldName, reFieldType);
+ }
+ }
+
+ cfw.addPush(1);
+ cfw.add(ByteCode.PUTSTATIC, mainClassName, "_reInitDone", "Z");
+ cfw.add(ByteCode.RETURN);
+ cfw.stopMethod((short)2);
+ }
+
+ private void emitConstantDudeInitializers(ClassFileWriter cfw)
+ {
+ int N = itsConstantListSize;
+ if (N == 0)
+ return;
+
+ cfw.startMethod("<clinit>", "()V",
+ (short)(ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_FINAL));
+
+ double[] array = itsConstantList;
+ for (int i = 0; i != N; ++i) {
+ double num = array[i];
+ String constantName = "_k" + i;
+ String constantType = getStaticConstantWrapperType(num);
+ cfw.addField(constantName, constantType,
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ int inum = (int)num;
+ if (inum == num) {
+ cfw.add(ByteCode.NEW, "java/lang/Integer");
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(inum);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Integer",
+ "<init>", "(I)V");
+ } else {
+ cfw.addPush(num);
+ addDoubleWrap(cfw);
+ }
+ cfw.add(ByteCode.PUTSTATIC, mainClassName,
+ constantName, constantType);
+ }
+
+ cfw.add(ByteCode.RETURN);
+ cfw.stopMethod((short)0);
+ }
+
+ void pushRegExpArray(ClassFileWriter cfw, ScriptOrFnNode n,
+ int contextArg, int scopeArg)
+ {
+ int regexpCount = n.getRegexpCount();
+ if (regexpCount == 0) throw badTree();
+
+ cfw.addPush(regexpCount);
+ cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
+
+ cfw.addALoad(contextArg);
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "checkRegExpProxy",
+ "(Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/RegExpProxy;");
+ // Stack: proxy, array
+ cfw.add(ByteCode.DUP);
+ cfw.addALoad(contextArg);
+ cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName,
+ REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE);
+ for (int i = 0; i != regexpCount; ++i) {
+ // Stack: proxy, array
+ cfw.add(ByteCode.DUP2);
+ cfw.addALoad(contextArg);
+ cfw.addALoad(scopeArg);
+ cfw.add(ByteCode.GETSTATIC, mainClassName,
+ getCompiledRegexpName(n, i), "Ljava/lang/Object;");
+ // Stack: compiledRegExp, scope, cx, proxy, array, proxy, array
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/RegExpProxy",
+ "wrapRegExp",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ // Stack: wrappedRegExp, array, proxy, array
+ cfw.addPush(i);
+ cfw.add(ByteCode.SWAP);
+ cfw.add(ByteCode.AASTORE);
+ // Stack: proxy, array
+ }
+ // remove proxy
+ cfw.add(ByteCode.POP);
+ }
+
+ void pushNumberAsObject(ClassFileWriter cfw, double num)
+ {
+ if (num == 0.0) {
+ if (1 / num > 0) {
+ // +0.0
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ "zeroObj", "Ljava/lang/Double;");
+ } else {
+ cfw.addPush(num);
+ addDoubleWrap(cfw);
+ }
+
+ } else if (num == 1.0) {
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ "oneObj", "Ljava/lang/Double;");
+ return;
+
+ } else if (num == -1.0) {
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ "minusOneObj", "Ljava/lang/Double;");
+
+ } else if (num != num) {
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "NaNobj", "Ljava/lang/Double;");
+
+ } else if (itsConstantListSize >= 2000) {
+ // 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.
+ cfw.addPush(num);
+ addDoubleWrap(cfw);
+
+ } else {
+ int N = itsConstantListSize;
+ int index = 0;
+ if (N == 0) {
+ itsConstantList = new double[64];
+ } else {
+ double[] array = itsConstantList;
+ while (index != N && array[index] != num) {
+ ++index;
+ }
+ if (N == array.length) {
+ array = new double[N * 2];
+ System.arraycopy(itsConstantList, 0, array, 0, N);
+ itsConstantList = array;
+ }
+ }
+ if (index == N) {
+ itsConstantList[N] = num;
+ itsConstantListSize = N + 1;
+ }
+ String constantName = "_k" + index;
+ String constantType = getStaticConstantWrapperType(num);
+ cfw.add(ByteCode.GETSTATIC, mainClassName,
+ constantName, constantType);
+ }
+ }
+
+ private static void addDoubleWrap(ClassFileWriter cfw)
+ {
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ "wrapDouble", "(D)Ljava/lang/Double;");
+ }
+
+ private static String getStaticConstantWrapperType(double num)
+ {
+ int inum = (int)num;
+ if (inum == num) {
+ return "Ljava/lang/Integer;";
+ } else {
+ return "Ljava/lang/Double;";
+ }
+ }
+ static void pushUndefined(ClassFileWriter cfw)
+ {
+ cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/Undefined",
+ "instance", "Ljava/lang/Object;");
+ }
+
+ int getIndex(ScriptOrFnNode n)
+ {
+ return scriptOrFnIndexes.getExisting(n);
+ }
+
+ static String getDirectTargetFieldName(int i)
+ {
+ return "_dt" + i;
+ }
+
+ String getDirectCtorName(ScriptOrFnNode n)
+ {
+ return "_n"+getIndex(n);
+ }
+
+ String getBodyMethodName(ScriptOrFnNode n)
+ {
+ return "_c"+getIndex(n);
+ }
+
+ String getBodyMethodSignature(ScriptOrFnNode n)
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append('(');
+ sb.append(mainClassSignature);
+ sb.append("Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;");
+ if (n.getType() == Token.FUNCTION) {
+ OptFunctionNode ofn = OptFunctionNode.get(n);
+ if (ofn.isTargetOfDirectCall()) {
+ int pCount = ofn.fnode.getParamCount();
+ for (int i = 0; i != pCount; i++) {
+ sb.append("Ljava/lang/Object;D");
+ }
+ }
+ }
+ sb.append("[Ljava/lang/Object;)Ljava/lang/Object;");
+ return sb.toString();
+ }
+
+ String getFunctionInitMethodName(OptFunctionNode ofn)
+ {
+ return "_i"+getIndex(ofn.fnode);
+ }
+
+ String getCompiledRegexpName(ScriptOrFnNode n, int regexpIndex)
+ {
+ return "_re"+getIndex(n)+"_"+regexpIndex;
+ }
+
+ static RuntimeException badTree()
+ {
+ throw new RuntimeException("Bad tree in codegen");
+ }
+
+ void setMainMethodClass(String className)
+ {
+ mainMethodClass = className;
+ }
+
+ static final String DEFAULT_MAIN_METHOD_CLASS
+ = "org.mozilla.javascript.optimizer.OptRuntime";
+
+ private static final String SUPER_CLASS_NAME
+ = "org.mozilla.javascript.NativeFunction";
+
+ static final String DIRECT_CALL_PARENT_FIELD = "_dcp";
+ private static final String ID_FIELD_NAME = "_id";
+
+ private static final String REGEXP_INIT_METHOD_NAME = "_reInit";
+ private static final String REGEXP_INIT_METHOD_SIGNATURE
+ = "(Lorg/mozilla/javascript/RegExpProxy;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")V";
+ static final String REGEXP_ARRAY_FIELD_NAME = "_re";
+ static final String REGEXP_ARRAY_FIELD_TYPE = "[Ljava/lang/Object;";
+
+ static final String FUNCTION_INIT_SIGNATURE
+ = "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")V";
+
+ static final String FUNCTION_CONSTRUCTOR_SIGNATURE
+ = "(Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Context;I)V";
+
+ private static final Object globalLock = new Object();
+ private static int globalSerialClassCounter;
+
+ private CompilerEnvirons compilerEnv;
+
+ private ObjArray directCallTargets;
+ ScriptOrFnNode[] scriptOrFnNodes;
+ private ObjToIntMap scriptOrFnIndexes;
+
+ private String mainMethodClass = DEFAULT_MAIN_METHOD_CLASS;
+
+ String mainClassName;
+ String mainClassSignature;
+
+ private double[] itsConstantList;
+ private int itsConstantListSize;
+}
+
+
+class BodyCodegen
+{
+ void generateBodyCode()
+ {
+ isGenerator = Codegen.isGenerator(scriptOrFn);
+
+ // generate the body of the current function or script object
+ initBodyGeneration();
+
+ if (isGenerator) {
+
+ // All functions in the generated bytecode have a unique name. Every
+ // generator has a unique prefix followed by _gen
+ String type = "(" +
+ codegen.mainClassSignature +
+ "Lorg/mozilla/javascript/Context;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "Ljava/lang/Object;" +
+ "Ljava/lang/Object;I)Ljava/lang/Object;";
+ cfw.startMethod(codegen.getBodyMethodName(scriptOrFn) + "_gen",
+ type,
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ } else {
+ cfw.startMethod(codegen.getBodyMethodName(scriptOrFn),
+ codegen.getBodyMethodSignature(scriptOrFn),
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ }
+
+ generatePrologue();
+ Node treeTop;
+ if (fnCurrent != null) {
+ treeTop = scriptOrFn.getLastChild();
+ } else {
+ treeTop = scriptOrFn;
+ }
+ generateStatement(treeTop);
+ generateEpilogue();
+
+ cfw.stopMethod((short)(localsMax + 1));
+
+ if (isGenerator) {
+ // generate the user visible method which when invoked will
+ // return a generator object
+ generateGenerator();
+ }
+ }
+
+ // This creates a the user-facing function that returns a NativeGenerator
+ // object.
+ private void generateGenerator()
+ {
+ cfw.startMethod(codegen.getBodyMethodName(scriptOrFn),
+ codegen.getBodyMethodSignature(scriptOrFn),
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+
+ initBodyGeneration();
+ argsLocal = firstFreeLocal++;
+ localsMax = firstFreeLocal;
+
+ // get top level scope
+ if (fnCurrent != null && !inDirectCallFunction
+ && (!compilerEnv.isUseDynamicScope()
+ || fnCurrent.fnode.getIgnoreDynamicScope()))
+ {
+ // Unless we're either in a direct call or using dynamic scope,
+ // use the enclosing scope of the function as our variable object.
+ cfw.addALoad(funObjLocal);
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Scriptable",
+ "getParentScope",
+ "()Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ }
+
+ // generators are forced to have an activation record
+ cfw.addALoad(funObjLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(argsLocal);
+ addScriptRuntimeInvoke("createFunctionActivation",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+
+ // create a function object
+ cfw.add(ByteCode.NEW, codegen.mainClassName);
+ // Call function constructor
+ cfw.add(ByteCode.DUP);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(contextLocal); // load 'cx'
+ cfw.addPush(scriptOrFnIndex);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName,
+ "<init>", Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE);
+
+ // Init mainScript field
+ cfw.add(ByteCode.DUP);
+ if (isTopLevel) Kit.codeBug(); // Only functions can be generators
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+ cfw.add(ByteCode.PUTFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+
+ generateNestedFunctionInits();
+
+ // create the NativeGenerator object that we return
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(thisObjLocal);
+ cfw.addLoadConstant(maxLocals);
+ cfw.addLoadConstant(maxStack);
+ addOptRuntimeInvoke("createNativeGenerator",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;II"
+ +")Lorg/mozilla/javascript/Scriptable;");
+
+ cfw.add(ByteCode.ARETURN);
+ cfw.stopMethod((short)(localsMax + 1));
+ }
+
+ private void generateNestedFunctionInits()
+ {
+ int functionCount = scriptOrFn.getFunctionCount();
+ for (int i = 0; i != functionCount; i++) {
+ OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, i);
+ if (ofn.fnode.getFunctionType()
+ == FunctionNode.FUNCTION_STATEMENT)
+ {
+ visitFunction(ofn, FunctionNode.FUNCTION_STATEMENT);
+ }
+ }
+ }
+
+ private void initBodyGeneration()
+ {
+ isTopLevel = (scriptOrFn == codegen.scriptOrFnNodes[0]);
+
+ varRegisters = null;
+ if (scriptOrFn.getType() == Token.FUNCTION) {
+ fnCurrent = OptFunctionNode.get(scriptOrFn);
+ hasVarsInRegs = !fnCurrent.fnode.requiresActivation();
+ if (hasVarsInRegs) {
+ int n = fnCurrent.fnode.getParamAndVarCount();
+ if (n != 0) {
+ varRegisters = new short[n];
+ }
+ }
+ inDirectCallFunction = fnCurrent.isTargetOfDirectCall();
+ if (inDirectCallFunction && !hasVarsInRegs) Codegen.badTree();
+ } else {
+ fnCurrent = null;
+ hasVarsInRegs = false;
+ inDirectCallFunction = false;
+ }
+
+ locals = new int[MAX_LOCALS];
+
+ funObjLocal = 0;
+ contextLocal = 1;
+ variableObjectLocal = 2;
+ thisObjLocal = 3;
+ localsMax = (short) 4; // number of parms + "this"
+ firstFreeLocal = 4;
+
+ popvLocal = -1;
+ argsLocal = -1;
+ itsZeroArgArray = -1;
+ itsOneArgArray = -1;
+ scriptRegexpLocal = -1;
+ epilogueLabel = -1;
+ enterAreaStartLabel = -1;
+ generatorStateLocal = -1;
+ }
+
+ /**
+ * Generate the prologue for a function or script.
+ */
+ private void generatePrologue()
+ {
+ if (inDirectCallFunction) {
+ int directParameterCount = scriptOrFn.getParamCount();
+ // 0 is reserved for function Object 'this'
+ // 1 is reserved for context
+ // 2 is reserved for parentScope
+ // 3 is reserved for script 'this'
+ if (firstFreeLocal != 4) Kit.codeBug();
+ for (int i = 0; i != directParameterCount; ++i) {
+ varRegisters[i] = firstFreeLocal;
+ // 3 is 1 for Object parm and 2 for double parm
+ firstFreeLocal += 3;
+ }
+ if (!fnCurrent.getParameterNumberContext()) {
+ // make sure that all parameters are objects
+ itsForcedObjectParameters = true;
+ for (int i = 0; i != directParameterCount; ++i) {
+ short reg = varRegisters[i];
+ cfw.addALoad(reg);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int isObjectLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPNE, isObjectLabel);
+ cfw.addDLoad(reg + 1);
+ addDoubleWrap();
+ cfw.addAStore(reg);
+ cfw.markLabel(isObjectLabel);
+ }
+ }
+ }
+
+ if (fnCurrent != null && !inDirectCallFunction
+ && (!compilerEnv.isUseDynamicScope()
+ || fnCurrent.fnode.getIgnoreDynamicScope()))
+ {
+ // Unless we're either in a direct call or using dynamic scope,
+ // use the enclosing scope of the function as our variable object.
+ cfw.addALoad(funObjLocal);
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Scriptable",
+ "getParentScope",
+ "()Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ }
+
+ // reserve 'args[]'
+ argsLocal = firstFreeLocal++;
+ localsMax = firstFreeLocal;
+
+ // Generate Generator specific prelude
+ if (isGenerator) {
+
+ // reserve 'args[]'
+ operationLocal = firstFreeLocal++;
+ localsMax = firstFreeLocal;
+
+ // Local 3 is a reference to a GeneratorState object. The rest
+ // of codegen expects local 3 to be a reference to the thisObj.
+ // So move the value in local 3 to generatorStateLocal, and load
+ // the saved thisObj from the GeneratorState object.
+ cfw.addALoad(thisObjLocal);
+ generatorStateLocal = firstFreeLocal++;
+ localsMax = firstFreeLocal;
+ cfw.add(ByteCode.CHECKCAST, OptRuntime.GeneratorState.CLASS_NAME);
+ cfw.add(ByteCode.DUP);
+ cfw.addAStore(generatorStateLocal);
+ cfw.add(ByteCode.GETFIELD,
+ OptRuntime.GeneratorState.CLASS_NAME,
+ OptRuntime.GeneratorState.thisObj_NAME,
+ OptRuntime.GeneratorState.thisObj_TYPE);
+ cfw.addAStore(thisObjLocal);
+
+ if (epilogueLabel == -1) {
+ epilogueLabel = cfw.acquireLabel();
+ }
+
+ ArrayList targets = ((FunctionNode)scriptOrFn).getResumptionPoints();
+ if (targets != null) {
+ // get resumption point
+ generateGetGeneratorResumptionPoint();
+
+ // generate dispatch table
+ generatorSwitch = cfw.addTableSwitch(0,
+ targets.size() + GENERATOR_START);
+ generateCheckForThrowOrClose(-1, false, GENERATOR_START);
+ }
+ }
+
+ if (fnCurrent == null) {
+ // See comments in case Token.REGEXP
+ if (scriptOrFn.getRegexpCount() != 0) {
+ scriptRegexpLocal = getNewWordLocal();
+ codegen.pushRegExpArray(cfw, scriptOrFn, contextLocal,
+ variableObjectLocal);
+ cfw.addAStore(scriptRegexpLocal);
+ }
+ }
+
+ if (compilerEnv.isGenerateObserverCount())
+ saveCurrentCodeOffset();
+
+ if (hasVarsInRegs) {
+ // No need to create activation. Pad arguments if need be.
+ int parmCount = scriptOrFn.getParamCount();
+ if (parmCount > 0 && !inDirectCallFunction) {
+ // Set up args array
+ // check length of arguments, pad if need be
+ cfw.addALoad(argsLocal);
+ cfw.add(ByteCode.ARRAYLENGTH);
+ cfw.addPush(parmCount);
+ int label = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ICMPGE, label);
+ cfw.addALoad(argsLocal);
+ cfw.addPush(parmCount);
+ addScriptRuntimeInvoke("padArguments",
+ "([Ljava/lang/Object;I"
+ +")[Ljava/lang/Object;");
+ cfw.addAStore(argsLocal);
+ cfw.markLabel(label);
+ }
+
+ int paramCount = fnCurrent.fnode.getParamCount();
+ int varCount = fnCurrent.fnode.getParamAndVarCount();
+ boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
+
+ // 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 != varCount; ++i) {
+ short reg = -1;
+ if (i < paramCount) {
+ if (!inDirectCallFunction) {
+ reg = getNewWordLocal();
+ cfw.addALoad(argsLocal);
+ cfw.addPush(i);
+ cfw.add(ByteCode.AALOAD);
+ cfw.addAStore(reg);
+ }
+ } else if (fnCurrent.isNumberVar(i)) {
+ reg = getNewWordPairLocal(constDeclarations[i]);
+ cfw.addPush(0.0);
+ cfw.addDStore(reg);
+ } else {
+ reg = getNewWordLocal(constDeclarations[i]);
+ if (firstUndefVar == -1) {
+ Codegen.pushUndefined(cfw);
+ firstUndefVar = reg;
+ } else {
+ cfw.addALoad(firstUndefVar);
+ }
+ cfw.addAStore(reg);
+ }
+ if (reg >= 0) {
+ if (constDeclarations[i]) {
+ cfw.addPush(0);
+ cfw.addIStore(reg + (fnCurrent.isNumberVar(i) ? 2 : 1));
+ }
+ varRegisters[i] = reg;
+ }
+
+ // Add debug table entry if we're generating debug info
+ if (compilerEnv.isGenerateDebugInfo()) {
+ String name = fnCurrent.fnode.getParamOrVarName(i);
+ String type = fnCurrent.isNumberVar(i)
+ ? "D" : "Ljava/lang/Object;";
+ int startPC = cfw.getCurrentCodeOffset();
+ if (reg < 0) {
+ reg = varRegisters[i];
+ }
+ cfw.addVariableDescriptor(name, type, startPC, reg);
+ }
+ }
+
+ // Skip creating activation object.
+ return;
+ }
+
+ // skip creating activation object for the body of a generator. The
+ // activation record required by a generator has already been created
+ // in generateGenerator().
+ if (isGenerator)
+ return;
+
+
+ String debugVariableName;
+ if (fnCurrent != null) {
+ debugVariableName = "activation";
+ cfw.addALoad(funObjLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(argsLocal);
+ addScriptRuntimeInvoke("createFunctionActivation",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("enterActivationFunction",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")V");
+ } else {
+ debugVariableName = "global";
+ cfw.addALoad(funObjLocal);
+ cfw.addALoad(thisObjLocal);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(0); // false to indicate it is not eval script
+ addScriptRuntimeInvoke("initScript",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Z"
+ +")V");
+ }
+
+ enterAreaStartLabel = cfw.acquireLabel();
+ epilogueLabel = cfw.acquireLabel();
+ cfw.markLabel(enterAreaStartLabel);
+
+ generateNestedFunctionInits();
+
+ // default is to generate debug info
+ if (compilerEnv.isGenerateDebugInfo()) {
+ cfw.addVariableDescriptor(debugVariableName,
+ "Lorg/mozilla/javascript/Scriptable;",
+ cfw.getCurrentCodeOffset(), variableObjectLocal);
+ }
+
+ if (fnCurrent == null) {
+ // OPT: use dataflow to prove that this assignment is dead
+ popvLocal = getNewWordLocal();
+ Codegen.pushUndefined(cfw);
+ cfw.addAStore(popvLocal);
+
+ int linenum = scriptOrFn.getEndLineno();
+ if (linenum != -1)
+ cfw.addLineNumberEntry((short)linenum);
+
+ } else {
+ if (fnCurrent.itsContainsCalls0) {
+ itsZeroArgArray = getNewWordLocal();
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "emptyArgs", "[Ljava/lang/Object;");
+ cfw.addAStore(itsZeroArgArray);
+ }
+ if (fnCurrent.itsContainsCalls1) {
+ itsOneArgArray = getNewWordLocal();
+ cfw.addPush(1);
+ cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
+ cfw.addAStore(itsOneArgArray);
+ }
+ }
+ }
+
+ private void generateGetGeneratorResumptionPoint()
+ {
+ cfw.addALoad(generatorStateLocal);
+ cfw.add(ByteCode.GETFIELD,
+ OptRuntime.GeneratorState.CLASS_NAME,
+ OptRuntime.GeneratorState.resumptionPoint_NAME,
+ OptRuntime.GeneratorState.resumptionPoint_TYPE);
+ }
+
+ private void generateSetGeneratorResumptionPoint(int nextState)
+ {
+ cfw.addALoad(generatorStateLocal);
+ cfw.addLoadConstant(nextState);
+ cfw.add(ByteCode.PUTFIELD,
+ OptRuntime.GeneratorState.CLASS_NAME,
+ OptRuntime.GeneratorState.resumptionPoint_NAME,
+ OptRuntime.GeneratorState.resumptionPoint_TYPE);
+ }
+
+ private void generateGetGeneratorStackState()
+ {
+ cfw.addALoad(generatorStateLocal);
+ addOptRuntimeInvoke("getGeneratorStackState",
+ "(Ljava/lang/Object;)[Ljava/lang/Object;");
+ }
+
+ private void generateEpilogue()
+ {
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ if (isGenerator) {
+ // generate locals initialization
+ HashMap liveLocals = ((FunctionNode)scriptOrFn).getLiveLocals();
+ if (liveLocals != null) {
+ ArrayList nodes = ((FunctionNode)scriptOrFn).getResumptionPoints();
+ for (int i = 0; i < nodes.size(); i++) {
+ Node node = (Node) nodes.get(i);
+ int[] live = (int [])liveLocals.get(node);
+ if (live != null) {
+ cfw.markTableSwitchCase(generatorSwitch,
+ getNextGeneratorState(node));
+ generateGetGeneratorLocalsState();
+ for (int j = 0; j < live.length; j++) {
+ cfw.add(ByteCode.DUP);
+ cfw.addLoadConstant(j);
+ cfw.add(ByteCode.AALOAD);
+ cfw.addAStore(live[j]);
+ }
+ cfw.add(ByteCode.POP);
+ cfw.add(ByteCode.GOTO, getTargetLabel(node));
+ }
+ }
+ }
+
+ // generate dispatch tables for finally
+ if (finallys != null) {
+ Enumeration en = finallys.keys();
+ while(en.hasMoreElements()) {
+ Node n = (Node) en.nextElement();
+ if (n.getType() == Token.FINALLY) {
+ FinallyReturnPoint ret =
+ (FinallyReturnPoint)finallys.get(n);
+ // the finally will jump here
+ cfw.markLabel(ret.tableLabel, (short)1);
+
+ // start generating a dispatch table
+ int startSwitch = cfw.addTableSwitch(0,
+ ret.jsrPoints.size() - 1);
+ int c = 0;
+ cfw.markTableSwitchDefault(startSwitch);
+ for (int i = 0; i < ret.jsrPoints.size(); i++) {
+ // generate gotos back to the JSR location
+ cfw.markTableSwitchCase(startSwitch, c);
+ cfw.add(ByteCode.GOTO,
+ ((Integer)ret.jsrPoints.get(i)).intValue());
+ c++;
+ }
+ }
+ }
+ }
+ }
+
+ if (epilogueLabel != -1) {
+ cfw.markLabel(epilogueLabel);
+ }
+
+ if (hasVarsInRegs) {
+ cfw.add(ByteCode.ARETURN);
+ return;
+ } else if (isGenerator) {
+ if (((FunctionNode)scriptOrFn).getResumptionPoints() != null) {
+ cfw.markTableSwitchDefault(generatorSwitch);
+ }
+
+ // change state for re-entry
+ generateSetGeneratorResumptionPoint(GENERATOR_TERMINATE);
+
+ // throw StopIteration
+ cfw.addALoad(variableObjectLocal);
+ addOptRuntimeInvoke("throwStopIteration",
+ "(Ljava/lang/Object;)V");
+
+ Codegen.pushUndefined(cfw);
+ cfw.add(ByteCode.ARETURN);
+
+ } else if (fnCurrent == null) {
+ cfw.addALoad(popvLocal);
+ cfw.add(ByteCode.ARETURN);
+ } else {
+ generateActivationExit();
+ cfw.add(ByteCode.ARETURN);
+
+ // Generate catch block to catch all and rethrow to call exit code
+ // under exception propagation as well.
+
+ int finallyHandler = cfw.acquireLabel();
+ cfw.markHandler(finallyHandler);
+ short exceptionObject = getNewWordLocal();
+ cfw.addAStore(exceptionObject);
+
+ // Duplicate generateActivationExit() in the catch block since it
+ // takes less space then full-featured ByteCode.JSR/ByteCode.RET
+ generateActivationExit();
+
+ cfw.addALoad(exceptionObject);
+ releaseWordLocal(exceptionObject);
+ // rethrow
+ cfw.add(ByteCode.ATHROW);
+
+ // mark the handler
+ cfw.addExceptionHandler(enterAreaStartLabel, epilogueLabel,
+ finallyHandler, null); // catch any
+ }
+ }
+
+ private void generateGetGeneratorLocalsState() {
+ cfw.addALoad(generatorStateLocal);
+ addOptRuntimeInvoke("getGeneratorLocalsState",
+ "(Ljava/lang/Object;)[Ljava/lang/Object;");
+ }
+
+ private void generateActivationExit()
+ {
+ if (fnCurrent == null || hasVarsInRegs) throw Kit.codeBug();
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("exitActivationFunction",
+ "(Lorg/mozilla/javascript/Context;)V");
+ }
+
+ private void generateStatement(Node node)
+ {
+ updateLineNumber(node);
+ int type = node.getType();
+ Node child = node.getFirstChild();
+ switch (type) {
+ case Token.LOOP:
+ case Token.LABEL:
+ case Token.WITH:
+ case Token.SCRIPT:
+ case Token.BLOCK:
+ case Token.EMPTY:
+ // no-ops.
+ while (child != null) {
+ generateStatement(child);
+ child = child.getNext();
+ }
+ break;
+
+ case Token.LOCAL_BLOCK: {
+ int local = getNewWordLocal();
+ if (isGenerator) {
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.addAStore(local);
+ }
+ node.putIntProp(Node.LOCAL_PROP, local);
+ while (child != null) {
+ generateStatement(child);
+ child = child.getNext();
+ }
+ releaseWordLocal((short)local);
+ node.removeProp(Node.LOCAL_PROP);
+ break;
+ }
+
+ case Token.FUNCTION: {
+ int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
+ OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex);
+ int t = ofn.fnode.getFunctionType();
+ if (t == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
+ visitFunction(ofn, t);
+ } else {
+ if (t != FunctionNode.FUNCTION_STATEMENT) {
+ throw Codegen.badTree();
+ }
+ }
+ break;
+ }
+
+ case Token.TRY:
+ visitTryCatchFinally((Node.Jump)node, child);
+ break;
+
+ case Token.CATCH_SCOPE:
+ {
+ // nothing stays on the stack on entry into a catch scope
+ cfw.setStackTop((short) 0);
+
+ int local = getLocalBlockRegister(node);
+ int scopeIndex
+ = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);
+
+ String name = child.getString(); // name of exception
+ child = child.getNext();
+ generateExpression(child, node); // load expression object
+ if (scopeIndex == 0) {
+ cfw.add(ByteCode.ACONST_NULL);
+ } else {
+ // Load previous catch scope object
+ cfw.addALoad(local);
+ }
+ cfw.addPush(name);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+
+ addScriptRuntimeInvoke(
+ "newCatchScope",
+ "(Ljava/lang/Throwable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(local);
+ }
+ break;
+
+ case Token.THROW:
+ generateExpression(child, node);
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ generateThrowJavaScriptException();
+ break;
+
+ case Token.RETHROW:
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ cfw.addALoad(getLocalBlockRegister(node));
+ cfw.add(ByteCode.ATHROW);
+ break;
+
+ case Token.RETURN_RESULT:
+ case Token.RETURN:
+ if (!isGenerator) {
+ if (child != null) {
+ generateExpression(child, node);
+ } else if (type == Token.RETURN) {
+ Codegen.pushUndefined(cfw);
+ } else {
+ if (popvLocal < 0) throw Codegen.badTree();
+ cfw.addALoad(popvLocal);
+ }
+ }
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ if (epilogueLabel == -1) {
+ if (!hasVarsInRegs) throw Codegen.badTree();
+ epilogueLabel = cfw.acquireLabel();
+ }
+ cfw.add(ByteCode.GOTO, epilogueLabel);
+ break;
+
+ case Token.SWITCH:
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ visitSwitch((Node.Jump)node, child);
+ break;
+
+ case Token.ENTERWITH:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "enterWith",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ incReferenceWordLocal(variableObjectLocal);
+ break;
+
+ case Token.LEAVEWITH:
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "leaveWith",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ decReferenceWordLocal(variableObjectLocal);
+ break;
+
+ case Token.ENUM_INIT_KEYS:
+ case Token.ENUM_INIT_VALUES:
+ case Token.ENUM_INIT_ARRAY:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ int enumType = type == Token.ENUM_INIT_KEYS
+ ? ScriptRuntime.ENUMERATE_KEYS :
+ type == Token.ENUM_INIT_VALUES
+ ? ScriptRuntime.ENUMERATE_VALUES :
+ ScriptRuntime.ENUMERATE_ARRAY;
+ cfw.addPush(enumType);
+ addScriptRuntimeInvoke("enumInit",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Ljava/lang/Object;");
+ cfw.addAStore(getLocalBlockRegister(node));
+ break;
+
+ case Token.EXPR_VOID:
+ if (child.getType() == Token.SETVAR) {
+ /* special case this so as to avoid unnecessary
+ load's & pop's */
+ visitSetVar(child, child.getFirstChild(), false);
+ }
+ else if (child.getType() == Token.SETCONSTVAR) {
+ /* special case this so as to avoid unnecessary
+ load's & pop's */
+ visitSetConstVar(child, child.getFirstChild(), false);
+ }
+ else if (child.getType() == Token.YIELD) {
+ generateYieldPoint(child, false);
+ }
+ else {
+ generateExpression(child, node);
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
+ cfw.add(ByteCode.POP2);
+ else
+ cfw.add(ByteCode.POP);
+ }
+ break;
+
+ case Token.EXPR_RESULT:
+ generateExpression(child, node);
+ if (popvLocal < 0) {
+ popvLocal = getNewWordLocal();
+ }
+ cfw.addAStore(popvLocal);
+ break;
+
+ case Token.TARGET:
+ {
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ int label = getTargetLabel(node);
+ cfw.markLabel(label);
+ if (compilerEnv.isGenerateObserverCount())
+ saveCurrentCodeOffset();
+ }
+ break;
+
+ case Token.JSR:
+ case Token.GOTO:
+ case Token.IFEQ:
+ case Token.IFNE:
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ visitGoto((Node.Jump)node, type, child);
+ break;
+
+ case Token.FINALLY:
+ {
+ if (compilerEnv.isGenerateObserverCount())
+ saveCurrentCodeOffset();
+ // there is exactly one value on the stack when enterring
+ // finally blocks: the return address (or its int encoding)
+ cfw.setStackTop((short)1);
+
+ // Save return address in a new local
+ int finallyRegister = getNewWordLocal();
+ if (isGenerator)
+ generateIntegerWrap();
+ cfw.addAStore(finallyRegister);
+
+ while (child != null) {
+ generateStatement(child);
+ child = child.getNext();
+ }
+ if (isGenerator) {
+ cfw.addALoad(finallyRegister);
+ cfw.add(ByteCode.CHECKCAST, "java/lang/Integer");
+ generateIntegerUnwrap();
+ FinallyReturnPoint ret =
+ (FinallyReturnPoint)finallys.get(node);
+ ret.tableLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, ret.tableLabel);
+ } else {
+ cfw.add(ByteCode.RET, finallyRegister);
+ }
+ releaseWordLocal((short)finallyRegister);
+ }
+ break;
+
+ case Token.DEBUGGER:
+ break;
+
+ default:
+ throw Codegen.badTree();
+ }
+
+ }
+
+ private void generateIntegerWrap()
+ {
+ cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/Integer", "valueOf",
+ "(I)Ljava/lang/Integer;");
+ }
+
+
+ private void generateIntegerUnwrap()
+ {
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/Integer",
+ "intValue", "()I");
+ }
+
+
+ private void generateThrowJavaScriptException()
+ {
+ cfw.add(ByteCode.NEW,
+ "org/mozilla/javascript/JavaScriptException");
+ cfw.add(ByteCode.DUP_X1);
+ cfw.add(ByteCode.SWAP);
+ cfw.addPush(scriptOrFn.getSourceName());
+ cfw.addPush(itsLineNumber);
+ cfw.addInvoke(
+ ByteCode.INVOKESPECIAL,
+ "org/mozilla/javascript/JavaScriptException",
+ "<init>",
+ "(Ljava/lang/Object;Ljava/lang/String;I)V");
+ cfw.add(ByteCode.ATHROW);
+ }
+
+ private int getNextGeneratorState(Node node)
+ {
+ int nodeIndex = ((FunctionNode)scriptOrFn).getResumptionPoints()
+ .indexOf(node);
+ return nodeIndex + GENERATOR_YIELD_START;
+ }
+
+ private void generateExpression(Node node, Node parent)
+ {
+ int type = node.getType();
+ Node child = node.getFirstChild();
+ switch (type) {
+ case Token.USE_STACK:
+ break;
+
+ case Token.FUNCTION:
+ if (fnCurrent != null || parent.getType() != Token.SCRIPT) {
+ int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
+ OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn,
+ fnIndex);
+ int t = ofn.fnode.getFunctionType();
+ if (t != FunctionNode.FUNCTION_EXPRESSION) {
+ throw Codegen.badTree();
+ }
+ visitFunction(ofn, t);
+ }
+ break;
+
+ case Token.NAME:
+ {
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(node.getString());
+ addScriptRuntimeInvoke(
+ "name",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/Object;");
+ }
+ break;
+
+ case Token.CALL:
+ case Token.NEW:
+ {
+ int specialType = node.getIntProp(Node.SPECIALCALL_PROP,
+ Node.NON_SPECIALCALL);
+ if (specialType == Node.NON_SPECIALCALL) {
+ OptFunctionNode target;
+ target = (OptFunctionNode)node.getProp(
+ Node.DIRECTCALL_PROP);
+
+ if (target != null) {
+ visitOptimizedCall(node, target, type, child);
+ } else if (type == Token.CALL) {
+ visitStandardCall(node, child);
+ } else {
+ visitStandardNew(node, child);
+ }
+ } else {
+ visitSpecialCall(node, type, specialType, child);
+ }
+ }
+ break;
+
+ case Token.REF_CALL:
+ generateFunctionAndThisObj(child, node);
+ // stack: ... functionObj thisObj
+ child = child.getNext();
+ generateCallArgArray(node, child, false);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "callRef",
+ "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Ref;");
+ break;
+
+ case Token.NUMBER:
+ {
+ double num = node.getDouble();
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ cfw.addPush(num);
+ } else {
+ codegen.pushNumberAsObject(cfw, num);
+ }
+ }
+ break;
+
+ case Token.STRING:
+ cfw.addPush(node.getString());
+ break;
+
+ case Token.THIS:
+ cfw.addALoad(thisObjLocal);
+ break;
+
+ case Token.THISFN:
+ cfw.add(ByteCode.ALOAD_0);
+ break;
+
+ case Token.NULL:
+ cfw.add(ByteCode.ACONST_NULL);
+ break;
+
+ case Token.TRUE:
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "TRUE", "Ljava/lang/Boolean;");
+ break;
+
+ case Token.FALSE:
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "FALSE", "Ljava/lang/Boolean;");
+ break;
+
+ case Token.REGEXP:
+ {
+ int i = node.getExistingIntProp(Node.REGEXP_PROP);
+ // Scripts can not use REGEXP_ARRAY_FIELD_NAME since
+ // it it will make script.exec non-reentrant so they
+ // store regexp array in a local variable while
+ // functions always access precomputed
+ // REGEXP_ARRAY_FIELD_NAME not to consume locals
+ if (fnCurrent == null) {
+ cfw.addALoad(scriptRegexpLocal);
+ } else {
+ cfw.addALoad(funObjLocal);
+ cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
+ Codegen.REGEXP_ARRAY_FIELD_NAME,
+ Codegen.REGEXP_ARRAY_FIELD_TYPE);
+ }
+ cfw.addPush(i);
+ cfw.add(ByteCode.AALOAD);
+ }
+ break;
+
+ case Token.COMMA: {
+ Node next = child.getNext();
+ while (next != null) {
+ generateExpression(child, node);
+ cfw.add(ByteCode.POP);
+ child = next;
+ next = next.getNext();
+ }
+ generateExpression(child, node);
+ break;
+ }
+
+ case Token.ENUM_NEXT:
+ case Token.ENUM_ID: {
+ int local = getLocalBlockRegister(node);
+ cfw.addALoad(local);
+ if (type == Token.ENUM_NEXT) {
+ addScriptRuntimeInvoke(
+ "enumNext", "(Ljava/lang/Object;)Ljava/lang/Boolean;");
+ } else {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("enumId",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ break;
+ }
+
+ case Token.ARRAYLIT:
+ visitArrayLiteral(node, child);
+ break;
+
+ case Token.OBJECTLIT:
+ visitObjectLiteral(node, child);
+ break;
+
+ case Token.NOT: {
+ int trueTarget = cfw.acquireLabel();
+ int falseTarget = cfw.acquireLabel();
+ int beyond = cfw.acquireLabel();
+ generateIfJump(child, node, trueTarget, falseTarget);
+
+ cfw.markLabel(trueTarget);
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "FALSE", "Ljava/lang/Boolean;");
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(falseTarget);
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "TRUE", "Ljava/lang/Boolean;");
+ cfw.markLabel(beyond);
+ cfw.adjustStackTop(-1);
+ break;
+ }
+
+ case Token.BITNOT:
+ generateExpression(child, node);
+ addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
+ cfw.addPush(-1); // implement ~a as (a ^ -1)
+ cfw.add(ByteCode.IXOR);
+ cfw.add(ByteCode.I2D);
+ addDoubleWrap();
+ break;
+
+ case Token.VOID:
+ generateExpression(child, node);
+ cfw.add(ByteCode.POP);
+ Codegen.pushUndefined(cfw);
+ break;
+
+ case Token.TYPEOF:
+ generateExpression(child, node);
+ addScriptRuntimeInvoke("typeof",
+ "(Ljava/lang/Object;"
+ +")Ljava/lang/String;");
+ break;
+
+ case Token.TYPEOFNAME:
+ visitTypeofname(node);
+ break;
+
+ case Token.INC:
+ case Token.DEC:
+ visitIncDec(node);
+ break;
+
+ case Token.OR:
+ case Token.AND: {
+ generateExpression(child, node);
+ cfw.add(ByteCode.DUP);
+ addScriptRuntimeInvoke("toBoolean",
+ "(Ljava/lang/Object;)Z");
+ int falseTarget = cfw.acquireLabel();
+ if (type == Token.AND)
+ cfw.add(ByteCode.IFEQ, falseTarget);
+ else
+ cfw.add(ByteCode.IFNE, falseTarget);
+ cfw.add(ByteCode.POP);
+ generateExpression(child.getNext(), node);
+ cfw.markLabel(falseTarget);
+ }
+ break;
+
+ case Token.HOOK : {
+ Node ifThen = child.getNext();
+ Node ifElse = ifThen.getNext();
+ generateExpression(child, node);
+ addScriptRuntimeInvoke("toBoolean",
+ "(Ljava/lang/Object;)Z");
+ int elseTarget = cfw.acquireLabel();
+ cfw.add(ByteCode.IFEQ, elseTarget);
+ short stack = cfw.getStackTop();
+ generateExpression(ifThen, node);
+ int afterHook = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, afterHook);
+ cfw.markLabel(elseTarget, stack);
+ generateExpression(ifElse, node);
+ cfw.markLabel(afterHook);
+ }
+ break;
+
+ case Token.ADD: {
+ generateExpression(child, node);
+ generateExpression(child.getNext(), node);
+ switch (node.getIntProp(Node.ISNUMBER_PROP, -1)) {
+ case Node.BOTH:
+ cfw.add(ByteCode.DADD);
+ break;
+ case Node.LEFT:
+ addOptRuntimeInvoke("add",
+ "(DLjava/lang/Object;)Ljava/lang/Object;");
+ break;
+ case Node.RIGHT:
+ addOptRuntimeInvoke("add",
+ "(Ljava/lang/Object;D)Ljava/lang/Object;");
+ break;
+ default:
+ if (child.getType() == Token.STRING) {
+ addScriptRuntimeInvoke("add",
+ "(Ljava/lang/String;"
+ +"Ljava/lang/Object;"
+ +")Ljava/lang/String;");
+ } else if (child.getNext().getType() == Token.STRING) {
+ addScriptRuntimeInvoke("add",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/String;");
+ } else {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("add",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+ }
+ break;
+
+ case Token.MUL:
+ visitArithmetic(node, ByteCode.DMUL, child, parent);
+ break;
+
+ case Token.SUB:
+ visitArithmetic(node, ByteCode.DSUB, child, parent);
+ break;
+
+ case Token.DIV:
+ case Token.MOD:
+ visitArithmetic(node, type == Token.DIV
+ ? ByteCode.DDIV
+ : ByteCode.DREM, child, parent);
+ break;
+
+ case Token.BITOR:
+ case Token.BITXOR:
+ case Token.BITAND:
+ case Token.LSH:
+ case Token.RSH:
+ case Token.URSH:
+ visitBitOp(node, type, child);
+ break;
+
+ case Token.POS:
+ case Token.NEG:
+ generateExpression(child, node);
+ addObjectToDouble();
+ if (type == Token.NEG) {
+ cfw.add(ByteCode.DNEG);
+ }
+ addDoubleWrap();
+ break;
+
+ case Token.TO_DOUBLE:
+ // cnvt to double (not Double)
+ generateExpression(child, node);
+ addObjectToDouble();
+ break;
+
+ case Token.TO_OBJECT: {
+ // convert from double
+ int prop = -1;
+ if (child.getType() == Token.NUMBER) {
+ prop = child.getIntProp(Node.ISNUMBER_PROP, -1);
+ }
+ if (prop != -1) {
+ child.removeProp(Node.ISNUMBER_PROP);
+ generateExpression(child, node);
+ child.putIntProp(Node.ISNUMBER_PROP, prop);
+ } else {
+ generateExpression(child, node);
+ addDoubleWrap();
+ }
+ break;
+ }
+
+ case Token.IN:
+ case Token.INSTANCEOF:
+ case Token.LE:
+ case Token.LT:
+ case Token.GE:
+ case Token.GT: {
+ int trueGOTO = cfw.acquireLabel();
+ int falseGOTO = cfw.acquireLabel();
+ visitIfJumpRelOp(node, child, trueGOTO, falseGOTO);
+ addJumpedBooleanWrap(trueGOTO, falseGOTO);
+ break;
+ }
+
+ case Token.EQ:
+ case Token.NE:
+ case Token.SHEQ:
+ case Token.SHNE: {
+ int trueGOTO = cfw.acquireLabel();
+ int falseGOTO = cfw.acquireLabel();
+ visitIfJumpEqOp(node, child, trueGOTO, falseGOTO);
+ addJumpedBooleanWrap(trueGOTO, falseGOTO);
+ break;
+ }
+
+ case Token.GETPROP:
+ case Token.GETPROPNOWARN:
+ visitGetProp(node, child);
+ break;
+
+ case Token.GETELEM:
+ generateExpression(child, node); // object
+ generateExpression(child.getNext(), node); // id
+ cfw.addALoad(contextLocal);
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ addScriptRuntimeInvoke(
+ "getObjectIndex",
+ "(Ljava/lang/Object;D"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ else {
+ addScriptRuntimeInvoke(
+ "getObjectElem",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ break;
+
+ case Token.GET_REF:
+ generateExpression(child, node); // reference
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "refGet",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ break;
+
+ case Token.GETVAR:
+ visitGetVar(node);
+ break;
+
+ case Token.SETVAR:
+ visitSetVar(node, child, true);
+ break;
+
+ case Token.SETNAME:
+ visitSetName(node, child);
+ break;
+
+ case Token.SETCONST:
+ visitSetConst(node, child);
+ break;
+
+ case Token.SETCONSTVAR:
+ visitSetConstVar(node, child, true);
+ break;
+
+ case Token.SETPROP:
+ case Token.SETPROP_OP:
+ visitSetProp(type, node, child);
+ break;
+
+ case Token.SETELEM:
+ case Token.SETELEM_OP:
+ visitSetElem(type, node, child);
+ break;
+
+ case Token.SET_REF:
+ case Token.SET_REF_OP:
+ {
+ generateExpression(child, node);
+ child = child.getNext();
+ if (type == Token.SET_REF_OP) {
+ cfw.add(ByteCode.DUP);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "refGet",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "refSet",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ break;
+
+ case Token.DEL_REF:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("refDel",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ break;
+
+ case Token.DELPROP:
+ generateExpression(child, node);
+ child = child.getNext();
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("delete",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ break;
+
+ case Token.BINDNAME:
+ {
+ while (child != null) {
+ generateExpression(child, node);
+ child = child.getNext();
+ }
+ // Generate code for "ScriptRuntime.bind(varObj, "s")"
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(node.getString());
+ addScriptRuntimeInvoke(
+ "bind",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+ break;
+
+ case Token.LOCAL_LOAD:
+ cfw.addALoad(getLocalBlockRegister(node));
+ break;
+
+ case Token.REF_SPECIAL:
+ {
+ String special = (String)node.getProp(Node.NAME_PROP);
+ generateExpression(child, node);
+ cfw.addPush(special);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "specialRef",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Ref;");
+ }
+ break;
+
+ case Token.REF_MEMBER:
+ case Token.REF_NS_MEMBER:
+ case Token.REF_NAME:
+ case Token.REF_NS_NAME:
+ {
+ int memberTypeFlags
+ = node.getIntProp(Node.MEMBER_TYPE_PROP, 0);
+ // generate possible target, possible namespace and member
+ do {
+ generateExpression(child, node);
+ child = child.getNext();
+ } while (child != null);
+ cfw.addALoad(contextLocal);
+ String methodName, signature;
+ switch (type) {
+ case Token.REF_MEMBER:
+ methodName = "memberRef";
+ signature = "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Lorg/mozilla/javascript/Ref;";
+ break;
+ case Token.REF_NS_MEMBER:
+ methodName = "memberRef";
+ signature = "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Lorg/mozilla/javascript/Ref;";
+ break;
+ case Token.REF_NAME:
+ methodName = "nameRef";
+ signature = "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"I"
+ +")Lorg/mozilla/javascript/Ref;";
+ cfw.addALoad(variableObjectLocal);
+ break;
+ case Token.REF_NS_NAME:
+ methodName = "nameRef";
+ signature = "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"I"
+ +")Lorg/mozilla/javascript/Ref;";
+ cfw.addALoad(variableObjectLocal);
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+ cfw.addPush(memberTypeFlags);
+ addScriptRuntimeInvoke(methodName, signature);
+ }
+ break;
+
+ case Token.DOTQUERY:
+ visitDotQuery(node, child);
+ break;
+
+ case Token.ESCXMLATTR:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("escapeAttributeValue",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/String;");
+ break;
+
+ case Token.ESCXMLTEXT:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("escapeTextValue",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/String;");
+ break;
+
+ case Token.DEFAULTNAMESPACE:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("setDefaultNamespace",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ break;
+
+ case Token.YIELD:
+ generateYieldPoint(node, true);
+ break;
+
+ case Token.WITHEXPR: {
+ Node enterWith = child;
+ Node with = enterWith.getNext();
+ Node leaveWith = with.getNext();
+ generateStatement(enterWith);
+ generateExpression(with.getFirstChild(), with);
+ generateStatement(leaveWith);
+ break;
+ }
+
+ case Token.ARRAYCOMP: {
+ Node initStmt = child;
+ Node expr = child.getNext();
+ generateStatement(initStmt);
+ generateExpression(expr, node);
+ break;
+ }
+
+ default:
+ throw new RuntimeException("Unexpected node type "+type);
+ }
+
+ }
+
+ private void generateYieldPoint(Node node, boolean exprContext) {
+ // save stack state
+ int top = cfw.getStackTop();
+ maxStack = maxStack > top ? maxStack : top;
+ if (cfw.getStackTop() != 0) {
+ generateGetGeneratorStackState();
+ for (int i = 0; i < top; i++) {
+ cfw.add(ByteCode.DUP_X1);
+ cfw.add(ByteCode.SWAP);
+ cfw.addLoadConstant(i);
+ cfw.add(ByteCode.SWAP);
+ cfw.add(ByteCode.AASTORE);
+ }
+ // pop the array object
+ cfw.add(ByteCode.POP);
+ }
+
+ // generate the yield argument
+ Node child = node.getFirstChild();
+ if (child != null)
+ generateExpression(child, node);
+ else
+ Codegen.pushUndefined(cfw);
+
+ // change the resumption state
+ int nextState = getNextGeneratorState(node);
+ generateSetGeneratorResumptionPoint(nextState);
+
+ boolean hasLocals = generateSaveLocals(node);
+
+ cfw.add(ByteCode.ARETURN);
+
+ generateCheckForThrowOrClose(getTargetLabel(node),
+ hasLocals, nextState);
+
+ // reconstruct the stack
+ if (top != 0) {
+ generateGetGeneratorStackState();
+ for (int i = 0; i < top; i++) {
+ cfw.add(ByteCode.DUP);
+ cfw.addLoadConstant(top - i - 1);
+ cfw.add(ByteCode.AALOAD);
+ cfw.add(ByteCode.SWAP);
+ }
+ cfw.add(ByteCode.POP);
+ }
+
+ // load return value from yield
+ if (exprContext) {
+ cfw.addALoad(argsLocal);
+ }
+ }
+
+ private void generateCheckForThrowOrClose(int label,
+ boolean hasLocals,
+ int nextState) {
+ int throwLabel = cfw.acquireLabel();
+ int closeLabel = cfw.acquireLabel();
+
+ // throw the user provided object, if the operation is .throw()
+ cfw.markLabel(throwLabel);
+ cfw.addALoad(argsLocal);
+ generateThrowJavaScriptException();
+
+ // throw our special internal exception if the generator is being closed
+ cfw.markLabel(closeLabel);
+ cfw.addALoad(argsLocal);
+ cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable");
+ cfw.add(ByteCode.ATHROW);
+
+ // mark the re-entry point
+ // jump here after initializing the locals
+ if (label != -1)
+ cfw.markLabel(label);
+ if (!hasLocals) {
+ // jump here directly if there are no locals
+ cfw.markTableSwitchCase(generatorSwitch, nextState);
+ }
+
+ // see if we need to dispatch for .close() or .throw()
+ cfw.addILoad(operationLocal);
+ cfw.addLoadConstant(NativeGenerator.GENERATOR_CLOSE);
+ cfw.add(ByteCode.IF_ICMPEQ, closeLabel);
+ cfw.addILoad(operationLocal);
+ cfw.addLoadConstant(NativeGenerator.GENERATOR_THROW);
+ cfw.add(ByteCode.IF_ICMPEQ, throwLabel);
+ }
+
+ private void generateIfJump(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 Token.NOT:
+ generateIfJump(child, node, falseLabel, trueLabel);
+ break;
+
+ case Token.OR:
+ case Token.AND: {
+ int interLabel = cfw.acquireLabel();
+ if (type == Token.AND) {
+ generateIfJump(child, node, interLabel, falseLabel);
+ }
+ else {
+ generateIfJump(child, node, trueLabel, interLabel);
+ }
+ cfw.markLabel(interLabel);
+ child = child.getNext();
+ generateIfJump(child, node, trueLabel, falseLabel);
+ break;
+ }
+
+ case Token.IN:
+ case Token.INSTANCEOF:
+ case Token.LE:
+ case Token.LT:
+ case Token.GE:
+ case Token.GT:
+ visitIfJumpRelOp(node, child, trueLabel, falseLabel);
+ break;
+
+ case Token.EQ:
+ case Token.NE:
+ case Token.SHEQ:
+ case Token.SHNE:
+ visitIfJumpEqOp(node, child, trueLabel, falseLabel);
+ break;
+
+ default:
+ // Generate generic code for non-optimized jump
+ generateExpression(node, parent);
+ addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z");
+ cfw.add(ByteCode.IFNE, trueLabel);
+ cfw.add(ByteCode.GOTO, falseLabel);
+ }
+ }
+
+ private void visitFunction(OptFunctionNode ofn, int functionType)
+ {
+ int fnIndex = codegen.getIndex(ofn.fnode);
+ cfw.add(ByteCode.NEW, codegen.mainClassName);
+ // Call function constructor
+ cfw.add(ByteCode.DUP);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(contextLocal); // load 'cx'
+ cfw.addPush(fnIndex);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName,
+ "<init>", Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE);
+
+ // Init mainScript field;
+ cfw.add(ByteCode.DUP);
+ if (isTopLevel) {
+ cfw.add(ByteCode.ALOAD_0);
+ } else {
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+ }
+ cfw.add(ByteCode.PUTFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+
+ int directTargetIndex = ofn.getDirectTargetIndex();
+ if (directTargetIndex >= 0) {
+ cfw.add(ByteCode.DUP);
+ if (isTopLevel) {
+ cfw.add(ByteCode.ALOAD_0);
+ } else {
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+ }
+ cfw.add(ByteCode.SWAP);
+ cfw.add(ByteCode.PUTFIELD,
+ codegen.mainClassName,
+ Codegen.getDirectTargetFieldName(directTargetIndex),
+ codegen.mainClassSignature);
+ }
+
+ if (functionType == FunctionNode.FUNCTION_EXPRESSION) {
+ // Leave closure object on stack and do not pass it to
+ // initFunction which suppose to connect statements to scope
+ return;
+ }
+ cfw.addPush(functionType);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(contextLocal); // load 'cx'
+ addOptRuntimeInvoke("initFunction",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"I"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")V");
+ }
+
+ private int getTargetLabel(Node target)
+ {
+ int labelId = target.labelId();
+ if (labelId == -1) {
+ labelId = cfw.acquireLabel();
+ target.labelId(labelId);
+ }
+ return labelId;
+ }
+
+ private void visitGoto(Node.Jump node, int type, Node child)
+ {
+ Node target = node.target;
+ if (type == Token.IFEQ || type == Token.IFNE) {
+ if (child == null) throw Codegen.badTree();
+ int targetLabel = getTargetLabel(target);
+ int fallThruLabel = cfw.acquireLabel();
+ if (type == Token.IFEQ)
+ generateIfJump(child, node, targetLabel, fallThruLabel);
+ else
+ generateIfJump(child, node, fallThruLabel, targetLabel);
+ cfw.markLabel(fallThruLabel);
+ } else {
+ if (type == Token.JSR) {
+ if (isGenerator) {
+ addGotoWithReturn(target);
+ } else {
+ addGoto(target, ByteCode.JSR);
+ }
+ } else {
+ addGoto(target, ByteCode.GOTO);
+ }
+ }
+ }
+
+ private void addGotoWithReturn(Node target) {
+ FinallyReturnPoint ret =
+ (FinallyReturnPoint)finallys.get(target);
+ cfw.addLoadConstant(ret.jsrPoints.size());
+ addGoto(target, ByteCode.GOTO);
+ int retLabel = cfw.acquireLabel();
+ cfw.markLabel(retLabel);
+ ret.jsrPoints.add(Integer.valueOf(retLabel));
+ }
+
+ private void visitArrayLiteral(Node node, Node child)
+ {
+ int count = 0;
+ for (Node cursor = child; cursor != null; cursor = cursor.getNext()) {
+ ++count;
+ }
+ // load array to store array literal objects
+ addNewObjectArray(count);
+ for (int i = 0; i != count; ++i) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ generateExpression(child, node);
+ cfw.add(ByteCode.AASTORE);
+ child = child.getNext();
+ }
+ int[] skipIndexes = (int[])node.getProp(Node.SKIP_INDEXES_PROP);
+ if (skipIndexes == null) {
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.add(ByteCode.ICONST_0);
+ } else {
+ cfw.addPush(OptRuntime.encodeIntArray(skipIndexes));
+ cfw.addPush(skipIndexes.length);
+ }
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addOptRuntimeInvoke("newArrayLiteral",
+ "([Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"I"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+
+ private void visitObjectLiteral(Node node, Node child)
+ {
+ Object[] properties = (Object[])node.getProp(Node.OBJECT_IDS_PROP);
+ int count = properties.length;
+
+ // load array with property ids
+ addNewObjectArray(count);
+ for (int i = 0; i != count; ++i) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ Object id = properties[i];
+ if (id instanceof String) {
+ cfw.addPush((String)id);
+ } else {
+ cfw.addPush(((Integer)id).intValue());
+ addScriptRuntimeInvoke("wrapInt", "(I)Ljava/lang/Integer;");
+ }
+ cfw.add(ByteCode.AASTORE);
+ }
+ // load array with property values
+ addNewObjectArray(count);
+ Node child2 = child;
+ for (int i = 0; i != count; ++i) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ int childType = child.getType();
+ if (childType == Token.GET) {
+ generateExpression(child.getFirstChild(), node);
+ } else if (childType == Token.SET) {
+ generateExpression(child.getFirstChild(), node);
+ } else {
+ generateExpression(child, node);
+ }
+ cfw.add(ByteCode.AASTORE);
+ child = child.getNext();
+ }
+ // load array with getterSetter values
+ cfw.addPush(count);
+ cfw.add(ByteCode.NEWARRAY, ByteCode.T_INT);
+ for (int i = 0; i != count; ++i) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ int childType = child2.getType();
+ if (childType == Token.GET) {
+ cfw.add(ByteCode.ICONST_M1);
+ } else if (childType == Token.SET) {
+ cfw.add(ByteCode.ICONST_1);
+ } else {
+ cfw.add(ByteCode.ICONST_0);
+ }
+ cfw.add(ByteCode.IASTORE);
+ child2 = child2.getNext();
+ }
+
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("newObjectLiteral",
+ "([Ljava/lang/Object;"
+ +"[Ljava/lang/Object;"
+ +"[I"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+
+ private void visitSpecialCall(Node node, int type, int specialType,
+ Node child)
+ {
+ cfw.addALoad(contextLocal);
+
+ if (type == Token.NEW) {
+ generateExpression(child, node);
+ // stack: ... cx functionObj
+ } else {
+ generateFunctionAndThisObj(child, node);
+ // stack: ... cx functionObj thisObj
+ }
+ child = child.getNext();
+
+ generateCallArgArray(node, child, false);
+
+ String methodName;
+ String callSignature;
+
+ if (type == Token.NEW) {
+ methodName = "newObjectSpecial";
+ callSignature = "(Lorg/mozilla/javascript/Context;"
+ +"Ljava/lang/Object;"
+ +"[Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"I" // call type
+ +")Ljava/lang/Object;";
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(thisObjLocal);
+ cfw.addPush(specialType);
+ } else {
+ methodName = "callSpecial";
+ callSignature = "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"I" // call type
+ +"Ljava/lang/String;I" // filename, linenumber
+ +")Ljava/lang/Object;";
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(thisObjLocal);
+ cfw.addPush(specialType);
+ String sourceName = scriptOrFn.getSourceName();
+ cfw.addPush(sourceName == null ? "" : sourceName);
+ cfw.addPush(itsLineNumber);
+ }
+
+ addOptRuntimeInvoke(methodName, callSignature);
+ }
+
+ private void visitStandardCall(Node node, Node child)
+ {
+ if (node.getType() != Token.CALL) throw Codegen.badTree();
+
+ Node firstArgChild = child.getNext();
+ int childType = child.getType();
+
+ String methodName;
+ String signature;
+
+ if (firstArgChild == null) {
+ if (childType == Token.NAME) {
+ // name() call
+ String name = child.getString();
+ cfw.addPush(name);
+ methodName = "callName0";
+ signature = "(Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else if (childType == Token.GETPROP) {
+ // x.name() call
+ Node propTarget = child.getFirstChild();
+ generateExpression(propTarget, node);
+ Node id = propTarget.getNext();
+ String property = id.getString();
+ cfw.addPush(property);
+ methodName = "callProp0";
+ signature = "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else if (childType == Token.GETPROPNOWARN) {
+ throw Kit.codeBug();
+ } else {
+ generateFunctionAndThisObj(child, node);
+ methodName = "call0";
+ signature = "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ }
+
+ } else if (childType == Token.NAME) {
+ // XXX: this optimization is only possible if name
+ // resolution
+ // is not affected by arguments evaluation and currently
+ // there are no checks for it
+ String name = child.getString();
+ generateCallArgArray(node, firstArgChild, false);
+ cfw.addPush(name);
+ methodName = "callName";
+ signature = "([Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else {
+ int argCount = 0;
+ for (Node arg = firstArgChild; arg != null; arg = arg.getNext()) {
+ ++argCount;
+ }
+ generateFunctionAndThisObj(child, node);
+ // stack: ... functionObj thisObj
+ if (argCount == 1) {
+ generateExpression(firstArgChild, node);
+ methodName = "call1";
+ signature = "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else if (argCount == 2) {
+ generateExpression(firstArgChild, node);
+ generateExpression(firstArgChild.getNext(), node);
+ methodName = "call2";
+ signature = "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else {
+ generateCallArgArray(node, firstArgChild, false);
+ methodName = "callN";
+ signature = "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ }
+ }
+
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addOptRuntimeInvoke(methodName, signature);
+ }
+
+ private void visitStandardNew(Node node, Node child)
+ {
+ if (node.getType() != Token.NEW) throw Codegen.badTree();
+
+ Node firstArgChild = child.getNext();
+
+ generateExpression(child, node);
+ // stack: ... functionObj
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ // stack: ... functionObj cx scope
+ generateCallArgArray(node, firstArgChild, false);
+ addScriptRuntimeInvoke(
+ "newObject",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+
+ private void visitOptimizedCall(Node node, OptFunctionNode target,
+ int type, Node child)
+ {
+ Node firstArgChild = child.getNext();
+
+ short thisObjLocal = 0;
+ if (type == Token.NEW) {
+ generateExpression(child, node);
+ } else {
+ generateFunctionAndThisObj(child, node);
+ thisObjLocal = getNewWordLocal();
+ cfw.addAStore(thisObjLocal);
+ }
+ // stack: ... functionObj
+
+ int beyond = cfw.acquireLabel();
+
+ int directTargetIndex = target.getDirectTargetIndex();
+ if (isTopLevel) {
+ cfw.add(ByteCode.ALOAD_0);
+ } else {
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+ }
+ cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
+ Codegen.getDirectTargetFieldName(directTargetIndex),
+ codegen.mainClassSignature);
+
+ cfw.add(ByteCode.DUP2);
+ // stack: ... functionObj directFunct functionObj directFunct
+
+ int regularCall = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPNE, regularCall);
+
+ // stack: ... functionObj directFunct
+ short stackHeight = cfw.getStackTop();
+ cfw.add(ByteCode.SWAP);
+ cfw.add(ByteCode.POP);
+ // stack: ... directFunct
+ if (compilerEnv.isUseDynamicScope()) {
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ } else {
+ cfw.add(ByteCode.DUP);
+ // stack: ... directFunct directFunct
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Scriptable",
+ "getParentScope",
+ "()Lorg/mozilla/javascript/Scriptable;");
+ // stack: ... directFunct scope
+ cfw.addALoad(contextLocal);
+ // stack: ... directFunct scope cx
+ cfw.add(ByteCode.SWAP);
+ }
+ // stack: ... directFunc cx scope
+
+ if (type == Token.NEW) {
+ cfw.add(ByteCode.ACONST_NULL);
+ } else {
+ cfw.addALoad(thisObjLocal);
+ }
+ // stack: ... directFunc cx scope thisObj
+/*
+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.
+*/
+ Node argChild = firstArgChild;
+ while (argChild != null) {
+ int dcp_register = nodeIsDirectCallParameter(argChild);
+ if (dcp_register >= 0) {
+ cfw.addALoad(dcp_register);
+ cfw.addDLoad(dcp_register + 1);
+ } else if (argChild.getIntProp(Node.ISNUMBER_PROP, -1)
+ == Node.BOTH)
+ {
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ generateExpression(argChild, node);
+ } else {
+ generateExpression(argChild, node);
+ cfw.addPush(0.0);
+ }
+ argChild = argChild.getNext();
+ }
+
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "emptyArgs", "[Ljava/lang/Object;");
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ codegen.mainClassName,
+ (type == Token.NEW)
+ ? codegen.getDirectCtorName(target.fnode)
+ : codegen.getBodyMethodName(target.fnode),
+ codegen.getBodyMethodSignature(target.fnode));
+
+ cfw.add(ByteCode.GOTO, beyond);
+
+ cfw.markLabel(regularCall, stackHeight);
+ // stack: ... functionObj directFunct
+ cfw.add(ByteCode.POP);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ // stack: ... functionObj cx scope
+ if (type != Token.NEW) {
+ cfw.addALoad(thisObjLocal);
+ releaseWordLocal(thisObjLocal);
+ // stack: ... functionObj cx scope thisObj
+ }
+ // XXX: this will generate code for the child array the second time,
+ // so expression code generation better not to alter tree structure...
+ generateCallArgArray(node, firstArgChild, true);
+
+ if (type == Token.NEW) {
+ addScriptRuntimeInvoke(
+ "newObject",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ } else {
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Callable",
+ "call",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Ljava/lang/Object;");
+ }
+
+ cfw.markLabel(beyond);
+ }
+
+ private void generateCallArgArray(Node node, Node argChild, boolean directCall)
+ {
+ int argCount = 0;
+ for (Node child = argChild; child != null; child = child.getNext()) {
+ ++argCount;
+ }
+ // load array object to set arguments
+ if (argCount == 1 && itsOneArgArray >= 0) {
+ cfw.addALoad(itsOneArgArray);
+ } else {
+ addNewObjectArray(argCount);
+ }
+ // Copy arguments into it
+ for (int i = 0; i != argCount; ++i) {
+ // If we are compiling a generator an argument could be the result
+ // of a yield. In that case we will have an immediate on the stack
+ // which we need to avoid
+ if (!isGenerator) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ }
+
+ if (!directCall) {
+ generateExpression(argChild, node);
+ } else {
+ // 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.
+ int dcp_register = nodeIsDirectCallParameter(argChild);
+ if (dcp_register >= 0) {
+ dcpLoadAsObject(dcp_register);
+ } else {
+ generateExpression(argChild, node);
+ int childNumberFlag
+ = argChild.getIntProp(Node.ISNUMBER_PROP, -1);
+ if (childNumberFlag == Node.BOTH) {
+ addDoubleWrap();
+ }
+ }
+ }
+
+ // When compiling generators, any argument to a method may be a
+ // yield expression. Hence we compile the argument first and then
+ // load the argument index and assign the value to the args array.
+ if (isGenerator) {
+ short tempLocal = getNewWordLocal();
+ cfw.addAStore(tempLocal);
+ cfw.add(ByteCode.CHECKCAST, "[Ljava/lang/Object;");
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ cfw.addALoad(tempLocal);
+ releaseWordLocal(tempLocal);
+ }
+
+ cfw.add(ByteCode.AASTORE);
+
+ argChild = argChild.getNext();
+ }
+ }
+
+ private void generateFunctionAndThisObj(Node node, Node parent)
+ {
+ // Place on stack (function object, function this) pair
+ int type = node.getType();
+ switch (node.getType()) {
+ case Token.GETPROPNOWARN:
+ throw Kit.codeBug();
+
+ case Token.GETPROP:
+ case Token.GETELEM: {
+ Node target = node.getFirstChild();
+ generateExpression(target, node);
+ Node id = target.getNext();
+ if (type == Token.GETPROP) {
+ String property = id.getString();
+ cfw.addPush(property);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getPropFunctionAndThis",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Callable;");
+ } else {
+ // Optimizer do not optimize this case for now
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
+ throw Codegen.badTree();
+ generateExpression(id, node); // id
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getElemFunctionAndThis",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Callable;");
+ }
+ break;
+ }
+
+ case Token.NAME: {
+ String name = node.getString();
+ cfw.addPush(name);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "getNameFunctionAndThis",
+ "(Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Callable;");
+ break;
+ }
+
+ default: // including GETVAR
+ generateExpression(node, parent);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getValueFunctionAndThis",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Callable;");
+ break;
+ }
+ // Get thisObj prepared by get(Name|Prop|Elem|Value)FunctionAndThis
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "lastStoredScriptable",
+ "(Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+
+ private void updateLineNumber(Node node)
+ {
+ itsLineNumber = node.getLineno();
+ if (itsLineNumber == -1)
+ return;
+ cfw.addLineNumberEntry((short)itsLineNumber);
+ }
+
+ private void visitTryCatchFinally(Node.Jump 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.
+
+ short savedVariableObject = getNewWordLocal();
+ cfw.addALoad(variableObjectLocal);
+ cfw.addAStore(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. */
+
+ int startLabel = cfw.acquireLabel();
+ cfw.markLabel(startLabel, (short)0);
+
+ Node catchTarget = node.target;
+ Node finallyTarget = node.getFinally();
+
+ // create a table for the equivalent of JSR returns
+ if (isGenerator && finallyTarget != null) {
+ FinallyReturnPoint ret = new FinallyReturnPoint();
+ if (finallys == null) {
+ finallys = new Hashtable();
+ }
+ // add the finally target to hashtable
+ finallys.put(finallyTarget, ret);
+ // add the finally node as well to the hash table
+ finallys.put(finallyTarget.getNext(), ret);
+ }
+
+ while (child != null) {
+ generateStatement(child);
+ child = child.getNext();
+ }
+
+ // control flow skips the handlers
+ int realEnd = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, realEnd);
+
+ int exceptionLocal = getLocalBlockRegister(node);
+ // javascript handler; unwrap exception and GOTO to javascript
+ // catch area.
+ if (catchTarget != null) {
+ // get the label to goto
+ int catchLabel = catchTarget.labelId();
+
+ generateCatchBlock(JAVASCRIPT_EXCEPTION, savedVariableObject,
+ catchLabel, startLabel, exceptionLocal);
+ /*
+ * catch WrappedExceptions, see if they are wrapped
+ * JavaScriptExceptions. Otherwise, rethrow.
+ */
+ generateCatchBlock(EVALUATOR_EXCEPTION, savedVariableObject,
+ catchLabel, startLabel, exceptionLocal);
+
+ /*
+ we also need to catch EcmaErrors and feed the
+ associated error object to the handler
+ */
+ generateCatchBlock(ECMAERROR_EXCEPTION, savedVariableObject,
+ catchLabel, startLabel, exceptionLocal);
+
+ Context cx = Context.getCurrentContext();
+ if (cx != null &&
+ cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS))
+ {
+ generateCatchBlock(THROWABLE_EXCEPTION, savedVariableObject,
+ catchLabel, startLabel, exceptionLocal);
+ }
+ }
+
+ // finally handler; catch all exceptions, store to a local; JSR to
+ // the finally, then re-throw.
+ if (finallyTarget != null) {
+ int finallyHandler = cfw.acquireLabel();
+ cfw.markHandler(finallyHandler);
+ cfw.addAStore(exceptionLocal);
+
+ // reset the variable object local
+ cfw.addALoad(savedVariableObject);
+ cfw.addAStore(variableObjectLocal);
+
+ // get the label to JSR to
+ int finallyLabel = finallyTarget.labelId();
+ if (isGenerator)
+ addGotoWithReturn(finallyTarget);
+ else
+ cfw.add(ByteCode.JSR, finallyLabel);
+
+ // rethrow
+ cfw.addALoad(exceptionLocal);
+ if (isGenerator)
+ cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable");
+ cfw.add(ByteCode.ATHROW);
+
+ // mark the handler
+ cfw.addExceptionHandler(startLabel, finallyLabel,
+ finallyHandler, null); // catch any
+ }
+ releaseWordLocal(savedVariableObject);
+ cfw.markLabel(realEnd);
+ }
+
+ private static final int JAVASCRIPT_EXCEPTION = 0;
+ private static final int EVALUATOR_EXCEPTION = 1;
+ private static final int ECMAERROR_EXCEPTION = 2;
+ private static final int THROWABLE_EXCEPTION = 3;
+
+ private void generateCatchBlock(int exceptionType,
+ short savedVariableObject,
+ int catchLabel, int startLabel,
+ int exceptionLocal)
+ {
+ int handler = cfw.acquireLabel();
+ cfw.markHandler(handler);
+
+ // MS JVM gets cranky if the exception object is left on the stack
+ cfw.addAStore(exceptionLocal);
+
+ // reset the variable object local
+ cfw.addALoad(savedVariableObject);
+ cfw.addAStore(variableObjectLocal);
+
+ String exceptionName;
+ if (exceptionType == JAVASCRIPT_EXCEPTION) {
+ exceptionName = "org/mozilla/javascript/JavaScriptException";
+ } else if (exceptionType == EVALUATOR_EXCEPTION) {
+ exceptionName = "org/mozilla/javascript/EvaluatorException";
+ } else if (exceptionType == ECMAERROR_EXCEPTION) {
+ exceptionName = "org/mozilla/javascript/EcmaError";
+ } else if (exceptionType == THROWABLE_EXCEPTION) {
+ exceptionName = "java/lang/Throwable";
+ } else {
+ throw Kit.codeBug();
+ }
+
+ // mark the handler
+ cfw.addExceptionHandler(startLabel, catchLabel, handler,
+ exceptionName);
+
+ cfw.add(ByteCode.GOTO, catchLabel);
+ }
+
+
+ private boolean generateSaveLocals(Node node)
+ {
+ int count = 0;
+ for (int i = 0; i < firstFreeLocal; i++) {
+ if (locals[i] != 0)
+ count++;
+ }
+
+ if (count == 0) {
+ ((FunctionNode)scriptOrFn).addLiveLocals(node, null);
+ return false;
+ }
+
+ // calculate the max locals
+ maxLocals = maxLocals > count ? maxLocals : count;
+
+ // create a locals list
+ int[] ls = new int[count];
+ int s = 0;
+ for (int i = 0; i < firstFreeLocal; i++) {
+ if (locals[i] != 0) {
+ ls[s] = i;
+ s++;
+ }
+ }
+
+ // save the locals
+ ((FunctionNode)scriptOrFn).addLiveLocals(node, ls);
+
+ // save locals
+ generateGetGeneratorLocalsState();
+ for (int i = 0; i < count; i++) {
+ cfw.add(ByteCode.DUP);
+ cfw.addLoadConstant(i);
+ cfw.addALoad(ls[i]);
+ cfw.add(ByteCode.AASTORE);
+ }
+ // pop the array off the stack
+ cfw.add(ByteCode.POP);
+
+ return true;
+ }
+
+ private void visitSwitch(Node.Jump switchNode, Node child)
+ {
+ // See comments in IRFactory.createSwitch() for description
+ // of SWITCH node
+
+ generateExpression(child, switchNode);
+ // save selector value
+ short selector = getNewWordLocal();
+ cfw.addAStore(selector);
+
+ for (Node.Jump caseNode = (Node.Jump)child.getNext();
+ caseNode != null;
+ caseNode = (Node.Jump)caseNode.getNext())
+ {
+ if (caseNode.getType() != Token.CASE)
+ throw Codegen.badTree();
+ Node test = caseNode.getFirstChild();
+ generateExpression(test, caseNode);
+ cfw.addALoad(selector);
+ addScriptRuntimeInvoke("shallowEq",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +")Z");
+ addGoto(caseNode.target, ByteCode.IFNE);
+ }
+ releaseWordLocal(selector);
+ }
+
+ private void visitTypeofname(Node node)
+ {
+ if (hasVarsInRegs) {
+ int varIndex = fnCurrent.fnode.getIndexForNameNode(node);
+ if (varIndex >= 0) {
+ if (fnCurrent.isNumberVar(varIndex)) {
+ cfw.addPush("number");
+ } else if (varIsDirectCallParameter(varIndex)) {
+ int dcp_register = varRegisters[varIndex];
+ cfw.addALoad(dcp_register);
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
+ "Ljava/lang/Class;");
+ int isNumberLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
+ short stack = cfw.getStackTop();
+ cfw.addALoad(dcp_register);
+ addScriptRuntimeInvoke("typeof",
+ "(Ljava/lang/Object;"
+ +")Ljava/lang/String;");
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(isNumberLabel, stack);
+ cfw.addPush("number");
+ cfw.markLabel(beyond);
+ } else {
+ cfw.addALoad(varRegisters[varIndex]);
+ addScriptRuntimeInvoke("typeof",
+ "(Ljava/lang/Object;"
+ +")Ljava/lang/String;");
+ }
+ return;
+ }
+ }
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(node.getString());
+ addScriptRuntimeInvoke("typeofName",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/String;");
+ }
+
+ /**
+ * Save the current code offset. This saved code offset is used to
+ * compute instruction counts in subsequent calls to
+ * {@link #addInstructionCount}.
+ */
+ private void saveCurrentCodeOffset() {
+ savedCodeOffset = cfw.getCurrentCodeOffset();
+ }
+
+ /**
+ * Generate calls to ScriptRuntime.addInstructionCount to keep track of
+ * executed instructions and call <code>observeInstructionCount()</code>
+ * if a threshold is exceeded.
+ */
+ private void addInstructionCount() {
+ int count = cfw.getCurrentCodeOffset() - savedCodeOffset;
+ if (count == 0)
+ return;
+ cfw.addALoad(contextLocal);
+ cfw.addPush(count);
+ addScriptRuntimeInvoke("addInstructionCount",
+ "(Lorg/mozilla/javascript/Context;"
+ +"I)V");
+ }
+
+ private void visitIncDec(Node node)
+ {
+ int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP);
+ Node child = node.getFirstChild();
+ switch (child.getType()) {
+ case Token.GETVAR:
+ if (!hasVarsInRegs) Kit.codeBug();
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
+ int varIndex = fnCurrent.getVarIndex(child);
+ short reg = varRegisters[varIndex];
+ int offset = varIsDirectCallParameter(varIndex) ? 1 : 0;
+ cfw.addDLoad(reg + offset);
+ if (post) {
+ cfw.add(ByteCode.DUP2);
+ }
+ cfw.addPush(1.0);
+ if ((incrDecrMask & Node.DECR_FLAG) == 0) {
+ cfw.add(ByteCode.DADD);
+ } else {
+ cfw.add(ByteCode.DSUB);
+ }
+ if (!post) {
+ cfw.add(ByteCode.DUP2);
+ }
+ cfw.addDStore(reg + offset);
+ } else {
+ boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
+ int varIndex = fnCurrent.getVarIndex(child);
+ short reg = varRegisters[varIndex];
+ cfw.addALoad(reg);
+ if (post) {
+ cfw.add(ByteCode.DUP);
+ }
+ addObjectToDouble();
+ cfw.addPush(1.0);
+ if ((incrDecrMask & Node.DECR_FLAG) == 0) {
+ cfw.add(ByteCode.DADD);
+ } else {
+ cfw.add(ByteCode.DSUB);
+ }
+ addDoubleWrap();
+ if (!post) {
+ cfw.add(ByteCode.DUP);
+ }
+ cfw.addAStore(reg);
+ break;
+ }
+ break;
+ case Token.NAME:
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(child.getString()); // push name
+ cfw.addALoad(contextLocal);
+ cfw.addPush(incrDecrMask);
+ addScriptRuntimeInvoke("nameIncrDecr",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I)Ljava/lang/Object;");
+ break;
+ case Token.GETPROPNOWARN:
+ throw Kit.codeBug();
+ case Token.GETPROP: {
+ Node getPropChild = child.getFirstChild();
+ generateExpression(getPropChild, node);
+ generateExpression(getPropChild.getNext(), node);
+ cfw.addALoad(contextLocal);
+ cfw.addPush(incrDecrMask);
+ addScriptRuntimeInvoke("propIncrDecr",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I)Ljava/lang/Object;");
+ break;
+ }
+ case Token.GETELEM: {
+ Node elemChild = child.getFirstChild();
+ generateExpression(elemChild, node);
+ generateExpression(elemChild.getNext(), node);
+ cfw.addALoad(contextLocal);
+ cfw.addPush(incrDecrMask);
+ if (elemChild.getNext().getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ addOptRuntimeInvoke("elemIncrDecr",
+ "(Ljava/lang/Object;"
+ +"D"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Ljava/lang/Object;");
+ } else {
+ addScriptRuntimeInvoke("elemIncrDecr",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Ljava/lang/Object;");
+ }
+ break;
+ }
+ case Token.GET_REF: {
+ Node refChild = child.getFirstChild();
+ generateExpression(refChild, node);
+ cfw.addALoad(contextLocal);
+ cfw.addPush(incrDecrMask);
+ addScriptRuntimeInvoke(
+ "refIncrDecr",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I)Ljava/lang/Object;");
+ break;
+ }
+ default:
+ Codegen.badTree();
+ }
+ }
+
+ private static boolean isArithmeticNode(Node node)
+ {
+ int type = node.getType();
+ return (type == Token.SUB)
+ || (type == Token.MOD)
+ || (type == Token.DIV)
+ || (type == Token.MUL);
+ }
+
+ private void visitArithmetic(Node node, int opCode, Node child,
+ Node parent)
+ {
+ int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
+ if (childNumberFlag != -1) {
+ generateExpression(child, node);
+ generateExpression(child.getNext(), node);
+ cfw.add(opCode);
+ }
+ else {
+ boolean childOfArithmetic = isArithmeticNode(parent);
+ generateExpression(child, node);
+ if (!isArithmeticNode(child))
+ addObjectToDouble();
+ generateExpression(child.getNext(), node);
+ if (!isArithmeticNode(child.getNext()))
+ addObjectToDouble();
+ cfw.add(opCode);
+ if (!childOfArithmetic) {
+ addDoubleWrap();
+ }
+ }
+ }
+
+ private void visitBitOp(Node node, int type, Node child)
+ {
+ int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
+ generateExpression(child, node);
+
+ // 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 == Token.URSH) {
+ addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)J");
+ generateExpression(child.getNext(), node);
+ addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
+ // Looks like we need to explicitly mask the shift to 5 bits -
+ // LUSHR takes 6 bits.
+ cfw.addPush(31);
+ cfw.add(ByteCode.IAND);
+ cfw.add(ByteCode.LUSHR);
+ cfw.add(ByteCode.L2D);
+ addDoubleWrap();
+ return;
+ }
+ if (childNumberFlag == -1) {
+ addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
+ generateExpression(child.getNext(), node);
+ addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
+ }
+ else {
+ addScriptRuntimeInvoke("toInt32", "(D)I");
+ generateExpression(child.getNext(), node);
+ addScriptRuntimeInvoke("toInt32", "(D)I");
+ }
+ switch (type) {
+ case Token.BITOR:
+ cfw.add(ByteCode.IOR);
+ break;
+ case Token.BITXOR:
+ cfw.add(ByteCode.IXOR);
+ break;
+ case Token.BITAND:
+ cfw.add(ByteCode.IAND);
+ break;
+ case Token.RSH:
+ cfw.add(ByteCode.ISHR);
+ break;
+ case Token.LSH:
+ cfw.add(ByteCode.ISHL);
+ break;
+ default:
+ throw Codegen.badTree();
+ }
+ cfw.add(ByteCode.I2D);
+ if (childNumberFlag == -1) {
+ addDoubleWrap();
+ }
+ }
+
+ private int nodeIsDirectCallParameter(Node node)
+ {
+ if (node.getType() == Token.GETVAR
+ && inDirectCallFunction && !itsForcedObjectParameters)
+ {
+ int varIndex = fnCurrent.getVarIndex(node);
+ if (fnCurrent.isParameter(varIndex)) {
+ return varRegisters[varIndex];
+ }
+ }
+ return -1;
+ }
+
+ private boolean varIsDirectCallParameter(int varIndex)
+ {
+ return fnCurrent.isParameter(varIndex)
+ && inDirectCallFunction && !itsForcedObjectParameters;
+ }
+
+ private void genSimpleCompare(int type, int trueGOTO, int falseGOTO)
+ {
+ if (trueGOTO == -1) throw Codegen.badTree();
+ switch (type) {
+ case Token.LE :
+ cfw.add(ByteCode.DCMPG);
+ cfw.add(ByteCode.IFLE, trueGOTO);
+ break;
+ case Token.GE :
+ cfw.add(ByteCode.DCMPL);
+ cfw.add(ByteCode.IFGE, trueGOTO);
+ break;
+ case Token.LT :
+ cfw.add(ByteCode.DCMPG);
+ cfw.add(ByteCode.IFLT, trueGOTO);
+ break;
+ case Token.GT :
+ cfw.add(ByteCode.DCMPL);
+ cfw.add(ByteCode.IFGT, trueGOTO);
+ break;
+ default :
+ throw Codegen.badTree();
+
+ }
+ if (falseGOTO != -1)
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ }
+
+ private void visitIfJumpRelOp(Node node, Node child,
+ int trueGOTO, int falseGOTO)
+ {
+ if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree();
+ int type = node.getType();
+ Node rChild = child.getNext();
+ if (type == Token.INSTANCEOF || type == Token.IN) {
+ generateExpression(child, node);
+ generateExpression(rChild, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ (type == Token.INSTANCEOF) ? "instanceOf" : "in",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Z");
+ cfw.add(ByteCode.IFNE, trueGOTO);
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ return;
+ }
+ int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
+ int left_dcp_register = nodeIsDirectCallParameter(child);
+ int right_dcp_register = nodeIsDirectCallParameter(rChild);
+ if (childNumberFlag != -1) {
+ // Force numeric context on both parameters and optimize
+ // direct call case as Optimizer currently does not handle it
+
+ if (childNumberFlag != Node.RIGHT) {
+ // Left already has number content
+ generateExpression(child, node);
+ } else if (left_dcp_register != -1) {
+ dcpLoadAsNumber(left_dcp_register);
+ } else {
+ generateExpression(child, node);
+ addObjectToDouble();
+ }
+
+ if (childNumberFlag != Node.LEFT) {
+ // Right already has number content
+ generateExpression(rChild, node);
+ } else if (right_dcp_register != -1) {
+ dcpLoadAsNumber(right_dcp_register);
+ } else {
+ generateExpression(rChild, node);
+ addObjectToDouble();
+ }
+
+ genSimpleCompare(type, trueGOTO, falseGOTO);
+
+ } else {
+ if (left_dcp_register != -1 && right_dcp_register != -1) {
+ // Generate code to dynamically check for number content
+ // if both operands are dcp
+ short stack = cfw.getStackTop();
+ int leftIsNotNumber = cfw.acquireLabel();
+ cfw.addALoad(left_dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ cfw.add(ByteCode.IF_ACMPNE, leftIsNotNumber);
+ cfw.addDLoad(left_dcp_register + 1);
+ dcpLoadAsNumber(right_dcp_register);
+ genSimpleCompare(type, trueGOTO, falseGOTO);
+ if (stack != cfw.getStackTop()) throw Codegen.badTree();
+
+ cfw.markLabel(leftIsNotNumber);
+ int rightIsNotNumber = cfw.acquireLabel();
+ cfw.addALoad(right_dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ cfw.add(ByteCode.IF_ACMPNE, rightIsNotNumber);
+ cfw.addALoad(left_dcp_register);
+ addObjectToDouble();
+ cfw.addDLoad(right_dcp_register + 1);
+ genSimpleCompare(type, trueGOTO, falseGOTO);
+ if (stack != cfw.getStackTop()) throw Codegen.badTree();
+
+ cfw.markLabel(rightIsNotNumber);
+ // Load both register as objects to call generic cmp_*
+ cfw.addALoad(left_dcp_register);
+ cfw.addALoad(right_dcp_register);
+
+ } else {
+ generateExpression(child, node);
+ generateExpression(rChild, node);
+ }
+
+ if (type == Token.GE || type == Token.GT) {
+ cfw.add(ByteCode.SWAP);
+ }
+ String routine = ((type == Token.LT)
+ || (type == Token.GT)) ? "cmp_LT" : "cmp_LE";
+ addScriptRuntimeInvoke(routine,
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +")Z");
+ cfw.add(ByteCode.IFNE, trueGOTO);
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ }
+ }
+
+ private void visitIfJumpEqOp(Node node, Node child,
+ int trueGOTO, int falseGOTO)
+ {
+ if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree();
+
+ short stackInitial = cfw.getStackTop();
+ int type = node.getType();
+ Node rChild = child.getNext();
+
+ // Optimize if one of operands is null
+ if (child.getType() == Token.NULL || rChild.getType() == Token.NULL) {
+ // eq is symmetric in this case
+ if (child.getType() == Token.NULL) {
+ child = rChild;
+ }
+ generateExpression(child, node);
+ if (type == Token.SHEQ || type == Token.SHNE) {
+ int testCode = (type == Token.SHEQ)
+ ? ByteCode.IFNULL : ByteCode.IFNONNULL;
+ cfw.add(testCode, trueGOTO);
+ } else {
+ if (type != Token.EQ) {
+ // swap false/true targets for !=
+ if (type != Token.NE) throw Codegen.badTree();
+ int tmp = trueGOTO;
+ trueGOTO = falseGOTO;
+ falseGOTO = tmp;
+ }
+ cfw.add(ByteCode.DUP);
+ int undefCheckLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IFNONNULL, undefCheckLabel);
+ short stack = cfw.getStackTop();
+ cfw.add(ByteCode.POP);
+ cfw.add(ByteCode.GOTO, trueGOTO);
+ cfw.markLabel(undefCheckLabel, stack);
+ Codegen.pushUndefined(cfw);
+ cfw.add(ByteCode.IF_ACMPEQ, trueGOTO);
+ }
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ } else {
+ int child_dcp_register = nodeIsDirectCallParameter(child);
+ if (child_dcp_register != -1
+ && rChild.getType() == Token.TO_OBJECT)
+ {
+ Node convertChild = rChild.getFirstChild();
+ if (convertChild.getType() == Token.NUMBER) {
+ cfw.addALoad(child_dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int notNumbersLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPNE, notNumbersLabel);
+ cfw.addDLoad(child_dcp_register + 1);
+ cfw.addPush(convertChild.getDouble());
+ cfw.add(ByteCode.DCMPL);
+ if (type == Token.EQ)
+ cfw.add(ByteCode.IFEQ, trueGOTO);
+ else
+ cfw.add(ByteCode.IFNE, trueGOTO);
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ cfw.markLabel(notNumbersLabel);
+ // fall thru into generic handling
+ }
+ }
+
+ generateExpression(child, node);
+ generateExpression(rChild, node);
+
+ String name;
+ int testCode;
+ switch (type) {
+ case Token.EQ:
+ name = "eq";
+ testCode = ByteCode.IFNE;
+ break;
+ case Token.NE:
+ name = "eq";
+ testCode = ByteCode.IFEQ;
+ break;
+ case Token.SHEQ:
+ name = "shallowEq";
+ testCode = ByteCode.IFNE;
+ break;
+ case Token.SHNE:
+ name = "shallowEq";
+ testCode = ByteCode.IFEQ;
+ break;
+ default:
+ throw Codegen.badTree();
+ }
+ addScriptRuntimeInvoke(name,
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +")Z");
+ cfw.add(testCode, trueGOTO);
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ }
+ if (stackInitial != cfw.getStackTop()) throw Codegen.badTree();
+ }
+
+ private void visitSetName(Node node, Node child)
+ {
+ String name = node.getFirstChild().getString();
+ while (child != null) {
+ generateExpression(child, node);
+ child = child.getNext();
+ }
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(name);
+ addScriptRuntimeInvoke(
+ "setName",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/Object;");
+ }
+
+ private void visitSetConst(Node node, Node child)
+ {
+ String name = node.getFirstChild().getString();
+ while (child != null) {
+ generateExpression(child, node);
+ child = child.getNext();
+ }
+ cfw.addALoad(contextLocal);
+ cfw.addPush(name);
+ addScriptRuntimeInvoke(
+ "setConst",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/Object;");
+ }
+
+ private void visitGetVar(Node node)
+ {
+ if (!hasVarsInRegs) Kit.codeBug();
+ int varIndex = fnCurrent.getVarIndex(node);
+ short reg = varRegisters[varIndex];
+ if (varIsDirectCallParameter(varIndex)) {
+ // 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 (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ dcpLoadAsNumber(reg);
+ } else {
+ dcpLoadAsObject(reg);
+ }
+ } else if (fnCurrent.isNumberVar(varIndex)) {
+ cfw.addDLoad(reg);
+ } else {
+ cfw.addALoad(reg);
+ }
+ }
+
+ private void visitSetVar(Node node, Node child, boolean needValue)
+ {
+ if (!hasVarsInRegs) Kit.codeBug();
+ int varIndex = fnCurrent.getVarIndex(node);
+ generateExpression(child.getNext(), node);
+ boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
+ short reg = varRegisters[varIndex];
+ boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
+ if (constDeclarations[varIndex]) {
+ if (!needValue) {
+ if (isNumber)
+ cfw.add(ByteCode.POP2);
+ else
+ cfw.add(ByteCode.POP);
+ }
+ }
+ else if (varIsDirectCallParameter(varIndex)) {
+ if (isNumber) {
+ if (needValue) cfw.add(ByteCode.DUP2);
+ cfw.addALoad(reg);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int isNumberLabel = cfw.acquireLabel();
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
+ short stack = cfw.getStackTop();
+ addDoubleWrap();
+ cfw.addAStore(reg);
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(isNumberLabel, stack);
+ cfw.addDStore(reg + 1);
+ cfw.markLabel(beyond);
+ }
+ else {
+ if (needValue) cfw.add(ByteCode.DUP);
+ cfw.addAStore(reg);
+ }
+ } else {
+ boolean isNumberVar = fnCurrent.isNumberVar(varIndex);
+ if (isNumber) {
+ if (isNumberVar) {
+ cfw.addDStore(reg);
+ if (needValue) cfw.addDLoad(reg);
+ } else {
+ if (needValue) cfw.add(ByteCode.DUP2);
+ // Cannot save number in variable since !isNumberVar,
+ // so convert to object
+ addDoubleWrap();
+ cfw.addAStore(reg);
+ }
+ } else {
+ if (isNumberVar) Kit.codeBug();
+ cfw.addAStore(reg);
+ if (needValue) cfw.addALoad(reg);
+ }
+ }
+ }
+
+ private void visitSetConstVar(Node node, Node child, boolean needValue)
+ {
+ if (!hasVarsInRegs) Kit.codeBug();
+ int varIndex = fnCurrent.getVarIndex(node);
+ generateExpression(child.getNext(), node);
+ boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
+ short reg = varRegisters[varIndex];
+ int beyond = cfw.acquireLabel();
+ int noAssign = cfw.acquireLabel();
+ if (isNumber) {
+ cfw.addILoad(reg + 2);
+ cfw.add(ByteCode.IFNE, noAssign);
+ short stack = cfw.getStackTop();
+ cfw.addPush(1);
+ cfw.addIStore(reg + 2);
+ cfw.addDStore(reg);
+ if (needValue) {
+ cfw.addDLoad(reg);
+ cfw.markLabel(noAssign, stack);
+ } else {
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(noAssign, stack);
+ cfw.add(ByteCode.POP2);
+ }
+ }
+ else {
+ cfw.addILoad(reg + 1);
+ cfw.add(ByteCode.IFNE, noAssign);
+ short stack = cfw.getStackTop();
+ cfw.addPush(1);
+ cfw.addIStore(reg + 1);
+ cfw.addAStore(reg);
+ if (needValue) {
+ cfw.addALoad(reg);
+ cfw.markLabel(noAssign, stack);
+ } else {
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(noAssign, stack);
+ cfw.add(ByteCode.POP);
+ }
+ }
+ cfw.markLabel(beyond);
+ }
+
+ private void visitGetProp(Node node, Node child)
+ {
+ generateExpression(child, node); // object
+ Node nameChild = child.getNext();
+ generateExpression(nameChild, node); // the name
+ if (node.getType() == Token.GETPROPNOWARN) {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectPropNoWarn",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ return;
+ }
+ /*
+ for 'this.foo' we call getObjectProp(Scriptable...) which can
+ skip some casting overhead.
+ */
+ int childType = child.getType();
+ if (childType == Token.THIS && nameChild.getType() == Token.STRING) {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectProp",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ } else {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectProp",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+
+ private void visitSetProp(int type, Node node, Node child)
+ {
+ Node objectChild = child;
+ generateExpression(child, node);
+ child = child.getNext();
+ if (type == Token.SETPROP_OP) {
+ cfw.add(ByteCode.DUP);
+ }
+ Node nameChild = child;
+ generateExpression(child, node);
+ child = child.getNext();
+ if (type == Token.SETPROP_OP) {
+ // stack: ... object object name -> ... object name object name
+ cfw.add(ByteCode.DUP_X1);
+ //for 'this.foo += ...' we call thisGet which can skip some
+ //casting overhead.
+ if (objectChild.getType() == Token.THIS
+ && nameChild.getType() == Token.STRING)
+ {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectProp",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ } else {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectProp",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "setObjectProp",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+
+ private void visitSetElem(int type, Node node, Node child)
+ {
+ generateExpression(child, node);
+ child = child.getNext();
+ if (type == Token.SETELEM_OP) {
+ cfw.add(ByteCode.DUP);
+ }
+ generateExpression(child, node);
+ child = child.getNext();
+ boolean indexIsNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
+ if (type == Token.SETELEM_OP) {
+ if (indexIsNumber) {
+ // stack: ... object object number
+ // -> ... object number object number
+ cfw.add(ByteCode.DUP2_X1);
+ cfw.addALoad(contextLocal);
+ addOptRuntimeInvoke(
+ "getObjectIndex",
+ "(Ljava/lang/Object;D"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ } else {
+ // stack: ... object object indexObject
+ // -> ... object indexObject object indexObject
+ cfw.add(ByteCode.DUP_X1);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectElem",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ if (indexIsNumber) {
+ addScriptRuntimeInvoke(
+ "setObjectIndex",
+ "(Ljava/lang/Object;"
+ +"D"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ } else {
+ addScriptRuntimeInvoke(
+ "setObjectElem",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+
+ private void visitDotQuery(Node node, Node child)
+ {
+ updateLineNumber(node);
+ generateExpression(child, node);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("enterDotQuery",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+
+ // add push null/pop with label in between to simplify code for loop
+ // continue when it is necessary to pop the null result from
+ // updateDotQuery
+ cfw.add(ByteCode.ACONST_NULL);
+ int queryLoopStart = cfw.acquireLabel();
+ cfw.markLabel(queryLoopStart); // loop continue jumps here
+ cfw.add(ByteCode.POP);
+
+ generateExpression(child.getNext(), node);
+ addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z");
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("updateDotQuery",
+ "(Z"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;");
+ cfw.add(ByteCode.DUP);
+ cfw.add(ByteCode.IFNULL, queryLoopStart);
+ // stack: ... non_null_result_of_updateDotQuery
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("leaveDotQuery",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ }
+
+ private int getLocalBlockRegister(Node node)
+ {
+ Node localBlock = (Node)node.getProp(Node.LOCAL_BLOCK_PROP);
+ int localSlot = localBlock.getExistingIntProp(Node.LOCAL_PROP);
+ return localSlot;
+ }
+
+ private void dcpLoadAsNumber(int dcp_register)
+ {
+ cfw.addALoad(dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int isNumberLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
+ short stack = cfw.getStackTop();
+ cfw.addALoad(dcp_register);
+ addObjectToDouble();
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(isNumberLabel, stack);
+ cfw.addDLoad(dcp_register + 1);
+ cfw.markLabel(beyond);
+ }
+
+ private void dcpLoadAsObject(int dcp_register)
+ {
+ cfw.addALoad(dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int isNumberLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
+ short stack = cfw.getStackTop();
+ cfw.addALoad(dcp_register);
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(isNumberLabel, stack);
+ cfw.addDLoad(dcp_register + 1);
+ addDoubleWrap();
+ cfw.markLabel(beyond);
+ }
+
+ private void addGoto(Node target, int jumpcode)
+ {
+ int targetLabel = getTargetLabel(target);
+ cfw.add(jumpcode, targetLabel);
+ }
+
+ private void addObjectToDouble()
+ {
+ addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)D");
+ }
+
+ private void addNewObjectArray(int size)
+ {
+ if (size == 0) {
+ if (itsZeroArgArray >= 0) {
+ cfw.addALoad(itsZeroArgArray);
+ } else {
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "emptyArgs", "[Ljava/lang/Object;");
+ }
+ } else {
+ cfw.addPush(size);
+ cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
+ }
+ }
+
+ private void addScriptRuntimeInvoke(String methodName,
+ String methodSignature)
+ {
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org.mozilla.javascript.ScriptRuntime",
+ methodName,
+ methodSignature);
+ }
+
+ private void addOptRuntimeInvoke(String methodName,
+ String methodSignature)
+ {
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ methodName,
+ methodSignature);
+ }
+
+ private void addJumpedBooleanWrap(int trueLabel, int falseLabel)
+ {
+ cfw.markLabel(falseLabel);
+ int skip = cfw.acquireLabel();
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "FALSE", "Ljava/lang/Boolean;");
+ cfw.add(ByteCode.GOTO, skip);
+ cfw.markLabel(trueLabel);
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "TRUE", "Ljava/lang/Boolean;");
+ cfw.markLabel(skip);
+ cfw.adjustStackTop(-1); // only have 1 of true/false
+ }
+
+ private void addDoubleWrap()
+ {
+ addOptRuntimeInvoke("wrapDouble", "(D)Ljava/lang/Double;");
+ }
+
+ /**
+ * Const locals use an extra slot to hold the has-been-assigned-once flag at
+ * runtime.
+ * @param isConst true iff the variable is const
+ * @return the register for the word pair (double/long)
+ */
+ private short getNewWordPairLocal(boolean isConst)
+ {
+ short result = getConsecutiveSlots(2, isConst);
+ if (result < (MAX_LOCALS - 1)) {
+ locals[result] = 1;
+ locals[result + 1] = 1;
+ if (isConst)
+ locals[result + 2] = 1;
+ if (result == firstFreeLocal) {
+ for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) {
+ if (locals[i] == 0) {
+ firstFreeLocal = (short) i;
+ if (localsMax < firstFreeLocal)
+ localsMax = firstFreeLocal;
+ return result;
+ }
+ }
+ }
+ else {
+ return result;
+ }
+ }
+ throw Context.reportRuntimeError("Program too complex " +
+ "(out of locals)");
+ }
+
+ private short getNewWordLocal(boolean isConst)
+ {
+ short result = getConsecutiveSlots(1, isConst);
+ if (result < (MAX_LOCALS - 1)) {
+ locals[result] = 1;
+ if (isConst)
+ locals[result + 1] = 1;
+ if (result == firstFreeLocal) {
+ for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) {
+ if (locals[i] == 0) {
+ firstFreeLocal = (short) i;
+ if (localsMax < firstFreeLocal)
+ localsMax = firstFreeLocal;
+ return result;
+ }
+ }
+ }
+ else {
+ return result;
+ }
+ }
+ throw Context.reportRuntimeError("Program too complex " +
+ "(out of locals)");
+ }
+
+ private short getNewWordLocal()
+ {
+ short result = firstFreeLocal;
+ locals[result] = 1;
+ for (int i = firstFreeLocal + 1; i < MAX_LOCALS; i++) {
+ if (locals[i] == 0) {
+ firstFreeLocal = (short) i;
+ if (localsMax < firstFreeLocal)
+ localsMax = firstFreeLocal;
+ return result;
+ }
+ }
+ throw Context.reportRuntimeError("Program too complex " +
+ "(out of locals)");
+ }
+
+ private short getConsecutiveSlots(int count, boolean isConst) {
+ if (isConst)
+ count++;
+ short result = firstFreeLocal;
+ while (true) {
+ if (result >= (MAX_LOCALS - 1))
+ break;
+ int i;
+ for (i = 0; i < count; i++)
+ if (locals[result + i] != 0)
+ break;
+ if (i >= count)
+ break;
+ result++;
+ }
+ return result;
+ }
+
+ // This is a valid call only for a local that is allocated by default.
+ private void incReferenceWordLocal(short local)
+ {
+ locals[local]++;
+ }
+
+ // This is a valid call only for a local that is allocated by default.
+ private void decReferenceWordLocal(short local)
+ {
+ locals[local]--;
+ }
+
+ private void releaseWordLocal(short local)
+ {
+ if (local < firstFreeLocal)
+ firstFreeLocal = local;
+ locals[local] = 0;
+ }
+
+
+ static final int GENERATOR_TERMINATE = -1;
+ static final int GENERATOR_START = 0;
+ static final int GENERATOR_YIELD_START = 1;
+
+ ClassFileWriter cfw;
+ Codegen codegen;
+ CompilerEnvirons compilerEnv;
+ ScriptOrFnNode scriptOrFn;
+ public int scriptOrFnIndex;
+ private int savedCodeOffset;
+
+ private OptFunctionNode fnCurrent;
+ private boolean isTopLevel;
+
+ private static final int MAX_LOCALS = 256;
+ private int[] locals;
+ private short firstFreeLocal;
+ private short localsMax;
+
+ private int itsLineNumber;
+
+ private boolean hasVarsInRegs;
+ private short[] varRegisters;
+ private boolean inDirectCallFunction;
+ private boolean itsForcedObjectParameters;
+ private int enterAreaStartLabel;
+ private int epilogueLabel;
+
+ // special known locals. If you add a new local here, be sure
+ // to initialize it to -1 in initBodyGeneration
+ private short variableObjectLocal;
+ private short popvLocal;
+ private short contextLocal;
+ private short argsLocal;
+ private short operationLocal;
+ private short thisObjLocal;
+ private short funObjLocal;
+ private short itsZeroArgArray;
+ private short itsOneArgArray;
+ private short scriptRegexpLocal;
+ private short generatorStateLocal;
+
+ private boolean isGenerator;
+ private int generatorSwitch;
+ private int maxLocals = 0;
+ private int maxStack = 0;
+
+ private Hashtable finallys;
+
+ class FinallyReturnPoint {
+ public ArrayList jsrPoints = new ArrayList();
+ public int tableLabel = 0;
+ }
+}
diff --git a/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java
new file mode 100644
index 0000000..607e649
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java
@@ -0,0 +1,134 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla 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/MPL/
+ *
+ * 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 Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (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
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * 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 MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+
+package org.mozilla.javascript.optimizer;
+
+class DataFlowBitSet {
+
+ private int itsBits[];
+ private int itsSize;
+
+ DataFlowBitSet(int size)
+ {
+ itsSize = size;
+ itsBits = new int[(size + 31) >> 5];
+ }
+
+ void set(int n)
+ {
+ if (!(0 <= n && n < itsSize)) badIndex(n);
+ itsBits[n >> 5] |= 1 << (n & 31);
+ }
+
+ boolean test(int n)
+ {
+ if (!(0 <= n && n < itsSize)) badIndex(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 (!(0 <= n && n < itsSize)) badIndex(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 sb = new StringBuffer();
+ sb.append("DataFlowBitSet, size = ");
+ sb.append(itsSize);
+ sb.append('\n');
+ int bitsLength = itsBits.length;
+ for (int i = 0; i < bitsLength; i++) {
+ sb.append(Integer.toHexString(itsBits[i]));
+ sb.append(' ');
+ }
+ return sb.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;
+ }
+
+ private void badIndex(int n)
+ {
+ throw new RuntimeException("DataFlowBitSet bad index " + n);
+ }
+}
diff --git a/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptFunctionNode.java b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptFunctionNode.java
new file mode 100644
index 0000000..e043165
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptFunctionNode.java
@@ -0,0 +1,149 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla 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/MPL/
+ *
+ * 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 Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Bob Jervis
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (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
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * 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 MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+final class OptFunctionNode
+{
+ OptFunctionNode(FunctionNode fnode)
+ {
+ this.fnode = fnode;
+ fnode.setCompilerData(this);
+ }
+
+ static OptFunctionNode get(ScriptOrFnNode scriptOrFn, int i)
+ {
+ FunctionNode fnode = scriptOrFn.getFunctionNode(i);
+ return (OptFunctionNode)fnode.getCompilerData();
+ }
+
+ static OptFunctionNode get(ScriptOrFnNode scriptOrFn)
+ {
+ return (OptFunctionNode)scriptOrFn.getCompilerData();
+ }
+
+ boolean isTargetOfDirectCall()
+ {
+ return directTargetIndex >= 0;
+ }
+
+ int getDirectTargetIndex()
+ {
+ return directTargetIndex;
+ }
+
+ void setDirectTargetIndex(int directTargetIndex)
+ {
+ // One time action
+ if (directTargetIndex < 0 || this.directTargetIndex >= 0)
+ Kit.codeBug();
+ this.directTargetIndex = directTargetIndex;
+ }
+
+ void setParameterNumberContext(boolean b)
+ {
+ itsParameterNumberContext = b;
+ }
+
+ boolean getParameterNumberContext()
+ {
+ return itsParameterNumberContext;
+ }
+
+ int getVarCount()
+ {
+ return fnode.getParamAndVarCount();
+ }
+
+ boolean isParameter(int varIndex)
+ {
+ return varIndex < fnode.getParamCount();
+ }
+
+ boolean isNumberVar(int varIndex)
+ {
+ varIndex -= fnode.getParamCount();
+ if (varIndex >= 0 && numberVarFlags != null) {
+ return numberVarFlags[varIndex];
+ }
+ return false;
+ }
+
+ void setIsNumberVar(int varIndex)
+ {
+ varIndex -= fnode.getParamCount();
+ // Can only be used with non-parameters
+ if (varIndex < 0) Kit.codeBug();
+ if (numberVarFlags == null) {
+ int size = fnode.getParamAndVarCount() - fnode.getParamCount();
+ numberVarFlags = new boolean[size];
+ }
+ numberVarFlags[varIndex] = true;
+ }
+
+ int getVarIndex(Node n)
+ {
+ int index = n.getIntProp(Node.VARIABLE_PROP, -1);
+ if (index == -1) {
+ Node node;
+ int type = n.getType();
+ if (type == Token.GETVAR) {
+ node = n;
+ } else if (type == Token.SETVAR ||
+ type == Token.SETCONSTVAR) {
+ node = n.getFirstChild();
+ } else {
+ throw Kit.codeBug();
+ }
+ index = fnode.getIndexForNameNode(node);
+ if (index < 0) throw Kit.codeBug();
+ n.putIntProp(Node.VARIABLE_PROP, index);
+ }
+ return index;
+ }
+
+ FunctionNode fnode;
+ private boolean[] numberVarFlags;
+ private int directTargetIndex = -1;
+ private boolean itsParameterNumberContext;
+ boolean itsContainsCalls0;
+ boolean itsContainsCalls1;
+}
diff --git a/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptRuntime.java b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptRuntime.java
new file mode 100644
index 0000000..ba8ca03
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptRuntime.java
@@ -0,0 +1,311 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla 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/MPL/
+ *
+ * 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 Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ * Hannes Wallnoefer
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (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
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * 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 MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+public final class OptRuntime extends ScriptRuntime
+{
+
+ public static final Double zeroObj = new Double(0.0);
+ public static final Double oneObj = new Double(1.0);
+ public static final Double minusOneObj = new Double(-1.0);
+
+ /**
+ * Implement ....() call shrinking optimizer code.
+ */
+ public static Object call0(Callable fun, Scriptable thisObj,
+ Context cx, Scriptable scope)
+ {
+ return fun.call(cx, scope, thisObj, ScriptRuntime.emptyArgs);
+ }
+
+ /**
+ * Implement ....(arg) call shrinking optimizer code.
+ */
+ public static Object call1(Callable fun, Scriptable thisObj, Object arg0,
+ Context cx, Scriptable scope)
+ {
+ return fun.call(cx, scope, thisObj, new Object[] { arg0 } );
+ }
+
+ /**
+ * Implement ....(arg0, arg1) call shrinking optimizer code.
+ */
+ public static Object call2(Callable fun, Scriptable thisObj,
+ Object arg0, Object arg1,
+ Context cx, Scriptable scope)
+ {
+ return fun.call(cx, scope, thisObj, new Object[] { arg0, arg1 });
+ }
+
+ /**
+ * Implement ....(arg0, arg1, ...) call shrinking optimizer code.
+ */
+ public static Object callN(Callable fun, Scriptable thisObj,
+ Object[] args,
+ Context cx, Scriptable scope)
+ {
+ return fun.call(cx, scope, thisObj, args);
+ }
+
+ /**
+ * Implement name(args) call shrinking optimizer code.
+ */
+ public static Object callName(Object[] args, String name,
+ Context cx, Scriptable scope)
+ {
+ Callable f = getNameFunctionAndThis(name, cx, scope);
+ Scriptable thisObj = lastStoredScriptable(cx);
+ return f.call(cx, scope, thisObj, args);
+ }
+
+ /**
+ * Implement name() call shrinking optimizer code.
+ */
+ public static Object callName0(String name,
+ Context cx, Scriptable scope)
+ {
+ Callable f = getNameFunctionAndThis(name, cx, scope);
+ Scriptable thisObj = lastStoredScriptable(cx);
+ return f.call(cx, scope, thisObj, ScriptRuntime.emptyArgs);
+ }
+
+ /**
+ * Implement x.property() call shrinking optimizer code.
+ */
+ public static Object callProp0(Object value, String property,
+ Context cx, Scriptable scope)
+ {
+ Callable f = getPropFunctionAndThis(value, property, cx);
+ Scriptable thisObj = lastStoredScriptable(cx);
+ return f.call(cx, scope, thisObj, ScriptRuntime.emptyArgs);
+ }
+
+ public static Object add(Object val1, double val2)
+ {
+ if (val1 instanceof Scriptable)
+ val1 = ((Scriptable) val1).getDefaultValue(null);
+ if (!(val1 instanceof String))
+ return wrapDouble(toNumber(val1) + val2);
+ return ((String)val1).concat(toString(val2));
+ }
+
+ public static Object add(double val1, Object val2)
+ {
+ if (val2 instanceof Scriptable)
+ val2 = ((Scriptable) val2).getDefaultValue(null);
+ if (!(val2 instanceof String))
+ return wrapDouble(toNumber(val2) + val1);
+ return toString(val1).concat((String)val2);
+ }
+
+ public static Object elemIncrDecr(Object obj, double index,
+ Context cx, int incrDecrMask)
+ {
+ return ScriptRuntime.elemIncrDecr(obj, new Double(index), cx,
+ incrDecrMask);
+ }
+
+ 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;
+ }
+
+ public static void initFunction(NativeFunction fn, int functionType,
+ Scriptable scope, Context cx)
+ {
+ ScriptRuntime.initFunction(cx, scope, fn, functionType, false);
+ }
+
+ public static Object callSpecial(Context cx, Callable fun,
+ Scriptable thisObj, Object[] args,
+ Scriptable scope,
+ Scriptable callerThis, int callType,
+ String fileName, int lineNumber)
+ {
+ return ScriptRuntime.callSpecial(cx, fun, thisObj, args, scope,
+ callerThis, callType,
+ fileName, lineNumber);
+ }
+
+ public static Object newObjectSpecial(Context cx, Object fun,
+ Object[] args, Scriptable scope,
+ Scriptable callerThis, int callType)
+ {
+ return ScriptRuntime.newSpecial(cx, fun, args, scope, callType);
+ }
+
+ public static Double wrapDouble(double num)
+ {
+ if (num == 0.0) {
+ if (1 / num > 0) {
+ // +0.0
+ return zeroObj;
+ }
+ } else if (num == 1.0) {
+ return oneObj;
+ } else if (num == -1.0) {
+ return minusOneObj;
+ } else if (num != num) {
+ return NaNobj;
+ }
+ return new Double(num);
+ }
+
+ static String encodeIntArray(int[] array)
+ {
+ // XXX: this extremely inefficient for small integers
+ if (array == null) { return null; }
+ int n = array.length;
+ char[] buffer = new char[1 + n * 2];
+ buffer[0] = 1;
+ for (int i = 0; i != n; ++i) {
+ int value = array[i];
+ int shift = 1 + i * 2;
+ buffer[shift] = (char)(value >>> 16);
+ buffer[shift + 1] = (char)value;
+ }
+ return new String(buffer);
+ }
+
+ private static int[] decodeIntArray(String str, int arraySize)
+ {
+ // XXX: this extremely inefficient for small integers
+ if (arraySize == 0) {
+ if (str != null) throw new IllegalArgumentException();
+ return null;
+ }
+ if (str.length() != 1 + arraySize * 2 && str.charAt(0) != 1) {
+ throw new IllegalArgumentException();
+ }
+ int[] array = new int[arraySize];
+ for (int i = 0; i != arraySize; ++i) {
+ int shift = 1 + i * 2;
+ array[i] = (str.charAt(shift) << 16) | str.charAt(shift + 1);
+ }
+ return array;
+ }
+
+ public static Scriptable newArrayLiteral(Object[] objects,
+ String encodedInts,
+ int skipCount,
+ Context cx,
+ Scriptable scope)
+ {
+ int[] skipIndexces = decodeIntArray(encodedInts, skipCount);
+ return newArrayLiteral(objects, skipIndexces, cx, scope);
+ }
+
+ public static void main(final Script script, final String[] args)
+ {
+ Context.call(new ContextAction() {
+ public Object run(Context cx)
+ {
+ ScriptableObject global = getGlobal(cx);
+
+ // get the command line arguments and define "arguments"
+ // array in the top-level object
+ Object[] argsCopy = new Object[args.length];
+ System.arraycopy(args, 0, argsCopy, 0, args.length);
+ Scriptable argsObj = cx.newArray(global, argsCopy);
+ global.defineProperty("arguments", argsObj,
+ ScriptableObject.DONTENUM);
+ script.exec(cx, global);
+ return null;
+ }
+ });
+ }
+
+ public static void throwStopIteration(Object obj) {
+ throw new JavaScriptException(
+ NativeIterator.getStopIterationObject((Scriptable)obj), "", 0);
+ }
+
+ public static Scriptable createNativeGenerator(NativeFunction funObj,
+ Scriptable scope,
+ Scriptable thisObj,
+ int maxLocals,
+ int maxStack)
+ {
+ return new NativeGenerator(scope, funObj,
+ new GeneratorState(thisObj, maxLocals, maxStack));
+ }
+
+ public static Object[] getGeneratorStackState(Object obj) {
+ GeneratorState rgs = (GeneratorState) obj;
+ if (rgs.stackState == null)
+ rgs.stackState = new Object[rgs.maxStack];
+ return rgs.stackState;
+ }
+
+ public static Object[] getGeneratorLocalsState(Object obj) {
+ GeneratorState rgs = (GeneratorState) obj;
+ if (rgs.localsState == null)
+ rgs.localsState = new Object[rgs.maxLocals];
+ return rgs.localsState;
+ }
+
+ public static class GeneratorState {
+ static final String CLASS_NAME =
+ "org/mozilla/javascript/optimizer/OptRuntime$GeneratorState";
+
+ public int resumptionPoint;
+ static final String resumptionPoint_NAME = "resumptionPoint";
+ static final String resumptionPoint_TYPE = "I";
+
+ public Scriptable thisObj;
+ static final String thisObj_NAME = "thisObj";
+ static final String thisObj_TYPE =
+ "Lorg/mozilla/javascript/Scriptable;";
+
+ Object[] stackState;
+ Object[] localsState;
+ int maxLocals;
+ int maxStack;
+
+ GeneratorState(Scriptable thisObj, int maxLocals, int maxStack) {
+ this.thisObj = thisObj;
+ this.maxLocals = maxLocals;
+ this.maxStack = maxStack;
+ }
+ }
+}
diff --git a/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptTransformer.java b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptTransformer.java
new file mode 100644
index 0000000..7cf679f
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptTransformer.java
@@ -0,0 +1,133 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla 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/MPL/
+ *
+ * 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 Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (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
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * 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 MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+import java.util.Hashtable;
+
+/**
+ * This class performs node transforms to prepare for optimization.
+ *
+ * @see NodeTransformer
+ * @author Norris Boyd
+ */
+
+class OptTransformer extends NodeTransformer {
+
+ OptTransformer(Hashtable possibleDirectCalls, ObjArray directCallTargets)
+ {
+ this.possibleDirectCalls = possibleDirectCalls;
+ this.directCallTargets = directCallTargets;
+ }
+
+ protected void visitNew(Node node, ScriptOrFnNode tree) {
+ detectDirectCall(node, tree);
+ super.visitNew(node, tree);
+ }
+
+ protected void visitCall(Node node, ScriptOrFnNode tree) {
+ detectDirectCall(node, tree);
+ super.visitCall(node, tree);
+ }
+
+ private void detectDirectCall(Node node, ScriptOrFnNode tree)
+ {
+ if (tree.getType() == Token.FUNCTION) {
+ Node left = node.getFirstChild();
+
+ // count the arguments
+ int argCount = 0;
+ Node arg = left.getNext();
+ while (arg != null) {
+ arg = arg.getNext();
+ argCount++;
+ }
+
+ if (argCount == 0) {
+ OptFunctionNode.get(tree).itsContainsCalls0 = true;
+ }
+
+ /*
+ * 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)
+ */
+ if (possibleDirectCalls != null) {
+ String targetName = null;
+ if (left.getType() == Token.NAME) {
+ targetName = left.getString();
+ } else if (left.getType() == Token.GETPROP) {
+ targetName = left.getFirstChild().getNext().getString();
+ } else if (left.getType() == Token.GETPROPNOWARN) {
+ throw Kit.codeBug();
+ }
+ if (targetName != null) {
+ OptFunctionNode ofn;
+ ofn = (OptFunctionNode)possibleDirectCalls.get(targetName);
+ if (ofn != null
+ && argCount == ofn.fnode.getParamCount()
+ && !ofn.fnode.requiresActivation())
+ {
+ // Refuse to directCall any function with more
+ // than 32 parameters - prevent code explosion
+ // for wacky test cases
+ if (argCount <= 32) {
+ node.putProp(Node.DIRECTCALL_PROP, ofn);
+ if (!ofn.isTargetOfDirectCall()) {
+ int index = directCallTargets.size();
+ directCallTargets.add(ofn);
+ ofn.setDirectTargetIndex(index);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private Hashtable possibleDirectCalls;
+ private ObjArray directCallTargets;
+}
diff --git a/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Optimizer.java b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Optimizer.java
new file mode 100644
index 0000000..575c7e7
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Optimizer.java
@@ -0,0 +1,510 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla 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/MPL/
+ *
+ * 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 Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (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
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * 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 MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+class Optimizer
+{
+
+ static final int NoType = 0;
+ static final int NumberType = 1;
+ static final int AnyType = 3;
+
+ // It is assumed that (NumberType | AnyType) == AnyType
+
+ void optimize(ScriptOrFnNode scriptOrFn)
+ {
+ // run on one function at a time for now
+ int functionCount = scriptOrFn.getFunctionCount();
+ for (int i = 0; i != functionCount; ++i) {
+ OptFunctionNode f = OptFunctionNode.get(scriptOrFn, i);
+ optimizeFunction(f);
+ }
+ }
+
+ private void optimizeFunction(OptFunctionNode theFunction)
+ {
+ if (theFunction.fnode.requiresActivation()) return;
+
+ inDirectCallFunction = theFunction.isTargetOfDirectCall();
+ this.theFunction = theFunction;
+
+ ObjArray statementsArray = new ObjArray();
+ buildStatementList_r(theFunction.fnode, statementsArray);
+ Node[] theStatementNodes = new Node[statementsArray.size()];
+ statementsArray.toArray(theStatementNodes);
+
+ Block.runFlowAnalyzes(theFunction, theStatementNodes);
+
+ if (!theFunction.fnode.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);
+ }
+
+ }
+
+
+/*
+ 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.
+*/
+ private void markDCPNumberContext(Node n)
+ {
+ if (inDirectCallFunction && n.getType() == Token.GETVAR) {
+ int varIndex = theFunction.getVarIndex(n);
+ if (theFunction.isParameter(varIndex)) {
+ parameterUsedInNumberContext = true;
+ }
+ }
+ }
+
+ private boolean convertParameter(Node n)
+ {
+ if (inDirectCallFunction && n.getType() == Token.GETVAR) {
+ int varIndex = theFunction.getVarIndex(n);
+ if (theFunction.isParameter(varIndex)) {
+ n.removeProp(Node.ISNUMBER_PROP);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int rewriteForNumberVariables(Node n)
+ {
+ switch (n.getType()) {
+ case Token.EXPR_VOID : {
+ Node child = n.getFirstChild();
+ int type = rewriteForNumberVariables(child);
+ if (type == NumberType)
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NoType;
+ }
+ case Token.NUMBER :
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+
+ case Token.GETVAR :
+ {
+ int varIndex = theFunction.getVarIndex(n);
+ if (inDirectCallFunction
+ && theFunction.isParameter(varIndex))
+ {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ else if (theFunction.isNumberVar(varIndex)) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ return NoType;
+ }
+
+ case Token.INC :
+ case Token.DEC : {
+ Node child = n.getFirstChild();
+ // "child" will be GETVAR or GETPROP or GETELEM
+ if (child.getType() == Token.GETVAR) {
+ if (rewriteForNumberVariables(child) == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ markDCPNumberContext(child);
+ return NumberType;
+ }
+ return NoType;
+ }
+ else if (child.getType() == Token.GETELEM) {
+ return rewriteForNumberVariables(child);
+ }
+ return NoType;
+ }
+ case Token.SETVAR : {
+ Node lChild = n.getFirstChild();
+ Node rChild = lChild.getNext();
+ int rType = rewriteForNumberVariables(rChild);
+ int varIndex = theFunction.getVarIndex(n);
+ if (inDirectCallFunction
+ && theFunction.isParameter(varIndex))
+ {
+ if (rType == NumberType) {
+ if (!convertParameter(rChild)) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ markDCPNumberContext(rChild);
+ return NoType;
+ }
+ else
+ return rType;
+ }
+ else if (theFunction.isNumberVar(varIndex)) {
+ if (rType != NumberType) {
+ n.removeChild(rChild);
+ n.addChildToBack(
+ new Node(Token.TO_DOUBLE, rChild));
+ }
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ markDCPNumberContext(rChild);
+ return NumberType;
+ }
+ else {
+ if (rType == NumberType) {
+ if (!convertParameter(rChild)) {
+ n.removeChild(rChild);
+ n.addChildToBack(
+ new Node(Token.TO_OBJECT, rChild));
+ }
+ }
+ return NoType;
+ }
+ }
+ case Token.LE :
+ case Token.LT :
+ case Token.GE :
+ case Token.GT : {
+ Node lChild = n.getFirstChild();
+ Node rChild = lChild.getNext();
+ int lType = rewriteForNumberVariables(lChild);
+ int rType = rewriteForNumberVariables(rChild);
+ markDCPNumberContext(lChild);
+ markDCPNumberContext(rChild);
+
+ if (convertParameter(lChild)) {
+ if (convertParameter(rChild)) {
+ return NoType;
+ } else if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT);
+ }
+ }
+ else if (convertParameter(rChild)) {
+ if (lType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ else {
+ if (lType == NumberType) {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ }
+ else {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ else {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT);
+ }
+ }
+ }
+ // we actually build a boolean value
+ return NoType;
+ }
+
+ case Token.ADD : {
+ Node lChild = n.getFirstChild();
+ Node rChild = lChild.getNext();
+ int lType = rewriteForNumberVariables(lChild);
+ int rType = rewriteForNumberVariables(rChild);
+
+
+ if (convertParameter(lChild)) {
+ if (convertParameter(rChild)) {
+ return NoType;
+ }
+ else {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT);
+ }
+ }
+ }
+ else {
+ if (convertParameter(rChild)) {
+ if (lType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ else {
+ if (lType == NumberType) {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ else {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ else {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP,
+ Node.RIGHT);
+ }
+ }
+ }
+ }
+ return NoType;
+ }
+
+ case Token.BITXOR :
+ case Token.BITOR :
+ case Token.BITAND :
+ case Token.RSH :
+ case Token.LSH :
+ case Token.SUB :
+ case Token.MUL :
+ case Token.DIV :
+ case Token.MOD : {
+ Node lChild = n.getFirstChild();
+ Node rChild = lChild.getNext();
+ int lType = rewriteForNumberVariables(lChild);
+ int rType = rewriteForNumberVariables(rChild);
+ markDCPNumberContext(lChild);
+ markDCPNumberContext(rChild);
+ if (lType == NumberType) {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ else {
+ if (!convertParameter(rChild)) {
+ n.removeChild(rChild);
+ n.addChildToBack(
+ new Node(Token.TO_DOUBLE, rChild));
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ }
+ return NumberType;
+ }
+ }
+ else {
+ if (rType == NumberType) {
+ if (!convertParameter(lChild)) {
+ n.removeChild(lChild);
+ n.addChildToFront(
+ new Node(Token.TO_DOUBLE, lChild));
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ }
+ return NumberType;
+ }
+ else {
+ if (!convertParameter(lChild)) {
+ n.removeChild(lChild);
+ n.addChildToFront(
+ new Node(Token.TO_DOUBLE, lChild));
+ }
+ if (!convertParameter(rChild)) {
+ n.removeChild(rChild);
+ n.addChildToBack(
+ new Node(Token.TO_DOUBLE, rChild));
+ }
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ }
+ }
+ case Token.SETELEM :
+ case Token.SETELEM_OP : {
+ Node arrayBase = n.getFirstChild();
+ Node arrayIndex = arrayBase.getNext();
+ Node rValue = arrayIndex.getNext();
+ int baseType = rewriteForNumberVariables(arrayBase);
+ if (baseType == NumberType) {// can never happen ???
+ if (!convertParameter(arrayBase)) {
+ n.removeChild(arrayBase);
+ n.addChildToFront(
+ new Node(Token.TO_OBJECT, arrayBase));
+ }
+ }
+ int indexType = rewriteForNumberVariables(arrayIndex);
+ if (indexType == NumberType) {
+ // setting the ISNUMBER_PROP signals the codegen
+ // to use the OptRuntime.setObjectIndex that takes
+ // a double index
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ markDCPNumberContext(arrayIndex);
+ }
+ int rValueType = rewriteForNumberVariables(rValue);
+ if (rValueType == NumberType) {
+ if (!convertParameter(rValue)) {
+ n.removeChild(rValue);
+ n.addChildToBack(
+ new Node(Token.TO_OBJECT, rValue));
+ }
+ }
+ return NoType;
+ }
+ case Token.GETELEM : {
+ Node arrayBase = n.getFirstChild();
+ Node arrayIndex = arrayBase.getNext();
+ int baseType = rewriteForNumberVariables(arrayBase);
+ if (baseType == NumberType) {// can never happen ???
+ if (!convertParameter(arrayBase)) {
+ n.removeChild(arrayBase);
+ n.addChildToFront(
+ new Node(Token.TO_OBJECT, arrayBase));
+ }
+ }
+ int indexType = rewriteForNumberVariables(arrayIndex);
+ if (indexType == NumberType) {
+ if (!convertParameter(arrayIndex)) {
+ // setting the ISNUMBER_PROP signals the codegen
+ // to use the OptRuntime.getObjectIndex that takes
+ // a double index
+ n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT);
+ }
+ }
+ return NoType;
+ }
+ case Token.CALL :
+ {
+ Node child = n.getFirstChild(); // the function node
+ if (child.getType() == Token.GETELEM) {
+ // Optimization of x[0]() is not supported
+ // so bypass GETELEM optimization that
+ // rewriteForNumberVariables would trigger
+ rewriteAsObjectChildren(child, child.getFirstChild());
+ } else {
+ rewriteForNumberVariables(child);
+ }
+ child = child.getNext(); // the first arg
+
+ OptFunctionNode target
+ = (OptFunctionNode)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.
+*/
+ while (child != null) {
+ int type = rewriteForNumberVariables(child);
+ if (type == NumberType) {
+ markDCPNumberContext(child);
+ }
+ child = child.getNext();
+ }
+ } else {
+ rewriteAsObjectChildren(n, child);
+ }
+ return NoType;
+ }
+ default : {
+ rewriteAsObjectChildren(n, n.getFirstChild());
+ return NoType;
+ }
+ }
+ }
+
+ private void rewriteAsObjectChildren(Node n, Node child)
+ {
+ // Force optimized children to be objects
+ while (child != null) {
+ Node nextChild = child.getNext();
+ int type = rewriteForNumberVariables(child);
+ if (type == NumberType) {
+ if (!convertParameter(child)) {
+ n.removeChild(child);
+ Node nuChild = new Node(Token.TO_OBJECT, child);
+ if (nextChild == null)
+ n.addChildToBack(nuChild);
+ else
+ n.addChildBefore(nuChild, nextChild);
+ }
+ }
+ child = nextChild;
+ }
+ }
+
+ private static void buildStatementList_r(Node node, ObjArray statements)
+ {
+ int type = node.getType();
+ if (type == Token.BLOCK
+ || type == Token.LOCAL_BLOCK
+ || type == Token.LOOP
+ || type == Token.FUNCTION)
+ {
+ Node child = node.getFirstChild();
+ while (child != null) {
+ buildStatementList_r(child, statements);
+ child = child.getNext();
+ }
+ } else {
+ statements.add(node);
+ }
+ }
+
+ private boolean inDirectCallFunction;
+ OptFunctionNode theFunction;
+ private boolean parameterUsedInNumberContext;
+}