org.mozilla.javascript.optimizer.Codegen Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhino Show documentation
Show all versions of rhino Show documentation
Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically
embedded into Java applications to provide scripting to end users.
The newest version!
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript.optimizer;
import static org.mozilla.classfile.ClassFileWriter.ACC_FINAL;
import static org.mozilla.classfile.ClassFileWriter.ACC_PRIVATE;
import static org.mozilla.classfile.ClassFileWriter.ACC_PROTECTED;
import static org.mozilla.classfile.ClassFileWriter.ACC_PUBLIC;
import static org.mozilla.classfile.ClassFileWriter.ACC_STATIC;
import static org.mozilla.classfile.ClassFileWriter.ACC_VOLATILE;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mozilla.classfile.ByteCode;
import org.mozilla.classfile.ClassFileWriter;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Evaluator;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.GeneratedClassLoader;
import org.mozilla.javascript.Kit;
import org.mozilla.javascript.NativeFunction;
import org.mozilla.javascript.ObjArray;
import org.mozilla.javascript.ObjToIntMap;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.SecurityController;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.ast.TemplateCharacters;
/**
* This class generates code for a given IR tree.
*
* @author Norris Boyd
* @author Roger Lawrence
*/
public class Codegen implements Evaluator {
@Override
public void captureStackInfo(RhinoException ex) {
throw new UnsupportedOperationException();
}
@Override
public String getSourcePositionFromStack(Context cx, int[] linep) {
throw new UnsupportedOperationException();
}
@Override
public String getPatchedStack(RhinoException ex, String nativeStackTrace) {
throw new UnsupportedOperationException();
}
@Override
public List getScriptStack(RhinoException ex) {
throw new UnsupportedOperationException();
}
@Override
public void setEvalScriptFlag(Script script) {
throw new UnsupportedOperationException();
}
@Override
public Object compile(
CompilerEnvirons compilerEnv,
ScriptNode tree,
String encodedSource,
boolean returnFunction) {
int serial;
synchronized (globalLock) {
serial = ++globalSerialClassCounter;
}
String baseName = "c";
if (tree.getSourceName().length() > 0) {
baseName = tree.getSourceName().replaceAll("\\W", "_");
if (!Character.isJavaIdentifierStart(baseName.charAt(0))) {
baseName = "_" + baseName;
}
}
String mainClassName = "org.mozilla.javascript.gen." + baseName + "_" + serial;
byte[] mainClassBytes =
compileToClassFile(compilerEnv, mainClassName, tree, encodedSource, returnFunction);
return new Object[] {mainClassName, mainClassBytes};
}
@Override
public Script createScriptObject(Object bytecode, Object staticSecurityDomain) {
Class> cl = defineClass(bytecode, staticSecurityDomain);
Script script;
try {
script = (Script) cl.getDeclaredConstructor().newInstance();
} catch (Exception ex) {
throw new RuntimeException("Unable to instantiate compiled class:" + ex.toString());
}
return script;
}
@Override
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, Integer.valueOf(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);
}
public byte[] compileToClassFile(
CompilerEnvirons compilerEnv,
String mainClassName,
ScriptNode 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);
}
initScriptNodesData(scriptOrFn);
this.mainClassName = mainClassName;
this.mainClassSignature = ClassFileWriter.classNameToSignature(mainClassName);
return generateCode(encodedSource);
}
private void transform(ScriptNode tree) {
initOptFunctions_r(tree);
int optLevel = compilerEnv.getOptimizationLevel();
Map 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.getName();
if (name.length() != 0) {
if (possibleDirectCalls == null) {
possibleDirectCalls = new HashMap<>();
}
possibleDirectCalls.put(name, ofn);
}
}
}
}
}
if (possibleDirectCalls != null) {
directCallTargets = new ObjArray();
}
OptTransformer ot = new OptTransformer(possibleDirectCalls, directCallTargets);
ot.transform(tree, compilerEnv);
if (optLevel > 0) {
(new Optimizer()).optimize(tree);
}
}
private static void initOptFunctions_r(ScriptNode scriptOrFn) {
for (int i = 0, N = scriptOrFn.getFunctionCount(); i != N; ++i) {
FunctionNode fn = scriptOrFn.getFunctionNode(i);
new OptFunctionNode(fn);
initOptFunctions_r(fn);
}
}
private void initScriptNodesData(ScriptNode scriptOrFn) {
ObjArray x = new ObjArray();
collectScriptNodes_r(scriptOrFn, x);
int count = x.size();
scriptOrFnNodes = new ScriptNode[count];
x.toArray(scriptOrFnNodes);
scriptOrFnIndexes = new ObjToIntMap(count);
for (int i = 0; i != count; ++i) {
scriptOrFnIndexes.put(scriptOrFnNodes[i], i);
}
}
private static void collectScriptNodes_r(ScriptNode n, ObjArray x) {
x.add(n);
int nestedCount = n.getFunctionCount();
for (int i = 0; i != nestedCount; ++i) {
collectScriptNodes_r(n.getFunctionNode(i), x);
}
}
private byte[] generateCode(String encodedSource) {
boolean hasScript = (scriptOrFnNodes[0].getType() == Token.SCRIPT);
boolean hasFunctions = (scriptOrFnNodes.length > 1 || !hasScript);
boolean isStrictMode = scriptOrFnNodes[0].isInStrictMode();
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", ACC_PRIVATE);
if (hasFunctions) {
generateFunctionConstructor(cfw);
}
if (hasScript) {
cfw.addInterface("org/mozilla/javascript/Script");
generateScriptCtor(cfw);
generateMain(cfw);
generateExecute(cfw);
}
generateCallMethod(cfw, isStrictMode);
generateResumeGenerator(cfw);
generateNativeFunctionOverrides(cfw, encodedSource);
int count = scriptOrFnNodes.length;
for (int i = 0; i != count; ++i) {
ScriptNode n = scriptOrFnNodes[i];
BodyCodegen bodygen = new BodyCodegen();
bodygen.cfw = cfw;
bodygen.codegen = this;
bodygen.compilerEnv = compilerEnv;
bodygen.scriptOrFn = n;
bodygen.scriptOrFnIndex = i;
bodygen.generateBodyCode();
if (n.getType() == Token.FUNCTION) {
OptFunctionNode ofn = OptFunctionNode.get(n);
generateFunctionInit(cfw, ofn);
if (ofn.isTargetOfDirectCall()) {
emitDirectConstructor(cfw, ofn);
}
}
}
emitRegExpInit(cfw);
emitTemplateLiteralInit(cfw);
emitConstantDudeInitializers(cfw);
return cfw.toByteArray();
}
private void emitDirectConstructor(ClassFileWriter cfw, OptFunctionNode ofn) {
/*
we generate ..
Scriptable directConstruct() {
Scriptable newInstance = createObject(cx, scope);
Object val = (cx, scope, newInstance, );
if (val instanceof Scriptable) {
return (Scriptable) val;
}
return newInstance;
}
*/
cfw.startMethod(
getDirectCtorName(ofn.fnode),
getBodyMethodSignature(ofn.fnode),
(short) (ACC_STATIC | 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(ScriptNode 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 (ScriptNode scriptOrFnNode : scriptOrFnNodes) {
if (isGenerator(scriptOrFnNode)) 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) (ACC_PUBLIC | 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++) {
ScriptNode 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, boolean isStrictMode) {
cfw.startMethod(
"call",
"(Lorg/mozilla/javascript/Context;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ "[Ljava/lang/Object;)Ljava/lang/Object;",
(short) (ACC_PUBLIC | 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.addPush(isStrictMode);
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;"
+ "Z"
+ ")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) {
ScriptNode 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) (ACC_PUBLIC | ACC_STATIC));
// load new ScriptImpl()
cfw.add(ByteCode.NEW, cfw.getClassName());
cfw.add(ByteCode.DUP);
cfw.addInvoke(ByteCode.INVOKESPECIAL, cfw.getClassName(), "", "()V");
// load 'args'
cfw.add(ByteCode.ALOAD_0);
// Call mainMethodClass.main(Script script, String[] args)
cfw.addInvoke(
ByteCode.INVOKESTATIC,
mainMethodClass,
"main",
"(Lorg/mozilla/javascript/Script;[Ljava/lang/String;)V");
cfw.add(ByteCode.RETURN);
// 1 = String[] args
cfw.stopMethod((short) 1);
}
private static void generateExecute(ClassFileWriter cfw) {
cfw.startMethod(
"exec",
"(Lorg/mozilla/javascript/Context;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ ")Ljava/lang/Object;",
(short) (ACC_PUBLIC | 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.addALoad(CONTEXT_ARG);
cfw.addInvoke(
ByteCode.INVOKEVIRTUAL,
"org.mozilla.javascript.Context",
"processMicrotasks",
"()V");
cfw.add(ByteCode.ARETURN);
// 3 = this + context + scope
cfw.stopMethod((short) 3);
}
private static void generateScriptCtor(ClassFileWriter cfw) {
cfw.startMethod("", "()V", ACC_PUBLIC);
cfw.addLoadThis();
cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, "", "()V");
// set id to 0
cfw.addLoadThis();
cfw.addPush(0);
cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
cfw.add(ByteCode.RETURN);
// 1 parameter = this
cfw.stopMethod((short) 1);
}
private void generateFunctionConstructor(ClassFileWriter cfw) {
final int SCOPE_ARG = 1;
final int CONTEXT_ARG = 2;
final int ID_ARG = 3;
cfw.startMethod("", FUNCTION_CONSTRUCTOR_SIGNATURE, ACC_PUBLIC);
cfw.addALoad(0);
cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, "", "()V");
cfw.addLoadThis();
cfw.addILoad(ID_ARG);
cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
cfw.addLoadThis();
cfw.addALoad(CONTEXT_ARG);
cfw.addALoad(SCOPE_ARG);
int start = (scriptOrFnNodes[0].getType() == Token.SCRIPT) ? 1 : 0;
int end = scriptOrFnNodes.length;
if (start == end) throw badTree();
boolean generateSwitch = (2 <= end - start);
int switchStart = 0;
int switchStackTop = 0;
if (generateSwitch) {
cfw.addILoad(ID_ARG);
// do switch from (start + 1, end - 1) mapping start to
// the default case
switchStart = cfw.addTableSwitch(start + 1, end - 1);
}
for (int i = start; i != end; ++i) {
if (generateSwitch) {
if (i == start) {
cfw.markTableSwitchDefault(switchStart);
switchStackTop = cfw.getStackTop();
} else {
cfw.markTableSwitchCase(switchStart, i - 1 - start, switchStackTop);
}
}
OptFunctionNode ofn = OptFunctionNode.get(scriptOrFnNodes[i]);
cfw.addInvoke(
ByteCode.INVOKESPECIAL,
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) (ACC_PRIVATE | 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
if (ofn.fnode.getRegexpCount() != 0) {
cfw.addALoad(CONTEXT_ARG);
cfw.addInvoke(
ByteCode.INVOKESTATIC,
mainClassName,
REGEXP_INIT_METHOD_NAME,
REGEXP_INIT_METHOD_SIGNATURE);
}
// emit all template literals
if (ofn.fnode.getTemplateLiteralCount() != 0) {
cfw.addInvoke(
ByteCode.INVOKESTATIC,
mainClassName,
TEMPLATE_LITERAL_INIT_METHOD_NAME,
TEMPLATE_LITERAL_INIT_METHOD_SIGNATURE);
}
cfw.add(ByteCode.RETURN);
// 3 = (scriptThis/functionRef) + scope + context
cfw.stopMethod((short) 3);
}
private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encodedSource) {
// Override NativeFunction.getLanguageVersion() with
// public int getLanguageVersion() { return ; }
cfw.startMethod("getLanguageVersion", "()I", 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 Do_isGeneratorFunction = 6;
final int Do_hasRestParameter = 7;
final int SWITCH_COUNT = 8;
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;", ACC_PUBLIC);
break;
case Do_getParamCount:
methodLocals = 1; // Only this
cfw.startMethod("getParamCount", "()I", ACC_PUBLIC);
break;
case Do_getParamAndVarCount:
methodLocals = 1; // Only this
cfw.startMethod("getParamAndVarCount", "()I", ACC_PUBLIC);
break;
case Do_getParamOrVarName:
methodLocals = 1 + 1; // this + paramOrVarIndex
cfw.startMethod("getParamOrVarName", "(I)Ljava/lang/String;", ACC_PUBLIC);
break;
case Do_getParamOrVarConst:
methodLocals = 1 + 1 + 1; // this + paramOrVarName
cfw.startMethod("getParamOrVarConst", "(I)Z", ACC_PUBLIC);
break;
case Do_getEncodedSource:
methodLocals = 1; // Only this
cfw.startMethod("getEncodedSource", "()Ljava/lang/String;", ACC_PUBLIC);
cfw.addPush(encodedSource);
break;
case Do_isGeneratorFunction:
methodLocals = 1; // Only this
cfw.startMethod("isGeneratorFunction", "()Z", ACC_PROTECTED);
break;
case Do_hasRestParameter:
methodLocals = 1; // Only this
cfw.startMethod("hasRestParameter", "()Z", ACC_PUBLIC);
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) {
ScriptNode 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).getName();
cfw.addPush(name);
}
cfw.add(ByteCode.ARETURN);
break;
case Do_getParamCount:
// Push number of defined parameters
if (n.hasRestParameter()) {
cfw.addPush(n.getParamCount() - 1);
} else {
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_isGeneratorFunction:
// Push a boolean if it's a generator
if (n instanceof FunctionNode) {
cfw.addPush(((FunctionNode) n).isES6Generator());
} else {
cfw.addPush(false);
}
cfw.add(ByteCode.IRETURN);
break;
case Do_hasRestParameter:
// Push boolean of defined hasRestParameter
cfw.addPush(n.hasRestParameter());
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) (ACC_STATIC | ACC_PRIVATE));
cfw.addField("_reInitDone", "Z", (short) (ACC_STATIC | ACC_PRIVATE | ACC_VOLATILE));
cfw.add(ByteCode.GETSTATIC, mainClassName, "_reInitDone", "Z");
int doInit = cfw.acquireLabel();
cfw.add(ByteCode.IFEQ, doInit);
cfw.add(ByteCode.RETURN);
cfw.markLabel(doInit);
// get regexp proxy and store it in local slot 1
cfw.addALoad(0); // context
cfw.addInvoke(
ByteCode.INVOKESTATIC,
"org/mozilla/javascript/ScriptRuntime",
"checkRegExpProxy",
"(Lorg/mozilla/javascript/Context;" + ")Lorg/mozilla/javascript/RegExpProxy;");
cfw.addAStore(1); // proxy
// We could apply double-checked locking here but concurrency
// shouldn't be a problem in practice
for (int i = 0; i != scriptOrFnNodes.length; ++i) {
ScriptNode 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) (ACC_STATIC | ACC_PRIVATE));
cfw.addALoad(1); // proxy
cfw.addALoad(0); // 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);
}
/**
* Overview:
*
*
* for each fn in functions(script) do
* let field = []
* for each templateLiteral in templateLiterals(fn) do
* let values = concat([[cooked(s), raw(s)] | s <- strings(templateLiteral)])
* field.push(values)
* end
* class[getTemplateLiteralName(fn)] = field
* end
*
*/
private void emitTemplateLiteralInit(ClassFileWriter cfw) {
// emit all template literals
int totalTemplateLiteralCount = 0;
for (ScriptNode n : scriptOrFnNodes) {
totalTemplateLiteralCount += n.getTemplateLiteralCount();
}
if (totalTemplateLiteralCount == 0) {
return;
}
cfw.startMethod(
TEMPLATE_LITERAL_INIT_METHOD_NAME,
TEMPLATE_LITERAL_INIT_METHOD_SIGNATURE,
(short) (ACC_STATIC | ACC_PRIVATE));
cfw.addField("_qInitDone", "Z", (short) (ACC_STATIC | ACC_PRIVATE | ACC_VOLATILE));
cfw.add(ByteCode.GETSTATIC, mainClassName, "_qInitDone", "Z");
int doInit = cfw.acquireLabel();
cfw.add(ByteCode.IFEQ, doInit);
cfw.add(ByteCode.RETURN);
cfw.markLabel(doInit);
// We could apply double-checked locking here but concurrency
// shouldn't be a problem in practice
for (ScriptNode n : scriptOrFnNodes) {
int qCount = n.getTemplateLiteralCount();
if (qCount == 0) continue;
String qFieldName = getTemplateLiteralName(n);
String qFieldType = "[Ljava/lang/Object;";
cfw.addField(qFieldName, qFieldType, (short) (ACC_STATIC | ACC_PRIVATE));
cfw.addPush(qCount);
cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
for (int j = 0; j < qCount; ++j) {
List strings = n.getTemplateLiteralStrings(j);
cfw.add(ByteCode.DUP);
cfw.addPush(j);
cfw.addPush(strings.size() * 2);
cfw.add(ByteCode.ANEWARRAY, "java/lang/String");
int k = 0;
for (TemplateCharacters s : strings) {
// cooked value
cfw.add(ByteCode.DUP);
cfw.addPush(k++);
if (s.getValue() != null) {
cfw.addPush(s.getValue());
} else {
cfw.add(ByteCode.ACONST_NULL);
}
cfw.add(ByteCode.AASTORE);
// raw value
cfw.add(ByteCode.DUP);
cfw.addPush(k++);
cfw.addPush(s.getRawValue());
cfw.add(ByteCode.AASTORE);
}
cfw.add(ByteCode.AASTORE);
}
cfw.add(ByteCode.PUTSTATIC, mainClassName, qFieldName, qFieldType);
}
cfw.addPush(true);
cfw.add(ByteCode.PUTSTATIC, mainClassName, "_qInitDone", "Z");
cfw.add(ByteCode.RETURN);
cfw.stopMethod((short) 0);
}
private void emitConstantDudeInitializers(ClassFileWriter cfw) {
int N = itsConstantListSize;
if (N == 0) return;
cfw.startMethod("", "()V", (short) (ACC_STATIC | 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) (ACC_STATIC | ACC_PRIVATE));
int inum = (int) num;
if (inum == num) {
cfw.addPush(inum);
cfw.addInvoke(
ByteCode.INVOKESTATIC,
"java/lang/Integer",
"valueOf",
"(I)Ljava/lang/Integer;");
} else {
cfw.addPush(num);
addDoubleWrap(cfw);
}
cfw.add(ByteCode.PUTSTATIC, mainClassName, constantName, constantType);
}
cfw.add(ByteCode.RETURN);
cfw.stopMethod((short) 0);
}
void pushNumberAsObject(ClassFileWriter cfw, double num) {
if (num == 0.0) {
if (1 / num > 0) {
// +0.0
cfw.add(
ByteCode.GETSTATIC,
"org/mozilla/javascript/ScriptRuntime",
"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 (Double.isNaN(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;";
}
return "Ljava/lang/Double;";
}
static void pushUndefined(ClassFileWriter cfw) {
cfw.add(
ByteCode.GETSTATIC,
"org/mozilla/javascript/Undefined",
"instance",
"Ljava/lang/Object;");
}
int getIndex(ScriptNode n) {
return scriptOrFnIndexes.getExisting(n);
}
String getDirectCtorName(ScriptNode n) {
return "_n" + getIndex(n);
}
String getBodyMethodName(ScriptNode n) {
return "_c_" + cleanName(n) + "_" + getIndex(n);
}
/** Gets a Java-compatible "informative" name for the the ScriptOrFnNode */
String cleanName(final ScriptNode n) {
String result = "";
if (n instanceof FunctionNode) {
Name name = ((FunctionNode) n).getFunctionName();
if (name == null) {
result = "anonymous";
} else {
result = name.getIdentifier();
}
} else {
result = "script";
}
return result;
}
String getBodyMethodSignature(ScriptNode n) {
StringBuilder sb = new StringBuilder();
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(ScriptNode n, int regexpIndex) {
return "_re" + getIndex(n) + "_" + regexpIndex;
}
String getTemplateLiteralName(ScriptNode n) {
return "_q" + getIndex(n);
}
static RuntimeException badTree() {
throw new RuntimeException("Bad tree in codegen");
}
public 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 ID_FIELD_NAME = "_id";
static final String REGEXP_INIT_METHOD_NAME = "_reInit";
static final String REGEXP_INIT_METHOD_SIGNATURE = "(Lorg/mozilla/javascript/Context;)V";
static final String TEMPLATE_LITERAL_INIT_METHOD_NAME = "_qInit";
static final String TEMPLATE_LITERAL_INIT_METHOD_SIGNATURE = "()V";
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;
ScriptNode[] scriptOrFnNodes;
private ObjToIntMap scriptOrFnIndexes;
private String mainMethodClass = DEFAULT_MAIN_METHOD_CLASS;
String mainClassName;
String mainClassSignature;
private double[] itsConstantList;
private int itsConstantListSize;
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy