From d7c5ad7d6263fd1baf9bfdbaa4c50b70ef2fbdb2 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Tue, 8 Jun 2010 08:22:05 +0200 Subject: reverted folder structure change for better mergeing with upstream --- .../org/mozilla/javascript/optimizer/Block.java | 615 +++ .../javascript/optimizer/ClassCompiler.java | 214 + .../org/mozilla/javascript/optimizer/Codegen.java | 5031 ++++++++++++++++++++ .../javascript/optimizer/DataFlowBitSet.java | 134 + .../javascript/optimizer/OptFunctionNode.java | 149 + .../mozilla/javascript/optimizer/OptRuntime.java | 311 ++ .../javascript/optimizer/OptTransformer.java | 133 + .../mozilla/javascript/optimizer/Optimizer.java | 510 ++ 8 files changed, 7097 insertions(+) create mode 100644 trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Block.java create mode 100644 trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/ClassCompiler.java create mode 100644 trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java create mode 100644 trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java create mode 100644 trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptFunctionNode.java create mode 100644 trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptRuntime.java create mode 100644 trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptTransformer.java create mode 100644 trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Optimizer.java (limited to 'trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer') 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 + * public static void main(Script sc, String[] args), it will be + * called when main(String[] args) is called in the generated + * class. The class name should be fully qulified name and include the + * package name like in org.foo.Bar. + */ + 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() { + Scriptable newInstance = createObject(cx, scope); + Object val = (cx, scope, newInstance, ); + 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(), + "", "()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("", "()V", ClassFileWriter.ACC_PUBLIC); + + cfw.addLoadThis(); + cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, + "", "()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("", FUNCTION_CONSTRUCTOR_SIGNATURE, + ClassFileWriter.ACC_PUBLIC); + cfw.addALoad(0); + cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, + "", "()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 ; } + + 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("", "()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", + "", "(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, + "", 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", + "", + "(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, + "", 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 observeInstructionCount() + * 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; +} -- cgit v1.2.3