diff options
author | Alexander Sulfrian <alexander@sulfrian.net> | 2010-06-08 09:01:43 +0200 |
---|---|---|
committer | Alexander Sulfrian <alexander@sulfrian.net> | 2010-06-08 09:01:43 +0200 |
commit | d1fa08fdc9cb11dccee76d668ff85df30458c295 (patch) | |
tree | 1d19df6405103577d872902486792e8c23bce711 /infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java | |
parent | d7c5ad7d6263fd1baf9bfdbaa4c50b70ef2fbdb2 (diff) | |
parent | 70d1f9d6fcaefe611e778b8dbf3bafea8934aa08 (diff) | |
download | etherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.tar.gz etherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.tar.xz etherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.zip |
Merge remote branch 'upstream/master'
Conflicts:
etherpad/src/etherpad/control/pro/admin/pro_admin_control.js
etherpad/src/etherpad/control/pro/pro_main_control.js
etherpad/src/etherpad/control/pro_help_control.js
etherpad/src/etherpad/globals.js
etherpad/src/etherpad/legacy_urls.js
etherpad/src/etherpad/pne/pne_utils.js
etherpad/src/etherpad/pro/pro_utils.js
etherpad/src/main.js
etherpad/src/plugins/fileUpload/templates/fileUpload.ejs
etherpad/src/plugins/testplugin/templates/page.ejs
etherpad/src/static/css/pad2_ejs.css
etherpad/src/static/css/pro-help.css
etherpad/src/static/img/jun09/pad/protop.gif
etherpad/src/static/js/store.js
etherpad/src/themes/default/templates/framed/framedheader-pro.ejs
etherpad/src/themes/default/templates/main/home.ejs
etherpad/src/themes/default/templates/pro-help/main.ejs
etherpad/src/themes/default/templates/pro-help/pro-help-template.ejs
infrastructure/com.etherpad/licensing.scala
trunk/etherpad/src/etherpad/collab/ace/contentcollector.js
trunk/etherpad/src/etherpad/collab/ace/linestylefilter.js
trunk/etherpad/src/static/css/home-opensource.css
trunk/etherpad/src/static/js/ace.js
trunk/etherpad/src/static/js/linestylefilter_client.js
trunk/etherpad/src/templates/email/eepnet_license_info.ejs
trunk/etherpad/src/templates/pad/pad_body2.ejs
trunk/etherpad/src/templates/pad/pad_content.ejs
trunk/etherpad/src/templates/pad/padfull_body.ejs
trunk/etherpad/src/templates/pro/admin/pne-license-manager.ejs
Diffstat (limited to 'infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java')
-rw-r--r-- | infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java | 5031 |
1 files changed, 5031 insertions, 0 deletions
diff --git a/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java b/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java new file mode 100644 index 0000000..64952bf --- /dev/null +++ b/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java @@ -0,0 +1,5031 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Kemal Bayram + * Igor Bukanov + * Bob Jervis + * Roger Lawrence + * Andi Vajda + * Hannes Wallnoefer + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + + +package org.mozilla.javascript.optimizer; + +import org.mozilla.javascript.*; +import org.mozilla.classfile.*; +import java.util.*; +import java.lang.reflect.Constructor; +import java.util.Hashtable; + +/** + * This class generates code for a given IR tree. + * + * @author Norris Boyd + * @author Roger Lawrence + */ + +public class Codegen implements Evaluator +{ + public void captureStackInfo(RhinoException ex) { + throw new UnsupportedOperationException(); + } + + public String getSourcePositionFromStack(Context cx, int[] linep) { + throw new UnsupportedOperationException(); + } + + public String getPatchedStack(RhinoException ex, String nativeStackTrace) { + throw new UnsupportedOperationException(); + } + + public List getScriptStack(RhinoException ex) { + throw new UnsupportedOperationException(); + } + + public void setEvalScriptFlag(Script script) { + throw new UnsupportedOperationException(); + } + + public Object compile(CompilerEnvirons compilerEnv, + ScriptOrFnNode tree, + String encodedSource, + boolean returnFunction) + { + int serial; + synchronized (globalLock) { + serial = ++globalSerialClassCounter; + } + String mainClassName = "org.mozilla.javascript.gen.c"+serial; + + byte[] mainClassBytes = compileToClassFile(compilerEnv, mainClassName, + tree, encodedSource, + returnFunction); + + return new Object[] { mainClassName, mainClassBytes }; + } + + public Script createScriptObject(Object bytecode, + Object staticSecurityDomain) + { + Class cl = defineClass(bytecode, staticSecurityDomain); + + Script script; + try { + script = (Script)cl.newInstance(); + } catch (Exception ex) { + throw new RuntimeException + ("Unable to instantiate compiled class:"+ex.toString()); + } + return script; + } + + public Function createFunctionObject(Context cx, Scriptable scope, + Object bytecode, + Object staticSecurityDomain) + { + Class cl = defineClass(bytecode, staticSecurityDomain); + + NativeFunction f; + try { + Constructor ctor = cl.getConstructors()[0]; + Object[] initArgs = { scope, cx, new Integer(0) }; + f = (NativeFunction)ctor.newInstance(initArgs); + } catch (Exception ex) { + throw new RuntimeException + ("Unable to instantiate compiled class:"+ex.toString()); + } + return f; + } + + private Class defineClass(Object bytecode, + Object staticSecurityDomain) + { + Object[] nameBytesPair = (Object[])bytecode; + String className = (String)nameBytesPair[0]; + byte[] classBytes = (byte[])nameBytesPair[1]; + + // The generated classes in this case refer only to Rhino classes + // which must be accessible through this class loader + ClassLoader rhinoLoader = getClass().getClassLoader(); + GeneratedClassLoader loader; + loader = SecurityController.createLoader(rhinoLoader, + staticSecurityDomain); + Exception e; + try { + Class cl = loader.defineClass(className, classBytes); + loader.linkClass(cl); + return cl; + } catch (SecurityException x) { + e = x; + } catch (IllegalArgumentException x) { + e = x; + } + throw new RuntimeException("Malformed optimizer package " + e); + } + + byte[] compileToClassFile(CompilerEnvirons compilerEnv, + String mainClassName, + ScriptOrFnNode scriptOrFn, + String encodedSource, + boolean returnFunction) + { + this.compilerEnv = compilerEnv; + + transform(scriptOrFn); + + if (Token.printTrees) { + /*APPJET*///System.out.println(scriptOrFn.toStringTree(scriptOrFn)); + } + + if (returnFunction) { + scriptOrFn = scriptOrFn.getFunctionNode(0); + } + + initScriptOrFnNodesData(scriptOrFn); + + this.mainClassName = mainClassName; + this.mainClassSignature + = ClassFileWriter.classNameToSignature(mainClassName); + + try { + return generateCode(encodedSource); + } catch (ClassFileWriter.ClassFileFormatException e) { + throw reportClassFileFormatException(scriptOrFn, e.getMessage()); + } + } + + private RuntimeException reportClassFileFormatException( + ScriptOrFnNode scriptOrFn, + String message) + { + String msg = scriptOrFn instanceof FunctionNode + ? ScriptRuntime.getMessage2("msg.while.compiling.fn", + ((FunctionNode)scriptOrFn).getFunctionName(), message) + : ScriptRuntime.getMessage1("msg.while.compiling.script", message); + return Context.reportRuntimeError(msg, scriptOrFn.getSourceName(), + scriptOrFn.getLineno(), null, 0); + } + + private void transform(ScriptOrFnNode tree) + { + initOptFunctions_r(tree); + + int optLevel = compilerEnv.getOptimizationLevel(); + + Hashtable possibleDirectCalls = null; + if (optLevel > 0) { + /* + * Collect all of the contained functions into a hashtable + * so that the call optimizer can access the class name & parameter + * count for any call it encounters + */ + if (tree.getType() == Token.SCRIPT) { + int functionCount = tree.getFunctionCount(); + for (int i = 0; i != functionCount; ++i) { + OptFunctionNode ofn = OptFunctionNode.get(tree, i); + if (ofn.fnode.getFunctionType() + == FunctionNode.FUNCTION_STATEMENT) + { + String name = ofn.fnode.getFunctionName(); + if (name.length() != 0) { + if (possibleDirectCalls == null) { + possibleDirectCalls = new Hashtable(); + } + possibleDirectCalls.put(name, ofn); + } + } + } + } + } + + if (possibleDirectCalls != null) { + directCallTargets = new ObjArray(); + } + + OptTransformer ot = new OptTransformer(possibleDirectCalls, + directCallTargets); + ot.transform(tree); + + if (optLevel > 0) { + (new Optimizer()).optimize(tree); + } + } + + private static void initOptFunctions_r(ScriptOrFnNode scriptOrFn) + { + for (int i = 0, N = scriptOrFn.getFunctionCount(); i != N; ++i) { + FunctionNode fn = scriptOrFn.getFunctionNode(i); + new OptFunctionNode(fn); + initOptFunctions_r(fn); + } + } + + private void initScriptOrFnNodesData(ScriptOrFnNode scriptOrFn) + { + ObjArray x = new ObjArray(); + collectScriptOrFnNodes_r(scriptOrFn, x); + + int count = x.size(); + scriptOrFnNodes = new ScriptOrFnNode[count]; + x.toArray(scriptOrFnNodes); + + scriptOrFnIndexes = new ObjToIntMap(count); + for (int i = 0; i != count; ++i) { + scriptOrFnIndexes.put(scriptOrFnNodes[i], i); + } + } + + private static void collectScriptOrFnNodes_r(ScriptOrFnNode n, + ObjArray x) + { + x.add(n); + int nestedCount = n.getFunctionCount(); + for (int i = 0; i != nestedCount; ++i) { + collectScriptOrFnNodes_r(n.getFunctionNode(i), x); + } + } + + private byte[] generateCode(String encodedSource) + { + boolean hasScript = (scriptOrFnNodes[0].getType() == Token.SCRIPT); + boolean hasFunctions = (scriptOrFnNodes.length > 1 || !hasScript); + + String sourceFile = null; + if (compilerEnv.isGenerateDebugInfo()) { + sourceFile = scriptOrFnNodes[0].getSourceName(); + } + + ClassFileWriter cfw = new ClassFileWriter(mainClassName, + SUPER_CLASS_NAME, + sourceFile); + cfw.addField(ID_FIELD_NAME, "I", + ClassFileWriter.ACC_PRIVATE); + cfw.addField(DIRECT_CALL_PARENT_FIELD, mainClassSignature, + ClassFileWriter.ACC_PRIVATE); + cfw.addField(REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE, + ClassFileWriter.ACC_PRIVATE); + + if (hasFunctions) { + generateFunctionConstructor(cfw); + } + + if (hasScript) { + cfw.addInterface("org/mozilla/javascript/Script"); + generateScriptCtor(cfw); + generateMain(cfw); + generateExecute(cfw); + } + + generateCallMethod(cfw); + generateResumeGenerator(cfw); + + generateNativeFunctionOverrides(cfw, encodedSource); + + int count = scriptOrFnNodes.length; + for (int i = 0; i != count; ++i) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + + BodyCodegen bodygen = new BodyCodegen(); + bodygen.cfw = cfw; + bodygen.codegen = this; + bodygen.compilerEnv = compilerEnv; + bodygen.scriptOrFn = n; + bodygen.scriptOrFnIndex = i; + + try { + bodygen.generateBodyCode(); + } catch (ClassFileWriter.ClassFileFormatException e) { + throw reportClassFileFormatException(n, e.getMessage()); + } + + if (n.getType() == Token.FUNCTION) { + OptFunctionNode ofn = OptFunctionNode.get(n); + generateFunctionInit(cfw, ofn); + if (ofn.isTargetOfDirectCall()) { + emitDirectConstructor(cfw, ofn); + } + } + } + + if (directCallTargets != null) { + int N = directCallTargets.size(); + for (int j = 0; j != N; ++j) { + cfw.addField(getDirectTargetFieldName(j), + mainClassSignature, + ClassFileWriter.ACC_PRIVATE); + } + } + + emitRegExpInit(cfw); + emitConstantDudeInitializers(cfw); + + return cfw.toByteArray(); + } + + private void emitDirectConstructor(ClassFileWriter cfw, + OptFunctionNode ofn) + { +/* + we generate .. + Scriptable directConstruct(<directCallArgs>) { + Scriptable newInstance = createObject(cx, scope); + Object val = <body-name>(cx, scope, newInstance, <directCallArgs>); + if (val instanceof Scriptable) { + return (Scriptable) val; + } + return newInstance; + } +*/ + cfw.startMethod(getDirectCtorName(ofn.fnode), + getBodyMethodSignature(ofn.fnode), + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + + int argCount = ofn.fnode.getParamCount(); + int firstLocal = (4 + argCount * 3) + 1; + + cfw.addALoad(0); // this + cfw.addALoad(1); // cx + cfw.addALoad(2); // scope + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/BaseFunction", + "createObject", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(firstLocal); + + cfw.addALoad(0); + cfw.addALoad(1); + cfw.addALoad(2); + cfw.addALoad(firstLocal); + for (int i = 0; i < argCount; i++) { + cfw.addALoad(4 + (i * 3)); + cfw.addDLoad(5 + (i * 3)); + } + cfw.addALoad(4 + argCount * 3); + cfw.addInvoke(ByteCode.INVOKESTATIC, + mainClassName, + getBodyMethodName(ofn.fnode), + getBodyMethodSignature(ofn.fnode)); + int exitLabel = cfw.acquireLabel(); + cfw.add(ByteCode.DUP); // make a copy of direct call result + cfw.add(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable"); + cfw.add(ByteCode.IFEQ, exitLabel); + // cast direct call result + cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable"); + cfw.add(ByteCode.ARETURN); + cfw.markLabel(exitLabel); + + cfw.addALoad(firstLocal); + cfw.add(ByteCode.ARETURN); + + cfw.stopMethod((short)(firstLocal + 1)); + } + + static boolean isGenerator(ScriptOrFnNode node) + { + return (node.getType() == Token.FUNCTION ) && + ((FunctionNode)node).isGenerator(); + } + + // How dispatch to generators works: + // Two methods are generated corresponding to a user-written generator. + // One of these creates a generator object (NativeGenerator), which is + // returned to the user. The other method contains all of the body code + // of the generator. + // When a user calls a generator, the call() method dispatches control to + // to the method that creates the NativeGenerator object. Subsequently when + // the user invokes .next(), .send() or any such method on the generator + // object, the resumeGenerator() below dispatches the call to the + // method corresponding to the generator body. As a matter of convention + // the generator body is given the name of the generator activation function + // appended by "_gen". + private void generateResumeGenerator(ClassFileWriter cfw) + { + boolean hasGenerators = false; + for (int i=0; i < scriptOrFnNodes.length; i++) { + if (isGenerator(scriptOrFnNodes[i])) + hasGenerators = true; + } + + // if there are no generators defined, we don't implement a + // resumeGenerator(). The base class provides a default implementation. + if (!hasGenerators) + return; + + cfw.startMethod("resumeGenerator", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "ILjava/lang/Object;" + + "Ljava/lang/Object;)Ljava/lang/Object;", + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL)); + + // load arguments for dispatch to the corresponding *_gen method + cfw.addALoad(0); + cfw.addALoad(1); + cfw.addALoad(2); + cfw.addALoad(4); + cfw.addALoad(5); + cfw.addILoad(3); + + cfw.addLoadThis(); + cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I"); + + int startSwitch = cfw.addTableSwitch(0, scriptOrFnNodes.length - 1); + cfw.markTableSwitchDefault(startSwitch); + int endlabel = cfw.acquireLabel(); + + for (int i = 0; i < scriptOrFnNodes.length; i++) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + cfw.markTableSwitchCase(startSwitch, i, (short)6); + if (isGenerator(n)) { + String type = "(" + + mainClassSignature + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;I)Ljava/lang/Object;"; + cfw.addInvoke(ByteCode.INVOKESTATIC, + mainClassName, + getBodyMethodName(n) + "_gen", + type); + cfw.add(ByteCode.ARETURN); + } else { + cfw.add(ByteCode.GOTO, endlabel); + } + } + + cfw.markLabel(endlabel); + pushUndefined(cfw); + cfw.add(ByteCode.ARETURN); + + + // this method uses as many locals as there are arguments (hence 6) + cfw.stopMethod((short)6); + } + + private void generateCallMethod(ClassFileWriter cfw) + { + cfw.startMethod("call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL)); + + // Generate code for: + // if (!ScriptRuntime.hasTopCall(cx)) { + // return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args); + // } + + int nonTopCallLabel = cfw.acquireLabel(); + cfw.addALoad(1); //cx + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + "hasTopCall", + "(Lorg/mozilla/javascript/Context;" + +")Z"); + cfw.add(ByteCode.IFNE, nonTopCallLabel); + cfw.addALoad(0); + cfw.addALoad(1); + cfw.addALoad(2); + cfw.addALoad(3); + cfw.addALoad(4); + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + "doTopCall", + "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Ljava/lang/Object;"); + cfw.add(ByteCode.ARETURN); + cfw.markLabel(nonTopCallLabel); + + // Now generate switch to call the real methods + cfw.addALoad(0); + cfw.addALoad(1); + cfw.addALoad(2); + cfw.addALoad(3); + cfw.addALoad(4); + + int end = scriptOrFnNodes.length; + boolean generateSwitch = (2 <= end); + + int switchStart = 0; + int switchStackTop = 0; + if (generateSwitch) { + cfw.addLoadThis(); + cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I"); + // do switch from (1, end - 1) mapping 0 to + // the default case + switchStart = cfw.addTableSwitch(1, end - 1); + } + + for (int i = 0; i != end; ++i) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + if (generateSwitch) { + if (i == 0) { + cfw.markTableSwitchDefault(switchStart); + switchStackTop = cfw.getStackTop(); + } else { + cfw.markTableSwitchCase(switchStart, i - 1, + switchStackTop); + } + } + if (n.getType() == Token.FUNCTION) { + OptFunctionNode ofn = OptFunctionNode.get(n); + if (ofn.isTargetOfDirectCall()) { + int pcount = ofn.fnode.getParamCount(); + if (pcount != 0) { + // loop invariant: + // stack top == arguments array from addALoad4() + for (int p = 0; p != pcount; ++p) { + cfw.add(ByteCode.ARRAYLENGTH); + cfw.addPush(p); + int undefArg = cfw.acquireLabel(); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ICMPLE, undefArg); + // get array[p] + cfw.addALoad(4); + cfw.addPush(p); + cfw.add(ByteCode.AALOAD); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(undefArg); + pushUndefined(cfw); + cfw.markLabel(beyond); + // Only one push + cfw.adjustStackTop(-1); + cfw.addPush(0.0); + // restore invariant + cfw.addALoad(4); + } + } + } + } + cfw.addInvoke(ByteCode.INVOKESTATIC, + mainClassName, + getBodyMethodName(n), + getBodyMethodSignature(n)); + cfw.add(ByteCode.ARETURN); + } + cfw.stopMethod((short)5); + // 5: this, cx, scope, js this, args[] + } + + private void generateMain(ClassFileWriter cfw) + { + cfw.startMethod("main", "([Ljava/lang/String;)V", + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_STATIC)); + + // load new ScriptImpl() + cfw.add(ByteCode.NEW, cfw.getClassName()); + cfw.add(ByteCode.DUP); + cfw.addInvoke(ByteCode.INVOKESPECIAL, cfw.getClassName(), + "<init>", "()V"); + // load 'args' + cfw.add(ByteCode.ALOAD_0); + // Call mainMethodClass.main(Script script, String[] args) + cfw.addInvoke(ByteCode.INVOKESTATIC, + mainMethodClass, + "main", + "(Lorg/mozilla/javascript/Script;[Ljava/lang/String;)V"); + cfw.add(ByteCode.RETURN); + // 1 = String[] args + cfw.stopMethod((short)1); + } + + private void generateExecute(ClassFileWriter cfw) + { + cfw.startMethod("exec", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;", + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL)); + + final int CONTEXT_ARG = 1; + final int SCOPE_ARG = 2; + + cfw.addLoadThis(); + cfw.addALoad(CONTEXT_ARG); + cfw.addALoad(SCOPE_ARG); + cfw.add(ByteCode.DUP); + cfw.add(ByteCode.ACONST_NULL); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + cfw.getClassName(), + "call", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Ljava/lang/Object;"); + + cfw.add(ByteCode.ARETURN); + // 3 = this + context + scope + cfw.stopMethod((short)3); + } + + private void generateScriptCtor(ClassFileWriter cfw) + { + cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC); + + cfw.addLoadThis(); + cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, + "<init>", "()V"); + // set id to 0 + cfw.addLoadThis(); + cfw.addPush(0); + cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I"); + + cfw.add(ByteCode.RETURN); + // 1 parameter = this + cfw.stopMethod((short)1); + } + + private void generateFunctionConstructor(ClassFileWriter cfw) + { + final int SCOPE_ARG = 1; + final int CONTEXT_ARG = 2; + final int ID_ARG = 3; + + cfw.startMethod("<init>", FUNCTION_CONSTRUCTOR_SIGNATURE, + ClassFileWriter.ACC_PUBLIC); + cfw.addALoad(0); + cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, + "<init>", "()V"); + + cfw.addLoadThis(); + cfw.addILoad(ID_ARG); + cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I"); + + cfw.addLoadThis(); + cfw.addALoad(CONTEXT_ARG); + cfw.addALoad(SCOPE_ARG); + + int start = (scriptOrFnNodes[0].getType() == Token.SCRIPT) ? 1 : 0; + int end = scriptOrFnNodes.length; + if (start == end) throw badTree(); + boolean generateSwitch = (2 <= end - start); + + int switchStart = 0; + int switchStackTop = 0; + if (generateSwitch) { + cfw.addILoad(ID_ARG); + // do switch from (start + 1, end - 1) mapping start to + // the default case + switchStart = cfw.addTableSwitch(start + 1, end - 1); + } + + for (int i = start; i != end; ++i) { + if (generateSwitch) { + if (i == start) { + cfw.markTableSwitchDefault(switchStart); + switchStackTop = cfw.getStackTop(); + } else { + cfw.markTableSwitchCase(switchStart, i - 1 - start, + switchStackTop); + } + } + OptFunctionNode ofn = OptFunctionNode.get(scriptOrFnNodes[i]); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + mainClassName, + getFunctionInitMethodName(ofn), + FUNCTION_INIT_SIGNATURE); + cfw.add(ByteCode.RETURN); + } + + // 4 = this + scope + context + id + cfw.stopMethod((short)4); + } + + private void generateFunctionInit(ClassFileWriter cfw, + OptFunctionNode ofn) + { + final int CONTEXT_ARG = 1; + final int SCOPE_ARG = 2; + cfw.startMethod(getFunctionInitMethodName(ofn), + FUNCTION_INIT_SIGNATURE, + (short)(ClassFileWriter.ACC_PRIVATE + | ClassFileWriter.ACC_FINAL)); + + // Call NativeFunction.initScriptFunction + cfw.addLoadThis(); + cfw.addALoad(CONTEXT_ARG); + cfw.addALoad(SCOPE_ARG); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/NativeFunction", + "initScriptFunction", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")V"); + + // precompile all regexp literals + int regexpCount = ofn.fnode.getRegexpCount(); + if (regexpCount != 0) { + cfw.addLoadThis(); + pushRegExpArray(cfw, ofn.fnode, CONTEXT_ARG, SCOPE_ARG); + cfw.add(ByteCode.PUTFIELD, mainClassName, + REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE); + } + + cfw.add(ByteCode.RETURN); + // 3 = (scriptThis/functionRef) + scope + context + cfw.stopMethod((short)3); + } + + private void generateNativeFunctionOverrides(ClassFileWriter cfw, + String encodedSource) + { + // Override NativeFunction.getLanguageVersion() with + // public int getLanguageVersion() { return <version-constant>; } + + cfw.startMethod("getLanguageVersion", "()I", + ClassFileWriter.ACC_PUBLIC); + + cfw.addPush(compilerEnv.getLanguageVersion()); + cfw.add(ByteCode.IRETURN); + + // 1: this and no argument or locals + cfw.stopMethod((short)1); + + // The rest of NativeFunction overrides require specific code for each + // script/function id + + final int Do_getFunctionName = 0; + final int Do_getParamCount = 1; + final int Do_getParamAndVarCount = 2; + final int Do_getParamOrVarName = 3; + final int Do_getEncodedSource = 4; + final int Do_getParamOrVarConst = 5; + final int SWITCH_COUNT = 6; + + for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) { + if (methodIndex == Do_getEncodedSource && encodedSource == null) { + continue; + } + + // Generate: + // prologue; + // switch over function id to implement function-specific action + // epilogue + + short methodLocals; + switch (methodIndex) { + case Do_getFunctionName: + methodLocals = 1; // Only this + cfw.startMethod("getFunctionName", "()Ljava/lang/String;", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getParamCount: + methodLocals = 1; // Only this + cfw.startMethod("getParamCount", "()I", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getParamAndVarCount: + methodLocals = 1; // Only this + cfw.startMethod("getParamAndVarCount", "()I", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getParamOrVarName: + methodLocals = 1 + 1; // this + paramOrVarIndex + cfw.startMethod("getParamOrVarName", "(I)Ljava/lang/String;", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getParamOrVarConst: + methodLocals = 1 + 1 + 1; // this + paramOrVarName + cfw.startMethod("getParamOrVarConst", "(I)Z", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getEncodedSource: + methodLocals = 1; // Only this + cfw.startMethod("getEncodedSource", "()Ljava/lang/String;", + ClassFileWriter.ACC_PUBLIC); + cfw.addPush(encodedSource); + break; + default: + throw Kit.codeBug(); + } + + int count = scriptOrFnNodes.length; + + int switchStart = 0; + int switchStackTop = 0; + if (count > 1) { + // Generate switch but only if there is more then one + // script/function + cfw.addLoadThis(); + cfw.add(ByteCode.GETFIELD, cfw.getClassName(), + ID_FIELD_NAME, "I"); + + // do switch from 1 .. count - 1 mapping 0 to the default case + switchStart = cfw.addTableSwitch(1, count - 1); + } + + for (int i = 0; i != count; ++i) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + if (i == 0) { + if (count > 1) { + cfw.markTableSwitchDefault(switchStart); + switchStackTop = cfw.getStackTop(); + } + } else { + cfw.markTableSwitchCase(switchStart, i - 1, + switchStackTop); + } + + // Impelemnet method-specific switch code + switch (methodIndex) { + case Do_getFunctionName: + // Push function name + if (n.getType() == Token.SCRIPT) { + cfw.addPush(""); + } else { + String name = ((FunctionNode)n).getFunctionName(); + cfw.addPush(name); + } + cfw.add(ByteCode.ARETURN); + break; + + case Do_getParamCount: + // Push number of defined parameters + cfw.addPush(n.getParamCount()); + cfw.add(ByteCode.IRETURN); + break; + + case Do_getParamAndVarCount: + // Push number of defined parameters and declared variables + cfw.addPush(n.getParamAndVarCount()); + cfw.add(ByteCode.IRETURN); + break; + + case Do_getParamOrVarName: + // Push name of parameter using another switch + // over paramAndVarCount + int paramAndVarCount = n.getParamAndVarCount(); + if (paramAndVarCount == 0) { + // The runtime should never call the method in this + // case but to make bytecode verifier happy return null + // as throwing execption takes more code + cfw.add(ByteCode.ACONST_NULL); + cfw.add(ByteCode.ARETURN); + } else if (paramAndVarCount == 1) { + // As above do not check for valid index but always + // return the name of the first param + cfw.addPush(n.getParamOrVarName(0)); + cfw.add(ByteCode.ARETURN); + } else { + // Do switch over getParamOrVarName + cfw.addILoad(1); // param or var index + // do switch from 1 .. paramAndVarCount - 1 mapping 0 + // to the default case + int paramSwitchStart = cfw.addTableSwitch( + 1, paramAndVarCount - 1); + for (int j = 0; j != paramAndVarCount; ++j) { + if (cfw.getStackTop() != 0) Kit.codeBug(); + String s = n.getParamOrVarName(j); + if (j == 0) { + cfw.markTableSwitchDefault(paramSwitchStart); + } else { + cfw.markTableSwitchCase(paramSwitchStart, j - 1, + 0); + } + cfw.addPush(s); + cfw.add(ByteCode.ARETURN); + } + } + break; + + case Do_getParamOrVarConst: + // Push name of parameter using another switch + // over paramAndVarCount + paramAndVarCount = n.getParamAndVarCount(); + boolean [] constness = n.getParamAndVarConst(); + if (paramAndVarCount == 0) { + // The runtime should never call the method in this + // case but to make bytecode verifier happy return null + // as throwing execption takes more code + cfw.add(ByteCode.ICONST_0); + cfw.add(ByteCode.IRETURN); + } else if (paramAndVarCount == 1) { + // As above do not check for valid index but always + // return the name of the first param + cfw.addPush(constness[0]); + cfw.add(ByteCode.IRETURN); + } else { + // Do switch over getParamOrVarName + cfw.addILoad(1); // param or var index + // do switch from 1 .. paramAndVarCount - 1 mapping 0 + // to the default case + int paramSwitchStart = cfw.addTableSwitch( + 1, paramAndVarCount - 1); + for (int j = 0; j != paramAndVarCount; ++j) { + if (cfw.getStackTop() != 0) Kit.codeBug(); + if (j == 0) { + cfw.markTableSwitchDefault(paramSwitchStart); + } else { + cfw.markTableSwitchCase(paramSwitchStart, j - 1, + 0); + } + cfw.addPush(constness[j]); + cfw.add(ByteCode.IRETURN); + } + } + break; + + case Do_getEncodedSource: + // Push number encoded source start and end + // to prepare for encodedSource.substring(start, end) + cfw.addPush(n.getEncodedSourceStart()); + cfw.addPush(n.getEncodedSourceEnd()); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + "java/lang/String", + "substring", + "(II)Ljava/lang/String;"); + cfw.add(ByteCode.ARETURN); + break; + + default: + throw Kit.codeBug(); + } + } + + cfw.stopMethod(methodLocals); + } + } + + private void emitRegExpInit(ClassFileWriter cfw) + { + // precompile all regexp literals + + int totalRegCount = 0; + for (int i = 0; i != scriptOrFnNodes.length; ++i) { + totalRegCount += scriptOrFnNodes[i].getRegexpCount(); + } + if (totalRegCount == 0) { + return; + } + + cfw.startMethod(REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE, + (short)(ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE + | ClassFileWriter.ACC_SYNCHRONIZED)); + cfw.addField("_reInitDone", "Z", + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + cfw.add(ByteCode.GETSTATIC, mainClassName, "_reInitDone", "Z"); + int doInit = cfw.acquireLabel(); + cfw.add(ByteCode.IFEQ, doInit); + cfw.add(ByteCode.RETURN); + cfw.markLabel(doInit); + + for (int i = 0; i != scriptOrFnNodes.length; ++i) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + int regCount = n.getRegexpCount(); + for (int j = 0; j != regCount; ++j) { + String reFieldName = getCompiledRegexpName(n, j); + String reFieldType = "Ljava/lang/Object;"; + String reString = n.getRegexpString(j); + String reFlags = n.getRegexpFlags(j); + cfw.addField(reFieldName, reFieldType, + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + cfw.addALoad(0); // proxy + cfw.addALoad(1); // context + cfw.addPush(reString); + if (reFlags == null) { + cfw.add(ByteCode.ACONST_NULL); + } else { + cfw.addPush(reFlags); + } + /*APPJET*/cfw.addLineNumberEntry((short)n.getRegexpLineno(j)); + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/RegExpProxy", + "compileRegExp", + "(Lorg/mozilla/javascript/Context;" + +"Ljava/lang/String;Ljava/lang/String;" + +")Ljava/lang/Object;"); + cfw.add(ByteCode.PUTSTATIC, mainClassName, + reFieldName, reFieldType); + } + } + + cfw.addPush(1); + cfw.add(ByteCode.PUTSTATIC, mainClassName, "_reInitDone", "Z"); + cfw.add(ByteCode.RETURN); + cfw.stopMethod((short)2); + } + + private void emitConstantDudeInitializers(ClassFileWriter cfw) + { + int N = itsConstantListSize; + if (N == 0) + return; + + cfw.startMethod("<clinit>", "()V", + (short)(ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_FINAL)); + + double[] array = itsConstantList; + for (int i = 0; i != N; ++i) { + double num = array[i]; + String constantName = "_k" + i; + String constantType = getStaticConstantWrapperType(num); + cfw.addField(constantName, constantType, + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + int inum = (int)num; + if (inum == num) { + cfw.add(ByteCode.NEW, "java/lang/Integer"); + cfw.add(ByteCode.DUP); + cfw.addPush(inum); + cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Integer", + "<init>", "(I)V"); + } else { + cfw.addPush(num); + addDoubleWrap(cfw); + } + cfw.add(ByteCode.PUTSTATIC, mainClassName, + constantName, constantType); + } + + cfw.add(ByteCode.RETURN); + cfw.stopMethod((short)0); + } + + void pushRegExpArray(ClassFileWriter cfw, ScriptOrFnNode n, + int contextArg, int scopeArg) + { + int regexpCount = n.getRegexpCount(); + if (regexpCount == 0) throw badTree(); + + cfw.addPush(regexpCount); + cfw.add(ByteCode.ANEWARRAY, "java/lang/Object"); + + cfw.addALoad(contextArg); + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + "checkRegExpProxy", + "(Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/RegExpProxy;"); + // Stack: proxy, array + cfw.add(ByteCode.DUP); + cfw.addALoad(contextArg); + cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName, + REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE); + for (int i = 0; i != regexpCount; ++i) { + // Stack: proxy, array + cfw.add(ByteCode.DUP2); + cfw.addALoad(contextArg); + cfw.addALoad(scopeArg); + cfw.add(ByteCode.GETSTATIC, mainClassName, + getCompiledRegexpName(n, i), "Ljava/lang/Object;"); + // Stack: compiledRegExp, scope, cx, proxy, array, proxy, array + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/RegExpProxy", + "wrapRegExp", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + // Stack: wrappedRegExp, array, proxy, array + cfw.addPush(i); + cfw.add(ByteCode.SWAP); + cfw.add(ByteCode.AASTORE); + // Stack: proxy, array + } + // remove proxy + cfw.add(ByteCode.POP); + } + + void pushNumberAsObject(ClassFileWriter cfw, double num) + { + if (num == 0.0) { + if (1 / num > 0) { + // +0.0 + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + "zeroObj", "Ljava/lang/Double;"); + } else { + cfw.addPush(num); + addDoubleWrap(cfw); + } + + } else if (num == 1.0) { + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + "oneObj", "Ljava/lang/Double;"); + return; + + } else if (num == -1.0) { + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + "minusOneObj", "Ljava/lang/Double;"); + + } else if (num != num) { + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "NaNobj", "Ljava/lang/Double;"); + + } else if (itsConstantListSize >= 2000) { + // There appears to be a limit in the JVM on either the number + // of static fields in a class or the size of the class + // initializer. Either way, we can't have any more than 2000 + // statically init'd constants. + cfw.addPush(num); + addDoubleWrap(cfw); + + } else { + int N = itsConstantListSize; + int index = 0; + if (N == 0) { + itsConstantList = new double[64]; + } else { + double[] array = itsConstantList; + while (index != N && array[index] != num) { + ++index; + } + if (N == array.length) { + array = new double[N * 2]; + System.arraycopy(itsConstantList, 0, array, 0, N); + itsConstantList = array; + } + } + if (index == N) { + itsConstantList[N] = num; + itsConstantListSize = N + 1; + } + String constantName = "_k" + index; + String constantType = getStaticConstantWrapperType(num); + cfw.add(ByteCode.GETSTATIC, mainClassName, + constantName, constantType); + } + } + + private static void addDoubleWrap(ClassFileWriter cfw) + { + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + "wrapDouble", "(D)Ljava/lang/Double;"); + } + + private static String getStaticConstantWrapperType(double num) + { + int inum = (int)num; + if (inum == num) { + return "Ljava/lang/Integer;"; + } else { + return "Ljava/lang/Double;"; + } + } + static void pushUndefined(ClassFileWriter cfw) + { + cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/Undefined", + "instance", "Ljava/lang/Object;"); + } + + int getIndex(ScriptOrFnNode n) + { + return scriptOrFnIndexes.getExisting(n); + } + + static String getDirectTargetFieldName(int i) + { + return "_dt" + i; + } + + String getDirectCtorName(ScriptOrFnNode n) + { + return "_n"+getIndex(n); + } + + String getBodyMethodName(ScriptOrFnNode n) + { + return "_c"+getIndex(n); + } + + String getBodyMethodSignature(ScriptOrFnNode n) + { + StringBuffer sb = new StringBuffer(); + sb.append('('); + sb.append(mainClassSignature); + sb.append("Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;"); + if (n.getType() == Token.FUNCTION) { + OptFunctionNode ofn = OptFunctionNode.get(n); + if (ofn.isTargetOfDirectCall()) { + int pCount = ofn.fnode.getParamCount(); + for (int i = 0; i != pCount; i++) { + sb.append("Ljava/lang/Object;D"); + } + } + } + sb.append("[Ljava/lang/Object;)Ljava/lang/Object;"); + return sb.toString(); + } + + String getFunctionInitMethodName(OptFunctionNode ofn) + { + return "_i"+getIndex(ofn.fnode); + } + + String getCompiledRegexpName(ScriptOrFnNode n, int regexpIndex) + { + return "_re"+getIndex(n)+"_"+regexpIndex; + } + + static RuntimeException badTree() + { + throw new RuntimeException("Bad tree in codegen"); + } + + void setMainMethodClass(String className) + { + mainMethodClass = className; + } + + static final String DEFAULT_MAIN_METHOD_CLASS + = "org.mozilla.javascript.optimizer.OptRuntime"; + + private static final String SUPER_CLASS_NAME + = "org.mozilla.javascript.NativeFunction"; + + static final String DIRECT_CALL_PARENT_FIELD = "_dcp"; + private static final String ID_FIELD_NAME = "_id"; + + private static final String REGEXP_INIT_METHOD_NAME = "_reInit"; + private static final String REGEXP_INIT_METHOD_SIGNATURE + = "(Lorg/mozilla/javascript/RegExpProxy;" + +"Lorg/mozilla/javascript/Context;" + +")V"; + static final String REGEXP_ARRAY_FIELD_NAME = "_re"; + static final String REGEXP_ARRAY_FIELD_TYPE = "[Ljava/lang/Object;"; + + static final String FUNCTION_INIT_SIGNATURE + = "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")V"; + + static final String FUNCTION_CONSTRUCTOR_SIGNATURE + = "(Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Context;I)V"; + + private static final Object globalLock = new Object(); + private static int globalSerialClassCounter; + + private CompilerEnvirons compilerEnv; + + private ObjArray directCallTargets; + ScriptOrFnNode[] scriptOrFnNodes; + private ObjToIntMap scriptOrFnIndexes; + + private String mainMethodClass = DEFAULT_MAIN_METHOD_CLASS; + + String mainClassName; + String mainClassSignature; + + private double[] itsConstantList; + private int itsConstantListSize; +} + + +class BodyCodegen +{ + void generateBodyCode() + { + isGenerator = Codegen.isGenerator(scriptOrFn); + + // generate the body of the current function or script object + initBodyGeneration(); + + if (isGenerator) { + + // All functions in the generated bytecode have a unique name. Every + // generator has a unique prefix followed by _gen + String type = "(" + + codegen.mainClassSignature + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;I)Ljava/lang/Object;"; + cfw.startMethod(codegen.getBodyMethodName(scriptOrFn) + "_gen", + type, + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + } else { + cfw.startMethod(codegen.getBodyMethodName(scriptOrFn), + codegen.getBodyMethodSignature(scriptOrFn), + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + } + + generatePrologue(); + Node treeTop; + if (fnCurrent != null) { + treeTop = scriptOrFn.getLastChild(); + } else { + treeTop = scriptOrFn; + } + generateStatement(treeTop); + generateEpilogue(); + + cfw.stopMethod((short)(localsMax + 1)); + + if (isGenerator) { + // generate the user visible method which when invoked will + // return a generator object + generateGenerator(); + } + } + + // This creates a the user-facing function that returns a NativeGenerator + // object. + private void generateGenerator() + { + cfw.startMethod(codegen.getBodyMethodName(scriptOrFn), + codegen.getBodyMethodSignature(scriptOrFn), + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + + initBodyGeneration(); + argsLocal = firstFreeLocal++; + localsMax = firstFreeLocal; + + // get top level scope + if (fnCurrent != null && !inDirectCallFunction + && (!compilerEnv.isUseDynamicScope() + || fnCurrent.fnode.getIgnoreDynamicScope())) + { + // Unless we're either in a direct call or using dynamic scope, + // use the enclosing scope of the function as our variable object. + cfw.addALoad(funObjLocal); + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + } + + // generators are forced to have an activation record + cfw.addALoad(funObjLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(argsLocal); + addScriptRuntimeInvoke("createFunctionActivation", + "(Lorg/mozilla/javascript/NativeFunction;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + + // create a function object + cfw.add(ByteCode.NEW, codegen.mainClassName); + // Call function constructor + cfw.add(ByteCode.DUP); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(contextLocal); // load 'cx' + cfw.addPush(scriptOrFnIndex); + cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName, + "<init>", Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE); + + // Init mainScript field + cfw.add(ByteCode.DUP); + if (isTopLevel) Kit.codeBug(); // Only functions can be generators + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + cfw.add(ByteCode.PUTFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + + generateNestedFunctionInits(); + + // create the NativeGenerator object that we return + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + cfw.addLoadConstant(maxLocals); + cfw.addLoadConstant(maxStack); + addOptRuntimeInvoke("createNativeGenerator", + "(Lorg/mozilla/javascript/NativeFunction;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;II" + +")Lorg/mozilla/javascript/Scriptable;"); + + cfw.add(ByteCode.ARETURN); + cfw.stopMethod((short)(localsMax + 1)); + } + + private void generateNestedFunctionInits() + { + int functionCount = scriptOrFn.getFunctionCount(); + for (int i = 0; i != functionCount; i++) { + OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, i); + if (ofn.fnode.getFunctionType() + == FunctionNode.FUNCTION_STATEMENT) + { + visitFunction(ofn, FunctionNode.FUNCTION_STATEMENT); + } + } + } + + private void initBodyGeneration() + { + isTopLevel = (scriptOrFn == codegen.scriptOrFnNodes[0]); + + varRegisters = null; + if (scriptOrFn.getType() == Token.FUNCTION) { + fnCurrent = OptFunctionNode.get(scriptOrFn); + hasVarsInRegs = !fnCurrent.fnode.requiresActivation(); + if (hasVarsInRegs) { + int n = fnCurrent.fnode.getParamAndVarCount(); + if (n != 0) { + varRegisters = new short[n]; + } + } + inDirectCallFunction = fnCurrent.isTargetOfDirectCall(); + if (inDirectCallFunction && !hasVarsInRegs) Codegen.badTree(); + } else { + fnCurrent = null; + hasVarsInRegs = false; + inDirectCallFunction = false; + } + + locals = new int[MAX_LOCALS]; + + funObjLocal = 0; + contextLocal = 1; + variableObjectLocal = 2; + thisObjLocal = 3; + localsMax = (short) 4; // number of parms + "this" + firstFreeLocal = 4; + + popvLocal = -1; + argsLocal = -1; + itsZeroArgArray = -1; + itsOneArgArray = -1; + scriptRegexpLocal = -1; + epilogueLabel = -1; + enterAreaStartLabel = -1; + generatorStateLocal = -1; + } + + /** + * Generate the prologue for a function or script. + */ + private void generatePrologue() + { + if (inDirectCallFunction) { + int directParameterCount = scriptOrFn.getParamCount(); + // 0 is reserved for function Object 'this' + // 1 is reserved for context + // 2 is reserved for parentScope + // 3 is reserved for script 'this' + if (firstFreeLocal != 4) Kit.codeBug(); + for (int i = 0; i != directParameterCount; ++i) { + varRegisters[i] = firstFreeLocal; + // 3 is 1 for Object parm and 2 for double parm + firstFreeLocal += 3; + } + if (!fnCurrent.getParameterNumberContext()) { + // make sure that all parameters are objects + itsForcedObjectParameters = true; + for (int i = 0; i != directParameterCount; ++i) { + short reg = varRegisters[i]; + cfw.addALoad(reg); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isObjectLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPNE, isObjectLabel); + cfw.addDLoad(reg + 1); + addDoubleWrap(); + cfw.addAStore(reg); + cfw.markLabel(isObjectLabel); + } + } + } + + if (fnCurrent != null && !inDirectCallFunction + && (!compilerEnv.isUseDynamicScope() + || fnCurrent.fnode.getIgnoreDynamicScope())) + { + // Unless we're either in a direct call or using dynamic scope, + // use the enclosing scope of the function as our variable object. + cfw.addALoad(funObjLocal); + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + } + + // reserve 'args[]' + argsLocal = firstFreeLocal++; + localsMax = firstFreeLocal; + + // Generate Generator specific prelude + if (isGenerator) { + + // reserve 'args[]' + operationLocal = firstFreeLocal++; + localsMax = firstFreeLocal; + + // Local 3 is a reference to a GeneratorState object. The rest + // of codegen expects local 3 to be a reference to the thisObj. + // So move the value in local 3 to generatorStateLocal, and load + // the saved thisObj from the GeneratorState object. + cfw.addALoad(thisObjLocal); + generatorStateLocal = firstFreeLocal++; + localsMax = firstFreeLocal; + cfw.add(ByteCode.CHECKCAST, OptRuntime.GeneratorState.CLASS_NAME); + cfw.add(ByteCode.DUP); + cfw.addAStore(generatorStateLocal); + cfw.add(ByteCode.GETFIELD, + OptRuntime.GeneratorState.CLASS_NAME, + OptRuntime.GeneratorState.thisObj_NAME, + OptRuntime.GeneratorState.thisObj_TYPE); + cfw.addAStore(thisObjLocal); + + if (epilogueLabel == -1) { + epilogueLabel = cfw.acquireLabel(); + } + + ArrayList targets = ((FunctionNode)scriptOrFn).getResumptionPoints(); + if (targets != null) { + // get resumption point + generateGetGeneratorResumptionPoint(); + + // generate dispatch table + generatorSwitch = cfw.addTableSwitch(0, + targets.size() + GENERATOR_START); + generateCheckForThrowOrClose(-1, false, GENERATOR_START); + } + } + + if (fnCurrent == null) { + // See comments in case Token.REGEXP + if (scriptOrFn.getRegexpCount() != 0) { + scriptRegexpLocal = getNewWordLocal(); + codegen.pushRegExpArray(cfw, scriptOrFn, contextLocal, + variableObjectLocal); + cfw.addAStore(scriptRegexpLocal); + } + } + + if (compilerEnv.isGenerateObserverCount()) + saveCurrentCodeOffset(); + + if (hasVarsInRegs) { + // No need to create activation. Pad arguments if need be. + int parmCount = scriptOrFn.getParamCount(); + if (parmCount > 0 && !inDirectCallFunction) { + // Set up args array + // check length of arguments, pad if need be + cfw.addALoad(argsLocal); + cfw.add(ByteCode.ARRAYLENGTH); + cfw.addPush(parmCount); + int label = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ICMPGE, label); + cfw.addALoad(argsLocal); + cfw.addPush(parmCount); + addScriptRuntimeInvoke("padArguments", + "([Ljava/lang/Object;I" + +")[Ljava/lang/Object;"); + cfw.addAStore(argsLocal); + cfw.markLabel(label); + } + + int paramCount = fnCurrent.fnode.getParamCount(); + int varCount = fnCurrent.fnode.getParamAndVarCount(); + boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst(); + + // REMIND - only need to initialize the vars that don't get a value + // before the next call and are used in the function + short firstUndefVar = -1; + for (int i = 0; i != varCount; ++i) { + short reg = -1; + if (i < paramCount) { + if (!inDirectCallFunction) { + reg = getNewWordLocal(); + cfw.addALoad(argsLocal); + cfw.addPush(i); + cfw.add(ByteCode.AALOAD); + cfw.addAStore(reg); + } + } else if (fnCurrent.isNumberVar(i)) { + reg = getNewWordPairLocal(constDeclarations[i]); + cfw.addPush(0.0); + cfw.addDStore(reg); + } else { + reg = getNewWordLocal(constDeclarations[i]); + if (firstUndefVar == -1) { + Codegen.pushUndefined(cfw); + firstUndefVar = reg; + } else { + cfw.addALoad(firstUndefVar); + } + cfw.addAStore(reg); + } + if (reg >= 0) { + if (constDeclarations[i]) { + cfw.addPush(0); + cfw.addIStore(reg + (fnCurrent.isNumberVar(i) ? 2 : 1)); + } + varRegisters[i] = reg; + } + + // Add debug table entry if we're generating debug info + if (compilerEnv.isGenerateDebugInfo()) { + String name = fnCurrent.fnode.getParamOrVarName(i); + String type = fnCurrent.isNumberVar(i) + ? "D" : "Ljava/lang/Object;"; + int startPC = cfw.getCurrentCodeOffset(); + if (reg < 0) { + reg = varRegisters[i]; + } + cfw.addVariableDescriptor(name, type, startPC, reg); + } + } + + // Skip creating activation object. + return; + } + + // skip creating activation object for the body of a generator. The + // activation record required by a generator has already been created + // in generateGenerator(). + if (isGenerator) + return; + + + String debugVariableName; + if (fnCurrent != null) { + debugVariableName = "activation"; + cfw.addALoad(funObjLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(argsLocal); + addScriptRuntimeInvoke("createFunctionActivation", + "(Lorg/mozilla/javascript/NativeFunction;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("enterActivationFunction", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")V"); + } else { + debugVariableName = "global"; + cfw.addALoad(funObjLocal); + cfw.addALoad(thisObjLocal); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addPush(0); // false to indicate it is not eval script + addScriptRuntimeInvoke("initScript", + "(Lorg/mozilla/javascript/NativeFunction;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Z" + +")V"); + } + + enterAreaStartLabel = cfw.acquireLabel(); + epilogueLabel = cfw.acquireLabel(); + cfw.markLabel(enterAreaStartLabel); + + generateNestedFunctionInits(); + + // default is to generate debug info + if (compilerEnv.isGenerateDebugInfo()) { + cfw.addVariableDescriptor(debugVariableName, + "Lorg/mozilla/javascript/Scriptable;", + cfw.getCurrentCodeOffset(), variableObjectLocal); + } + + if (fnCurrent == null) { + // OPT: use dataflow to prove that this assignment is dead + popvLocal = getNewWordLocal(); + Codegen.pushUndefined(cfw); + cfw.addAStore(popvLocal); + + int linenum = scriptOrFn.getEndLineno(); + if (linenum != -1) + cfw.addLineNumberEntry((short)linenum); + + } else { + if (fnCurrent.itsContainsCalls0) { + itsZeroArgArray = getNewWordLocal(); + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "emptyArgs", "[Ljava/lang/Object;"); + cfw.addAStore(itsZeroArgArray); + } + if (fnCurrent.itsContainsCalls1) { + itsOneArgArray = getNewWordLocal(); + cfw.addPush(1); + cfw.add(ByteCode.ANEWARRAY, "java/lang/Object"); + cfw.addAStore(itsOneArgArray); + } + } + } + + private void generateGetGeneratorResumptionPoint() + { + cfw.addALoad(generatorStateLocal); + cfw.add(ByteCode.GETFIELD, + OptRuntime.GeneratorState.CLASS_NAME, + OptRuntime.GeneratorState.resumptionPoint_NAME, + OptRuntime.GeneratorState.resumptionPoint_TYPE); + } + + private void generateSetGeneratorResumptionPoint(int nextState) + { + cfw.addALoad(generatorStateLocal); + cfw.addLoadConstant(nextState); + cfw.add(ByteCode.PUTFIELD, + OptRuntime.GeneratorState.CLASS_NAME, + OptRuntime.GeneratorState.resumptionPoint_NAME, + OptRuntime.GeneratorState.resumptionPoint_TYPE); + } + + private void generateGetGeneratorStackState() + { + cfw.addALoad(generatorStateLocal); + addOptRuntimeInvoke("getGeneratorStackState", + "(Ljava/lang/Object;)[Ljava/lang/Object;"); + } + + private void generateEpilogue() + { + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + if (isGenerator) { + // generate locals initialization + HashMap liveLocals = ((FunctionNode)scriptOrFn).getLiveLocals(); + if (liveLocals != null) { + ArrayList nodes = ((FunctionNode)scriptOrFn).getResumptionPoints(); + for (int i = 0; i < nodes.size(); i++) { + Node node = (Node) nodes.get(i); + int[] live = (int [])liveLocals.get(node); + if (live != null) { + cfw.markTableSwitchCase(generatorSwitch, + getNextGeneratorState(node)); + generateGetGeneratorLocalsState(); + for (int j = 0; j < live.length; j++) { + cfw.add(ByteCode.DUP); + cfw.addLoadConstant(j); + cfw.add(ByteCode.AALOAD); + cfw.addAStore(live[j]); + } + cfw.add(ByteCode.POP); + cfw.add(ByteCode.GOTO, getTargetLabel(node)); + } + } + } + + // generate dispatch tables for finally + if (finallys != null) { + Enumeration en = finallys.keys(); + while(en.hasMoreElements()) { + Node n = (Node) en.nextElement(); + if (n.getType() == Token.FINALLY) { + FinallyReturnPoint ret = + (FinallyReturnPoint)finallys.get(n); + // the finally will jump here + cfw.markLabel(ret.tableLabel, (short)1); + + // start generating a dispatch table + int startSwitch = cfw.addTableSwitch(0, + ret.jsrPoints.size() - 1); + int c = 0; + cfw.markTableSwitchDefault(startSwitch); + for (int i = 0; i < ret.jsrPoints.size(); i++) { + // generate gotos back to the JSR location + cfw.markTableSwitchCase(startSwitch, c); + cfw.add(ByteCode.GOTO, + ((Integer)ret.jsrPoints.get(i)).intValue()); + c++; + } + } + } + } + } + + if (epilogueLabel != -1) { + cfw.markLabel(epilogueLabel); + } + + if (hasVarsInRegs) { + cfw.add(ByteCode.ARETURN); + return; + } else if (isGenerator) { + if (((FunctionNode)scriptOrFn).getResumptionPoints() != null) { + cfw.markTableSwitchDefault(generatorSwitch); + } + + // change state for re-entry + generateSetGeneratorResumptionPoint(GENERATOR_TERMINATE); + + // throw StopIteration + cfw.addALoad(variableObjectLocal); + addOptRuntimeInvoke("throwStopIteration", + "(Ljava/lang/Object;)V"); + + Codegen.pushUndefined(cfw); + cfw.add(ByteCode.ARETURN); + + } else if (fnCurrent == null) { + cfw.addALoad(popvLocal); + cfw.add(ByteCode.ARETURN); + } else { + generateActivationExit(); + cfw.add(ByteCode.ARETURN); + + // Generate catch block to catch all and rethrow to call exit code + // under exception propagation as well. + + int finallyHandler = cfw.acquireLabel(); + cfw.markHandler(finallyHandler); + short exceptionObject = getNewWordLocal(); + cfw.addAStore(exceptionObject); + + // Duplicate generateActivationExit() in the catch block since it + // takes less space then full-featured ByteCode.JSR/ByteCode.RET + generateActivationExit(); + + cfw.addALoad(exceptionObject); + releaseWordLocal(exceptionObject); + // rethrow + cfw.add(ByteCode.ATHROW); + + // mark the handler + cfw.addExceptionHandler(enterAreaStartLabel, epilogueLabel, + finallyHandler, null); // catch any + } + } + + private void generateGetGeneratorLocalsState() { + cfw.addALoad(generatorStateLocal); + addOptRuntimeInvoke("getGeneratorLocalsState", + "(Ljava/lang/Object;)[Ljava/lang/Object;"); + } + + private void generateActivationExit() + { + if (fnCurrent == null || hasVarsInRegs) throw Kit.codeBug(); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("exitActivationFunction", + "(Lorg/mozilla/javascript/Context;)V"); + } + + private void generateStatement(Node node) + { + updateLineNumber(node); + int type = node.getType(); + Node child = node.getFirstChild(); + switch (type) { + case Token.LOOP: + case Token.LABEL: + case Token.WITH: + case Token.SCRIPT: + case Token.BLOCK: + case Token.EMPTY: + // no-ops. + while (child != null) { + generateStatement(child); + child = child.getNext(); + } + break; + + case Token.LOCAL_BLOCK: { + int local = getNewWordLocal(); + if (isGenerator) { + cfw.add(ByteCode.ACONST_NULL); + cfw.addAStore(local); + } + node.putIntProp(Node.LOCAL_PROP, local); + while (child != null) { + generateStatement(child); + child = child.getNext(); + } + releaseWordLocal((short)local); + node.removeProp(Node.LOCAL_PROP); + break; + } + + case Token.FUNCTION: { + int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP); + OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex); + int t = ofn.fnode.getFunctionType(); + if (t == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) { + visitFunction(ofn, t); + } else { + if (t != FunctionNode.FUNCTION_STATEMENT) { + throw Codegen.badTree(); + } + } + break; + } + + case Token.TRY: + visitTryCatchFinally((Node.Jump)node, child); + break; + + case Token.CATCH_SCOPE: + { + // nothing stays on the stack on entry into a catch scope + cfw.setStackTop((short) 0); + + int local = getLocalBlockRegister(node); + int scopeIndex + = node.getExistingIntProp(Node.CATCH_SCOPE_PROP); + + String name = child.getString(); // name of exception + child = child.getNext(); + generateExpression(child, node); // load expression object + if (scopeIndex == 0) { + cfw.add(ByteCode.ACONST_NULL); + } else { + // Load previous catch scope object + cfw.addALoad(local); + } + cfw.addPush(name); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + + addScriptRuntimeInvoke( + "newCatchScope", + "(Ljava/lang/Throwable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(local); + } + break; + + case Token.THROW: + generateExpression(child, node); + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + generateThrowJavaScriptException(); + break; + + case Token.RETHROW: + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + cfw.addALoad(getLocalBlockRegister(node)); + cfw.add(ByteCode.ATHROW); + break; + + case Token.RETURN_RESULT: + case Token.RETURN: + if (!isGenerator) { + if (child != null) { + generateExpression(child, node); + } else if (type == Token.RETURN) { + Codegen.pushUndefined(cfw); + } else { + if (popvLocal < 0) throw Codegen.badTree(); + cfw.addALoad(popvLocal); + } + } + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + if (epilogueLabel == -1) { + if (!hasVarsInRegs) throw Codegen.badTree(); + epilogueLabel = cfw.acquireLabel(); + } + cfw.add(ByteCode.GOTO, epilogueLabel); + break; + + case Token.SWITCH: + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + visitSwitch((Node.Jump)node, child); + break; + + case Token.ENTERWITH: + generateExpression(child, node); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke( + "enterWith", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + incReferenceWordLocal(variableObjectLocal); + break; + + case Token.LEAVEWITH: + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke( + "leaveWith", + "(Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + decReferenceWordLocal(variableObjectLocal); + break; + + case Token.ENUM_INIT_KEYS: + case Token.ENUM_INIT_VALUES: + case Token.ENUM_INIT_ARRAY: + generateExpression(child, node); + cfw.addALoad(contextLocal); + int enumType = type == Token.ENUM_INIT_KEYS + ? ScriptRuntime.ENUMERATE_KEYS : + type == Token.ENUM_INIT_VALUES + ? ScriptRuntime.ENUMERATE_VALUES : + ScriptRuntime.ENUMERATE_ARRAY; + cfw.addPush(enumType); + addScriptRuntimeInvoke("enumInit", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Ljava/lang/Object;"); + cfw.addAStore(getLocalBlockRegister(node)); + break; + + case Token.EXPR_VOID: + if (child.getType() == Token.SETVAR) { + /* special case this so as to avoid unnecessary + load's & pop's */ + visitSetVar(child, child.getFirstChild(), false); + } + else if (child.getType() == Token.SETCONSTVAR) { + /* special case this so as to avoid unnecessary + load's & pop's */ + visitSetConstVar(child, child.getFirstChild(), false); + } + else if (child.getType() == Token.YIELD) { + generateYieldPoint(child, false); + } + else { + generateExpression(child, node); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) + cfw.add(ByteCode.POP2); + else + cfw.add(ByteCode.POP); + } + break; + + case Token.EXPR_RESULT: + generateExpression(child, node); + if (popvLocal < 0) { + popvLocal = getNewWordLocal(); + } + cfw.addAStore(popvLocal); + break; + + case Token.TARGET: + { + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + int label = getTargetLabel(node); + cfw.markLabel(label); + if (compilerEnv.isGenerateObserverCount()) + saveCurrentCodeOffset(); + } + break; + + case Token.JSR: + case Token.GOTO: + case Token.IFEQ: + case Token.IFNE: + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + visitGoto((Node.Jump)node, type, child); + break; + + case Token.FINALLY: + { + if (compilerEnv.isGenerateObserverCount()) + saveCurrentCodeOffset(); + // there is exactly one value on the stack when enterring + // finally blocks: the return address (or its int encoding) + cfw.setStackTop((short)1); + + // Save return address in a new local + int finallyRegister = getNewWordLocal(); + if (isGenerator) + generateIntegerWrap(); + cfw.addAStore(finallyRegister); + + while (child != null) { + generateStatement(child); + child = child.getNext(); + } + if (isGenerator) { + cfw.addALoad(finallyRegister); + cfw.add(ByteCode.CHECKCAST, "java/lang/Integer"); + generateIntegerUnwrap(); + FinallyReturnPoint ret = + (FinallyReturnPoint)finallys.get(node); + ret.tableLabel = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, ret.tableLabel); + } else { + cfw.add(ByteCode.RET, finallyRegister); + } + releaseWordLocal((short)finallyRegister); + } + break; + + case Token.DEBUGGER: + break; + + default: + throw Codegen.badTree(); + } + + } + + private void generateIntegerWrap() + { + cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/Integer", "valueOf", + "(I)Ljava/lang/Integer;"); + } + + + private void generateIntegerUnwrap() + { + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/Integer", + "intValue", "()I"); + } + + + private void generateThrowJavaScriptException() + { + cfw.add(ByteCode.NEW, + "org/mozilla/javascript/JavaScriptException"); + cfw.add(ByteCode.DUP_X1); + cfw.add(ByteCode.SWAP); + cfw.addPush(scriptOrFn.getSourceName()); + cfw.addPush(itsLineNumber); + cfw.addInvoke( + ByteCode.INVOKESPECIAL, + "org/mozilla/javascript/JavaScriptException", + "<init>", + "(Ljava/lang/Object;Ljava/lang/String;I)V"); + cfw.add(ByteCode.ATHROW); + } + + private int getNextGeneratorState(Node node) + { + int nodeIndex = ((FunctionNode)scriptOrFn).getResumptionPoints() + .indexOf(node); + return nodeIndex + GENERATOR_YIELD_START; + } + + private void generateExpression(Node node, Node parent) + { + int type = node.getType(); + Node child = node.getFirstChild(); + switch (type) { + case Token.USE_STACK: + break; + + case Token.FUNCTION: + if (fnCurrent != null || parent.getType() != Token.SCRIPT) { + int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP); + OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, + fnIndex); + int t = ofn.fnode.getFunctionType(); + if (t != FunctionNode.FUNCTION_EXPRESSION) { + throw Codegen.badTree(); + } + visitFunction(ofn, t); + } + break; + + case Token.NAME: + { + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addPush(node.getString()); + addScriptRuntimeInvoke( + "name", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Ljava/lang/Object;"); + } + break; + + case Token.CALL: + case Token.NEW: + { + int specialType = node.getIntProp(Node.SPECIALCALL_PROP, + Node.NON_SPECIALCALL); + if (specialType == Node.NON_SPECIALCALL) { + OptFunctionNode target; + target = (OptFunctionNode)node.getProp( + Node.DIRECTCALL_PROP); + + if (target != null) { + visitOptimizedCall(node, target, type, child); + } else if (type == Token.CALL) { + visitStandardCall(node, child); + } else { + visitStandardNew(node, child); + } + } else { + visitSpecialCall(node, type, specialType, child); + } + } + break; + + case Token.REF_CALL: + generateFunctionAndThisObj(child, node); + // stack: ... functionObj thisObj + child = child.getNext(); + generateCallArgArray(node, child, false); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "callRef", + "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Ref;"); + break; + + case Token.NUMBER: + { + double num = node.getDouble(); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + cfw.addPush(num); + } else { + codegen.pushNumberAsObject(cfw, num); + } + } + break; + + case Token.STRING: + cfw.addPush(node.getString()); + break; + + case Token.THIS: + cfw.addALoad(thisObjLocal); + break; + + case Token.THISFN: + cfw.add(ByteCode.ALOAD_0); + break; + + case Token.NULL: + cfw.add(ByteCode.ACONST_NULL); + break; + + case Token.TRUE: + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + break; + + case Token.FALSE: + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + break; + + case Token.REGEXP: + { + int i = node.getExistingIntProp(Node.REGEXP_PROP); + // Scripts can not use REGEXP_ARRAY_FIELD_NAME since + // it it will make script.exec non-reentrant so they + // store regexp array in a local variable while + // functions always access precomputed + // REGEXP_ARRAY_FIELD_NAME not to consume locals + if (fnCurrent == null) { + cfw.addALoad(scriptRegexpLocal); + } else { + cfw.addALoad(funObjLocal); + cfw.add(ByteCode.GETFIELD, codegen.mainClassName, + Codegen.REGEXP_ARRAY_FIELD_NAME, + Codegen.REGEXP_ARRAY_FIELD_TYPE); + } + cfw.addPush(i); + cfw.add(ByteCode.AALOAD); + } + break; + + case Token.COMMA: { + Node next = child.getNext(); + while (next != null) { + generateExpression(child, node); + cfw.add(ByteCode.POP); + child = next; + next = next.getNext(); + } + generateExpression(child, node); + break; + } + + case Token.ENUM_NEXT: + case Token.ENUM_ID: { + int local = getLocalBlockRegister(node); + cfw.addALoad(local); + if (type == Token.ENUM_NEXT) { + addScriptRuntimeInvoke( + "enumNext", "(Ljava/lang/Object;)Ljava/lang/Boolean;"); + } else { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("enumId", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + break; + } + + case Token.ARRAYLIT: + visitArrayLiteral(node, child); + break; + + case Token.OBJECTLIT: + visitObjectLiteral(node, child); + break; + + case Token.NOT: { + int trueTarget = cfw.acquireLabel(); + int falseTarget = cfw.acquireLabel(); + int beyond = cfw.acquireLabel(); + generateIfJump(child, node, trueTarget, falseTarget); + + cfw.markLabel(trueTarget); + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(falseTarget); + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + cfw.markLabel(beyond); + cfw.adjustStackTop(-1); + break; + } + + case Token.BITNOT: + generateExpression(child, node); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + cfw.addPush(-1); // implement ~a as (a ^ -1) + cfw.add(ByteCode.IXOR); + cfw.add(ByteCode.I2D); + addDoubleWrap(); + break; + + case Token.VOID: + generateExpression(child, node); + cfw.add(ByteCode.POP); + Codegen.pushUndefined(cfw); + break; + + case Token.TYPEOF: + generateExpression(child, node); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;" + +")Ljava/lang/String;"); + break; + + case Token.TYPEOFNAME: + visitTypeofname(node); + break; + + case Token.INC: + case Token.DEC: + visitIncDec(node); + break; + + case Token.OR: + case Token.AND: { + generateExpression(child, node); + cfw.add(ByteCode.DUP); + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)Z"); + int falseTarget = cfw.acquireLabel(); + if (type == Token.AND) + cfw.add(ByteCode.IFEQ, falseTarget); + else + cfw.add(ByteCode.IFNE, falseTarget); + cfw.add(ByteCode.POP); + generateExpression(child.getNext(), node); + cfw.markLabel(falseTarget); + } + break; + + case Token.HOOK : { + Node ifThen = child.getNext(); + Node ifElse = ifThen.getNext(); + generateExpression(child, node); + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)Z"); + int elseTarget = cfw.acquireLabel(); + cfw.add(ByteCode.IFEQ, elseTarget); + short stack = cfw.getStackTop(); + generateExpression(ifThen, node); + int afterHook = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, afterHook); + cfw.markLabel(elseTarget, stack); + generateExpression(ifElse, node); + cfw.markLabel(afterHook); + } + break; + + case Token.ADD: { + generateExpression(child, node); + generateExpression(child.getNext(), node); + switch (node.getIntProp(Node.ISNUMBER_PROP, -1)) { + case Node.BOTH: + cfw.add(ByteCode.DADD); + break; + case Node.LEFT: + addOptRuntimeInvoke("add", + "(DLjava/lang/Object;)Ljava/lang/Object;"); + break; + case Node.RIGHT: + addOptRuntimeInvoke("add", + "(Ljava/lang/Object;D)Ljava/lang/Object;"); + break; + default: + if (child.getType() == Token.STRING) { + addScriptRuntimeInvoke("add", + "(Ljava/lang/String;" + +"Ljava/lang/Object;" + +")Ljava/lang/String;"); + } else if (child.getNext().getType() == Token.STRING) { + addScriptRuntimeInvoke("add", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +")Ljava/lang/String;"); + } else { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("add", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + } + break; + + case Token.MUL: + visitArithmetic(node, ByteCode.DMUL, child, parent); + break; + + case Token.SUB: + visitArithmetic(node, ByteCode.DSUB, child, parent); + break; + + case Token.DIV: + case Token.MOD: + visitArithmetic(node, type == Token.DIV + ? ByteCode.DDIV + : ByteCode.DREM, child, parent); + break; + + case Token.BITOR: + case Token.BITXOR: + case Token.BITAND: + case Token.LSH: + case Token.RSH: + case Token.URSH: + visitBitOp(node, type, child); + break; + + case Token.POS: + case Token.NEG: + generateExpression(child, node); + addObjectToDouble(); + if (type == Token.NEG) { + cfw.add(ByteCode.DNEG); + } + addDoubleWrap(); + break; + + case Token.TO_DOUBLE: + // cnvt to double (not Double) + generateExpression(child, node); + addObjectToDouble(); + break; + + case Token.TO_OBJECT: { + // convert from double + int prop = -1; + if (child.getType() == Token.NUMBER) { + prop = child.getIntProp(Node.ISNUMBER_PROP, -1); + } + if (prop != -1) { + child.removeProp(Node.ISNUMBER_PROP); + generateExpression(child, node); + child.putIntProp(Node.ISNUMBER_PROP, prop); + } else { + generateExpression(child, node); + addDoubleWrap(); + } + break; + } + + case Token.IN: + case Token.INSTANCEOF: + case Token.LE: + case Token.LT: + case Token.GE: + case Token.GT: { + int trueGOTO = cfw.acquireLabel(); + int falseGOTO = cfw.acquireLabel(); + visitIfJumpRelOp(node, child, trueGOTO, falseGOTO); + addJumpedBooleanWrap(trueGOTO, falseGOTO); + break; + } + + case Token.EQ: + case Token.NE: + case Token.SHEQ: + case Token.SHNE: { + int trueGOTO = cfw.acquireLabel(); + int falseGOTO = cfw.acquireLabel(); + visitIfJumpEqOp(node, child, trueGOTO, falseGOTO); + addJumpedBooleanWrap(trueGOTO, falseGOTO); + break; + } + + case Token.GETPROP: + case Token.GETPROPNOWARN: + visitGetProp(node, child); + break; + + case Token.GETELEM: + generateExpression(child, node); // object + generateExpression(child.getNext(), node); // id + cfw.addALoad(contextLocal); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + addScriptRuntimeInvoke( + "getObjectIndex", + "(Ljava/lang/Object;D" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + else { + addScriptRuntimeInvoke( + "getObjectElem", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + break; + + case Token.GET_REF: + generateExpression(child, node); // reference + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "refGet", + "(Lorg/mozilla/javascript/Ref;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + break; + + case Token.GETVAR: + visitGetVar(node); + break; + + case Token.SETVAR: + visitSetVar(node, child, true); + break; + + case Token.SETNAME: + visitSetName(node, child); + break; + + case Token.SETCONST: + visitSetConst(node, child); + break; + + case Token.SETCONSTVAR: + visitSetConstVar(node, child, true); + break; + + case Token.SETPROP: + case Token.SETPROP_OP: + visitSetProp(type, node, child); + break; + + case Token.SETELEM: + case Token.SETELEM_OP: + visitSetElem(type, node, child); + break; + + case Token.SET_REF: + case Token.SET_REF_OP: + { + generateExpression(child, node); + child = child.getNext(); + if (type == Token.SET_REF_OP) { + cfw.add(ByteCode.DUP); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "refGet", + "(Lorg/mozilla/javascript/Ref;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "refSet", + "(Lorg/mozilla/javascript/Ref;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + break; + + case Token.DEL_REF: + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("refDel", + "(Lorg/mozilla/javascript/Ref;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + break; + + case Token.DELPROP: + generateExpression(child, node); + child = child.getNext(); + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("delete", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + break; + + case Token.BINDNAME: + { + while (child != null) { + generateExpression(child, node); + child = child.getNext(); + } + // Generate code for "ScriptRuntime.bind(varObj, "s")" + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addPush(node.getString()); + addScriptRuntimeInvoke( + "bind", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + break; + + case Token.LOCAL_LOAD: + cfw.addALoad(getLocalBlockRegister(node)); + break; + + case Token.REF_SPECIAL: + { + String special = (String)node.getProp(Node.NAME_PROP); + generateExpression(child, node); + cfw.addPush(special); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "specialRef", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Ref;"); + } + break; + + case Token.REF_MEMBER: + case Token.REF_NS_MEMBER: + case Token.REF_NAME: + case Token.REF_NS_NAME: + { + int memberTypeFlags + = node.getIntProp(Node.MEMBER_TYPE_PROP, 0); + // generate possible target, possible namespace and member + do { + generateExpression(child, node); + child = child.getNext(); + } while (child != null); + cfw.addALoad(contextLocal); + String methodName, signature; + switch (type) { + case Token.REF_MEMBER: + methodName = "memberRef"; + signature = "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Lorg/mozilla/javascript/Ref;"; + break; + case Token.REF_NS_MEMBER: + methodName = "memberRef"; + signature = "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Lorg/mozilla/javascript/Ref;"; + break; + case Token.REF_NAME: + methodName = "nameRef"; + signature = "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"I" + +")Lorg/mozilla/javascript/Ref;"; + cfw.addALoad(variableObjectLocal); + break; + case Token.REF_NS_NAME: + methodName = "nameRef"; + signature = "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"I" + +")Lorg/mozilla/javascript/Ref;"; + cfw.addALoad(variableObjectLocal); + break; + default: + throw Kit.codeBug(); + } + cfw.addPush(memberTypeFlags); + addScriptRuntimeInvoke(methodName, signature); + } + break; + + case Token.DOTQUERY: + visitDotQuery(node, child); + break; + + case Token.ESCXMLATTR: + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("escapeAttributeValue", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/String;"); + break; + + case Token.ESCXMLTEXT: + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("escapeTextValue", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/String;"); + break; + + case Token.DEFAULTNAMESPACE: + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("setDefaultNamespace", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + break; + + case Token.YIELD: + generateYieldPoint(node, true); + break; + + case Token.WITHEXPR: { + Node enterWith = child; + Node with = enterWith.getNext(); + Node leaveWith = with.getNext(); + generateStatement(enterWith); + generateExpression(with.getFirstChild(), with); + generateStatement(leaveWith); + break; + } + + case Token.ARRAYCOMP: { + Node initStmt = child; + Node expr = child.getNext(); + generateStatement(initStmt); + generateExpression(expr, node); + break; + } + + default: + throw new RuntimeException("Unexpected node type "+type); + } + + } + + private void generateYieldPoint(Node node, boolean exprContext) { + // save stack state + int top = cfw.getStackTop(); + maxStack = maxStack > top ? maxStack : top; + if (cfw.getStackTop() != 0) { + generateGetGeneratorStackState(); + for (int i = 0; i < top; i++) { + cfw.add(ByteCode.DUP_X1); + cfw.add(ByteCode.SWAP); + cfw.addLoadConstant(i); + cfw.add(ByteCode.SWAP); + cfw.add(ByteCode.AASTORE); + } + // pop the array object + cfw.add(ByteCode.POP); + } + + // generate the yield argument + Node child = node.getFirstChild(); + if (child != null) + generateExpression(child, node); + else + Codegen.pushUndefined(cfw); + + // change the resumption state + int nextState = getNextGeneratorState(node); + generateSetGeneratorResumptionPoint(nextState); + + boolean hasLocals = generateSaveLocals(node); + + cfw.add(ByteCode.ARETURN); + + generateCheckForThrowOrClose(getTargetLabel(node), + hasLocals, nextState); + + // reconstruct the stack + if (top != 0) { + generateGetGeneratorStackState(); + for (int i = 0; i < top; i++) { + cfw.add(ByteCode.DUP); + cfw.addLoadConstant(top - i - 1); + cfw.add(ByteCode.AALOAD); + cfw.add(ByteCode.SWAP); + } + cfw.add(ByteCode.POP); + } + + // load return value from yield + if (exprContext) { + cfw.addALoad(argsLocal); + } + } + + private void generateCheckForThrowOrClose(int label, + boolean hasLocals, + int nextState) { + int throwLabel = cfw.acquireLabel(); + int closeLabel = cfw.acquireLabel(); + + // throw the user provided object, if the operation is .throw() + cfw.markLabel(throwLabel); + cfw.addALoad(argsLocal); + generateThrowJavaScriptException(); + + // throw our special internal exception if the generator is being closed + cfw.markLabel(closeLabel); + cfw.addALoad(argsLocal); + cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable"); + cfw.add(ByteCode.ATHROW); + + // mark the re-entry point + // jump here after initializing the locals + if (label != -1) + cfw.markLabel(label); + if (!hasLocals) { + // jump here directly if there are no locals + cfw.markTableSwitchCase(generatorSwitch, nextState); + } + + // see if we need to dispatch for .close() or .throw() + cfw.addILoad(operationLocal); + cfw.addLoadConstant(NativeGenerator.GENERATOR_CLOSE); + cfw.add(ByteCode.IF_ICMPEQ, closeLabel); + cfw.addILoad(operationLocal); + cfw.addLoadConstant(NativeGenerator.GENERATOR_THROW); + cfw.add(ByteCode.IF_ICMPEQ, throwLabel); + } + + private void generateIfJump(Node node, Node parent, + int trueLabel, int falseLabel) + { + // System.out.println("gen code for " + node.toString()); + + int type = node.getType(); + Node child = node.getFirstChild(); + + switch (type) { + case Token.NOT: + generateIfJump(child, node, falseLabel, trueLabel); + break; + + case Token.OR: + case Token.AND: { + int interLabel = cfw.acquireLabel(); + if (type == Token.AND) { + generateIfJump(child, node, interLabel, falseLabel); + } + else { + generateIfJump(child, node, trueLabel, interLabel); + } + cfw.markLabel(interLabel); + child = child.getNext(); + generateIfJump(child, node, trueLabel, falseLabel); + break; + } + + case Token.IN: + case Token.INSTANCEOF: + case Token.LE: + case Token.LT: + case Token.GE: + case Token.GT: + visitIfJumpRelOp(node, child, trueLabel, falseLabel); + break; + + case Token.EQ: + case Token.NE: + case Token.SHEQ: + case Token.SHNE: + visitIfJumpEqOp(node, child, trueLabel, falseLabel); + break; + + default: + // Generate generic code for non-optimized jump + generateExpression(node, parent); + addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z"); + cfw.add(ByteCode.IFNE, trueLabel); + cfw.add(ByteCode.GOTO, falseLabel); + } + } + + private void visitFunction(OptFunctionNode ofn, int functionType) + { + int fnIndex = codegen.getIndex(ofn.fnode); + cfw.add(ByteCode.NEW, codegen.mainClassName); + // Call function constructor + cfw.add(ByteCode.DUP); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(contextLocal); // load 'cx' + cfw.addPush(fnIndex); + cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName, + "<init>", Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE); + + // Init mainScript field; + cfw.add(ByteCode.DUP); + if (isTopLevel) { + cfw.add(ByteCode.ALOAD_0); + } else { + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + } + cfw.add(ByteCode.PUTFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + + int directTargetIndex = ofn.getDirectTargetIndex(); + if (directTargetIndex >= 0) { + cfw.add(ByteCode.DUP); + if (isTopLevel) { + cfw.add(ByteCode.ALOAD_0); + } else { + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + } + cfw.add(ByteCode.SWAP); + cfw.add(ByteCode.PUTFIELD, + codegen.mainClassName, + Codegen.getDirectTargetFieldName(directTargetIndex), + codegen.mainClassSignature); + } + + if (functionType == FunctionNode.FUNCTION_EXPRESSION) { + // Leave closure object on stack and do not pass it to + // initFunction which suppose to connect statements to scope + return; + } + cfw.addPush(functionType); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(contextLocal); // load 'cx' + addOptRuntimeInvoke("initFunction", + "(Lorg/mozilla/javascript/NativeFunction;" + +"I" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Context;" + +")V"); + } + + private int getTargetLabel(Node target) + { + int labelId = target.labelId(); + if (labelId == -1) { + labelId = cfw.acquireLabel(); + target.labelId(labelId); + } + return labelId; + } + + private void visitGoto(Node.Jump node, int type, Node child) + { + Node target = node.target; + if (type == Token.IFEQ || type == Token.IFNE) { + if (child == null) throw Codegen.badTree(); + int targetLabel = getTargetLabel(target); + int fallThruLabel = cfw.acquireLabel(); + if (type == Token.IFEQ) + generateIfJump(child, node, targetLabel, fallThruLabel); + else + generateIfJump(child, node, fallThruLabel, targetLabel); + cfw.markLabel(fallThruLabel); + } else { + if (type == Token.JSR) { + if (isGenerator) { + addGotoWithReturn(target); + } else { + addGoto(target, ByteCode.JSR); + } + } else { + addGoto(target, ByteCode.GOTO); + } + } + } + + private void addGotoWithReturn(Node target) { + FinallyReturnPoint ret = + (FinallyReturnPoint)finallys.get(target); + cfw.addLoadConstant(ret.jsrPoints.size()); + addGoto(target, ByteCode.GOTO); + int retLabel = cfw.acquireLabel(); + cfw.markLabel(retLabel); + ret.jsrPoints.add(Integer.valueOf(retLabel)); + } + + private void visitArrayLiteral(Node node, Node child) + { + int count = 0; + for (Node cursor = child; cursor != null; cursor = cursor.getNext()) { + ++count; + } + // load array to store array literal objects + addNewObjectArray(count); + for (int i = 0; i != count; ++i) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + generateExpression(child, node); + cfw.add(ByteCode.AASTORE); + child = child.getNext(); + } + int[] skipIndexes = (int[])node.getProp(Node.SKIP_INDEXES_PROP); + if (skipIndexes == null) { + cfw.add(ByteCode.ACONST_NULL); + cfw.add(ByteCode.ICONST_0); + } else { + cfw.addPush(OptRuntime.encodeIntArray(skipIndexes)); + cfw.addPush(skipIndexes.length); + } + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addOptRuntimeInvoke("newArrayLiteral", + "([Ljava/lang/Object;" + +"Ljava/lang/String;" + +"I" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + + private void visitObjectLiteral(Node node, Node child) + { + Object[] properties = (Object[])node.getProp(Node.OBJECT_IDS_PROP); + int count = properties.length; + + // load array with property ids + addNewObjectArray(count); + for (int i = 0; i != count; ++i) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + Object id = properties[i]; + if (id instanceof String) { + cfw.addPush((String)id); + } else { + cfw.addPush(((Integer)id).intValue()); + addScriptRuntimeInvoke("wrapInt", "(I)Ljava/lang/Integer;"); + } + cfw.add(ByteCode.AASTORE); + } + // load array with property values + addNewObjectArray(count); + Node child2 = child; + for (int i = 0; i != count; ++i) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + int childType = child.getType(); + if (childType == Token.GET) { + generateExpression(child.getFirstChild(), node); + } else if (childType == Token.SET) { + generateExpression(child.getFirstChild(), node); + } else { + generateExpression(child, node); + } + cfw.add(ByteCode.AASTORE); + child = child.getNext(); + } + // load array with getterSetter values + cfw.addPush(count); + cfw.add(ByteCode.NEWARRAY, ByteCode.T_INT); + for (int i = 0; i != count; ++i) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + int childType = child2.getType(); + if (childType == Token.GET) { + cfw.add(ByteCode.ICONST_M1); + } else if (childType == Token.SET) { + cfw.add(ByteCode.ICONST_1); + } else { + cfw.add(ByteCode.ICONST_0); + } + cfw.add(ByteCode.IASTORE); + child2 = child2.getNext(); + } + + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("newObjectLiteral", + "([Ljava/lang/Object;" + +"[Ljava/lang/Object;" + +"[I" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + + private void visitSpecialCall(Node node, int type, int specialType, + Node child) + { + cfw.addALoad(contextLocal); + + if (type == Token.NEW) { + generateExpression(child, node); + // stack: ... cx functionObj + } else { + generateFunctionAndThisObj(child, node); + // stack: ... cx functionObj thisObj + } + child = child.getNext(); + + generateCallArgArray(node, child, false); + + String methodName; + String callSignature; + + if (type == Token.NEW) { + methodName = "newObjectSpecial"; + callSignature = "(Lorg/mozilla/javascript/Context;" + +"Ljava/lang/Object;" + +"[Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"I" // call type + +")Ljava/lang/Object;"; + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + cfw.addPush(specialType); + } else { + methodName = "callSpecial"; + callSignature = "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"I" // call type + +"Ljava/lang/String;I" // filename, linenumber + +")Ljava/lang/Object;"; + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + cfw.addPush(specialType); + String sourceName = scriptOrFn.getSourceName(); + cfw.addPush(sourceName == null ? "" : sourceName); + cfw.addPush(itsLineNumber); + } + + addOptRuntimeInvoke(methodName, callSignature); + } + + private void visitStandardCall(Node node, Node child) + { + if (node.getType() != Token.CALL) throw Codegen.badTree(); + + Node firstArgChild = child.getNext(); + int childType = child.getType(); + + String methodName; + String signature; + + if (firstArgChild == null) { + if (childType == Token.NAME) { + // name() call + String name = child.getString(); + cfw.addPush(name); + methodName = "callName0"; + signature = "(Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else if (childType == Token.GETPROP) { + // x.name() call + Node propTarget = child.getFirstChild(); + generateExpression(propTarget, node); + Node id = propTarget.getNext(); + String property = id.getString(); + cfw.addPush(property); + methodName = "callProp0"; + signature = "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else if (childType == Token.GETPROPNOWARN) { + throw Kit.codeBug(); + } else { + generateFunctionAndThisObj(child, node); + methodName = "call0"; + signature = "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } + + } else if (childType == Token.NAME) { + // XXX: this optimization is only possible if name + // resolution + // is not affected by arguments evaluation and currently + // there are no checks for it + String name = child.getString(); + generateCallArgArray(node, firstArgChild, false); + cfw.addPush(name); + methodName = "callName"; + signature = "([Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else { + int argCount = 0; + for (Node arg = firstArgChild; arg != null; arg = arg.getNext()) { + ++argCount; + } + generateFunctionAndThisObj(child, node); + // stack: ... functionObj thisObj + if (argCount == 1) { + generateExpression(firstArgChild, node); + methodName = "call1"; + signature = "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else if (argCount == 2) { + generateExpression(firstArgChild, node); + generateExpression(firstArgChild.getNext(), node); + methodName = "call2"; + signature = "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else { + generateCallArgArray(node, firstArgChild, false); + methodName = "callN"; + signature = "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } + } + + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addOptRuntimeInvoke(methodName, signature); + } + + private void visitStandardNew(Node node, Node child) + { + if (node.getType() != Token.NEW) throw Codegen.badTree(); + + Node firstArgChild = child.getNext(); + + generateExpression(child, node); + // stack: ... functionObj + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + // stack: ... functionObj cx scope + generateCallArgArray(node, firstArgChild, false); + addScriptRuntimeInvoke( + "newObject", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + + private void visitOptimizedCall(Node node, OptFunctionNode target, + int type, Node child) + { + Node firstArgChild = child.getNext(); + + short thisObjLocal = 0; + if (type == Token.NEW) { + generateExpression(child, node); + } else { + generateFunctionAndThisObj(child, node); + thisObjLocal = getNewWordLocal(); + cfw.addAStore(thisObjLocal); + } + // stack: ... functionObj + + int beyond = cfw.acquireLabel(); + + int directTargetIndex = target.getDirectTargetIndex(); + if (isTopLevel) { + cfw.add(ByteCode.ALOAD_0); + } else { + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + } + cfw.add(ByteCode.GETFIELD, codegen.mainClassName, + Codegen.getDirectTargetFieldName(directTargetIndex), + codegen.mainClassSignature); + + cfw.add(ByteCode.DUP2); + // stack: ... functionObj directFunct functionObj directFunct + + int regularCall = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPNE, regularCall); + + // stack: ... functionObj directFunct + short stackHeight = cfw.getStackTop(); + cfw.add(ByteCode.SWAP); + cfw.add(ByteCode.POP); + // stack: ... directFunct + if (compilerEnv.isUseDynamicScope()) { + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + } else { + cfw.add(ByteCode.DUP); + // stack: ... directFunct directFunct + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()Lorg/mozilla/javascript/Scriptable;"); + // stack: ... directFunct scope + cfw.addALoad(contextLocal); + // stack: ... directFunct scope cx + cfw.add(ByteCode.SWAP); + } + // stack: ... directFunc cx scope + + if (type == Token.NEW) { + cfw.add(ByteCode.ACONST_NULL); + } else { + cfw.addALoad(thisObjLocal); + } + // stack: ... directFunc cx scope thisObj +/* +Remember that directCall parameters are paired in 1 aReg and 1 dReg +If the argument is an incoming arg, just pass the orginal pair thru. +Else, if the argument is known to be typed 'Number', pass Void.TYPE +in the aReg and the number is the dReg +Else pass the JS object in the aReg and 0.0 in the dReg. +*/ + Node argChild = firstArgChild; + while (argChild != null) { + int dcp_register = nodeIsDirectCallParameter(argChild); + if (dcp_register >= 0) { + cfw.addALoad(dcp_register); + cfw.addDLoad(dcp_register + 1); + } else if (argChild.getIntProp(Node.ISNUMBER_PROP, -1) + == Node.BOTH) + { + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + generateExpression(argChild, node); + } else { + generateExpression(argChild, node); + cfw.addPush(0.0); + } + argChild = argChild.getNext(); + } + + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "emptyArgs", "[Ljava/lang/Object;"); + cfw.addInvoke(ByteCode.INVOKESTATIC, + codegen.mainClassName, + (type == Token.NEW) + ? codegen.getDirectCtorName(target.fnode) + : codegen.getBodyMethodName(target.fnode), + codegen.getBodyMethodSignature(target.fnode)); + + cfw.add(ByteCode.GOTO, beyond); + + cfw.markLabel(regularCall, stackHeight); + // stack: ... functionObj directFunct + cfw.add(ByteCode.POP); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + // stack: ... functionObj cx scope + if (type != Token.NEW) { + cfw.addALoad(thisObjLocal); + releaseWordLocal(thisObjLocal); + // stack: ... functionObj cx scope thisObj + } + // XXX: this will generate code for the child array the second time, + // so expression code generation better not to alter tree structure... + generateCallArgArray(node, firstArgChild, true); + + if (type == Token.NEW) { + addScriptRuntimeInvoke( + "newObject", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + } else { + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Callable", + "call", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Ljava/lang/Object;"); + } + + cfw.markLabel(beyond); + } + + private void generateCallArgArray(Node node, Node argChild, boolean directCall) + { + int argCount = 0; + for (Node child = argChild; child != null; child = child.getNext()) { + ++argCount; + } + // load array object to set arguments + if (argCount == 1 && itsOneArgArray >= 0) { + cfw.addALoad(itsOneArgArray); + } else { + addNewObjectArray(argCount); + } + // Copy arguments into it + for (int i = 0; i != argCount; ++i) { + // If we are compiling a generator an argument could be the result + // of a yield. In that case we will have an immediate on the stack + // which we need to avoid + if (!isGenerator) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + } + + if (!directCall) { + generateExpression(argChild, node); + } else { + // If this has also been a directCall sequence, the Number + // flag will have remained set for any parameter so that + // the values could be copied directly into the outgoing + // args. Here we want to force it to be treated as not in + // a Number context, so we set the flag off. + int dcp_register = nodeIsDirectCallParameter(argChild); + if (dcp_register >= 0) { + dcpLoadAsObject(dcp_register); + } else { + generateExpression(argChild, node); + int childNumberFlag + = argChild.getIntProp(Node.ISNUMBER_PROP, -1); + if (childNumberFlag == Node.BOTH) { + addDoubleWrap(); + } + } + } + + // When compiling generators, any argument to a method may be a + // yield expression. Hence we compile the argument first and then + // load the argument index and assign the value to the args array. + if (isGenerator) { + short tempLocal = getNewWordLocal(); + cfw.addAStore(tempLocal); + cfw.add(ByteCode.CHECKCAST, "[Ljava/lang/Object;"); + cfw.add(ByteCode.DUP); + cfw.addPush(i); + cfw.addALoad(tempLocal); + releaseWordLocal(tempLocal); + } + + cfw.add(ByteCode.AASTORE); + + argChild = argChild.getNext(); + } + } + + private void generateFunctionAndThisObj(Node node, Node parent) + { + // Place on stack (function object, function this) pair + int type = node.getType(); + switch (node.getType()) { + case Token.GETPROPNOWARN: + throw Kit.codeBug(); + + case Token.GETPROP: + case Token.GETELEM: { + Node target = node.getFirstChild(); + generateExpression(target, node); + Node id = target.getNext(); + if (type == Token.GETPROP) { + String property = id.getString(); + cfw.addPush(property); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getPropFunctionAndThis", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Callable;"); + } else { + // Optimizer do not optimize this case for now + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) + throw Codegen.badTree(); + generateExpression(id, node); // id + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getElemFunctionAndThis", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Callable;"); + } + break; + } + + case Token.NAME: { + String name = node.getString(); + cfw.addPush(name); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke( + "getNameFunctionAndThis", + "(Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Callable;"); + break; + } + + default: // including GETVAR + generateExpression(node, parent); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getValueFunctionAndThis", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Callable;"); + break; + } + // Get thisObj prepared by get(Name|Prop|Elem|Value)FunctionAndThis + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "lastStoredScriptable", + "(Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + + private void updateLineNumber(Node node) + { + itsLineNumber = node.getLineno(); + if (itsLineNumber == -1) + return; + cfw.addLineNumberEntry((short)itsLineNumber); + } + + private void visitTryCatchFinally(Node.Jump node, Node child) + { + /* Save the variable object, in case there are with statements + * enclosed by the try block and we catch some exception. + * We'll restore it for the catch block so that catch block + * statements get the right scope. + */ + + // OPT we only need to do this if there are enclosed WITH + // statements; could statically check and omit this if there aren't any. + + // XXX OPT Maybe instead do syntactic transforms to associate + // each 'with' with a try/finally block that does the exitwith. + + short savedVariableObject = getNewWordLocal(); + cfw.addALoad(variableObjectLocal); + cfw.addAStore(savedVariableObject); + + /* + * Generate the code for the tree; most of the work is done in IRFactory + * and NodeTransformer; Codegen just adds the java handlers for the + * javascript catch and finally clauses. */ + + int startLabel = cfw.acquireLabel(); + cfw.markLabel(startLabel, (short)0); + + Node catchTarget = node.target; + Node finallyTarget = node.getFinally(); + + // create a table for the equivalent of JSR returns + if (isGenerator && finallyTarget != null) { + FinallyReturnPoint ret = new FinallyReturnPoint(); + if (finallys == null) { + finallys = new Hashtable(); + } + // add the finally target to hashtable + finallys.put(finallyTarget, ret); + // add the finally node as well to the hash table + finallys.put(finallyTarget.getNext(), ret); + } + + while (child != null) { + generateStatement(child); + child = child.getNext(); + } + + // control flow skips the handlers + int realEnd = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, realEnd); + + int exceptionLocal = getLocalBlockRegister(node); + // javascript handler; unwrap exception and GOTO to javascript + // catch area. + if (catchTarget != null) { + // get the label to goto + int catchLabel = catchTarget.labelId(); + + generateCatchBlock(JAVASCRIPT_EXCEPTION, savedVariableObject, + catchLabel, startLabel, exceptionLocal); + /* + * catch WrappedExceptions, see if they are wrapped + * JavaScriptExceptions. Otherwise, rethrow. + */ + generateCatchBlock(EVALUATOR_EXCEPTION, savedVariableObject, + catchLabel, startLabel, exceptionLocal); + + /* + we also need to catch EcmaErrors and feed the + associated error object to the handler + */ + generateCatchBlock(ECMAERROR_EXCEPTION, savedVariableObject, + catchLabel, startLabel, exceptionLocal); + + Context cx = Context.getCurrentContext(); + if (cx != null && + cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) + { + generateCatchBlock(THROWABLE_EXCEPTION, savedVariableObject, + catchLabel, startLabel, exceptionLocal); + } + } + + // finally handler; catch all exceptions, store to a local; JSR to + // the finally, then re-throw. + if (finallyTarget != null) { + int finallyHandler = cfw.acquireLabel(); + cfw.markHandler(finallyHandler); + cfw.addAStore(exceptionLocal); + + // reset the variable object local + cfw.addALoad(savedVariableObject); + cfw.addAStore(variableObjectLocal); + + // get the label to JSR to + int finallyLabel = finallyTarget.labelId(); + if (isGenerator) + addGotoWithReturn(finallyTarget); + else + cfw.add(ByteCode.JSR, finallyLabel); + + // rethrow + cfw.addALoad(exceptionLocal); + if (isGenerator) + cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable"); + cfw.add(ByteCode.ATHROW); + + // mark the handler + cfw.addExceptionHandler(startLabel, finallyLabel, + finallyHandler, null); // catch any + } + releaseWordLocal(savedVariableObject); + cfw.markLabel(realEnd); + } + + private static final int JAVASCRIPT_EXCEPTION = 0; + private static final int EVALUATOR_EXCEPTION = 1; + private static final int ECMAERROR_EXCEPTION = 2; + private static final int THROWABLE_EXCEPTION = 3; + + private void generateCatchBlock(int exceptionType, + short savedVariableObject, + int catchLabel, int startLabel, + int exceptionLocal) + { + int handler = cfw.acquireLabel(); + cfw.markHandler(handler); + + // MS JVM gets cranky if the exception object is left on the stack + cfw.addAStore(exceptionLocal); + + // reset the variable object local + cfw.addALoad(savedVariableObject); + cfw.addAStore(variableObjectLocal); + + String exceptionName; + if (exceptionType == JAVASCRIPT_EXCEPTION) { + exceptionName = "org/mozilla/javascript/JavaScriptException"; + } else if (exceptionType == EVALUATOR_EXCEPTION) { + exceptionName = "org/mozilla/javascript/EvaluatorException"; + } else if (exceptionType == ECMAERROR_EXCEPTION) { + exceptionName = "org/mozilla/javascript/EcmaError"; + } else if (exceptionType == THROWABLE_EXCEPTION) { + exceptionName = "java/lang/Throwable"; + } else { + throw Kit.codeBug(); + } + + // mark the handler + cfw.addExceptionHandler(startLabel, catchLabel, handler, + exceptionName); + + cfw.add(ByteCode.GOTO, catchLabel); + } + + + private boolean generateSaveLocals(Node node) + { + int count = 0; + for (int i = 0; i < firstFreeLocal; i++) { + if (locals[i] != 0) + count++; + } + + if (count == 0) { + ((FunctionNode)scriptOrFn).addLiveLocals(node, null); + return false; + } + + // calculate the max locals + maxLocals = maxLocals > count ? maxLocals : count; + + // create a locals list + int[] ls = new int[count]; + int s = 0; + for (int i = 0; i < firstFreeLocal; i++) { + if (locals[i] != 0) { + ls[s] = i; + s++; + } + } + + // save the locals + ((FunctionNode)scriptOrFn).addLiveLocals(node, ls); + + // save locals + generateGetGeneratorLocalsState(); + for (int i = 0; i < count; i++) { + cfw.add(ByteCode.DUP); + cfw.addLoadConstant(i); + cfw.addALoad(ls[i]); + cfw.add(ByteCode.AASTORE); + } + // pop the array off the stack + cfw.add(ByteCode.POP); + + return true; + } + + private void visitSwitch(Node.Jump switchNode, Node child) + { + // See comments in IRFactory.createSwitch() for description + // of SWITCH node + + generateExpression(child, switchNode); + // save selector value + short selector = getNewWordLocal(); + cfw.addAStore(selector); + + for (Node.Jump caseNode = (Node.Jump)child.getNext(); + caseNode != null; + caseNode = (Node.Jump)caseNode.getNext()) + { + if (caseNode.getType() != Token.CASE) + throw Codegen.badTree(); + Node test = caseNode.getFirstChild(); + generateExpression(test, caseNode); + cfw.addALoad(selector); + addScriptRuntimeInvoke("shallowEq", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +")Z"); + addGoto(caseNode.target, ByteCode.IFNE); + } + releaseWordLocal(selector); + } + + private void visitTypeofname(Node node) + { + if (hasVarsInRegs) { + int varIndex = fnCurrent.fnode.getIndexForNameNode(node); + if (varIndex >= 0) { + if (fnCurrent.isNumberVar(varIndex)) { + cfw.addPush("number"); + } else if (varIsDirectCallParameter(varIndex)) { + int dcp_register = varRegisters[varIndex]; + cfw.addALoad(dcp_register); + cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel); + short stack = cfw.getStackTop(); + cfw.addALoad(dcp_register); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;" + +")Ljava/lang/String;"); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(isNumberLabel, stack); + cfw.addPush("number"); + cfw.markLabel(beyond); + } else { + cfw.addALoad(varRegisters[varIndex]); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;" + +")Ljava/lang/String;"); + } + return; + } + } + cfw.addALoad(variableObjectLocal); + cfw.addPush(node.getString()); + addScriptRuntimeInvoke("typeofName", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Ljava/lang/String;"); + } + + /** + * Save the current code offset. This saved code offset is used to + * compute instruction counts in subsequent calls to + * {@link #addInstructionCount}. + */ + private void saveCurrentCodeOffset() { + savedCodeOffset = cfw.getCurrentCodeOffset(); + } + + /** + * Generate calls to ScriptRuntime.addInstructionCount to keep track of + * executed instructions and call <code>observeInstructionCount()</code> + * if a threshold is exceeded. + */ + private void addInstructionCount() { + int count = cfw.getCurrentCodeOffset() - savedCodeOffset; + if (count == 0) + return; + cfw.addALoad(contextLocal); + cfw.addPush(count); + addScriptRuntimeInvoke("addInstructionCount", + "(Lorg/mozilla/javascript/Context;" + +"I)V"); + } + + private void visitIncDec(Node node) + { + int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP); + Node child = node.getFirstChild(); + switch (child.getType()) { + case Token.GETVAR: + if (!hasVarsInRegs) Kit.codeBug(); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + boolean post = ((incrDecrMask & Node.POST_FLAG) != 0); + int varIndex = fnCurrent.getVarIndex(child); + short reg = varRegisters[varIndex]; + int offset = varIsDirectCallParameter(varIndex) ? 1 : 0; + cfw.addDLoad(reg + offset); + if (post) { + cfw.add(ByteCode.DUP2); + } + cfw.addPush(1.0); + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + cfw.add(ByteCode.DADD); + } else { + cfw.add(ByteCode.DSUB); + } + if (!post) { + cfw.add(ByteCode.DUP2); + } + cfw.addDStore(reg + offset); + } else { + boolean post = ((incrDecrMask & Node.POST_FLAG) != 0); + int varIndex = fnCurrent.getVarIndex(child); + short reg = varRegisters[varIndex]; + cfw.addALoad(reg); + if (post) { + cfw.add(ByteCode.DUP); + } + addObjectToDouble(); + cfw.addPush(1.0); + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + cfw.add(ByteCode.DADD); + } else { + cfw.add(ByteCode.DSUB); + } + addDoubleWrap(); + if (!post) { + cfw.add(ByteCode.DUP); + } + cfw.addAStore(reg); + break; + } + break; + case Token.NAME: + cfw.addALoad(variableObjectLocal); + cfw.addPush(child.getString()); // push name + cfw.addALoad(contextLocal); + cfw.addPush(incrDecrMask); + addScriptRuntimeInvoke("nameIncrDecr", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"I)Ljava/lang/Object;"); + break; + case Token.GETPROPNOWARN: + throw Kit.codeBug(); + case Token.GETPROP: { + Node getPropChild = child.getFirstChild(); + generateExpression(getPropChild, node); + generateExpression(getPropChild.getNext(), node); + cfw.addALoad(contextLocal); + cfw.addPush(incrDecrMask); + addScriptRuntimeInvoke("propIncrDecr", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"I)Ljava/lang/Object;"); + break; + } + case Token.GETELEM: { + Node elemChild = child.getFirstChild(); + generateExpression(elemChild, node); + generateExpression(elemChild.getNext(), node); + cfw.addALoad(contextLocal); + cfw.addPush(incrDecrMask); + if (elemChild.getNext().getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + addOptRuntimeInvoke("elemIncrDecr", + "(Ljava/lang/Object;" + +"D" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Ljava/lang/Object;"); + } else { + addScriptRuntimeInvoke("elemIncrDecr", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Ljava/lang/Object;"); + } + break; + } + case Token.GET_REF: { + Node refChild = child.getFirstChild(); + generateExpression(refChild, node); + cfw.addALoad(contextLocal); + cfw.addPush(incrDecrMask); + addScriptRuntimeInvoke( + "refIncrDecr", + "(Lorg/mozilla/javascript/Ref;" + +"Lorg/mozilla/javascript/Context;" + +"I)Ljava/lang/Object;"); + break; + } + default: + Codegen.badTree(); + } + } + + private static boolean isArithmeticNode(Node node) + { + int type = node.getType(); + return (type == Token.SUB) + || (type == Token.MOD) + || (type == Token.DIV) + || (type == Token.MUL); + } + + private void visitArithmetic(Node node, int opCode, Node child, + Node parent) + { + int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1); + if (childNumberFlag != -1) { + generateExpression(child, node); + generateExpression(child.getNext(), node); + cfw.add(opCode); + } + else { + boolean childOfArithmetic = isArithmeticNode(parent); + generateExpression(child, node); + if (!isArithmeticNode(child)) + addObjectToDouble(); + generateExpression(child.getNext(), node); + if (!isArithmeticNode(child.getNext())) + addObjectToDouble(); + cfw.add(opCode); + if (!childOfArithmetic) { + addDoubleWrap(); + } + } + } + + private void visitBitOp(Node node, int type, Node child) + { + int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1); + generateExpression(child, node); + + // special-case URSH; work with the target arg as a long, so + // that we can return a 32-bit unsigned value, and call + // toUint32 instead of toInt32. + if (type == Token.URSH) { + addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)J"); + generateExpression(child.getNext(), node); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + // Looks like we need to explicitly mask the shift to 5 bits - + // LUSHR takes 6 bits. + cfw.addPush(31); + cfw.add(ByteCode.IAND); + cfw.add(ByteCode.LUSHR); + cfw.add(ByteCode.L2D); + addDoubleWrap(); + return; + } + if (childNumberFlag == -1) { + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + generateExpression(child.getNext(), node); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + } + else { + addScriptRuntimeInvoke("toInt32", "(D)I"); + generateExpression(child.getNext(), node); + addScriptRuntimeInvoke("toInt32", "(D)I"); + } + switch (type) { + case Token.BITOR: + cfw.add(ByteCode.IOR); + break; + case Token.BITXOR: + cfw.add(ByteCode.IXOR); + break; + case Token.BITAND: + cfw.add(ByteCode.IAND); + break; + case Token.RSH: + cfw.add(ByteCode.ISHR); + break; + case Token.LSH: + cfw.add(ByteCode.ISHL); + break; + default: + throw Codegen.badTree(); + } + cfw.add(ByteCode.I2D); + if (childNumberFlag == -1) { + addDoubleWrap(); + } + } + + private int nodeIsDirectCallParameter(Node node) + { + if (node.getType() == Token.GETVAR + && inDirectCallFunction && !itsForcedObjectParameters) + { + int varIndex = fnCurrent.getVarIndex(node); + if (fnCurrent.isParameter(varIndex)) { + return varRegisters[varIndex]; + } + } + return -1; + } + + private boolean varIsDirectCallParameter(int varIndex) + { + return fnCurrent.isParameter(varIndex) + && inDirectCallFunction && !itsForcedObjectParameters; + } + + private void genSimpleCompare(int type, int trueGOTO, int falseGOTO) + { + if (trueGOTO == -1) throw Codegen.badTree(); + switch (type) { + case Token.LE : + cfw.add(ByteCode.DCMPG); + cfw.add(ByteCode.IFLE, trueGOTO); + break; + case Token.GE : + cfw.add(ByteCode.DCMPL); + cfw.add(ByteCode.IFGE, trueGOTO); + break; + case Token.LT : + cfw.add(ByteCode.DCMPG); + cfw.add(ByteCode.IFLT, trueGOTO); + break; + case Token.GT : + cfw.add(ByteCode.DCMPL); + cfw.add(ByteCode.IFGT, trueGOTO); + break; + default : + throw Codegen.badTree(); + + } + if (falseGOTO != -1) + cfw.add(ByteCode.GOTO, falseGOTO); + } + + private void visitIfJumpRelOp(Node node, Node child, + int trueGOTO, int falseGOTO) + { + if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree(); + int type = node.getType(); + Node rChild = child.getNext(); + if (type == Token.INSTANCEOF || type == Token.IN) { + generateExpression(child, node); + generateExpression(rChild, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + (type == Token.INSTANCEOF) ? "instanceOf" : "in", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Z"); + cfw.add(ByteCode.IFNE, trueGOTO); + cfw.add(ByteCode.GOTO, falseGOTO); + return; + } + int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1); + int left_dcp_register = nodeIsDirectCallParameter(child); + int right_dcp_register = nodeIsDirectCallParameter(rChild); + if (childNumberFlag != -1) { + // Force numeric context on both parameters and optimize + // direct call case as Optimizer currently does not handle it + + if (childNumberFlag != Node.RIGHT) { + // Left already has number content + generateExpression(child, node); + } else if (left_dcp_register != -1) { + dcpLoadAsNumber(left_dcp_register); + } else { + generateExpression(child, node); + addObjectToDouble(); + } + + if (childNumberFlag != Node.LEFT) { + // Right already has number content + generateExpression(rChild, node); + } else if (right_dcp_register != -1) { + dcpLoadAsNumber(right_dcp_register); + } else { + generateExpression(rChild, node); + addObjectToDouble(); + } + + genSimpleCompare(type, trueGOTO, falseGOTO); + + } else { + if (left_dcp_register != -1 && right_dcp_register != -1) { + // Generate code to dynamically check for number content + // if both operands are dcp + short stack = cfw.getStackTop(); + int leftIsNotNumber = cfw.acquireLabel(); + cfw.addALoad(left_dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + cfw.add(ByteCode.IF_ACMPNE, leftIsNotNumber); + cfw.addDLoad(left_dcp_register + 1); + dcpLoadAsNumber(right_dcp_register); + genSimpleCompare(type, trueGOTO, falseGOTO); + if (stack != cfw.getStackTop()) throw Codegen.badTree(); + + cfw.markLabel(leftIsNotNumber); + int rightIsNotNumber = cfw.acquireLabel(); + cfw.addALoad(right_dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + cfw.add(ByteCode.IF_ACMPNE, rightIsNotNumber); + cfw.addALoad(left_dcp_register); + addObjectToDouble(); + cfw.addDLoad(right_dcp_register + 1); + genSimpleCompare(type, trueGOTO, falseGOTO); + if (stack != cfw.getStackTop()) throw Codegen.badTree(); + + cfw.markLabel(rightIsNotNumber); + // Load both register as objects to call generic cmp_* + cfw.addALoad(left_dcp_register); + cfw.addALoad(right_dcp_register); + + } else { + generateExpression(child, node); + generateExpression(rChild, node); + } + + if (type == Token.GE || type == Token.GT) { + cfw.add(ByteCode.SWAP); + } + String routine = ((type == Token.LT) + || (type == Token.GT)) ? "cmp_LT" : "cmp_LE"; + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +")Z"); + cfw.add(ByteCode.IFNE, trueGOTO); + cfw.add(ByteCode.GOTO, falseGOTO); + } + } + + private void visitIfJumpEqOp(Node node, Node child, + int trueGOTO, int falseGOTO) + { + if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree(); + + short stackInitial = cfw.getStackTop(); + int type = node.getType(); + Node rChild = child.getNext(); + + // Optimize if one of operands is null + if (child.getType() == Token.NULL || rChild.getType() == Token.NULL) { + // eq is symmetric in this case + if (child.getType() == Token.NULL) { + child = rChild; + } + generateExpression(child, node); + if (type == Token.SHEQ || type == Token.SHNE) { + int testCode = (type == Token.SHEQ) + ? ByteCode.IFNULL : ByteCode.IFNONNULL; + cfw.add(testCode, trueGOTO); + } else { + if (type != Token.EQ) { + // swap false/true targets for != + if (type != Token.NE) throw Codegen.badTree(); + int tmp = trueGOTO; + trueGOTO = falseGOTO; + falseGOTO = tmp; + } + cfw.add(ByteCode.DUP); + int undefCheckLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IFNONNULL, undefCheckLabel); + short stack = cfw.getStackTop(); + cfw.add(ByteCode.POP); + cfw.add(ByteCode.GOTO, trueGOTO); + cfw.markLabel(undefCheckLabel, stack); + Codegen.pushUndefined(cfw); + cfw.add(ByteCode.IF_ACMPEQ, trueGOTO); + } + cfw.add(ByteCode.GOTO, falseGOTO); + } else { + int child_dcp_register = nodeIsDirectCallParameter(child); + if (child_dcp_register != -1 + && rChild.getType() == Token.TO_OBJECT) + { + Node convertChild = rChild.getFirstChild(); + if (convertChild.getType() == Token.NUMBER) { + cfw.addALoad(child_dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPNE, notNumbersLabel); + cfw.addDLoad(child_dcp_register + 1); + cfw.addPush(convertChild.getDouble()); + cfw.add(ByteCode.DCMPL); + if (type == Token.EQ) + cfw.add(ByteCode.IFEQ, trueGOTO); + else + cfw.add(ByteCode.IFNE, trueGOTO); + cfw.add(ByteCode.GOTO, falseGOTO); + cfw.markLabel(notNumbersLabel); + // fall thru into generic handling + } + } + + generateExpression(child, node); + generateExpression(rChild, node); + + String name; + int testCode; + switch (type) { + case Token.EQ: + name = "eq"; + testCode = ByteCode.IFNE; + break; + case Token.NE: + name = "eq"; + testCode = ByteCode.IFEQ; + break; + case Token.SHEQ: + name = "shallowEq"; + testCode = ByteCode.IFNE; + break; + case Token.SHNE: + name = "shallowEq"; + testCode = ByteCode.IFEQ; + break; + default: + throw Codegen.badTree(); + } + addScriptRuntimeInvoke(name, + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +")Z"); + cfw.add(testCode, trueGOTO); + cfw.add(ByteCode.GOTO, falseGOTO); + } + if (stackInitial != cfw.getStackTop()) throw Codegen.badTree(); + } + + private void visitSetName(Node node, Node child) + { + String name = node.getFirstChild().getString(); + while (child != null) { + generateExpression(child, node); + child = child.getNext(); + } + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addPush(name); + addScriptRuntimeInvoke( + "setName", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Ljava/lang/Object;"); + } + + private void visitSetConst(Node node, Node child) + { + String name = node.getFirstChild().getString(); + while (child != null) { + generateExpression(child, node); + child = child.getNext(); + } + cfw.addALoad(contextLocal); + cfw.addPush(name); + addScriptRuntimeInvoke( + "setConst", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Ljava/lang/String;" + +")Ljava/lang/Object;"); + } + + private void visitGetVar(Node node) + { + if (!hasVarsInRegs) Kit.codeBug(); + int varIndex = fnCurrent.getVarIndex(node); + short reg = varRegisters[varIndex]; + if (varIsDirectCallParameter(varIndex)) { + // Remember that here the isNumber flag means that we + // want to use the incoming parameter in a Number + // context, so test the object type and convert the + // value as necessary. + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + dcpLoadAsNumber(reg); + } else { + dcpLoadAsObject(reg); + } + } else if (fnCurrent.isNumberVar(varIndex)) { + cfw.addDLoad(reg); + } else { + cfw.addALoad(reg); + } + } + + private void visitSetVar(Node node, Node child, boolean needValue) + { + if (!hasVarsInRegs) Kit.codeBug(); + int varIndex = fnCurrent.getVarIndex(node); + generateExpression(child.getNext(), node); + boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1); + short reg = varRegisters[varIndex]; + boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst(); + if (constDeclarations[varIndex]) { + if (!needValue) { + if (isNumber) + cfw.add(ByteCode.POP2); + else + cfw.add(ByteCode.POP); + } + } + else if (varIsDirectCallParameter(varIndex)) { + if (isNumber) { + if (needValue) cfw.add(ByteCode.DUP2); + cfw.addALoad(reg); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = cfw.acquireLabel(); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel); + short stack = cfw.getStackTop(); + addDoubleWrap(); + cfw.addAStore(reg); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(isNumberLabel, stack); + cfw.addDStore(reg + 1); + cfw.markLabel(beyond); + } + else { + if (needValue) cfw.add(ByteCode.DUP); + cfw.addAStore(reg); + } + } else { + boolean isNumberVar = fnCurrent.isNumberVar(varIndex); + if (isNumber) { + if (isNumberVar) { + cfw.addDStore(reg); + if (needValue) cfw.addDLoad(reg); + } else { + if (needValue) cfw.add(ByteCode.DUP2); + // Cannot save number in variable since !isNumberVar, + // so convert to object + addDoubleWrap(); + cfw.addAStore(reg); + } + } else { + if (isNumberVar) Kit.codeBug(); + cfw.addAStore(reg); + if (needValue) cfw.addALoad(reg); + } + } + } + + private void visitSetConstVar(Node node, Node child, boolean needValue) + { + if (!hasVarsInRegs) Kit.codeBug(); + int varIndex = fnCurrent.getVarIndex(node); + generateExpression(child.getNext(), node); + boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1); + short reg = varRegisters[varIndex]; + int beyond = cfw.acquireLabel(); + int noAssign = cfw.acquireLabel(); + if (isNumber) { + cfw.addILoad(reg + 2); + cfw.add(ByteCode.IFNE, noAssign); + short stack = cfw.getStackTop(); + cfw.addPush(1); + cfw.addIStore(reg + 2); + cfw.addDStore(reg); + if (needValue) { + cfw.addDLoad(reg); + cfw.markLabel(noAssign, stack); + } else { + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(noAssign, stack); + cfw.add(ByteCode.POP2); + } + } + else { + cfw.addILoad(reg + 1); + cfw.add(ByteCode.IFNE, noAssign); + short stack = cfw.getStackTop(); + cfw.addPush(1); + cfw.addIStore(reg + 1); + cfw.addAStore(reg); + if (needValue) { + cfw.addALoad(reg); + cfw.markLabel(noAssign, stack); + } else { + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(noAssign, stack); + cfw.add(ByteCode.POP); + } + } + cfw.markLabel(beyond); + } + + private void visitGetProp(Node node, Node child) + { + generateExpression(child, node); // object + Node nameChild = child.getNext(); + generateExpression(nameChild, node); // the name + if (node.getType() == Token.GETPROPNOWARN) { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectPropNoWarn", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + return; + } + /* + for 'this.foo' we call getObjectProp(Scriptable...) which can + skip some casting overhead. + */ + int childType = child.getType(); + if (childType == Token.THIS && nameChild.getType() == Token.STRING) { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectProp", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } else { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectProp", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + + private void visitSetProp(int type, Node node, Node child) + { + Node objectChild = child; + generateExpression(child, node); + child = child.getNext(); + if (type == Token.SETPROP_OP) { + cfw.add(ByteCode.DUP); + } + Node nameChild = child; + generateExpression(child, node); + child = child.getNext(); + if (type == Token.SETPROP_OP) { + // stack: ... object object name -> ... object name object name + cfw.add(ByteCode.DUP_X1); + //for 'this.foo += ...' we call thisGet which can skip some + //casting overhead. + if (objectChild.getType() == Token.THIS + && nameChild.getType() == Token.STRING) + { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectProp", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } else { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectProp", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "setObjectProp", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + + private void visitSetElem(int type, Node node, Node child) + { + generateExpression(child, node); + child = child.getNext(); + if (type == Token.SETELEM_OP) { + cfw.add(ByteCode.DUP); + } + generateExpression(child, node); + child = child.getNext(); + boolean indexIsNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1); + if (type == Token.SETELEM_OP) { + if (indexIsNumber) { + // stack: ... object object number + // -> ... object number object number + cfw.add(ByteCode.DUP2_X1); + cfw.addALoad(contextLocal); + addOptRuntimeInvoke( + "getObjectIndex", + "(Ljava/lang/Object;D" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } else { + // stack: ... object object indexObject + // -> ... object indexObject object indexObject + cfw.add(ByteCode.DUP_X1); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectElem", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + generateExpression(child, node); + cfw.addALoad(contextLocal); + if (indexIsNumber) { + addScriptRuntimeInvoke( + "setObjectIndex", + "(Ljava/lang/Object;" + +"D" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } else { + addScriptRuntimeInvoke( + "setObjectElem", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + + private void visitDotQuery(Node node, Node child) + { + updateLineNumber(node); + generateExpression(child, node); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("enterDotQuery", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + + // add push null/pop with label in between to simplify code for loop + // continue when it is necessary to pop the null result from + // updateDotQuery + cfw.add(ByteCode.ACONST_NULL); + int queryLoopStart = cfw.acquireLabel(); + cfw.markLabel(queryLoopStart); // loop continue jumps here + cfw.add(ByteCode.POP); + + generateExpression(child.getNext(), node); + addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z"); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("updateDotQuery", + "(Z" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"); + cfw.add(ByteCode.DUP); + cfw.add(ByteCode.IFNULL, queryLoopStart); + // stack: ... non_null_result_of_updateDotQuery + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("leaveDotQuery", + "(Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + } + + private int getLocalBlockRegister(Node node) + { + Node localBlock = (Node)node.getProp(Node.LOCAL_BLOCK_PROP); + int localSlot = localBlock.getExistingIntProp(Node.LOCAL_PROP); + return localSlot; + } + + private void dcpLoadAsNumber(int dcp_register) + { + cfw.addALoad(dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel); + short stack = cfw.getStackTop(); + cfw.addALoad(dcp_register); + addObjectToDouble(); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(isNumberLabel, stack); + cfw.addDLoad(dcp_register + 1); + cfw.markLabel(beyond); + } + + private void dcpLoadAsObject(int dcp_register) + { + cfw.addALoad(dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel); + short stack = cfw.getStackTop(); + cfw.addALoad(dcp_register); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(isNumberLabel, stack); + cfw.addDLoad(dcp_register + 1); + addDoubleWrap(); + cfw.markLabel(beyond); + } + + private void addGoto(Node target, int jumpcode) + { + int targetLabel = getTargetLabel(target); + cfw.add(jumpcode, targetLabel); + } + + private void addObjectToDouble() + { + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)D"); + } + + private void addNewObjectArray(int size) + { + if (size == 0) { + if (itsZeroArgArray >= 0) { + cfw.addALoad(itsZeroArgArray); + } else { + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "emptyArgs", "[Ljava/lang/Object;"); + } + } else { + cfw.addPush(size); + cfw.add(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + + private void addScriptRuntimeInvoke(String methodName, + String methodSignature) + { + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org.mozilla.javascript.ScriptRuntime", + methodName, + methodSignature); + } + + private void addOptRuntimeInvoke(String methodName, + String methodSignature) + { + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + methodName, + methodSignature); + } + + private void addJumpedBooleanWrap(int trueLabel, int falseLabel) + { + cfw.markLabel(falseLabel); + int skip = cfw.acquireLabel(); + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + cfw.add(ByteCode.GOTO, skip); + cfw.markLabel(trueLabel); + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + cfw.markLabel(skip); + cfw.adjustStackTop(-1); // only have 1 of true/false + } + + private void addDoubleWrap() + { + addOptRuntimeInvoke("wrapDouble", "(D)Ljava/lang/Double;"); + } + + /** + * Const locals use an extra slot to hold the has-been-assigned-once flag at + * runtime. + * @param isConst true iff the variable is const + * @return the register for the word pair (double/long) + */ + private short getNewWordPairLocal(boolean isConst) + { + short result = getConsecutiveSlots(2, isConst); + if (result < (MAX_LOCALS - 1)) { + locals[result] = 1; + locals[result + 1] = 1; + if (isConst) + locals[result + 2] = 1; + if (result == firstFreeLocal) { + for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) { + if (locals[i] == 0) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + } + else { + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private short getNewWordLocal(boolean isConst) + { + short result = getConsecutiveSlots(1, isConst); + if (result < (MAX_LOCALS - 1)) { + locals[result] = 1; + if (isConst) + locals[result + 1] = 1; + if (result == firstFreeLocal) { + for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) { + if (locals[i] == 0) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + } + else { + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private short getNewWordLocal() + { + short result = firstFreeLocal; + locals[result] = 1; + for (int i = firstFreeLocal + 1; i < MAX_LOCALS; i++) { + if (locals[i] == 0) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private short getConsecutiveSlots(int count, boolean isConst) { + if (isConst) + count++; + short result = firstFreeLocal; + while (true) { + if (result >= (MAX_LOCALS - 1)) + break; + int i; + for (i = 0; i < count; i++) + if (locals[result + i] != 0) + break; + if (i >= count) + break; + result++; + } + return result; + } + + // This is a valid call only for a local that is allocated by default. + private void incReferenceWordLocal(short local) + { + locals[local]++; + } + + // This is a valid call only for a local that is allocated by default. + private void decReferenceWordLocal(short local) + { + locals[local]--; + } + + private void releaseWordLocal(short local) + { + if (local < firstFreeLocal) + firstFreeLocal = local; + locals[local] = 0; + } + + + static final int GENERATOR_TERMINATE = -1; + static final int GENERATOR_START = 0; + static final int GENERATOR_YIELD_START = 1; + + ClassFileWriter cfw; + Codegen codegen; + CompilerEnvirons compilerEnv; + ScriptOrFnNode scriptOrFn; + public int scriptOrFnIndex; + private int savedCodeOffset; + + private OptFunctionNode fnCurrent; + private boolean isTopLevel; + + private static final int MAX_LOCALS = 256; + private int[] locals; + private short firstFreeLocal; + private short localsMax; + + private int itsLineNumber; + + private boolean hasVarsInRegs; + private short[] varRegisters; + private boolean inDirectCallFunction; + private boolean itsForcedObjectParameters; + private int enterAreaStartLabel; + private int epilogueLabel; + + // special known locals. If you add a new local here, be sure + // to initialize it to -1 in initBodyGeneration + private short variableObjectLocal; + private short popvLocal; + private short contextLocal; + private short argsLocal; + private short operationLocal; + private short thisObjLocal; + private short funObjLocal; + private short itsZeroArgArray; + private short itsOneArgArray; + private short scriptRegexpLocal; + private short generatorStateLocal; + + private boolean isGenerator; + private int generatorSwitch; + private int maxLocals = 0; + private int maxStack = 0; + + private Hashtable finallys; + + class FinallyReturnPoint { + public ArrayList jsrPoints = new ArrayList(); + public int tableLabel = 0; + } +} |