aboutsummaryrefslogtreecommitdiffstats
path: root/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java
diff options
context:
space:
mode:
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.java5031
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;
+ }
+}