rogerl%netscape.com 11acc154a5 Added 'flat' argument to NewRegExp to force literal interpretation of
entire string.


git-svn-id: svn://10.0.0.236/trunk@55674 18797224-902f-48f8-a5cc-f745e15eee43
1999-12-08 01:48:03 +00:00

4215 lines
165 KiB
Java

/*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1997-1999 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Norris Boyd
* Roger Lawrence
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
* provisions of the GPL are applicable instead of those above.
* If you wish to allow use of your version of this file only
* under the terms of the GPL and not to allow others to use your
* version of this file under the NPL, indicate your decision by
* deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete
* the provisions above, a recipient may use your version of this
* file under either the NPL or the GPL.
*/
package org.mozilla.javascript.optimizer;
import org.mozilla.javascript.*;
import org.mozilla.classfile.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
/**
* This class generates code for a given IR tree.
*
* @author Norris Boyd
* @author Roger Lawrence
*/
public class Codegen extends Interpreter {
public Codegen() {
itsConstantList = new ConstantList();
}
public IRFactory createIRFactory(TokenStream ts,
ClassNameHelper nameHelper, Scriptable scope)
{
return new OptIRFactory(ts, nameHelper, scope);
}
public Node transform(Node tree, TokenStream ts, Scriptable scope) {
OptTransformer opt = new OptTransformer(new Hashtable(11));
return opt.transform(tree, null, ts, scope);
}
public Object compile(Context cx, Scriptable scope, Node tree,
Object securityDomain,
SecuritySupport securitySupport,
ClassNameHelper nameHelper)
throws IOException
{
Vector classFiles = new Vector();
Vector names = new Vector();
String generatedName = null;
Exception e = null;
Class result = null;
try {
if (cx.getOptimizationLevel() > 0) {
(new Optimizer()).optimize(tree, cx.getOptimizationLevel());
}
generatedName = generateCode(tree, names, classFiles, nameHelper);
for (int i=0; i < names.size(); i++) {
String name = (String) names.elementAt(i);
byte[] classFile = (byte[]) classFiles.elementAt(i);
if (nameHelper.getGeneratingDirectory() != null) {
try {
int lastDot = name.lastIndexOf('.');
if (lastDot != -1)
name = name.substring(lastDot+1);
String filename
= nameHelper.getTargetClassFileName(name);
FileOutputStream out = new FileOutputStream(filename);
out.write(classFile);
out.close();
}
catch (IOException iox) {
throw WrappedException.wrapException(iox);
}
} else {
try {
Class clazz;
if (securitySupport == null) {
if (Context.isSecurityDomainRequired())
throw new SecurityException("Required " +
"security context missing");
if (classLoader == null)
classLoader = new JavaScriptClassLoader();
clazz = classLoader.defineClass(name, classFile);
ClassLoader loader = clazz.getClassLoader();
clazz = loader.loadClass(name);
} else {
clazz = securitySupport.defineClass(name, classFile,
securityDomain);
}
if (name.equals(generatedName))
result = clazz;
} catch (ClassFormatError ex) {
throw new RuntimeException(ex.toString());
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex.toString());
}
}
}
}
catch (SecurityException x) {
e = x;
}
catch (IllegalArgumentException x) {
e = x;
}
if (e != null)
throw new RuntimeException("Malformed optimizer package " + e);
OptClassNameHelper onh = (OptClassNameHelper) nameHelper;
if (onh.getTargetImplements() != null ||
onh.getTargetExtends() != null)
{
String name = onh.getJavaScriptClassName(null, true);
ScriptableObject obj = new NativeObject();
ShallowNodeIterator i = tree.getChildIterator();
while (i.hasMoreElements()) {
Node n = i.nextNode();
if (n.getType() == TokenStream.FUNCTION)
obj.put((String) n.getDatum(), obj,
n.getProp(Node.FUNCTION_PROP));
}
try {
Class superClass = onh.getTargetExtends();
if (superClass == null)
superClass = Object.class;
JavaAdapter.createAdapterClass(cx, obj, name,
superClass,
onh.getTargetImplements(),
generatedName,
onh);
} catch (ClassNotFoundException exn) {
// should never happen
throw new Error(exn.toString());
}
}
if (tree instanceof OptFunctionNode) {
return ScriptRuntime.createFunctionObject(scope, result, cx);
} else {
try {
if (result == null)
return null;
NativeScript script = (NativeScript) result.newInstance();
if (scope != null) {
script.setPrototype(script.getClassPrototype(scope,
"Script"));
script.setParentScope(scope);
}
return script;
}
catch (InstantiationException x) {
}
catch (IllegalAccessException x) {
}
throw new RuntimeException("Unable to instantiate compiled class");
}
}
void addByteCode(byte theOpcode)
{
classFile.add(theOpcode);
}
void addByteCode(byte theOpcode, int theOperand)
{
classFile.add(theOpcode, theOperand);
}
void addByteCode(byte theOpcode, String className)
{
classFile.add(theOpcode, className);
}
void addVirtualInvoke(String className, String methodName, String parameterSignature, String resultSignature)
{
classFile.add(ByteCode.INVOKEVIRTUAL,
className,
methodName,
parameterSignature,
resultSignature);
}
void addStaticInvoke(String className, String methodName, String parameterSignature, String resultSignature)
{
classFile.add(ByteCode.INVOKESTATIC,
className,
methodName,
parameterSignature,
resultSignature);
}
void addScriptRuntimeInvoke(String methodName, String parameterSignature, String resultSignature)
{
classFile.add(ByteCode.INVOKESTATIC,
"org/mozilla/javascript/ScriptRuntime",
methodName,
parameterSignature,
resultSignature);
}
void addOptRuntimeInvoke(String methodName, String parameterSignature, String resultSignature)
{
classFile.add(ByteCode.INVOKESTATIC,
"org/mozilla/javascript/optimizer/OptRuntime",
methodName,
parameterSignature,
resultSignature);
}
void addSpecialInvoke(String className, String methodName, String parameterSignature, String resultSignature)
{
classFile.add(ByteCode.INVOKESPECIAL,
className,
methodName,
parameterSignature,
resultSignature);
}
void addDoubleConstructor()
{
classFile.add(ByteCode.INVOKESPECIAL,
"java/lang/Double",
"<init>", "(D)", "V");
}
public int markLabel(int label)
{
return classFile.markLabel(label);
}
public int markLabel(int label, short stackheight)
{
return classFile.markLabel(label, stackheight);
}
public int acquireLabel()
{
return classFile.acquireLabel();
}
public void emitDirectConstructor(OptFunctionNode fnNode)
{
/*
we generate ..
Scriptable directConstruct(<directCallArgs>) {
Scriptable newInstance = new NativeObject();
newInstance.setPrototype(getClassPrototype());
newInstance.setParentScope(getParentScope());
Object val = callDirect(<directCallArgs>);
// except that the incoming thisArg is discarded
// and replaced by newInstance
//
if (val != null && val != Undefined.instance &&
val instanceof Scriptable)
{
return (Scriptable) val;
}
return newInstance;
}
*/
short flags = (short)(ClassFileWriter.ACC_PUBLIC
| ClassFileWriter.ACC_FINAL);
classFile.startMethod("constructDirect",
fnNode.getDirectCallParameterSignature()
+ "Ljava/lang/Object;",
flags);
int argCount = fnNode.getVariableTable().getParameterCount();
short firstLocal = (short)((4 + argCount * 3) + 1);
addByteCode(ByteCode.NEW, "org/mozilla/javascript/NativeObject");
addByteCode(ByteCode.DUP);
classFile.add(ByteCode.INVOKESPECIAL,
"org/mozilla/javascript/NativeObject",
"<init>", "()", "V");
astore(firstLocal);
aload(firstLocal);
aload((short)0);
addVirtualInvoke("org/mozilla/javascript/NativeFunction",
"getClassPrototype",
"()", "Lorg/mozilla/javascript/Scriptable;");
classFile.add(ByteCode.INVOKEINTERFACE,
"org/mozilla/javascript/Scriptable",
"setPrototype",
"(Lorg/mozilla/javascript/Scriptable;)",
"V");
aload(firstLocal);
aload((short)0);
addVirtualInvoke("org/mozilla/javascript/NativeFunction",
"getParentScope",
"()", "Lorg/mozilla/javascript/Scriptable;");
classFile.add(ByteCode.INVOKEINTERFACE,
"org/mozilla/javascript/Scriptable",
"setPrototype",
"(Lorg/mozilla/javascript/Scriptable;)",
"V");
aload((short)0);
aload((short)1);
aload((short)2);
aload(firstLocal);
for (int i = 0; i < argCount; i++) {
aload((short)(4 + (i * 3)));
dload((short)(5 + (i * 3)));
}
aload((short)(4 + argCount * 3));
addVirtualInvoke(this.name,
"callDirect",
fnNode.getDirectCallParameterSignature(),
"Ljava/lang/Object;");
astore((short)(firstLocal + 1));
int exitLabel = acquireLabel();
aload((short)(firstLocal + 1));
addByteCode(ByteCode.IFNULL, exitLabel);
aload((short)(firstLocal + 1));
pushUndefined();
addByteCode(ByteCode.IF_ACMPEQ, exitLabel);
aload((short)(firstLocal + 1));
addByteCode(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable");
addByteCode(ByteCode.IFEQ, exitLabel);
aload((short)(firstLocal + 1));
addByteCode(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable");
addByteCode(ByteCode.ARETURN);
markLabel(exitLabel);
aload(firstLocal);
addByteCode(ByteCode.ARETURN);
classFile.stopMethod((short)(firstLocal + 2), null);
}
public String generateCode(Node tree, Vector names, Vector classFiles,
ClassNameHelper nameHelper)
{
this.itsNameHelper = (OptClassNameHelper) nameHelper;
this.namesVector = names;
this.classFilesVector = classFiles;
Context cx = Context.getCurrentContext();
itsSourceFile = null;
if (cx.isGeneratingDebug())
itsSourceFile = (String) tree.getProp(Node.SOURCENAME_PROP);
version = cx.getLanguageVersion();
optLevel = cx.getOptimizationLevel();
debugLevel = cx.getDebugLevel();
inFunction = tree.getType() == TokenStream.FUNCTION;
if (debugLevel >= 3) {
superClassName = inFunction
? debugFunctionSuperClassName
: debugScriptSuperClassName;
} else {
superClassName = inFunction
? normalFunctionSuperClassName
: normalScriptSuperClassName;
}
superClassSlashName = superClassName.replace('.', '/');
Node codegenBase;
if (inFunction) {
OptFunctionNode fnNode = (OptFunctionNode) tree;
inDirectCallFunction = fnNode.isTargetOfDirectCall();
vars = (OptVariableTable) fnNode.getVariableTable();
this.name = fnNode.getClassName();
classFile = new ClassFileWriter(name, superClassName, itsSourceFile);
Node args = tree.getFirstChild();
String name = fnNode.getFunctionName();
generateInit(cx, "<init>", tree, name, args);
if (fnNode.isTargetOfDirectCall()) {
classFile.startMethod("call",
"(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/Scriptable;" +
"[Ljava/lang/Object;)Ljava/lang/Object;",
(short)(ClassFileWriter.ACC_PUBLIC
+ ClassFileWriter.ACC_FINAL));
addByteCode(ByteCode.ALOAD_0);
addByteCode(ByteCode.ALOAD_1);
addByteCode(ByteCode.ALOAD_2);
addByteCode(ByteCode.ALOAD_3);
for (int i = 0; i < vars.getParameterCount(); i++) {
push(i);
addByteCode(ByteCode.ALOAD, 4);
addByteCode(ByteCode.ARRAYLENGTH);
int undefArg = acquireLabel();
int beyond = acquireLabel();
addByteCode(ByteCode.IF_ICMPGE, undefArg);
addByteCode(ByteCode.ALOAD, 4);
push(i);
addByteCode(ByteCode.AALOAD);
push(0.0);
addByteCode(ByteCode.GOTO, beyond);
markLabel(undefArg);
pushUndefined();
push(0.0);
markLabel(beyond);
}
addByteCode(ByteCode.ALOAD, 4);
addVirtualInvoke(this.name,
"callDirect",
fnNode.getDirectCallParameterSignature(),
"Ljava/lang/Object;");
addByteCode(ByteCode.ARETURN);
classFile.stopMethod((short)5, null);
// 1 for this, 1 for js this, 1 for args[]
emitDirectConstructor(fnNode);
startNewMethod("callDirect",
fnNode.getDirectCallParameterSignature() +
"Ljava/lang/Object;",
1, false, true);
vars.assignParameterJRegs();
if (!fnNode.getParameterNumberContext()) {
// make sure that all parameters are objects
itsForcedObjectParameters = true;
for (int i = 0; i < vars.getParameterCount(); i++) {
OptLocalVariable lVar = (OptLocalVariable) vars.get(i);
aload(lVar.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
int isObjectLabel = acquireLabel();
addByteCode(ByteCode.IF_ACMPNE, isObjectLabel);
addByteCode(ByteCode.NEW,"java/lang/Double");
addByteCode(ByteCode.DUP);
dload((short)(lVar.getJRegister() + 1));
addDoubleConstructor();
astore(lVar.getJRegister());
markLabel(isObjectLabel);
}
}
generatePrologue(cx, tree, true, vars.getParameterCount());
} else {
startNewMethod("call",
"(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/Scriptable;" +
"[Ljava/lang/Object;)Ljava/lang/Object;",
1, false, true);
generatePrologue(cx, tree, true, -1);
}
codegenBase = tree.getLastChild();
} else {
// better be a script
if (tree.getType() != TokenStream.SCRIPT)
badTree();
vars = (OptVariableTable) tree.getProp(Node.VARS_PROP);
boolean isPrimary = itsNameHelper.getTargetExtends() == null &&
itsNameHelper.getTargetImplements() == null;
this.name = itsNameHelper.getJavaScriptClassName(null, isPrimary);
classFile = new ClassFileWriter(name, superClassName, itsSourceFile);
classFile.addInterface("org/mozilla/javascript/Script");
generateScriptCtor(cx, tree);
generateMain(cx);
generateInit(cx, "initScript", tree, "", null);
generateExecute(cx);
startNewMethod("call",
"(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/Scriptable;" +
"[Ljava/lang/Object;)Ljava/lang/Object;",
1, false, true);
generatePrologue(cx, tree, false, -1);
tree.addChildToBack(new Node(TokenStream.RETURN));
codegenBase = tree;
}
generateCodeFromNode(codegenBase, null, -1, -1);
generateEpilogue();
finishMethod(cx, debugVars);
emitConstantDudeInitializers();
// this needs to be done after the primary code generation is done
if (debugLevel >= 1)
generateDebugInit(cx, tree);
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
try {
classFile.write(out);
}
catch (IOException ioe) {
throw new RuntimeException("unexpected IOException");
}
byte[] bytes = out.toByteArray();
namesVector.addElement(name);
classFilesVector.addElement(bytes);
classFile = null;
return name;
}
private void generateCodeFromNode(Node node, Node parent, int trueLabel,
int falseLabel)
{
// System.out.println("gen code for " + node.toString());
int type = node.getType();
Node child = node.getFirstChild();
switch (type) {
case TokenStream.LOOP:
case TokenStream.WITH:
case TokenStream.LABEL:
visitStatement(node);
while (child != null) {
generateCodeFromNode(child, node, trueLabel, falseLabel);
child = child.getNextSibling();
}
break;
case TokenStream.CASE:
case TokenStream.DEFAULT:
// XXX shouldn't these be StatementNodes?
case TokenStream.SCRIPT:
case TokenStream.BLOCK:
case TokenStream.VOID:
case TokenStream.NOP:
// no-ops.
visitStatement(node);
while (child != null) {
generateCodeFromNode(child, node, trueLabel, falseLabel);
child = child.getNextSibling();
}
break;
case TokenStream.FUNCTION:
if (inFunction || parent.getType() != TokenStream.SCRIPT)
visitFunction(node);
break;
case TokenStream.NAME:
visitName(node);
break;
case TokenStream.NEW:
case TokenStream.CALL:
visitCall(node, type, child);
break;
case TokenStream.NUMBER:
case TokenStream.STRING:
visitLiteral(node);
break;
case TokenStream.PRIMARY:
visitPrimary(node);
break;
case TokenStream.OBJECT:
visitObject(node);
break;
case TokenStream.TRY:
visitTryCatchFinally(node, child);
break;
case TokenStream.THROW:
visitThrow(node, child);
break;
case TokenStream.RETURN:
visitReturn(node, child);
break;
case TokenStream.SWITCH:
visitSwitch(node, child);
break;
case TokenStream.COMMA:
generateCodeFromNode(child, node, -1, -1);
addByteCode(ByteCode.POP);
generateCodeFromNode(child.getNextSibling(),
node, trueLabel, falseLabel);
break;
case TokenStream.NEWSCOPE:
addScriptRuntimeInvoke("newScope", "()",
"Lorg/mozilla/javascript/Scriptable;");
break;
case TokenStream.ENTERWITH:
visitEnterWith(node, child);
break;
case TokenStream.LEAVEWITH:
visitLeaveWith(node, child);
break;
case TokenStream.ENUMINIT:
visitEnumInit(node, child);
break;
case TokenStream.ENUMNEXT:
visitEnumNext(node, child);
break;
case TokenStream.ENUMDONE:
visitEnumDone(node, child);
break;
case TokenStream.POP:
visitStatement(node);
if (child.getType() == TokenStream.SETVAR) {
/* special case this so as to avoid unnecessary
load's & pop's */
visitSetVar(child, child.getFirstChild(), false);
}
else {
while (child != null) {
generateCodeFromNode(child, node, trueLabel, falseLabel);
child = child.getNextSibling();
}
if (node.getProp(Node.ISNUMBER_PROP) != null)
addByteCode(ByteCode.POP2);
else
addByteCode(ByteCode.POP);
}
break;
case TokenStream.POPV:
visitStatement(node);
while (child != null) {
generateCodeFromNode(child, node, trueLabel, falseLabel);
child = child.getNextSibling();
}
astore(scriptResultLocal);
break;
case TokenStream.TARGET:
visitTarget(node);
break;
case TokenStream.JSR:
case TokenStream.GOTO:
case TokenStream.IFEQ:
case TokenStream.IFNE:
visitGOTO(node, type, child);
break;
case TokenStream.UNARYOP:
visitUnary(node, child, trueLabel, falseLabel);
break;
case TokenStream.TYPEOF:
visitTypeof(node, child);
break;
case TokenStream.INC:
visitIncDec(node, true);
break;
case TokenStream.DEC:
visitIncDec(node, false);
break;
case TokenStream.OR:
case TokenStream.AND: {
if (trueLabel == -1) {
generateCodeFromNode(child, node, trueLabel, falseLabel);
addByteCode(ByteCode.DUP);
addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)", "Z");
int falseTarget = acquireLabel();
if (type == TokenStream.AND)
addByteCode(ByteCode.IFEQ, falseTarget);
else
addByteCode(ByteCode.IFNE, falseTarget);
addByteCode(ByteCode.POP);
generateCodeFromNode(child.getNextSibling(), node, trueLabel, falseLabel);
markLabel(falseTarget);
}
else {
int interLabel = acquireLabel();
if (type == TokenStream.AND) {
generateCodeFromNode(child, node, interLabel, falseLabel);
if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT))
|| (child.getType() == TokenStream.AND)
|| (child.getType() == TokenStream.OR)
|| (child.getType() == TokenStream.RELOP)
|| (child.getType() == TokenStream.EQOP)) {
}
else {
addScriptRuntimeInvoke("toBoolean",
"(Ljava/lang/Object;)", "Z");
addByteCode(ByteCode.IFNE, interLabel);
addByteCode(ByteCode.GOTO, falseLabel);
}
}
else {
generateCodeFromNode(child, node, trueLabel, interLabel);
if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT))
|| (child.getType() == TokenStream.AND)
|| (child.getType() == TokenStream.OR)
|| (child.getType() == TokenStream.RELOP)
|| (child.getType() == TokenStream.EQOP)) {
}
else {
addScriptRuntimeInvoke("toBoolean",
"(Ljava/lang/Object;)", "Z");
addByteCode(ByteCode.IFNE, trueLabel);
addByteCode(ByteCode.GOTO, interLabel);
}
}
markLabel(interLabel);
child = child.getNextSibling();
generateCodeFromNode(child, node, trueLabel, falseLabel);
if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT))
|| (child.getType() == TokenStream.AND)
|| (child.getType() == TokenStream.OR)
|| (child.getType() == TokenStream.RELOP)
|| (child.getType() == TokenStream.EQOP)) {
}
else {
addScriptRuntimeInvoke("toBoolean",
"(Ljava/lang/Object;)", "Z");
addByteCode(ByteCode.IFNE, trueLabel);
addByteCode(ByteCode.GOTO, falseLabel);
}
}
}
break;
case TokenStream.ADD: {
generateCodeFromNode(child, node, trueLabel, falseLabel);
generateCodeFromNode(child.getNextSibling(),
node, trueLabel, falseLabel);
Integer childNumberFlag =
(Integer)(node.getProp(Node.ISNUMBER_PROP));
if (childNumberFlag != null) {
if (childNumberFlag.intValue() == Node.BOTH) {
addByteCode(ByteCode.DADD);
}
else {
if (childNumberFlag.intValue() == Node.LEFT) {
addOptRuntimeInvoke("add",
"(DLjava/lang/Object;)",
"Ljava/lang/Object;");
}
else {
addOptRuntimeInvoke("add",
"(Ljava/lang/Object;D)",
"Ljava/lang/Object;");
}
}
}
else {
addScriptRuntimeInvoke("add",
"(Ljava/lang/Object;Ljava/lang/Object;)",
"Ljava/lang/Object;");
}
}
break;
case TokenStream.MUL:
visitArithmetic(node, ByteCode.DMUL, child, parent);
break;
case TokenStream.SUB:
visitArithmetic(node, ByteCode.DSUB, child, parent);
break;
case TokenStream.DIV:
case TokenStream.MOD:
visitArithmetic(node, type == TokenStream.DIV
? ByteCode.DDIV
: ByteCode.DREM, child, parent);
break;
case TokenStream.BITOR:
case TokenStream.BITXOR:
case TokenStream.BITAND:
case TokenStream.LSH:
case TokenStream.RSH:
case TokenStream.URSH:
visitBitOp(node, type, child);
break;
case TokenStream.CONVERT: {
Object toType = node.getProp(Node.TYPE_PROP);
if (toType == ScriptRuntime.NumberClass) {
addByteCode(ByteCode.NEW, "java/lang/Double");
addByteCode(ByteCode.DUP);
generateCodeFromNode(child, node, trueLabel, falseLabel);
addScriptRuntimeInvoke("toNumber",
"(Ljava/lang/Object;)", "D");
addDoubleConstructor();
}
else {
if (toType == ScriptRuntime.DoubleClass) {
// cnvt to double
// (not Double)
generateCodeFromNode(child, node, trueLabel, falseLabel);
addScriptRuntimeInvoke("toNumber",
"(Ljava/lang/Object;)", "D");
}
else {
if (toType == ScriptRuntime.ObjectClass) {// convert from double
if ((child.getType() == TokenStream.NUMBER)
&& (child.getProp(Node.ISNUMBER_PROP)
!= null)) {
Object oldProp
= child.getProp(Node.ISNUMBER_PROP);
child.putProp(Node.ISNUMBER_PROP, null);
generateCodeFromNode(child, node, trueLabel, falseLabel);
child.putProp(Node.ISNUMBER_PROP, oldProp);
}
else {
addByteCode(ByteCode.NEW, "java/lang/Double");
addByteCode(ByteCode.DUP);
generateCodeFromNode(child, node, trueLabel, falseLabel);
addDoubleConstructor();
}
}
else
badTree();
}
}
}
break;
case TokenStream.RELOP:
if (trueLabel == -1) // need a result Object
visitRelOp(node, child, parent);
else
visitGOTOingRelOp(node, child, parent, trueLabel, falseLabel);
break;
case TokenStream.EQOP:
visitEqOp(node, child, parent, trueLabel, falseLabel);
break;
case TokenStream.GETPROP:
visitGetProp(node, child);
break;
case TokenStream.GETELEM:
while (child != null) {
generateCodeFromNode(child, node, trueLabel, falseLabel);
child = child.getNextSibling();
}
aload(variableObjectLocal);
if (node.getProp(Node.ISNUMBER_PROP) != null) {
addOptRuntimeInvoke("getElem",
"(Ljava/lang/Object;D" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
else {
addScriptRuntimeInvoke("getElem",
"(Ljava/lang/Object;Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
break;
case TokenStream.GETVAR: {
OptLocalVariable lVar
= (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP));
visitGetVar(lVar,
node.getProp(Node.ISNUMBER_PROP) != null,
node.getString());
}
break;
case TokenStream.SETVAR:
visitSetVar(node, child, true);
break;
case TokenStream.SETNAME:
visitSetName(node, child);
break;
case TokenStream.SETPROP:
visitSetProp(node, child);
break;
case TokenStream.SETELEM:
while (child != null) {
generateCodeFromNode(child, node, trueLabel, falseLabel);
child = child.getNextSibling();
}
aload(variableObjectLocal);
if (node.getProp(Node.ISNUMBER_PROP) != null) {
addOptRuntimeInvoke("setElem",
"(Ljava/lang/Object;D" +
"Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
else {
addScriptRuntimeInvoke("setElem",
"(Ljava/lang/Object;Ljava/lang/Object;" +
"Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
break;
case TokenStream.DELPROP:
while (child != null) {
generateCodeFromNode(child, node, trueLabel, falseLabel);
child = child.getNextSibling();
}
addScriptRuntimeInvoke("delete",
"(Ljava/lang/Object;Ljava/lang/Object;)",
"Ljava/lang/Object;");
break;
case TokenStream.BINDNAME:
case TokenStream.GETBASE:
visitBind(node, type, child);
break;
case TokenStream.GETTHIS:
generateCodeFromNode(child, node, trueLabel, falseLabel);
addScriptRuntimeInvoke("getThis",
"(Lorg/mozilla/javascript/Scriptable;)",
"Lorg/mozilla/javascript/Scriptable;");
break;
case TokenStream.PARENT:
generateCodeFromNode(child, node, trueLabel, falseLabel);
addScriptRuntimeInvoke("getParent",
"(Ljava/lang/Object;)",
"Lorg/mozilla/javascript/Scriptable;");
break;
case TokenStream.NEWTEMP:
visitNewTemp(node, child);
break;
case TokenStream.USETEMP:
visitUseTemp(node, child);
break;
case TokenStream.NEWLOCAL:
visitNewLocal(node, child);
break;
case TokenStream.USELOCAL:
visitUseLocal(node, child);
break;
default:
throw new RuntimeException("Unexpected node type " +
TokenStream.tokenToName(type));
}
}
private void startNewMethod(String methodName, String methodDesc,
int parmCount, boolean isStatic,
boolean isFinal)
{
locals = new boolean[MAX_LOCALS];
localsMax = (short) (parmCount+1); // number of parms + "this"
firstFreeLocal = 0;
contextLocal = -1;
variableObjectLocal = -1;
scriptResultLocal = -1;
argsLocal = -1;
thisObjLocal = -1;
funObjLocal = -1;
debug_pcLocal = -1;
debugStopSubRetLocal = -1;
itsZeroArgArray = -1;
itsOneArgArray = -1;
short flags = ClassFileWriter.ACC_PUBLIC;
if (isStatic)
flags |= ClassFileWriter.ACC_STATIC;
if (isFinal)
flags |= ClassFileWriter.ACC_FINAL;
epilogueLabel = -1;
classFile.startMethod(methodName, methodDesc, (short) flags);
}
private void finishMethod(Context cx, VariableTable vars) {
classFile.stopMethod((short)(localsMax + 1), vars);
contextLocal = -1;
}
private void generateMain(Context cx) {
startNewMethod("main", "([Ljava/lang/String;)V", 1, true, true);
push(this.name); // load the name of this class
addByteCode(ByteCode.ALOAD_0); // load 'args'
addScriptRuntimeInvoke("main",
"(Ljava/lang/String;[Ljava/lang/String;)",
"V");
addByteCode(ByteCode.RETURN);
finishMethod(cx, null);
}
private void generateExecute(Context cx) {
String signature = "(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/Scriptable;)" +
"Ljava/lang/Object;";
startNewMethod("exec", signature, 2, false, true);
String slashName = this.name.replace('.', '/');
if (!trivialInit) {
// to begin a script, call the initScript method
addByteCode(ByteCode.ALOAD_0); // load 'this'
addByteCode(ByteCode.ALOAD_2); // load 'scope'
addByteCode(ByteCode.ALOAD_1); // load 'cx'
addVirtualInvoke(slashName,
"initScript",
"(Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/Context;)",
"V");
}
if (debugLevel >= 3) {
addByteCode(ByteCode.ALOAD_1); // load 'cx'
addByteCode(ByteCode.ALOAD_0); // load 'this'
addByteCode(ByteCode.ALOAD_2); // load 'scope'
addStaticInvoke("org/mozilla/javascript/debug/NativeDelegate",
"executeDebug",
"(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/NativeFunction;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
else {
addByteCode(ByteCode.ALOAD_0); // load 'this'
addByteCode(ByteCode.ALOAD_1); // load 'cx'
addByteCode(ByteCode.ALOAD_2); // load 'scope'
addByteCode(ByteCode.DUP);
addByteCode(ByteCode.ACONST_NULL);
addVirtualInvoke(slashName,
"call",
"(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/Scriptable;" +
"[Ljava/lang/Object;)",
"Ljava/lang/Object;");
}
addByteCode(ByteCode.ARETURN);
finishMethod(cx, null);
}
private void generateScriptCtor(Context cx, Node tree) {
startNewMethod("<init>", "()V", 1, false, false);
addByteCode(ByteCode.ALOAD_0);
addSpecialInvoke(superClassSlashName,
"<init>", "()", "V");
addByteCode(ByteCode.RETURN);
finishMethod(cx, null);
}
/**
* Indicate that the init is non-trivial.
*
* For trivial inits we can omit creating the init method
* altogether. (Only applies to scripts, since function
* inits are constructors, which are required.) For trivial
* inits we also omit the call to initScript from exec().
*/
private void setNonTrivialInit(String methodName) {
if (!trivialInit)
return; // already set
trivialInit = false;
startNewMethod(methodName, "(Lorg/mozilla/javascript/Scriptable;"
+ "Lorg/mozilla/javascript/Context;)V",
1, false, false);
reserveWordLocal(0); // reserve 0 for 'this'
variableObjectLocal = reserveWordLocal(1); // reserve 1 for 'scope'
contextLocal = reserveWordLocal(2); // reserve 2 for 'context'
}
private void generateInit(Context cx, String methodName,
Node tree, String name, Node args)
{
trivialInit = true;
boolean inCtor = false;
VariableTable vars;
if (tree instanceof OptFunctionNode)
vars = ((OptFunctionNode)tree).getVariableTable();
else
vars = (VariableTable) tree.getProp(Node.VARS_PROP);
if (methodName.equals("<init>")) {
inCtor = true;
setNonTrivialInit(methodName);
addByteCode(ByteCode.ALOAD_0);
addSpecialInvoke(superClassSlashName, "<init>", "()", "V");
addByteCode(ByteCode.ALOAD_0);
addByteCode(ByteCode.ALOAD_1);
classFile.add(ByteCode.PUTFIELD,
"org/mozilla/javascript/ScriptableObject",
"parent", "Lorg/mozilla/javascript/Scriptable;");
}
/*
* Generate code to initialize names field with a
* string array that is the names of the function,
* the parameters and the vars. Initialize argCount
* to the number of formal parameters.
*/
if (name.length() != 0 || (vars != null && vars.size() > 0)) {
setNonTrivialInit(methodName);
if (vars == null)
vars = new OptVariableTable();
push(vars.size() + 1);
addByteCode(ByteCode.ANEWARRAY, "java/lang/String");
addByteCode(ByteCode.DUP);
short x = getNewWordLocal();
astore(x);
addByteCode(ByteCode.DUP);
push(0);
classFile.addLoadConstant(name);
addByteCode(ByteCode.AASTORE);
if (vars != null) {
for (int i = 0; i < vars.size(); i++) {
aload(x);
push(i + 1);
push(vars.getName(i));
addByteCode(ByteCode.AASTORE);
}
}
releaseWordLocal(x);
addByteCode(ByteCode.ALOAD_0);
addByteCode(ByteCode.SWAP);
classFile.add(ByteCode.PUTFIELD,
"org/mozilla/javascript/NativeFunction",
"names", "[Ljava/lang/String;");
}
int parmCount = vars == null ? 0 : vars.getParameterCount();
if (parmCount != 0) {
setNonTrivialInit(methodName);
addByteCode(ByteCode.ALOAD_0);
push(parmCount);
classFile.add(ByteCode.PUTFIELD,
"org/mozilla/javascript/NativeFunction",
"argCount", "S");
}
// Initialize NativeFunction.version with Context's version.
if (cx.getLanguageVersion() != 0) {
setNonTrivialInit(methodName);
addByteCode(ByteCode.ALOAD_0);
push(cx.getLanguageVersion());
classFile.add(ByteCode.PUTFIELD,
"org/mozilla/javascript/NativeFunction",
"version", "S");
}
// add the String representing the source.
String source = (String) tree.getProp(Node.SOURCE_PROP);
if ((source != null)
&& cx.isGeneratingSource()
&& (source.length() < 65536)) {
setNonTrivialInit(methodName);
addByteCode(ByteCode.ALOAD_0);
push(source);
classFile.add(ByteCode.PUTFIELD,
"org/mozilla/javascript/NativeFunction",
"source", "Ljava/lang/String;");
}
// precompile all regexp literals
Vector regexps = (Vector) tree.getProp(Node.REGEXP_PROP);
if (regexps != null) {
setNonTrivialInit(methodName);
generateRegExpLiterals(regexps, inCtor);
}
Vector fns = (Vector) tree.getProp(Node.FUNCTION_PROP);
if (fns != null) {
setNonTrivialInit(methodName);
generateFunctionInits(fns);
}
if (tree instanceof OptFunctionNode) {
OptFunctionNode fnNode = (OptFunctionNode)tree;
Vector directCallTargets
= ((OptFunctionNode)tree).getDirectCallTargets();
if (directCallTargets != null) {
setNonTrivialInit(methodName);
classFile.addField("EmptyArray",
"[Ljava/lang/Object;",
(short)ClassFileWriter.ACC_PRIVATE);
addByteCode(ByteCode.ALOAD_0);
push(0);
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
classFile.add(ByteCode.PUTFIELD,
classFile.fullyQualifiedForm(this.name),
"EmptyArray",
"[Ljava/lang/Object;");
}
if (fnNode.isTargetOfDirectCall()) {
setNonTrivialInit(methodName);
String className
= classFile.fullyQualifiedForm(fnNode.getClassName());
String fieldName = className.replace('/', '_');
classFile.addField(fieldName,
"L" + className + ";",
(short)(ClassFileWriter.ACC_PUBLIC
+ ClassFileWriter.ACC_STATIC));
addByteCode(ByteCode.ALOAD_0);
classFile.add(ByteCode.PUTSTATIC,
className,
fieldName,
"L" + className + ";");
}
}
// debug stuff ... (should be last thing in method)
if (debugLevel >= 1) {
// save debugLevel
setNonTrivialInit(methodName);
addByteCode(ByteCode.ALOAD_0); // 'this'
push(debugLevel);
classFile.add(ByteCode.PUTFIELD,
"org/mozilla/javascript/NativeFunction",
"debug_level", "I");
// generate call to "debugInit()"
String slashName = this.name.replace('.', '/');
addByteCode(ByteCode.ALOAD_0); // load 'this'
addVirtualInvoke(slashName,
"debugInit","()", "V");
}
if (!trivialInit) {
addByteCode(ByteCode.RETURN);
finishMethod(cx, null);
}
}
// this is only called if (debugLevel >= 1)
private void generateDebugInit(Context cx, Node tree) {
startNewMethod("debugInit", "()V", 0, false, true);
// save source name
String srcName = (String) tree.getProp(Node.SOURCENAME_PROP);
addByteCode(ByteCode.ALOAD_0); // 'this'
if (srcName != null)
push(srcName);
else
addByteCode(ByteCode.ACONST_NULL);
classFile.add(ByteCode.PUTFIELD,
"org/mozilla/javascript/NativeFunction",
"debug_srcName", "Ljava/lang/String;");
if (debugLevel >= 3) {
// at this debug level we know that our superclass
// is debug specific. Generate more debug info into it.
// save the base and end line numbers
Integer prop;
prop = (Integer) tree.getProp(Node.BASE_LINENO_PROP);
if (null != prop) {
addByteCode(ByteCode.ALOAD_0); // 'this'
push(prop.intValue());
classFile.add(ByteCode.PUTFIELD,
superClassSlashName,
"debug_baseLineno", "I");
}
prop = (Integer) tree.getProp(Node.END_LINENO_PROP);
if (null != prop) {
addByteCode(ByteCode.ALOAD_0); // 'this'
push(prop.intValue());
classFile.add(ByteCode.PUTFIELD,
superClassSlashName,
"debug_endLineno", "I");
}
// trap support
if (debugLevel >= 6) {
writeDebugPCEntries();
buildDebugTrapMap();
}
// generate call to script loading hook
// *always do this last*
addByteCode(ByteCode.ALOAD_0); //'this'
addVirtualInvoke(superClassSlashName,
"callLoadingScriptHook",
"()", "V");
}
addByteCode(ByteCode.RETURN);
finishMethod(cx, null);
}
private void generateRegExpLiterals(Vector regexps, boolean inCtor) {
for (int i=0; i < regexps.size(); i++) {
Node regexp = (Node) regexps.elementAt(i);
StringBuffer sb = new StringBuffer("_re");
sb.append(i);
String fieldName = sb.toString();
short flags = ClassFileWriter.ACC_PRIVATE;
if (inCtor)
flags |= ClassFileWriter.ACC_FINAL;
classFile.addField(fieldName,
"Lorg/mozilla/javascript/regexp/NativeRegExp;",
flags);
addByteCode(ByteCode.ALOAD_0); // load 'this'
addByteCode(ByteCode.NEW, "org/mozilla/javascript/regexp/NativeRegExp");
addByteCode(ByteCode.DUP);
aload(contextLocal); // load 'context'
aload(variableObjectLocal); // load 'scope'
Node left = regexp.getFirstChild();
push(left.getString());
Node right = regexp.getLastChild();
if (left == right) {
addByteCode(ByteCode.ACONST_NULL);
} else {
push(right.getString());
}
push(0);
addSpecialInvoke("org/mozilla/javascript/regexp/NativeRegExp",
"<init>",
"(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Ljava/lang/String;Ljava/lang/String;Z)",
"V");
regexp.putProp(Node.REGEXP_PROP, fieldName);
classFile.add(ByteCode.PUTFIELD,
classFile.fullyQualifiedForm(this.name),
fieldName, "Lorg/mozilla/javascript/regexp/NativeRegExp;");
}
}
private void generateFunctionInits(Vector fns) {
// make an array to put them in, so they're available
// for decompilation.
push(fns.size());
addByteCode(ByteCode.ANEWARRAY, "org/mozilla/javascript/NativeFunction");
/* note that nested function decompilation currently
* depends on the elements of the nestedFunctions array
* being defined in source order. (per function/script,
* starting from 0.) Change Parser and NativeFunction if
* changing ordering.
*/
for (short i = 0; i < fns.size(); i++) {
addByteCode(ByteCode.DUP); // make another reference to the array
push(i); // to set up for aastore at end of loop
Node def = (Node) fns.elementAt(i);
Codegen codegen = new Codegen();
String fnClassName = codegen.generateCode(def, namesVector,
classFilesVector,
itsNameHelper);
addByteCode(ByteCode.NEW, fnClassName);
addByteCode(ByteCode.DUP);
if (inFunction) {
addByteCode(ByteCode.ALOAD_0); // load "this" argument
} else {
aload(variableObjectLocal);
}
aload(contextLocal); // load 'cx'
addSpecialInvoke(fnClassName, "<init>",
"(Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/Context;)",
"V");
// 'fn' still on stack
// load 'scope'
if (inFunction) {
addByteCode(ByteCode.ALOAD_0); // load "this" argument
} else {
aload(variableObjectLocal);
}
// load 'fnName'
String str = def.getString();
if (str != null) {
push(str);
} else {
addByteCode(ByteCode.ACONST_NULL);
}
// load 'cx'
aload(contextLocal);
addScriptRuntimeInvoke("initFunction",
"(Lorg/mozilla/javascript/NativeFunction;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Ljava/lang/String;" +
"Lorg/mozilla/javascript/Context;)",
"Lorg/mozilla/javascript/NativeFunction;");
def.putProp(Node.FUNCTION_PROP, new Short(i));
addByteCode(ByteCode.AASTORE); // store NativeFunction
// instance to array
}
// add the array as the nestedFunctions field; array should
// still be on the stack.
addByteCode(ByteCode.ALOAD_0); // load 'this'
addByteCode(ByteCode.SWAP); // swap with original array reference
classFile.add(ByteCode.PUTFIELD, "org/mozilla/javascript/NativeFunction",
"nestedFunctions", "[Lorg/mozilla/javascript/NativeFunction;");
}
/**
* Generate the prologue for a function or script.
*
* @param tree the tree to generate code for
* @param inFunction true if generating the prologue for a function
* (as opposed to a script)
* @param directParameterCount number of parameters for direct call,
* or -1 if not direct call
*/
private void generatePrologue(Context cx, Node tree, boolean inFunction,
int directParameterCount)
{
funObjLocal = reserveWordLocal(0);
contextLocal = reserveWordLocal(1);
variableObjectLocal = reserveWordLocal(2);
thisObjLocal = reserveWordLocal(3);
if (directParameterCount > 0) {
for (int i = 0; i < (3 * directParameterCount); i++)
reserveWordLocal(i + 4); // reserve 'args'
}
// reserve 'args[]'
argsLocal = reserveWordLocal(directParameterCount <= 0
? 4 : (3 * directParameterCount) + 4);
// These locals are to be pre-allocated since they need function scope.
// They are primarily used by the exception handling mechanism
Integer localCount = (Integer)(tree.getProp(Node.LOCALCOUNT_PROP));
if (localCount != null) {
itsLocalAllocationBase = (short)(argsLocal + 1);
for (int i = 0; i < localCount.intValue(); i++) {
reserveWordLocal(itsLocalAllocationBase + i);
}
}
hasVarsInRegs = inFunction &&
!((OptFunctionNode)tree).requiresActivation();
if (hasVarsInRegs) {
// No need to create activation. Pad arguments if need be.
int parmCount = vars.getParameterCount();
if (inFunction && parmCount > 0 && directParameterCount < 0) {
// Set up args array
// check length of arguments, pad if need be
aload(argsLocal);
addByteCode(ByteCode.ARRAYLENGTH);
push(parmCount);
int label = acquireLabel();
addByteCode(ByteCode.IF_ICMPGE, label);
aload(argsLocal);
push(parmCount);
addScriptRuntimeInvoke("padArguments",
"([Ljava/lang/Object;I)",
"[Ljava/lang/Object;");
astore(argsLocal);
markLabel(label);
}
// REMIND - only need to initialize the vars that don't get a value
// before the next call and are used in the function
short firstUndefVar = -1;
for (int i = 0; i < vars.size(); i++) {
OptLocalVariable lVar = (OptLocalVariable) vars.get(i);
if (lVar.isNumber()) {
lVar.assignJRegister(getNewWordPairLocal());
push(0.0);
dstore(lVar.getJRegister());
} else if (lVar.isParameter()) {
if (directParameterCount < 0) {
lVar.assignJRegister(getNewWordLocal());
aload(argsLocal);
push(i);
addByteCode(ByteCode.AALOAD);
astore(lVar.getJRegister());
}
} else {
lVar.assignJRegister(getNewWordLocal());
if (firstUndefVar == -1) {
pushUndefined();
firstUndefVar = lVar.getJRegister();
} else {
aload(firstUndefVar);
}
astore(lVar.getJRegister());
}
lVar.setStartPC(classFile.getCurrentCodeOffset());
}
// Indicate that we should generate debug information for
// the variable table. (If we're generating debug info at
// all.)
debugVars = vars;
// Skip creating activation object.
return;
}
if (directParameterCount > 0) {
// We're going to create an activation object, so we
// need to get an args array with all the arguments in it.
aload(argsLocal);
push(directParameterCount);
addOptRuntimeInvoke("padStart",
"([Ljava/lang/Object;I)",
"[Ljava/lang/Object;");
astore(argsLocal);
for (int i=0; i < directParameterCount; i++) {
aload(argsLocal);
push(i);
// "3" is 1 for Object parm and 2 for double parm, and
// "4" is to account for the context, etc. parms
aload((short) (3*i+4));
addByteCode(ByteCode.AASTORE);
}
}
String debugVariableName;
if (inFunction) {
aload(contextLocal);
aload(variableObjectLocal);
aload(funObjLocal);
aload(thisObjLocal);
aload(argsLocal);
addScriptRuntimeInvoke("initVarObj",
"(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/NativeFunction;" +
"Lorg/mozilla/javascript/Scriptable;" +
"[Ljava/lang/Object;)",
"Lorg/mozilla/javascript/Scriptable;");
debugVariableName = "activation";
} else {
aload(contextLocal);
aload(variableObjectLocal);
aload(funObjLocal);
aload(thisObjLocal);
push(0);
addScriptRuntimeInvoke("initScript",
"(Lorg/mozilla/javascript/Context;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/NativeFunction;" +
"Lorg/mozilla/javascript/Scriptable;Z)",
"Lorg/mozilla/javascript/Scriptable;");
debugVariableName = "global";
}
astore(variableObjectLocal);
if (cx.isGeneratingDebug()) {
debugVars = new OptVariableTable();
debugVars.addLocal(debugVariableName);
OptLocalVariable lv = (OptLocalVariable) debugVars.get(debugVariableName);
lv.assignJRegister(variableObjectLocal);
lv.setStartPC(classFile.getCurrentCodeOffset());
}
if (!inFunction) {
// OPT: use dataflow to prove that this assignment is dead
scriptResultLocal = getNewWordLocal();
pushUndefined();
astore(scriptResultLocal);
}
if (inFunction && ((OptFunctionNode)tree).containsCalls(-1)) {
if (((OptFunctionNode)tree).containsCalls(0)) {
itsZeroArgArray = getNewWordLocal();
classFile.add(ByteCode.GETSTATIC,
"org/mozilla/javascript/ScriptRuntime",
"emptyArgs", "[Ljava/lang/Object;");
astore(itsZeroArgArray);
}
if (((OptFunctionNode)tree).containsCalls(1)) {
itsOneArgArray = getNewWordLocal();
push(1);
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
astore(itsOneArgArray);
}
}
if (debugLevel > 0) { // XXX is 0 correct?
// setup the local for 'pc' with a default value
debug_pcLocal = getNewWordLocal();
addByteCode(ByteCode.ICONST_0);
istore(debug_pcLocal);
if (debugLevel >= 6) {
debugStopSubLabel = acquireLabel();
generateDebugStopSubroutine();
}
}
}
private void generateEpilogue() {
if (epilogueLabel != -1) {
classFile.markLabel(epilogueLabel);
}
if (!hasVarsInRegs || !inFunction || debugLevel > 0) {
// restore caller's activation
aload(contextLocal);
addScriptRuntimeInvoke("popActivation",
"(Lorg/mozilla/javascript/Context;)",
"V");
}
addByteCode(ByteCode.ARETURN);
}
private void generateDebugStopSubroutine() {
// local to store our subroutine return address
// int stackTop = classFile.getStackTop();
debugStopSubRetLocal = getNewWordLocal();
// generate the code to jump over this subroutine
// (needed if generated anyplace other than after the last return)
int labelSkip = acquireLabel();
addByteCode(ByteCode.GOTO, labelSkip);
//
// the actual subroutine starts here
//
classFile.adjustStackTop(1);
markLabel(debugStopSubLabel);
astore(debugStopSubRetLocal);
// System.out.println(1+" " +stackTop+" "+classFile.getStackTop());
int labelRetPop0 = acquireLabel();
int labelRetPop1 = acquireLabel();
int labelRetPop2 = acquireLabel();
int labelCheckForStopPoint = acquireLabel();
int labelHandleHookAnswer = acquireLabel();
int labelNotThrow = acquireLabel();
// parse the flag to see what should be done
addByteCode(ByteCode.ALOAD_0); // 'this'
classFile.add(ByteCode.GETFIELD,
superClassSlashName,
"debug_stopFlags", "I");
addByteCode(ByteCode.DUP);
addByteCode(ByteCode.ICONST_1);
addByteCode(ByteCode.IAND);
addByteCode(ByteCode.IFEQ, labelCheckForStopPoint);
addByteCode(ByteCode.POP);
// if here then an immediate interrupt was requested. (stack empty)
// System.out.println(2+" " +stackTop+" "+classFile.getStackTop());
// call our "hook caller" helper in Debug superclass
addByteCode(ByteCode.ALOAD_0); // 'this'
aload(contextLocal);
addByteCode(ByteCode.ICONST_1); // retVal[1] array
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
addByteCode(ByteCode.DUP_X2); //retVal will be on stack after call
iload(debug_pcLocal);
aload(variableObjectLocal);
addVirtualInvoke(superClassSlashName,
"callInterruptHook",
"(Lorg/mozilla/javascript/Context;[Ljava/lang/Object;" +
"ILorg/mozilla/javascript/Scriptable;)",
"I");
// jump to labelHandleHookAnswer (same code for both hooks)
addByteCode(ByteCode.GOTO, labelHandleHookAnswer);
/********************************************************/
// if here, check to see if this is our stoppoint
// the stack has 'debug_stopFlags'
classFile.adjustStackTop(-1);
markLabel(labelCheckForStopPoint);
// sanity check that breakpoint flag is really set
addByteCode(ByteCode.ICONST_2);
addByteCode(ByteCode.IAND);
addByteCode(ByteCode.IFEQ, labelRetPop0);
// check to see if we are at a pc with a breakpoint (stack empty)
// System.out.println(3+" " +stackTop+" "+classFile.getStackTop());
// We have an array of 32bit ints. We use the pc to index bits in
// that array. If the bit is set then we trap, otherwise continue.
// To figure out which bit the pc represents we do bitwise work to
// calc an index and mask. Similar work is done in Java in the class
// org.mozilla.javascript.debug.NativeDelegate to set and clear these
// bits.
// get current pc
iload(debug_pcLocal);
addByteCode(ByteCode.DUP);
// calc the index into trapMap
addByteCode(ByteCode.ICONST_5);
addByteCode(ByteCode.ISHR);
addByteCode(ByteCode.SWAP);
// calc the mask
push(0x1f);
addByteCode(ByteCode.IAND);
addByteCode(ByteCode.ICONST_1);
addByteCode(ByteCode.SWAP);
addByteCode(ByteCode.ISHL);
addByteCode(ByteCode.SWAP);
// stack has mask, index
// get refernce to trapMap
addByteCode(ByteCode.ALOAD_0); // 'this'
classFile.add(ByteCode.GETFIELD,
superClassSlashName,
"debug_trapMap", "[I");
// get our entry
addByteCode(ByteCode.SWAP);
addByteCode(ByteCode.IALOAD);
// 'and' against the map
addByteCode(ByteCode.IAND);
addByteCode(ByteCode.IFEQ, labelRetPop0);
// here if we've hit a trap (stack empty)
// System.out.println(4+" " +stackTop+" "+classFile.getStackTop());
// call our "hook caller" helper in Debug superclass
addByteCode(ByteCode.ALOAD_0); // 'this'
aload(contextLocal);
addByteCode(ByteCode.ICONST_1); // retVal[1] array
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
addByteCode(ByteCode.DUP_X2); //retVal will be on stack after call
iload(debug_pcLocal);
aload(variableObjectLocal);
addVirtualInvoke(superClassSlashName,
"callTrappedHook",
"(Lorg/mozilla/javascript/Context;[Ljava/lang/Object;" +
"ILorg/mozilla/javascript/Scriptable;)",
"I");
// fall through to labelHandleHookAnswer (same code for both hooks)
// Deal with Hook Answer, stack has retval object, action code int
markLabel(labelHandleHookAnswer);
// available actions...
// org.mozilla.javascript.DeepBytecodeHook.CONTINUE = 0;
// org.mozilla.javascript.DeepBytecodeHook.THROW = 1;
// org.mozilla.javascript.DeepBytecodeHook.RETURN_VALUE = 2;
// is it just 'continue'?
addByteCode(ByteCode.DUP);
addByteCode(ByteCode.IFEQ, labelRetPop2);
// either a throw or return value -- get the value (Object[0])
addByteCode(ByteCode.SWAP);
addByteCode(ByteCode.ICONST_0);
addByteCode(ByteCode.AALOAD);
addByteCode(ByteCode.SWAP);
addByteCode(ByteCode.DUP);
addByteCode(ByteCode.ICONST_1);
addByteCode(ByteCode.IF_ICMPNE, labelNotThrow);
addByteCode(ByteCode.POP); // get the object to the top
addByteCode(ByteCode.CHECKCAST, "java/lang/Throwable");
addByteCode(ByteCode.ATHROW);
classFile.adjustStackTop(2);
markLabel(labelNotThrow);
// sanity check
addByteCode(ByteCode.ICONST_2);
addByteCode(ByteCode.IF_ICMPNE, labelRetPop1);
addByteCode(ByteCode.ARETURN);
classFile.adjustStackTop(2);
markLabel(labelRetPop2);
addByteCode(ByteCode.POP);
markLabel(labelRetPop1);
addByteCode(ByteCode.POP);
markLabel(labelRetPop0);
addByteCode(ByteCode.RET, debugStopSubRetLocal);
// target for jumping over the subroutine
markLabel(labelSkip);
addByteCode(ByteCode.NOP);
// System.out.println(5+" " +stackTop+" "+classFile.getStackTop());
}
private short addDebugPCEntry(short lineno) {
if (0 == debugLineEntryCount) {
debugLineMap = new short[DEBUG_LINE_MAP_INITIAL_SIZE];
}
if (debugLineMap.length == debugLineEntryCount+1) {
short[] newMap =
new short[debugLineMap.length+DEBUG_LINE_MAP_RESIZE_INCREMENT];
for (short i = 0; i < debugLineMap.length; i++)
newMap[i] = debugLineMap[i];
debugLineMap = newMap;
}
debugLineMap[++debugLineEntryCount] = lineno;
return debugLineEntryCount;
}
private void writeDebugPCEntries() {
String s = null;
if (null != debugLineMap && 0 != debugLineEntryCount) {
// I have a short[]. I want a char[]. Java won't let me cast.
char[] ca = new char[debugLineEntryCount+1];
for (short i = 0; i < debugLineEntryCount+1; i++)
ca[i] = (char) debugLineMap[i];
s = new String(ca);
}
addByteCode(ByteCode.ALOAD_0); // 'this'
if (s == null)
addByteCode(ByteCode.ACONST_NULL);
else
push(s);
classFile.add(ByteCode.PUTFIELD,
superClassSlashName,
"debug_linenoMap", "Ljava/lang/String;");
}
private void buildDebugTrapMap() {
if (null != debugLineMap && 0 != debugLineEntryCount) {
short count = (short)((debugLineEntryCount+1)/32);
if (0 != (debugLineEntryCount+1)%32)
count++;
addByteCode(ByteCode.ALOAD_0); // 'this'
push(count);
addByteCode(ByteCode.NEWARRAY, 10); // array of int
classFile.add(ByteCode.PUTFIELD,
superClassSlashName,
"debug_trapMap", "[I");
}
}
private void visitFunction(Node node) {
aload(variableObjectLocal);
Node fn = (Node) node.getProp(Node.FUNCTION_PROP);
Short index = (Short) fn.getProp(Node.FUNCTION_PROP);
aload(funObjLocal);
classFile.add(ByteCode.GETFIELD, "org/mozilla/javascript/NativeFunction",
"nestedFunctions", "[Lorg/mozilla/javascript/NativeFunction;");
push(index.shortValue());
addByteCode(ByteCode.AALOAD);
addVirtualInvoke("java/lang/Object", "getClass", "()", "Ljava/lang/Class;");
aload(contextLocal);
addScriptRuntimeInvoke("createFunctionObject",
"(Lorg/mozilla/javascript/Scriptable;"+
"Ljava/lang/Class;" +
"Lorg/mozilla/javascript/Context;)",
"Lorg/mozilla/javascript/NativeFunction;");
}
private void visitTarget(Node node) {
Object lblObect = node.getProp(Node.LABEL_PROP);
if (lblObect == null) {
int label = markLabel(acquireLabel());
node.putProp(Node.LABEL_PROP, new Integer(label));
}
else {
int label = ((Integer)lblObect).intValue();
markLabel(label);
}
}
private void visitGOTO(Node node, int type, Node child) {
Node target = (Node)(node.getProp(Node.TARGET_PROP));
Object lblObect = target.getProp(Node.LABEL_PROP);
int targetLabel;
if (lblObect == null) {
targetLabel = acquireLabel();
target.putProp(Node.LABEL_PROP, new Integer(targetLabel));
}
else
targetLabel = ((Integer)lblObect).intValue();
int fallThruLabel = acquireLabel();
if ((type == TokenStream.IFEQ) || (type == TokenStream.IFNE)) {
if (child == null) {
// can have a null child from visitSwitch which
// has already generated the code for the child
// and just needs the GOTO code emitted
addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)", "Z");
if (type == TokenStream.IFEQ)
addByteCode(ByteCode.IFNE, targetLabel);
else
addByteCode(ByteCode.IFEQ, targetLabel);
}
else {
if (type == TokenStream.IFEQ)
generateCodeFromNode(child, node, targetLabel, fallThruLabel);
else
generateCodeFromNode(child, node, fallThruLabel, targetLabel);
if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT))
|| (child.getType() == TokenStream.AND)
|| (child.getType() == TokenStream.OR)
|| (child.getType() == TokenStream.RELOP)
|| (child.getType() == TokenStream.EQOP)) {
}
else {
addScriptRuntimeInvoke("toBoolean",
"(Ljava/lang/Object;)", "Z");
if (type == TokenStream.IFEQ)
addByteCode(ByteCode.IFNE, targetLabel);
else
addByteCode(ByteCode.IFEQ, targetLabel);
}
}
}
else {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
if (type == TokenStream.JSR)
addByteCode(ByteCode.JSR, targetLabel);
else
addByteCode(ByteCode.GOTO, targetLabel);
}
markLabel(fallThruLabel);
}
private void visitEnumInit(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
aload(variableObjectLocal);
addScriptRuntimeInvoke("initEnum",
"(Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)",
"Ljava/util/Enumeration;");
short x = getNewWordLocal();
astore(x);
node.putProp(Node.LOCAL_PROP, new Integer(x));
}
private void visitEnumNext(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
Node init = (Node) node.getProp(Node.ENUM_PROP);
Integer local = (Integer) init.getProp(Node.LOCAL_PROP);
aload(local.shortValue());
addScriptRuntimeInvoke("nextEnum", "(Ljava/util/Enumeration;)", "Ljava/lang/Object;");
}
private void visitEnumDone(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
Node init = (Node) node.getProp(Node.ENUM_PROP);
Integer local = (Integer) init.getProp(Node.LOCAL_PROP);
releaseWordLocal(local.shortValue());
}
private void visitEnterWith(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
aload(variableObjectLocal);
addScriptRuntimeInvoke("enterWith",
"(Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Lorg/mozilla/javascript/Scriptable;");
astore(variableObjectLocal);
}
private void visitLeaveWith(Node node, Node child) {
aload(variableObjectLocal);
addScriptRuntimeInvoke("leaveWith",
"(Lorg/mozilla/javascript/Scriptable;)",
"Lorg/mozilla/javascript/Scriptable;");
astore(variableObjectLocal);
}
private void resetTargets(Node node)
{
if (node.getType() == TokenStream.TARGET) {
node.putProp(Node.LABEL_PROP, null);
}
Node child = node.getFirstChild();
while (child != null) {
resetTargets(child);
child = child.getNextSibling();
}
}
private void visitCall(Node node, int type, Node child) {
/*
* Generate code for call.
*/
Node chelsea = child; // remember the first child for later
OptFunctionNode target = (OptFunctionNode)node.getProp(Node.DIRECTCALL_PROP);
if (target != null) {
generateCodeFromNode(child, node, -1, -1);
int regularCall = acquireLabel();
String className = classFile.fullyQualifiedForm(target.getClassName());
String fieldName = className.replace('/', '_');
classFile.add(ByteCode.GETSTATIC,
classFile.fullyQualifiedForm(className),
fieldName,
"L" + className + ";");
short stackHeight = classFile.getStackTop();
addByteCode(ByteCode.DUP2);
addByteCode(ByteCode.IF_ACMPNE, regularCall);
addByteCode(ByteCode.SWAP);
addByteCode(ByteCode.POP);
addByteCode(ByteCode.DUP);
classFile.add(ByteCode.INVOKEINTERFACE,
"org/mozilla/javascript/Scriptable",
"getParentScope",
"()", "Lorg/mozilla/javascript/Scriptable;");
aload(contextLocal);
addByteCode(ByteCode.SWAP);
if (type == TokenStream.NEW)
addByteCode(ByteCode.ACONST_NULL);
else {
child = child.getNextSibling();
generateCodeFromNode(child, node, -1, -1);
}
/*
Remember that directCall parameters are paired in 1 aReg and 1 dReg
If the argument is an incoming arg, just pass the orginal pair thru.
Else, if the argument is known to be typed 'Number', pass Void.TYPE
in the aReg and the number is the dReg
Else pass the JS object in the aReg and 0.0 in the dReg.
*/
child = child.getNextSibling();
while (child != null) {
boolean handled = false;
if ((child.getType() == TokenStream.GETVAR)
&& inDirectCallFunction) {
OptLocalVariable lVar
= (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP));
if (lVar.isParameter()) {
handled = true;
aload(lVar.getJRegister());
dload((short)(lVar.getJRegister() + 1));
}
}
if (!handled) {
Integer childNumberFlag
= (Integer)(child.getProp(Node.ISNUMBER_PROP));
if ((childNumberFlag != null)
&& (childNumberFlag.intValue() == Node.BOTH)) {
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
generateCodeFromNode(child, node, -1, -1);
}
else {
generateCodeFromNode(child, node, -1, -1);
push(0.0);
}
}
resetTargets(child);
child = child.getNextSibling();
}
addByteCode(ByteCode.ALOAD_0);
classFile.add(ByteCode.GETFIELD,
classFile.fullyQualifiedForm(this.name),
"EmptyArray",
"[Ljava/lang/Object;");
if (type == TokenStream.NEW)
addVirtualInvoke(target.getClassName(),
"constructDirect",
target.getDirectCallParameterSignature(),
"Ljava/lang/Object;");
else
addVirtualInvoke(target.getClassName(),
"callDirect",
target.getDirectCallParameterSignature(),
"Ljava/lang/Object;");
int beyond = acquireLabel();
addByteCode(ByteCode.GOTO, beyond);
markLabel(regularCall, stackHeight);
addByteCode(ByteCode.POP);
visitRegularCall(node, type, chelsea, true);
markLabel(beyond);
}
else {
visitRegularCall(node, type, chelsea, false);
}
}
private String getSimpleCallName(Node callNode)
/*
Find call trees that look like this :
(they arise from simple function invocations)
CALL <-- this is the callNode node
GETPROP
NEWTEMP [USES: 1]
GETBASE 'name'
STRING 'name'
GETTHIS
USETEMP [TEMP: NEWTEMP [USES: 1]]
<-- arguments would be here
and return the name found.
*/
{
if (true) {
Node callBase = callNode.getFirstChild();
if (callBase.getType() == TokenStream.GETPROP) {
Node callBaseChild = callBase.getFirstChild();
if (callBaseChild.getType() == TokenStream.NEWTEMP) {
Node callBaseID = callBaseChild.getNextSibling();
Node tempChild = callBaseChild.getFirstChild();
if (tempChild.getType() == TokenStream.GETBASE) {
String functionName = tempChild.getString();
if ((callBaseID != null) &&
(callBaseID.getType() == TokenStream.STRING)) {
if (functionName.equals(callBaseID.getString())) {
Node thisChild = callBase.getNextSibling();
if (thisChild.getType() == TokenStream.GETTHIS) {
Node useChild = thisChild.getFirstChild();
if (useChild.getType() == TokenStream.USETEMP) {
Node temp = (Node)useChild.getProp(Node.TEMP_PROP);
if (temp == callBaseChild) {
return functionName;
}
}
}
}
}
}
}
}
}
return null;
}
private void constructArgArray(int argCount)
{
if (argCount == 0) {
if (itsZeroArgArray >= 0)
aload(itsZeroArgArray);
else {
push(0);
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
}
}
else {
if (argCount == 1) {
if (itsOneArgArray >= 0)
aload(itsOneArgArray);
else {
push(1);
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
}
}
else {
push(argCount);
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
}
}
}
private void visitRegularCall(Node node, int type,
Node child, boolean firstArgDone)
{
/*
* Generate code for call.
*
* push <arity>
* anewarray Ljava/lang/Object;
* // "initCount" instances of code from here...
* dup
* push <i>
* <gen code for child>
* aastore
* //...to here
* invokestatic call
*/
OptFunctionNode target = (OptFunctionNode)node.getProp(Node.DIRECTCALL_PROP);
Node chelsea = child;
int childCount = 0;
int argSkipCount = (type == TokenStream.NEW) ? 1 : 2;
while (child != null) {
childCount++;
child = child.getNextSibling();
}
child = chelsea; // re-start the iterator from the first child,
// REMIND - too bad we couldn't just back-patch the count ?
int argIndex = -argSkipCount;
if (firstArgDone && (child != null)) {
child = child.getNextSibling();
argIndex++;
aload(contextLocal);
addByteCode(ByteCode.SWAP);
}
else
aload(contextLocal);
if (firstArgDone && (type == TokenStream.NEW))
constructArgArray(childCount - argSkipCount);
boolean isSpecialCall = node.getProp(Node.SPECIALCALL_PROP) != null;
boolean isSimpleCall = false;
String simpleCallName = null;
if (type != TokenStream.NEW) {
simpleCallName = getSimpleCallName(node);
if (simpleCallName != null && !isSpecialCall && debugLevel < 3) {
isSimpleCall = true;
push(simpleCallName);
aload(variableObjectLocal);
child = child.getNextSibling().getNextSibling();
argIndex = 0;
push(childCount - argSkipCount);
addByteCode(ByteCode.ANEWARRAY, "java/lang/Object");
}
}
while (child != null) {
if (argIndex < 0) // not moving these arguments to the array
generateCodeFromNode(child, node, -1, -1);
else {
addByteCode(ByteCode.DUP);
push(argIndex);
if (target != null) {
/*
If this has also been a directCall sequence, the Number flag will
have remained set for any parameter so that the values could be
copied directly into the outgoing args. Here we want to force it
to be treated as not in a Number context, so we set the flag off.
*/
boolean handled = false;
if ((child.getType() == TokenStream.GETVAR)
&& inDirectCallFunction) {
OptLocalVariable lVar
= (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP));
if (lVar.isParameter()) {
child.putProp(Node.ISNUMBER_PROP, null);
generateCodeFromNode(child, node, -1, -1);
handled = true;
}
}
if (!handled) {
Integer childNumberFlag
= (Integer)(child.getProp(Node.ISNUMBER_PROP));
if ((childNumberFlag != null)
&& (childNumberFlag.intValue() == Node.BOTH)) {
addByteCode(ByteCode.NEW,"java/lang/Double");
addByteCode(ByteCode.DUP);
generateCodeFromNode(child, node, -1, -1);
addDoubleConstructor();
}
else
generateCodeFromNode(child, node, -1, -1);
}
}
else
generateCodeFromNode(child, node, -1, -1);
addByteCode(ByteCode.AASTORE);
}
argIndex++;
if (argIndex == 0) {
// now we need to construct the array
// (even if there are no args to load
// into it) - REMIND could pass null
// instead ?
constructArgArray(childCount - argSkipCount);
}
child = child.getNextSibling();
}
// set our pc into the top frame
if (debugLevel >= 1) {
aload(contextLocal);
aload(variableObjectLocal);
iload(debug_pcLocal);
addStaticInvoke("org/mozilla/javascript/debug/NativeDelegate",
"setPC",
"(Lorg/mozilla/javascript/Context;Lorg/mozilla/javascript/Scriptable;I)",
"V");
}
String className;
String methodNameNewObj;
String methodNameCall;
String callSignature;
if (isSpecialCall) {
className = "org/mozilla/javascript/ScriptRuntime";
methodNameNewObj = "newObjectSpecial";
methodNameCall = "callSpecial";
if (type != TokenStream.NEW) {
callSignature = "(Lorg/mozilla/javascript/Context;" +
"Ljava/lang/Object;" +
"Ljava/lang/Object;" +
"[Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Lorg/mozilla/javascript/Scriptable;" +
"Ljava/lang/String;I)"; // filename & linenumber
aload(thisObjLocal);
aload(variableObjectLocal);
push(itsSourceFile);
push(itsLineNumber);
} else {
callSignature = "(Lorg/mozilla/javascript/Context;" +
"Ljava/lang/Object;" +
"[Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;)";
aload(variableObjectLocal);
}
} else {
methodNameNewObj = "newObject";
if (isSimpleCall) {
callSignature = "(Lorg/mozilla/javascript/Context;" +
"Ljava/lang/String;" +
"Lorg/mozilla/javascript/Scriptable;" +
"[Ljava/lang/Object;)";
methodNameCall = "callSimple";
className = "org/mozilla/javascript/optimizer/OptRuntime";
} else {
aload(variableObjectLocal);
if (type == TokenStream.NEW) {
callSignature = "(Lorg/mozilla/javascript/Context;" +
"Ljava/lang/Object;" +
"[Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;)";
} else {
callSignature = "(Lorg/mozilla/javascript/Context;" +
"Ljava/lang/Object;" +
"Ljava/lang/Object;" +
"[Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;)";
}
methodNameCall = "call";
className = "org/mozilla/javascript/ScriptRuntime";
}
}
/* NOTE: we assure above !(isSimpleCall && debugLevel >= 3). So
* we don't need to worry about the SimpleCall case in debug code.
*/
if (debugLevel >= 3) {
className = "org/mozilla/javascript/debug/NativeDelegate";
if (isSpecialCall) {
methodNameNewObj = "newObjectSpecialDebug";
methodNameCall = "callSpecialDebug";
} else {
methodNameNewObj = "newObjectDebug";
methodNameCall = "callDebug";
}
}
if (type == TokenStream.NEW) {
addStaticInvoke(className,
methodNameNewObj,
callSignature,
"Lorg/mozilla/javascript/Scriptable;");
} else {
addStaticInvoke(className,
methodNameCall,
callSignature,
"Ljava/lang/Object;");
}
}
private void visitStatement(Node node) {
Object datum = node.getDatum();
if (datum == null || !(datum instanceof Number))
return;
itsLineNumber = ((Number) datum).shortValue();
if (itsLineNumber == -1)
return;
classFile.addLineNumberEntry((short)itsLineNumber);
if (debugLevel >= 1) {
if (debugLevel >= 6) {
int pc = addDebugPCEntry((short)itsLineNumber);
push(pc);
istore(debug_pcLocal);
// see if a stop is requested anywhere in this function.
// if the flag is non-zero, then let the subroutine decide
// what to do.
addByteCode(ByteCode.ALOAD_0); // load 'this'
classFile.add(ByteCode.GETFIELD,
superClassSlashName,
"debug_stopFlags", "I");
int label = acquireLabel();
addByteCode(ByteCode.IFEQ, label);
addByteCode(ByteCode.JSR, debugStopSubLabel);
classFile.adjustStackTop(-1);
markLabel(label);
}
else {
push(itsLineNumber);
istore(debug_pcLocal);
}
}
}
private void visitTryCatchFinally(Node node, Node child) {
/* Save the variable object, in case there are with statements
* enclosed by the try block and we catch some exception.
* We'll restore it for the catch block so that catch block
* statements get the right scope.
*/
// OPT we only need to do this if there are enclosed WITH
// statements; could statically check and omit this if there aren't any.
// XXX OPT Maybe instead do syntactic transforms to associate
// each 'with' with a try/finally block that does the exitwith.
// For that matter: Why do we have leavewith?
// XXX does Java have any kind of MOV(reg, reg)?
short savedVariableObject = getNewWordLocal();
aload(variableObjectLocal);
astore(savedVariableObject);
/*
* Generate the code for the tree; most of the work is done in IRFactory
* and NodeTransformer; Codegen just adds the java handlers for the
* javascript catch and finally clauses. */
// need to set the stack top to 1 to account for the incoming exception
int startLabel = markLabel(acquireLabel(), (short)1);
visitStatement(node);
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
Node catchTarget = (Node)node.getProp(Node.TARGET_PROP);
Node finallyTarget = (Node)node.getProp(Node.FINALLY_PROP);
// control flow skips the handlers
int realEnd = acquireLabel();
addByteCode(ByteCode.GOTO, realEnd);
// javascript handler; unwrap exception and GOTO to javascript
// catch area.
if (catchTarget != null) {
int jsHandler = classFile.markHandler(acquireLabel());
// MS JVM gets cranky if the exception object is left on the stack
short exceptionObject = getNewWordLocal();
astore(exceptionObject);
// reset the variable object local
aload(savedVariableObject);
astore(variableObjectLocal);
aload(exceptionObject);
releaseWordLocal(exceptionObject);
// unwrap the exception...
addScriptRuntimeInvoke("unwrapJavaScriptException",
"(Lorg/mozilla/javascript/JavaScriptException;)",
"Ljava/lang/Object;");
// get the label to goto
int catchLabel =
((Integer)catchTarget.getProp(Node.LABEL_PROP)).intValue();
addByteCode(ByteCode.GOTO, catchLabel);
// mark the handler
classFile.addExceptionHandler
(startLabel, catchLabel, jsHandler,
"org/mozilla/javascript/JavaScriptException");
/*
we also need to catch EcmaErrors and feed the
associated error object to the handler
*/
jsHandler = classFile.markHandler(acquireLabel());
exceptionObject = getNewWordLocal();
astore(exceptionObject);
aload(savedVariableObject);
astore(variableObjectLocal);
aload(exceptionObject);
addVirtualInvoke("org/mozilla/javascript/EcmaError",
"getErrorObject",
"()",
"Lorg/mozilla/javascript/Scriptable;");
releaseWordLocal(exceptionObject);
addByteCode(ByteCode.GOTO, catchLabel);
classFile.addExceptionHandler
(startLabel, catchLabel, jsHandler,
"org/mozilla/javascript/EcmaError");
}
// finally handler; catch all exceptions, store to a local; JSR to
// the finally, then re-throw.
if (finallyTarget != null) {
int finallyHandler = classFile.markHandler(acquireLabel());
// reset the variable object local
aload(savedVariableObject);
astore(variableObjectLocal);
short exnLocal = itsLocalAllocationBase++;
astore(exnLocal);
// get the label to JSR to
int finallyLabel =
((Integer)finallyTarget.getProp(Node.LABEL_PROP)).intValue();
addByteCode(ByteCode.JSR, finallyLabel);
// rethrow
aload(exnLocal);
addByteCode(ByteCode.ATHROW);
// mark the handler
classFile.addExceptionHandler(startLabel, finallyLabel,
finallyHandler, null); // catch any
}
releaseWordLocal(savedVariableObject);
markLabel(realEnd);
}
private void visitThrow(Node node, Node child) {
visitStatement(node);
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
addByteCode(ByteCode.NEW,
"org/mozilla/javascript/JavaScriptException");
addByteCode(ByteCode.DUP_X1);
addByteCode(ByteCode.SWAP);
addSpecialInvoke("org/mozilla/javascript/JavaScriptException",
"<init>", "(Ljava/lang/Object;)", "V");
addByteCode(ByteCode.ATHROW);
}
private void visitReturn(Node node, Node child) {
visitStatement(node);
if (child != null) {
do {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
} while (child != null);
} else if (inFunction) {
pushUndefined();
} else {
aload(scriptResultLocal);
}
if (epilogueLabel == -1)
epilogueLabel = classFile.acquireLabel();
addByteCode(ByteCode.GOTO, epilogueLabel);
}
private void visitSwitch(Node node, Node child) {
visitStatement(node);
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
// save selector value
short selector = getNewWordLocal();
astore(selector);
Vector cases = (Vector) node.getProp(Node.CASES_PROP);
for (int i=0; i < cases.size(); i++) {
Node thisCase = (Node) cases.elementAt(i);
Node first = thisCase.getFirstChild();
generateCodeFromNode(first, thisCase, -1, -1);
aload(selector);
addScriptRuntimeInvoke("seqB",
"(Ljava/lang/Object;Ljava/lang/Object;)",
"Ljava/lang/Boolean;");
Node target = new Node(TokenStream.TARGET);
thisCase.replaceChild(first, target);
generateGOTO(TokenStream.IFEQ, target);
}
Node defaultNode = (Node) node.getProp(Node.DEFAULT_PROP);
if (defaultNode != null) {
Node defaultTarget = new Node(TokenStream.TARGET);
defaultNode.getFirstChild().addChildToFront(defaultTarget);
generateGOTO(TokenStream.GOTO, defaultTarget);
}
Node breakTarget = (Node) node.getProp(Node.BREAK_PROP);
generateGOTO(TokenStream.GOTO, breakTarget);
}
private void generateGOTO(int type, Node target) {
Node GOTO = new Node(type);
GOTO.putProp(Node.TARGET_PROP, target);
visitGOTO(GOTO, type, null);
}
private void visitUnary(Node node, Node child, int trueGOTO, int falseGOTO) {
int op = node.getInt();
switch (op) {
case TokenStream.NOT:
{
if (trueGOTO != -1) {
generateCodeFromNode(child, node, falseGOTO, trueGOTO);
if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT))
|| (child.getType() == TokenStream.AND)
|| (child.getType() == TokenStream.OR)
|| (child.getType() == TokenStream.RELOP)
|| (child.getType() == TokenStream.EQOP)) {
}
else {
addScriptRuntimeInvoke("toBoolean",
"(Ljava/lang/Object;)", "Z");
addByteCode(ByteCode.IFNE, falseGOTO);
addByteCode(ByteCode.GOTO, trueGOTO);
}
}
else {
int trueTarget = acquireLabel();
int falseTarget = acquireLabel();
int beyond = acquireLabel();
generateCodeFromNode(child, node, trueTarget, falseTarget);
if (((child.getType() == TokenStream.UNARYOP) && (child.getInt() == TokenStream.NOT))
|| (child.getType() == TokenStream.AND)
|| (child.getType() == TokenStream.OR)
|| (child.getType() == TokenStream.RELOP)
|| (child.getType() == TokenStream.EQOP)) {
}
else {
addScriptRuntimeInvoke("toBoolean",
"(Ljava/lang/Object;)", "Z");
addByteCode(ByteCode.IFEQ, falseTarget);
addByteCode(ByteCode.GOTO, trueTarget);
}
markLabel(trueTarget);
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"FALSE", "Ljava/lang/Boolean;");
addByteCode(ByteCode.GOTO, beyond);
markLabel(falseTarget);
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"TRUE", "Ljava/lang/Boolean;");
markLabel(beyond);
classFile.adjustStackTop(-1);
}
break;
}
case TokenStream.TYPEOF:
visitTypeof(node, child);
break;
case TokenStream.VOID:
generateCodeFromNode(child, node, -1, -1);
addByteCode(ByteCode.POP);
pushUndefined();
break;
case TokenStream.BITNOT:
addByteCode(ByteCode.NEW, "java/lang/Double");
addByteCode(ByteCode.DUP);
generateCodeFromNode(child, node, -1, -1);
addScriptRuntimeInvoke("toInt32",
"(Ljava/lang/Object;)", "I");
push(-1); // implement ~a as (a ^ -1)
addByteCode(ByteCode.IXOR);
addByteCode(ByteCode.I2D);
addDoubleConstructor();
break;
case TokenStream.ADD:
case TokenStream.SUB:
addByteCode(ByteCode.NEW, "java/lang/Double");
addByteCode(ByteCode.DUP);
generateCodeFromNode(child, node, -1, -1);
addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D");
if (op == TokenStream.SUB) {
addByteCode(ByteCode.DNEG);
}
addDoubleConstructor();
break;
default:
badTree();
}
}
private void visitTypeof(Node node, Node child) {
if (node.getType() == TokenStream.UNARYOP) {
generateCodeFromNode(child, node, -1, -1);
addScriptRuntimeInvoke("typeof",
"(Ljava/lang/Object;)", "Ljava/lang/String;");
return;
}
String name = node.getString();
if (hasVarsInRegs) {
OptLocalVariable lVar = (OptLocalVariable) vars.get(name);
if (lVar != null) {
if (lVar.isNumber()) {
push("number");
return;
}
visitGetVar(lVar, false, name);
addScriptRuntimeInvoke("typeof",
"(Ljava/lang/Object;)", "Ljava/lang/String;");
return;
}
}
aload(variableObjectLocal);
push(name);
addScriptRuntimeInvoke("typeofName",
"(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)",
"Ljava/lang/String;");
}
private void visitIncDec(Node node, boolean isInc)
{
Node child = node.getFirstChild();
if (node.getProp(Node.ISNUMBER_PROP) != null) {
OptLocalVariable lVar
= (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP));
if (lVar.getJRegister() == -1)
lVar.assignJRegister(getNewWordPairLocal());
dload(lVar.getJRegister());
addByteCode(ByteCode.DUP2);
push(1.0);
addByteCode((isInc) ? ByteCode.DADD : ByteCode.DSUB);
dstore(lVar.getJRegister());
} else {
OptLocalVariable lVar
= (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP));
String routine = (isInc) ? "postIncrement" : "postDecrement";
if (hasVarsInRegs && child.getType() == TokenStream.GETVAR) {
if (lVar == null)
lVar = (OptLocalVariable) vars.get(child.getString());
if (lVar.getJRegister() == -1)
lVar.assignJRegister(getNewWordLocal());
aload(lVar.getJRegister());
addByteCode(ByteCode.DUP);
addScriptRuntimeInvoke(routine,
"(Ljava/lang/Object;)", "Ljava/lang/Object;");
astore(lVar.getJRegister());
} else {
if (child.getType() == TokenStream.GETPROP) {
Node getPropChild = child.getFirstChild();
generateCodeFromNode(getPropChild, node, -1, -1);
generateCodeFromNode(getPropChild.getNextSibling(), node, -1, -1);
aload(variableObjectLocal);
addScriptRuntimeInvoke(routine,
"(Ljava/lang/Object;Ljava/lang/String;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
else {
if (child.getType() == TokenStream.GETELEM) {
routine += "Elem";
Node getPropChild = child.getFirstChild();
generateCodeFromNode(getPropChild, node, -1, -1);
generateCodeFromNode(getPropChild.getNextSibling(), node, -1, -1);
aload(variableObjectLocal);
addScriptRuntimeInvoke(routine,
"(Ljava/lang/Object;Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
else {
aload(variableObjectLocal);
push(child.getString()); // push name
addScriptRuntimeInvoke(routine,
"(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)",
"Ljava/lang/Object;");
}
}
}
}
}
private boolean isArithmeticNode(Node node) {
int type = node.getType();
return (type == TokenStream.SUB)
|| (type == TokenStream.MOD)
|| (type == TokenStream.DIV)
|| (type == TokenStream.MUL);
}
private void visitArithmetic(Node node, byte opCode, Node child,
Node parent)
{
Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP));
if (childNumberFlag != null) {
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
addByteCode(opCode);
}
else {
boolean childOfArithmetic = isArithmeticNode(parent);
if (!childOfArithmetic) {
addByteCode(ByteCode.NEW, "java/lang/Double");
addByteCode(ByteCode.DUP);
}
generateCodeFromNode(child, node, -1, -1);
if (!isArithmeticNode(child))
addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D");
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
if (!isArithmeticNode(child.getNextSibling()))
addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D");
addByteCode(opCode);
if (!childOfArithmetic) {
addDoubleConstructor();
}
}
}
private void visitBitOp(Node node, int type, Node child) {
Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP));
if (childNumberFlag == null) {
addByteCode(ByteCode.NEW, "java/lang/Double");
addByteCode(ByteCode.DUP);
}
generateCodeFromNode(child, node, -1, -1);
// special-case URSH; work with the target arg as a long, so
// that we can return a 32-bit unsigned value, and call
// toUint32 instead of toInt32.
if (type == TokenStream.URSH) {
addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)", "J");
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I");
// Looks like we need to explicitly mask the shift to 5 bits -
// LUSHR takes 6 bits.
push(31);
addByteCode(ByteCode.IAND);
addByteCode(ByteCode.LUSHR);
addByteCode(ByteCode.L2D);
addDoubleConstructor();
return;
}
if (childNumberFlag == null) {
addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I");
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)", "I");
}
else {
addScriptRuntimeInvoke("toInt32", "(D)", "I");
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
addScriptRuntimeInvoke("toInt32", "(D)", "I");
}
switch (type) {
case TokenStream.BITOR:
addByteCode(ByteCode.IOR);
break;
case TokenStream.BITXOR:
addByteCode(ByteCode.IXOR);
break;
case TokenStream.BITAND:
addByteCode(ByteCode.IAND);
break;
case TokenStream.RSH:
addByteCode(ByteCode.ISHR);
break;
case TokenStream.LSH:
addByteCode(ByteCode.ISHL);
break;
default:
badTree();
}
addByteCode(ByteCode.I2D);
if (childNumberFlag == null) {
addDoubleConstructor();
}
}
private boolean nodeIsDirectCallParameter(Node node) {
if (node.getType() == TokenStream.GETVAR) {
OptLocalVariable lVar
= (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP));
if (lVar != null && lVar.isParameter() && inDirectCallFunction &&
!itsForcedObjectParameters)
{
return true;
}
}
return false;
}
private void genSimpleCompare(int op, int trueGOTO, int falseGOTO) {
switch (op) {
case TokenStream.LE :
addByteCode(ByteCode.DCMPG);
addByteCode(ByteCode.IFLE, trueGOTO);
break;
case TokenStream.GE :
addByteCode(ByteCode.DCMPL);
addByteCode(ByteCode.IFGE, trueGOTO);
break;
case TokenStream.LT :
addByteCode(ByteCode.DCMPG);
addByteCode(ByteCode.IFLT, trueGOTO);
break;
case TokenStream.GT :
addByteCode(ByteCode.DCMPL);
addByteCode(ByteCode.IFGT, trueGOTO);
break;
}
if (falseGOTO != -1)
addByteCode(ByteCode.GOTO, falseGOTO);
}
private void visitGOTOingRelOp(Node node, Node child, Node parent,
int trueGOTO, int falseGOTO)
{
int op = node.getInt();
Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP));
if ((childNumberFlag != null)
&& (childNumberFlag.intValue() == Node.BOTH)) {
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
genSimpleCompare(op, trueGOTO, falseGOTO);
}
else {
if (op == TokenStream.INSTANCEOF) {
aload(variableObjectLocal);
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
addScriptRuntimeInvoke("instanceOf",
"(Lorg/mozilla/javascript/Scriptable;"+
"Ljava/lang/Object;Ljava/lang/Object;)", "Z");
addByteCode(ByteCode.IFNE, trueGOTO);
addByteCode(ByteCode.GOTO, falseGOTO);
} else if (op == TokenStream.IN) {
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
aload(variableObjectLocal);
addScriptRuntimeInvoke("in",
"(Ljava/lang/Object;Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Scriptable;)","Z");
addByteCode(ByteCode.IFNE, trueGOTO);
addByteCode(ByteCode.GOTO, falseGOTO);
} else {
Node rChild = child.getNextSibling();
boolean leftIsDCP = nodeIsDirectCallParameter(child);
boolean rightIsDCP = nodeIsDirectCallParameter(rChild);
if (leftIsDCP || rightIsDCP) {
if (leftIsDCP) {
if (rightIsDCP) {
OptLocalVariable lVar1
= (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP));
aload(lVar1.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
int notNumbersLabel = acquireLabel();
addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel);
OptLocalVariable lVar2
= (OptLocalVariable)(rChild.getProp(Node.VARIABLE_PROP));
aload(lVar2.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel);
dload((short)(lVar1.getJRegister() + 1));
dload((short)(lVar2.getJRegister() + 1));
genSimpleCompare(op, trueGOTO, falseGOTO);
markLabel(notNumbersLabel);
// fall thru to generic handling
}
else { // just the left child is a DCP, if the right child
// is a number it's worth testing the left
if ((childNumberFlag != null)
&& (childNumberFlag.intValue() == Node.RIGHT)) {
OptLocalVariable lVar1
= (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP));
aload(lVar1.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
int notNumbersLabel = acquireLabel();
addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel);
dload((short)(lVar1.getJRegister() + 1));
generateCodeFromNode(rChild, node, -1, -1);
genSimpleCompare(op, trueGOTO, falseGOTO);
markLabel(notNumbersLabel);
// fall thru to generic handling
}
}
}
else { // just the right child is a DCP, if the left child
// is a number it's worth testing the right
if ((childNumberFlag != null)
&& (childNumberFlag.intValue() == Node.LEFT)) {
OptLocalVariable lVar2
= (OptLocalVariable)(rChild.getProp(Node.VARIABLE_PROP));
aload(lVar2.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
int notNumbersLabel = acquireLabel();
addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel);
generateCodeFromNode(child, node, -1, -1);
dload((short)(lVar2.getJRegister() + 1));
genSimpleCompare(op, trueGOTO, falseGOTO);
markLabel(notNumbersLabel);
// fall thru to generic handling
}
}
}
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(rChild, node, -1, -1);
if (childNumberFlag == null) {
if (op == TokenStream.GE || op == TokenStream.GT) {
addByteCode(ByteCode.SWAP);
}
String routine = ((op == TokenStream.LT)
|| (op == TokenStream.GT)) ? "cmp_LT" : "cmp_LE";
addScriptRuntimeInvoke(routine,
"(Ljava/lang/Object;Ljava/lang/Object;)", "I");
}
else {
boolean doubleThenObject
= (childNumberFlag.intValue() == Node.LEFT);
if (op == TokenStream.GE || op == TokenStream.GT) {
if (doubleThenObject) {
addByteCode(ByteCode.DUP_X2);
addByteCode(ByteCode.POP);
doubleThenObject = false;
}
else {
addByteCode(ByteCode.DUP2_X1);
addByteCode(ByteCode.POP2);
doubleThenObject = true;
}
}
String routine = ((op == TokenStream.LT)
|| (op == TokenStream.GT)) ? "cmp_LT" : "cmp_LE";
if (doubleThenObject)
addOptRuntimeInvoke(routine,
"(DLjava/lang/Object;)", "I");
else
addOptRuntimeInvoke(routine,
"(Ljava/lang/Object;D)", "I");
}
addByteCode(ByteCode.IFNE, trueGOTO);
addByteCode(ByteCode.GOTO, falseGOTO);
}
}
}
private void visitRelOp(Node node, Node child, Node parent) {
/*
this is the version that returns an Object result
*/
int op = node.getInt();
Integer childNumberFlag = (Integer)(node.getProp(Node.ISNUMBER_PROP));
if (((childNumberFlag != null)
&& (childNumberFlag.intValue() == Node.BOTH))
|| (op == TokenStream.INSTANCEOF)
|| (op == TokenStream.IN))
{
if (op == TokenStream.INSTANCEOF)
aload(variableObjectLocal);
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
int trueGOTO = acquireLabel();
int skip = acquireLabel();
if (op == TokenStream.INSTANCEOF) {
addScriptRuntimeInvoke("instanceOf",
"(Lorg/mozilla/javascript/Scriptable;"+
"Ljava/lang/Object;Ljava/lang/Object;)", "Z");
addByteCode(ByteCode.IFNE, trueGOTO);
}
else {
if (op == TokenStream.IN) {
addScriptRuntimeInvoke("in",
"(Ljava/lang/Object;Ljava/lang/Object;)","Z");
addByteCode(ByteCode.IFNE, trueGOTO);
}
else {
genSimpleCompare(op, trueGOTO, -1);
}
}
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"FALSE", "Ljava/lang/Boolean;");
addByteCode(ByteCode.GOTO, skip);
markLabel(trueGOTO);
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"TRUE", "Ljava/lang/Boolean;");
markLabel(skip);
classFile.adjustStackTop(-1); // only have 1 of true/false
}
else {
String routine = ((op == TokenStream.LT)
|| (op == TokenStream.GT)) ? "cmp_LTB" : "cmp_LEB";
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
if (childNumberFlag == null) {
if (op == TokenStream.GE || op == TokenStream.GT) {
addByteCode(ByteCode.SWAP);
}
addScriptRuntimeInvoke(routine,
"(Ljava/lang/Object;Ljava/lang/Object;)",
"Ljava/lang/Boolean;");
}
else {
boolean doubleThenObject
= (childNumberFlag.intValue() == Node.LEFT);
if (op == TokenStream.GE || op == TokenStream.GT) {
if (doubleThenObject) {
addByteCode(ByteCode.DUP_X2);
addByteCode(ByteCode.POP);
doubleThenObject = false;
}
else {
addByteCode(ByteCode.DUP2_X1);
addByteCode(ByteCode.POP2);
doubleThenObject = true;
}
}
if (doubleThenObject)
addOptRuntimeInvoke(routine,
"(DLjava/lang/Object;)",
"Ljava/lang/Boolean;");
else
addOptRuntimeInvoke(routine,
"(Ljava/lang/Object;D)",
"Ljava/lang/Boolean;");
}
}
}
private Number nodeIsConvertToObjectOfNumber(Node node)
{
if (node.getType() == TokenStream.CONVERT) {
Object toType = node.getProp(Node.TYPE_PROP);
if (toType == ScriptRuntime.ObjectClass) {
Node convertChild = node.getFirstChild();
if (convertChild.getType() == TokenStream.NUMBER)
return (Number)convertChild.getDatum();
}
}
return null;
}
private void visitEqOp(Node node, Node child, Node parent, int trueGOTO, int falseGOTO) {
int op = node.getInt();
Node rightChild = child.getNextSibling();
if (trueGOTO == -1) {
if (rightChild.getType() == TokenStream.PRIMARY &&
rightChild.getInt() == TokenStream.NULL)
{
generateCodeFromNode(child, node, -1, -1);
addByteCode(ByteCode.DUP);
addByteCode(ByteCode.IFNULL, 15);
pushUndefined();
addByteCode(ByteCode.IF_ACMPEQ, 10);
if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ))
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"FALSE", "Ljava/lang/Boolean;");
else
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"TRUE", "Ljava/lang/Boolean;");
addByteCode(ByteCode.GOTO, 7);
addByteCode(ByteCode.POP);
if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ))
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"TRUE", "Ljava/lang/Boolean;");
else
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"FALSE", "Ljava/lang/Boolean;");
return;
}
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
// JavaScript 1.2 uses shallow equality for == and !=
String name;
switch (op) {
case TokenStream.EQ:
name = version == Context.VERSION_1_2 ? "seqB" : "eqB";
break;
case TokenStream.NE:
name = version == Context.VERSION_1_2 ? "sneB" : "neB";
break;
case TokenStream.SHEQ:
name = "seqB";
break;
case TokenStream.SHNE:
name = "sneB";
break;
default:
name = null;
badTree();
}
addScriptRuntimeInvoke(name,
"(Ljava/lang/Object;Ljava/lang/Object;)",
"Ljava/lang/Boolean;");
}
else {
if (rightChild.getType() == TokenStream.PRIMARY &&
rightChild.getInt() == TokenStream.NULL)
{
generateCodeFromNode(child, node, -1, -1);
/*
since we have to test for null && undefined we end up having to
push the operand twice and so have to GOTO to a pop site if the
first test passes.
We can avoid that for operands that are 'simple' i.e. don't generate
a lot of code and don't have side-effects.
For now, 'simple' means GETVAR
*/
boolean simpleChild = (child.getType() == TokenStream.GETVAR);
if (!simpleChild) addByteCode(ByteCode.DUP);
int popGOTO = acquireLabel();
if ((op == TokenStream.EQ) || (op == TokenStream.SHEQ)) {
addByteCode(ByteCode.IFNULL,
(simpleChild) ? trueGOTO : popGOTO);
short popStack = classFile.getStackTop();
if (simpleChild) generateCodeFromNode(child, node, -1, -1);
pushUndefined();
addByteCode(ByteCode.IF_ACMPEQ, trueGOTO);
addByteCode(ByteCode.GOTO, falseGOTO);
if (!simpleChild) {
markLabel(popGOTO, popStack);
addByteCode(ByteCode.POP);
addByteCode(ByteCode.GOTO, trueGOTO);
}
}
else {
addByteCode(ByteCode.IFNULL,
(simpleChild) ? falseGOTO : popGOTO);
short popStack = classFile.getStackTop();
if (simpleChild) generateCodeFromNode(child, node, -1, -1);
pushUndefined();
addByteCode(ByteCode.IF_ACMPEQ, falseGOTO);
addByteCode(ByteCode.GOTO, trueGOTO);
if (!simpleChild) {
markLabel(popGOTO, popStack);
addByteCode(ByteCode.POP);
addByteCode(ByteCode.GOTO, falseGOTO);
}
}
return;
}
Node rChild = child.getNextSibling();
Number numChild = nodeIsConvertToObjectOfNumber(rChild);
if (nodeIsDirectCallParameter(child)
&& (numChild != null)) {
OptLocalVariable lVar1
= (OptLocalVariable)(child.getProp(Node.VARIABLE_PROP));
aload(lVar1.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
int notNumbersLabel = acquireLabel();
addByteCode(ByteCode.IF_ACMPNE, notNumbersLabel);
dload((short)(lVar1.getJRegister() + 1));
push(numChild.doubleValue());
addByteCode(ByteCode.DCMPL);
if (op == TokenStream.EQ)
addByteCode(ByteCode.IFEQ, trueGOTO);
else
addByteCode(ByteCode.IFNE, trueGOTO);
addByteCode(ByteCode.GOTO, falseGOTO);
markLabel(notNumbersLabel);
// fall thru into generic handling
}
generateCodeFromNode(child, node, -1, -1);
generateCodeFromNode(rChild, node, -1, -1);
String name;
switch (op) {
case TokenStream.EQ:
name = version == Context.VERSION_1_2 ? "shallowEq" : "eq";
addScriptRuntimeInvoke(name,
"(Ljava/lang/Object;Ljava/lang/Object;)", "Z");
break;
case TokenStream.NE:
name = version == Context.VERSION_1_2 ? "shallowNeq" : "neq";
addOptRuntimeInvoke(name,
"(Ljava/lang/Object;Ljava/lang/Object;)", "Z");
break;
case TokenStream.SHEQ:
name = "shallowEq";
addScriptRuntimeInvoke(name,
"(Ljava/lang/Object;Ljava/lang/Object;)", "Z");
break;
case TokenStream.SHNE:
name = "shallowNeq";
addOptRuntimeInvoke(name,
"(Ljava/lang/Object;Ljava/lang/Object;)", "Z");
break;
default:
name = null;
badTree();
}
addByteCode(ByteCode.IFNE, trueGOTO);
addByteCode(ByteCode.GOTO, falseGOTO);
}
}
private void visitLiteral(Node node) {
if (node.getType() == TokenStream.STRING) {
// just load the string constant
push(node.getString());
} else {
Number num = (Number) node.getDatum();
if (node.getProp(Node.ISNUMBER_PROP) != null) {
push(num.doubleValue());
}
else {
/*
* Generate code to create the new numeric constant
*
* new java/lang/<WrapperType>
* dup
* push <number>
* invokestatic java/lang/<WrapperType>/<init>(D)V
*/
String wrapperType = "";
String signature = "";
boolean isInteger = false;
if (num instanceof Float)
num = new Double(num.floatValue());
// OPT: cache getClass() and compare == Type.class
if (num instanceof Integer) {
wrapperType = "Integer";
signature = "I";
isInteger = true;
/*
} else if (num instanceof Float) {
wrapperType = "Float";
signature = "F";
*/
} else if (num instanceof Double) {
wrapperType = "Double";
signature = "D";
} else if (num instanceof Byte) {
wrapperType = "Byte";
signature = "B";
isInteger = true;
} else if (num instanceof Short) {
wrapperType = "Short";
signature = "S";
isInteger = true;
} else {
throw Context.reportRuntimeError(
"NumberNode contains unsupported Number type: " +
num.getClass().getName());
}
/*
There appears to be a limit in the JVM on either the number of
static fields in a class or the size of the class initializer.
Either way, we can't have any more than 2000 statically init'd
constants.
*/
if (itsConstantList.itsTop >= 2000) {
addByteCode(ByteCode.NEW, "java/lang/" + wrapperType);
addByteCode(ByteCode.DUP);
if (isInteger)
push(num.longValue());
else
push(num.doubleValue());
addSpecialInvoke("java/lang/"
+ wrapperType,
"<init>",
"(" +
signature
+ ")",
"V");
}
else {
classFile.add(ByteCode.GETSTATIC,
classFile.fullyQualifiedForm(this.name),
"jsK_"
+ itsConstantList.addConstant(wrapperType,
signature,
num,
isInteger),
"Ljava/lang/" + wrapperType + ";");
}
}
}
}
private void emitConstantDudeInitializers() {
if (itsConstantList.itsTop == 0)
return;
classFile.startMethod("<clinit>", "()V",
(short)(ClassFileWriter.ACC_STATIC + ClassFileWriter.ACC_FINAL));
for (int i = 0; i < itsConstantList.itsTop; i++) {
addByteCode(ByteCode.NEW,
"java/lang/"
+ itsConstantList.itsList[i].itsWrapperType);
addByteCode(ByteCode.DUP);
if (itsConstantList.itsList[i].itsIsInteger)
push(itsConstantList.itsList[i].itsLValue);
else
push(itsConstantList.itsList[i].itsDValue);
addSpecialInvoke("java/lang/"
+ itsConstantList.itsList[i].itsWrapperType,
"<init>",
"(" +
itsConstantList.itsList[i].itsSignature
+ ")",
"V");
classFile.addField("jsK_" + i,
"Ljava/lang/"
+ itsConstantList.itsList[i].itsWrapperType + ";",
ClassFileWriter.ACC_STATIC);
classFile.add(ByteCode.PUTSTATIC,
classFile.fullyQualifiedForm(this.name),
"jsK_" + i,
"Ljava/lang/"
+ itsConstantList.itsList[i].itsWrapperType + ";");
}
addByteCode(ByteCode.RETURN);
classFile.stopMethod((short)0, null);
}
private void visitPrimary(Node node) {
int op = node.getInt();
switch (op) {
case TokenStream.THIS:
aload(thisObjLocal);
break;
case TokenStream.NULL:
addByteCode(ByteCode.ACONST_NULL);
break;
case TokenStream.TRUE:
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"TRUE", "Ljava/lang/Boolean;");
break;
case TokenStream.FALSE:
classFile.add(ByteCode.GETSTATIC, "java/lang/Boolean",
"FALSE", "Ljava/lang/Boolean;");
break;
case TokenStream.UNDEFINED:
pushUndefined();
break;
default:
badTree();
}
}
private void visitObject(Node node) {
Node regexp = (Node) node.getProp(Node.REGEXP_PROP);
String fieldName = (String)(regexp.getProp(Node.REGEXP_PROP));
aload(funObjLocal);
classFile.add(ByteCode.GETFIELD,
classFile.fullyQualifiedForm(this.name),
fieldName, "Lorg/mozilla/javascript/regexp/NativeRegExp;");
}
private void visitName(Node node) {
aload(variableObjectLocal); // get variable object
push(node.getString()); // push name
addScriptRuntimeInvoke("name",
"(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)",
"Ljava/lang/Object;");
}
private void visitSetName(Node node, Node child) {
String name = node.getFirstChild().getString();
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
aload(variableObjectLocal);
push(name);
addScriptRuntimeInvoke("setName",
"(Lorg/mozilla/javascript/Scriptable;Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)",
"Ljava/lang/Object;");
}
private void visitGetVar(OptLocalVariable lVar, boolean isNumber,
String name)
{
// TODO: Clean up use of lVar here and in set.
if (hasVarsInRegs && lVar == null)
lVar = (OptLocalVariable) vars.get(name);
if (lVar != null) {
if (lVar.getJRegister() == -1)
if (lVar.isNumber())
lVar.assignJRegister(getNewWordPairLocal());
else
lVar.assignJRegister(getNewWordLocal());
if (lVar.isParameter() && inDirectCallFunction &&
!itsForcedObjectParameters)
{
/*
Remember that here the isNumber flag means that we want to
use the incoming parameter in a Number context, so test the
object type and convert the value as necessary.
*/
if (isNumber) {
aload(lVar.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
int isNumberLabel = acquireLabel();
int beyond = acquireLabel();
addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel);
aload(lVar.getJRegister());
addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)", "D");
addByteCode(ByteCode.GOTO, beyond);
markLabel(isNumberLabel);
dload((short)(lVar.getJRegister() + 1));
markLabel(beyond);
} else {
aload(lVar.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
int isNumberLabel = acquireLabel();
int beyond = acquireLabel();
addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel);
aload(lVar.getJRegister());
addByteCode(ByteCode.GOTO, beyond);
markLabel(isNumberLabel);
addByteCode(ByteCode.NEW,"java/lang/Double");
addByteCode(ByteCode.DUP);
dload((short)(lVar.getJRegister() + 1));
addDoubleConstructor();
markLabel(beyond);
}
} else {
if (lVar.isNumber())
dload(lVar.getJRegister());
else
aload(lVar.getJRegister());
}
return;
}
aload(variableObjectLocal);
push(name);
aload(variableObjectLocal);
addScriptRuntimeInvoke("getProp",
"(Ljava/lang/Object;Ljava/lang/String;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
private void visitSetVar(Node node, Node child, boolean needValue) {
OptLocalVariable lVar = (OptLocalVariable)(node.getProp(Node.VARIABLE_PROP));
// XXX is this right? If so, clean up.
if (hasVarsInRegs && lVar == null)
lVar = (OptLocalVariable) vars.get(child.getString());
if (lVar != null) {
generateCodeFromNode(child.getNextSibling(), node, -1, -1);
if (lVar.getJRegister() == -1) {
if (lVar.isNumber())
lVar.assignJRegister(getNewWordPairLocal());
else
lVar.assignJRegister(getNewWordLocal());
}
if (lVar.isParameter()
&& inDirectCallFunction
&& !itsForcedObjectParameters) {
if (node.getProp(Node.ISNUMBER_PROP) != null) {
if (needValue) addByteCode(ByteCode.DUP2);
aload(lVar.getJRegister());
classFile.add(ByteCode.GETSTATIC,
"java/lang/Void",
"TYPE",
"Ljava/lang/Class;");
int isNumberLabel = acquireLabel();
int beyond = acquireLabel();
addByteCode(ByteCode.IF_ACMPEQ, isNumberLabel);
addByteCode(ByteCode.NEW,"java/lang/Double");
addByteCode(ByteCode.DUP);
addByteCode(ByteCode.DUP2_X2);
addByteCode(ByteCode.POP2);
addDoubleConstructor();
astore(lVar.getJRegister());
addByteCode(ByteCode.GOTO, beyond);
markLabel(isNumberLabel);
dstore((short)(lVar.getJRegister() + 1));
markLabel(beyond);
}
else {
if (needValue) addByteCode(ByteCode.DUP);
astore(lVar.getJRegister());
}
}
else {
if (node.getProp(Node.ISNUMBER_PROP) != null) {
dstore(lVar.getJRegister());
if (needValue) dload(lVar.getJRegister());
}
else {
astore(lVar.getJRegister());
if (needValue) aload(lVar.getJRegister());
}
}
return;
}
// default: just treat like any other name lookup
child.setType(TokenStream.BINDNAME);
node.setType(TokenStream.SETNAME);
visitSetName(node, child);
if (!needValue)
addByteCode(ByteCode.POP);
}
private void visitGetProp(Node node, Node child) {
String s = (String) node.getProp(Node.SPECIAL_PROP_PROP);
if (s != null) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
aload(variableObjectLocal);
String runtimeMethod = null;
if (s.equals("__proto__")) {
runtimeMethod = "getProto";
} else if (s.equals("__parent__")) {
runtimeMethod = "getParent";
} else {
badTree();
}
addScriptRuntimeInvoke(runtimeMethod,
"(Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)",
"Lorg/mozilla/javascript/Scriptable;");
return;
}
Node nameChild = child.getNextSibling();
/*
for 'this.foo' we call thisGet which can skip some
casting overhead.
*/
generateCodeFromNode(child, node, -1, -1); // the object
generateCodeFromNode(nameChild, node, -1, -1); // the name
if (nameChild.getType() == TokenStream.STRING) {
if ((child.getType() == TokenStream.PRIMARY &&
child.getInt() == TokenStream.THIS)
|| ((child.getType() == TokenStream.NEWTEMP)
&& (child.getFirstChild().getType() == TokenStream.PRIMARY)
&& (child.getFirstChild().getInt() == TokenStream.THIS))
) {
aload(variableObjectLocal);
addOptRuntimeInvoke("thisGet",
"(Lorg/mozilla/javascript/Scriptable;" +
"Ljava/lang/String;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
else {
aload(variableObjectLocal);
addScriptRuntimeInvoke("getProp",
"(Ljava/lang/Object;Ljava/lang/String;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
}
else {
aload(variableObjectLocal);
addScriptRuntimeInvoke("getProp",
"(Ljava/lang/Object;Ljava/lang/String;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
}
private void visitSetProp(Node node, Node child) {
String s = (String) node.getProp(Node.SPECIAL_PROP_PROP);
if (s != null) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
aload(variableObjectLocal);
String runtimeMethod = null;
if (s.equals("__proto__")) {
runtimeMethod = "setProto";
} else if (s.equals("__parent__")) {
runtimeMethod = "setParent";
} else {
badTree();
}
addScriptRuntimeInvoke(runtimeMethod,
"(Ljava/lang/Object;Ljava/lang/Object;" +
"Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
return;
}
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
aload(variableObjectLocal);
addScriptRuntimeInvoke("setProp",
"(Ljava/lang/Object;Ljava/lang/String;" +
"Ljava/lang/Object;Lorg/mozilla/javascript/Scriptable;)",
"Ljava/lang/Object;");
}
private void visitBind(Node node, int type, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
// Generate code for "ScriptRuntime.bind(varObj, "s")"
aload(variableObjectLocal); // get variable object
push(node.getString()); // push name
addScriptRuntimeInvoke(type == TokenStream.BINDNAME ? "bind" : "getBase",
"(Lorg/mozilla/javascript/Scriptable;Ljava/lang/String;)",
"Lorg/mozilla/javascript/Scriptable;");
}
private void getParentScope(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
classFile.add(ByteCode.INVOKEINTERFACE,
"org/mozilla/javascript/Scriptable",
"getParentScope",
"()", "Lorg/mozilla/javascript/Scriptable;");
}
private short getLocalFromNode(Node node) {
Integer localProp = (Integer) node.getProp(Node.LOCAL_PROP);
if (localProp == null) {
// for NEWLOCAL & USELOCAL, use the next pre-allocated
// register, otherwise for NEWTEMP & USETEMP, get the
// next available from the pool
short local = ((node.getType() == TokenStream.NEWLOCAL)
|| (node.getType() == TokenStream.USELOCAL)) ?
itsLocalAllocationBase++ : getNewWordLocal();
node.putProp(Node.LOCAL_PROP, new Integer(local));
return local;
} else {
return localProp.shortValue();
}
}
private void visitNewTemp(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
short local = getLocalFromNode(node);
addByteCode(ByteCode.DUP);
astore(local);
Integer n = (Integer) node.getProp(Node.USES_PROP);
if (n == null || n.intValue() == 0)
releaseWordLocal(local);
}
private void visitUseTemp(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
Node temp = (Node) node.getProp(Node.TEMP_PROP);
short local = getLocalFromNode(temp);
// if the temp node has a magic TARGET property,
// treat it as a RET to that temp.
if (node.getProp(Node.TARGET_PROP) != null)
addByteCode(ByteCode.RET, local);
else
aload(local);
Integer n = (Integer) temp.getProp(Node.USES_PROP);
if (n == null) {
releaseWordLocal(local);
} else {
if (n.intValue() < Integer.MAX_VALUE) {
int i = n.intValue() - 1;
if (i == 0)
releaseWordLocal(local);
temp.putProp(Node.USES_PROP, new Integer(i));
}
}
}
private void visitNewLocal(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
short local = getLocalFromNode(node);
addByteCode(ByteCode.DUP);
astore(local);
}
private void visitUseLocal(Node node, Node child) {
while (child != null) {
generateCodeFromNode(child, node, -1, -1);
child = child.getNextSibling();
}
Node temp = (Node) node.getProp(Node.LOCAL_PROP);
short local = getLocalFromNode(temp);
// if the temp node has a magic TARGET property,
// treat it as a RET to that temp.
if (node.getProp(Node.TARGET_PROP) != null)
addByteCode(ByteCode.RET, local);
else
aload(local);
}
private void dstore(short local) {
xstore(ByteCode.DSTORE_0, ByteCode.DSTORE, local);
}
private void istore(short local) {
xstore(ByteCode.ISTORE_0, ByteCode.ISTORE, local);
}
private void astore(short local) {
xstore(ByteCode.ASTORE_0, ByteCode.ASTORE, local);
}
private void xstore(byte shortOp, byte op, short local) {
switch (local) {
case 0:
addByteCode(shortOp);
break;
case 1:
addByteCode((byte)(shortOp + 1));
break;
case 2:
addByteCode((byte)(shortOp + 2));
break;
case 3:
addByteCode((byte)(shortOp + 3));
break;
default:
if (local < 0 || local >= Byte.MAX_VALUE)
throw new RuntimeException("bad local");
addByteCode(op, (byte)local);
break;
}
}
private void dload(short local) {
xload(ByteCode.DLOAD_0, ByteCode.DLOAD, local);
}
private void iload(short local) {
xload(ByteCode.ILOAD_0, ByteCode.ILOAD, local);
}
private void aload(short local) {
xload(ByteCode.ALOAD_0, ByteCode.ALOAD, local);
}
private void xload(byte shortOp, byte op, short local) {
switch (local) {
case 0:
addByteCode(shortOp);
break;
case 1:
addByteCode((byte)(shortOp + 1));
break;
case 2:
addByteCode((byte)(shortOp + 2));
break;
case 3:
addByteCode((byte)(shortOp + 3));
break;
default:
if (local < 0 || local >= Byte.MAX_VALUE)
throw new RuntimeException("bad local");
addByteCode(op, (byte) local);
break;
}
}
private short getNewWordPairLocal() {
short result = firstFreeLocal;
while (true) {
if (result >= (MAX_LOCALS - 1))
break;
if (!locals[result]
&& !locals[result + 1])
break;
result++;
}
if (result < (MAX_LOCALS - 1)) {
locals[result] = true;
locals[result + 1] = true;
if (result == firstFreeLocal) {
for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) {
if (!locals[i]) {
firstFreeLocal = (short) i;
if (localsMax < firstFreeLocal)
localsMax = firstFreeLocal;
return result;
}
}
}
else {
return result;
}
}
throw Context.reportRuntimeError("Program too complex " +
"(out of locals)");
}
private short reserveWordLocal(int local) {
if (getNewWordLocal() != local)
throw new RuntimeException("Local allocation error");
return (short) local;
}
private short getNewWordLocal() {
short result = firstFreeLocal;
locals[result] = true;
for (int i = firstFreeLocal + 1; i < MAX_LOCALS; i++) {
if (!locals[i]) {
firstFreeLocal = (short) i;
if (localsMax < firstFreeLocal)
localsMax = firstFreeLocal;
return result;
}
}
throw Context.reportRuntimeError("Program too complex " +
"(out of locals)");
}
private void releaseWordpairLocal(short local) {
if (local < firstFreeLocal)
firstFreeLocal = local;
locals[local] = false;
locals[local + 1] = false;
}
private void releaseWordLocal(short local) {
if (local < firstFreeLocal)
firstFreeLocal = local;
locals[local] = false;
}
private void push(long l) {
if (l == -1) {
addByteCode(ByteCode.ICONST_M1);
} else if (0 <= l && l <= 5) {
addByteCode((byte) (ByteCode.ICONST_0 + l));
} else if (Byte.MIN_VALUE <= l && l <= Byte.MAX_VALUE) {
addByteCode(ByteCode.BIPUSH, (byte) l);
} else if (Short.MIN_VALUE <= l && l <= Short.MAX_VALUE) {
addByteCode(ByteCode.SIPUSH, (short) l);
} else if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE) {
classFile.addLoadConstant((int)l);
} else {
classFile.addLoadConstant((long)l);
}
}
private void push(double d) {
if (d == 0.0) {
addByteCode(ByteCode.DCONST_0);
} else if (d == 1.0) {
addByteCode(ByteCode.DCONST_1);
/* XXX this breaks all sorts of simple math.
} else if (Float.MIN_VALUE <= d && d <= Float.MAX_VALUE) {
loadWordConstant(classFile.addFloatConstant((float) d));
*/
} else {
classFile.addLoadConstant((double)d);
}
}
private void push(String s) {
classFile.addLoadConstant(s);
}
private void pushUndefined() {
classFile.add(ByteCode.GETSTATIC, "org/mozilla/javascript/Undefined",
"instance", "Lorg/mozilla/javascript/Scriptable;");
}
private void badTree() {
throw new RuntimeException("Bad tree in codegen");
}
private static final String normalFunctionSuperClassName =
"org.mozilla.javascript.NativeFunction";
private static final String normalScriptSuperClassName =
"org.mozilla.javascript.NativeScript";
private static final String debugFunctionSuperClassName =
"org.mozilla.javascript.debug.NativeFunctionDebug";
private static final String debugScriptSuperClassName =
"org.mozilla.javascript.debug.NativeScriptDebug";
private String superClassName;
private String superClassSlashName;
private String name;
private int ordinal;
boolean inFunction;
boolean inDirectCallFunction;
private ClassFileWriter classFile;
private Vector namesVector;
private Vector classFilesVector;
private short scriptRuntimeIndex;
private int version;
private OptClassNameHelper itsNameHelper;
private static JavaScriptClassLoader classLoader;
private String itsSourceFile;
private int itsLineNumber;
private int stackDepth;
private int stackDepthMax;
private static final int MAX_LOCALS = 256;
private boolean[] locals;
private short firstFreeLocal;
private short localsMax;
private ConstantList itsConstantList;
// special known locals. If you add a new local here, be sure
// to initialize it to -1 in startNewMethod
private short variableObjectLocal;
private short scriptResultLocal;
private short contextLocal;
private short argsLocal;
private short thisObjLocal;
private short funObjLocal;
private short debug_pcLocal;
private short debugStopSubRetLocal;
private short itsZeroArgArray;
private short itsOneArgArray;
private boolean hasVarsInRegs;
private boolean itsForcedObjectParameters;
private boolean trivialInit;
private short itsLocalAllocationBase;
private OptVariableTable vars;
private OptVariableTable debugVars;
private int epilogueLabel;
private int optLevel;
private int debugLevel;
private int debugStopSubLabel;
private short[] debugLineMap;
private short debugLineEntryCount;
private static final int DEBUG_LINE_MAP_INITIAL_SIZE = 100;
private static final int DEBUG_LINE_MAP_RESIZE_INCREMENT = 100;
}
class ConstantList {
int addConstant(String wrapperType, String signature, Number num, boolean isInteger)
{
long lValue = num.longValue();
double dValue = num.doubleValue();
for (int i = 0; i < itsTop; i++) {
if (signature.equals(itsList[i].itsSignature)) {
if (itsList[i].itsIsInteger) {
if (isInteger && (lValue == itsList[i].itsLValue)) {
return i;
}
}
else {
if (!isInteger && (dValue == itsList[i].itsDValue)) {
return i;
}
}
}
}
if (itsTop == itsList.length) {
ConstantDude[] newList = new ConstantDude[itsList.length * 2];
System.arraycopy(itsList, 0, newList, 0, itsList.length);
itsList = newList;
}
if (isInteger) {
itsList[itsTop] = new ConstantDude(wrapperType, signature, lValue);
}
else {
itsList[itsTop] = new ConstantDude(wrapperType, signature, dValue);
}
return itsTop++;
}
ConstantDude[] itsList = new ConstantDude[128];
int itsTop;
}
class ConstantDude {
ConstantDude(String wrapperType, String signature, long value)
{
itsWrapperType = wrapperType;
itsSignature = signature;
itsIsInteger = true;
itsLValue = value;
}
ConstantDude(String wrapperType, String signature, double value)
{
itsWrapperType = wrapperType;
itsSignature = signature;
itsIsInteger = false;
itsDValue = value;
}
String itsWrapperType;
String itsSignature;
long itsLValue;
double itsDValue;
boolean itsIsInteger;
}