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