org.mozilla.javascript.CodeGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhino-runtime Show documentation
Show all versions of rhino-runtime Show documentation
Rhino JavaScript runtime jar, excludes tools & JSR-223 Script Engine wrapper.
/* -*- 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;
import java.math.BigInteger;
import java.util.List;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Jump;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.ast.TemplateCharacters;
import org.mozilla.javascript.ast.VariableInitializer;
/** Generates bytecode for the Interpreter. */
class CodeGenerator extends Icode {
private static final int MIN_LABEL_TABLE_SIZE = 32;
private static final int MIN_FIXUP_TABLE_SIZE = 40;
private CompilerEnvirons compilerEnv;
private boolean itsInFunctionFlag;
private boolean itsInTryFlag;
private InterpreterData itsData;
private ScriptNode scriptOrFn;
private int iCodeTop;
private int stackDepth;
private int lineNumber;
private int doubleTableTop;
private ObjToIntMap strings = new ObjToIntMap(20);
private ObjToIntMap bigInts = new ObjToIntMap(20);
private int localTop;
private int[] labelTable;
private int labelTableTop;
// fixupTable[i] = (label_index << 32) | fixup_site
private long[] fixupTable;
private int fixupTableTop;
private ObjArray literalIds = new ObjArray();
private int exceptionTableTop;
// ECF_ or Expression Context Flags constants: for now only TAIL
private static final int ECF_TAIL = 1 << 0;
public InterpreterData compile(
CompilerEnvirons compilerEnv,
ScriptNode tree,
String encodedSource,
boolean returnFunction) {
this.compilerEnv = compilerEnv;
if (Token.printTrees) {
System.out.println("before transform:");
System.out.println(tree.toStringTree(tree));
}
new NodeTransformer().transform(tree, compilerEnv);
if (Token.printTrees) {
System.out.println("after transform:");
System.out.println(tree.toStringTree(tree));
}
if (returnFunction) {
scriptOrFn = tree.getFunctionNode(0);
} else {
scriptOrFn = tree;
}
itsData =
new InterpreterData(
compilerEnv.getLanguageVersion(),
scriptOrFn.getSourceName(),
encodedSource,
scriptOrFn.isInStrictMode());
itsData.topLevel = true;
if (returnFunction) {
generateFunctionICode();
} else {
generateICodeFromTree(scriptOrFn);
}
return itsData;
}
private void generateFunctionICode() {
itsInFunctionFlag = true;
FunctionNode theFunction = (FunctionNode) scriptOrFn;
itsData.itsFunctionType = theFunction.getFunctionType();
itsData.itsNeedsActivation = theFunction.requiresActivation();
if (theFunction.getFunctionName() != null) {
itsData.itsName = theFunction.getName();
}
if (theFunction.isGenerator()) {
addIcode(Icode_GENERATOR);
addUint16(theFunction.getBaseLineno() & 0xFFFF);
}
if (theFunction.isInStrictMode()) {
itsData.isStrict = true;
}
if (theFunction.isES6Generator()) {
itsData.isES6Generator = true;
}
itsData.declaredAsVar = (theFunction.getParent() instanceof VariableInitializer);
generateICodeFromTree(theFunction.getLastChild());
}
private void generateICodeFromTree(Node tree) {
generateNestedFunctions();
generateRegExpLiterals();
generateTemplateLiterals();
visitStatement(tree, 0);
fixLabelGotos();
// add RETURN_RESULT only to scripts as function always ends with RETURN
if (itsData.itsFunctionType == 0) {
addToken(Token.RETURN_RESULT);
}
if (itsData.itsICode.length != iCodeTop) {
// Make itsData.itsICode length exactly iCodeTop to save memory
// and catch bugs with jumps beyond icode as early as possible
byte[] tmp = new byte[iCodeTop];
System.arraycopy(itsData.itsICode, 0, tmp, 0, iCodeTop);
itsData.itsICode = tmp;
}
if (strings.size() == 0) {
itsData.itsStringTable = null;
} else {
itsData.itsStringTable = new String[strings.size()];
ObjToIntMap.Iterator iter = strings.newIterator();
for (iter.start(); !iter.done(); iter.next()) {
String str = (String) iter.getKey();
int index = iter.getValue();
if (itsData.itsStringTable[index] != null) Kit.codeBug();
itsData.itsStringTable[index] = str;
}
}
if (doubleTableTop == 0) {
itsData.itsDoubleTable = null;
} else if (itsData.itsDoubleTable.length != doubleTableTop) {
double[] tmp = new double[doubleTableTop];
System.arraycopy(itsData.itsDoubleTable, 0, tmp, 0, doubleTableTop);
itsData.itsDoubleTable = tmp;
}
if (bigInts.size() == 0) {
itsData.itsBigIntTable = null;
} else {
itsData.itsBigIntTable = new BigInteger[bigInts.size()];
ObjToIntMap.Iterator iter = bigInts.newIterator();
for (iter.start(); !iter.done(); iter.next()) {
BigInteger bigInt = (BigInteger) iter.getKey();
int index = iter.getValue();
if (itsData.itsBigIntTable[index] != null) Kit.codeBug();
itsData.itsBigIntTable[index] = bigInt;
}
}
if (exceptionTableTop != 0 && itsData.itsExceptionTable.length != exceptionTableTop) {
int[] tmp = new int[exceptionTableTop];
System.arraycopy(itsData.itsExceptionTable, 0, tmp, 0, exceptionTableTop);
itsData.itsExceptionTable = tmp;
}
itsData.itsMaxVars = scriptOrFn.getParamAndVarCount();
// itsMaxFrameArray: interpret method needs this amount for its
// stack and sDbl arrays
itsData.itsMaxFrameArray = itsData.itsMaxVars + itsData.itsMaxLocals + itsData.itsMaxStack;
itsData.argNames = scriptOrFn.getParamAndVarNames();
itsData.argIsConst = scriptOrFn.getParamAndVarConst();
itsData.argCount = scriptOrFn.getParamCount();
itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart();
itsData.encodedSourceEnd = scriptOrFn.getEncodedSourceEnd();
if (literalIds.size() != 0) {
itsData.literalIds = literalIds.toArray();
}
if (Token.printICode) Interpreter.dumpICode(itsData);
}
private void generateNestedFunctions() {
int functionCount = scriptOrFn.getFunctionCount();
if (functionCount == 0) return;
InterpreterData[] array = new InterpreterData[functionCount];
for (int i = 0; i != functionCount; i++) {
FunctionNode fn = scriptOrFn.getFunctionNode(i);
CodeGenerator gen = new CodeGenerator();
gen.compilerEnv = compilerEnv;
gen.scriptOrFn = fn;
gen.itsData = new InterpreterData(itsData);
gen.generateFunctionICode();
array[i] = gen.itsData;
final AstNode fnParent = fn.getParent();
if (!(fnParent instanceof AstRoot
|| fnParent instanceof Scope
|| fnParent instanceof Block)) {
gen.itsData.declaredAsFunctionExpression = true;
}
}
itsData.itsNestedFunctions = array;
}
private void generateRegExpLiterals() {
int N = scriptOrFn.getRegexpCount();
if (N == 0) return;
Context cx = Context.getContext();
RegExpProxy rep = ScriptRuntime.checkRegExpProxy(cx);
Object[] array = new Object[N];
for (int i = 0; i != N; i++) {
String string = scriptOrFn.getRegexpString(i);
String flags = scriptOrFn.getRegexpFlags(i);
array[i] = rep.compileRegExp(cx, string, flags);
}
itsData.itsRegExpLiterals = array;
}
private void generateTemplateLiterals() {
int N = scriptOrFn.getTemplateLiteralCount();
if (N == 0) return;
Object[] array = new Object[N];
for (int i = 0; i != N; i++) {
List strings = scriptOrFn.getTemplateLiteralStrings(i);
int j = 0;
String[] values = new String[strings.size() * 2];
for (TemplateCharacters s : strings) {
values[j++] = s.getValue();
values[j++] = s.getRawValue();
}
array[i] = values;
}
itsData.itsTemplateLiterals = array;
}
private void updateLineNumber(Node node) {
int lineno = node.getLineno();
if (lineno != lineNumber && lineno >= 0) {
if (itsData.firstLinePC < 0) {
itsData.firstLinePC = lineno;
}
lineNumber = lineno;
addIcode(Icode_LINE);
addUint16(lineno & 0xFFFF);
}
}
private static RuntimeException badTree(Node node) {
throw new RuntimeException(node.toString());
}
private void visitStatement(Node node, int initialStackDepth) {
int type = node.getType();
Node child = node.getFirstChild();
switch (type) {
case Token.FUNCTION:
{
int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
int fnType = scriptOrFn.getFunctionNode(fnIndex).getFunctionType();
// Only function expressions or function expression
// statements need closure code creating new function
// object on stack as function statements are initialized
// at script/function start.
// In addition, function expressions can not be present here
// at statement level, they must only be present as expressions.
if (fnType == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
addIndexOp(Icode_CLOSURE_STMT, fnIndex);
} else {
if (fnType != FunctionNode.FUNCTION_STATEMENT) {
throw Kit.codeBug();
}
}
// For function statements or function expression statements
// in scripts, we need to ensure that the result of the script
// is the function if it is the last statement in the script.
// For example, eval("function () {}") should return a
// function, not undefined.
if (!itsInFunctionFlag) {
addIndexOp(Icode_CLOSURE_EXPR, fnIndex);
stackChange(1);
addIcode(Icode_POP_RESULT);
stackChange(-1);
}
}
break;
case Token.LABEL:
case Token.LOOP:
case Token.BLOCK:
case Token.EMPTY:
case Token.WITH:
updateLineNumber(node);
// fall through
case Token.SCRIPT:
while (child != null) {
visitStatement(child, initialStackDepth);
child = child.getNext();
}
break;
case Token.ENTERWITH:
visitExpression(child, 0);
addToken(Token.ENTERWITH);
stackChange(-1);
break;
case Token.LEAVEWITH:
addToken(Token.LEAVEWITH);
break;
case Token.LOCAL_BLOCK:
{
int local = allocLocal();
node.putIntProp(Node.LOCAL_PROP, local);
updateLineNumber(node);
while (child != null) {
visitStatement(child, initialStackDepth);
child = child.getNext();
}
addIndexOp(Icode_LOCAL_CLEAR, local);
releaseLocal(local);
}
break;
case Token.DEBUGGER:
addIcode(Icode_DEBUGGER);
break;
case Token.SWITCH:
updateLineNumber(node);
// See comments in IRFactory.createSwitch() for description
// of SWITCH node
{
visitExpression(child, 0);
for (Jump caseNode = (Jump) child.getNext();
caseNode != null;
caseNode = (Jump) caseNode.getNext()) {
if (caseNode.getType() != Token.CASE) throw badTree(caseNode);
Node test = caseNode.getFirstChild();
addIcode(Icode_DUP);
stackChange(1);
visitExpression(test, 0);
addToken(Token.SHEQ);
stackChange(-1);
// If true, Icode_IFEQ_POP will jump and remove case
// value from stack
addGoto(caseNode.target, Icode_IFEQ_POP);
stackChange(-1);
}
addIcode(Icode_POP);
stackChange(-1);
}
break;
case Token.TARGET:
markTargetLabel(node);
break;
case Token.IFEQ:
case Token.IFNE:
{
Node target = ((Jump) node).target;
visitExpression(child, 0);
addGoto(target, type);
stackChange(-1);
}
break;
case Token.GOTO:
{
Node target = ((Jump) node).target;
addGoto(target, type);
}
break;
case Token.JSR:
{
Node target = ((Jump) node).target;
addGoto(target, Icode_GOSUB);
}
break;
case Token.FINALLY:
{
// Account for incomming GOTOSUB address
stackChange(1);
int finallyRegister = getLocalBlockRef(node);
addIndexOp(Icode_STARTSUB, finallyRegister);
stackChange(-1);
while (child != null) {
visitStatement(child, initialStackDepth);
child = child.getNext();
}
addIndexOp(Icode_RETSUB, finallyRegister);
}
break;
case Token.EXPR_VOID:
case Token.EXPR_RESULT:
updateLineNumber(node);
visitExpression(child, 0);
addIcode((type == Token.EXPR_VOID) ? Icode_POP : Icode_POP_RESULT);
stackChange(-1);
break;
case Token.TRY:
{
Jump tryNode = (Jump) node;
int exceptionObjectLocal = getLocalBlockRef(tryNode);
int scopeLocal = allocLocal();
addIndexOp(Icode_SCOPE_SAVE, scopeLocal);
int tryStart = iCodeTop;
boolean savedFlag = itsInTryFlag;
itsInTryFlag = true;
while (child != null) {
visitStatement(child, initialStackDepth);
child = child.getNext();
}
itsInTryFlag = savedFlag;
Node catchTarget = tryNode.target;
if (catchTarget != null) {
int catchStartPC = labelTable[getTargetLabel(catchTarget)];
addExceptionHandler(
tryStart,
catchStartPC,
catchStartPC,
false,
exceptionObjectLocal,
scopeLocal);
}
Node finallyTarget = tryNode.getFinally();
if (finallyTarget != null) {
int finallyStartPC = labelTable[getTargetLabel(finallyTarget)];
addExceptionHandler(
tryStart,
finallyStartPC,
finallyStartPC,
true,
exceptionObjectLocal,
scopeLocal);
}
addIndexOp(Icode_LOCAL_CLEAR, scopeLocal);
releaseLocal(scopeLocal);
}
break;
case Token.CATCH_SCOPE:
{
int localIndex = getLocalBlockRef(node);
int scopeIndex = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);
String name = child.getString();
child = child.getNext();
visitExpression(child, 0); // load expression object
addStringPrefix(name);
addIndexPrefix(localIndex);
addToken(Token.CATCH_SCOPE);
addUint8(scopeIndex != 0 ? 1 : 0);
stackChange(-1);
}
break;
case Token.THROW:
updateLineNumber(node);
visitExpression(child, 0);
addToken(Token.THROW);
addUint16(lineNumber & 0xFFFF);
stackChange(-1);
break;
case Token.RETHROW:
updateLineNumber(node);
addIndexOp(Token.RETHROW, getLocalBlockRef(node));
break;
case Token.RETURN:
updateLineNumber(node);
if (node.getIntProp(Node.GENERATOR_END_PROP, 0) != 0) {
if ((child == null)
|| (compilerEnv.getLanguageVersion() < Context.VERSION_ES6)) {
// End generator function with no result, or old language version
// in which generators never return a result.
addIcode(Icode_GENERATOR_END);
addUint16(lineNumber & 0xFFFF);
} else {
visitExpression(child, ECF_TAIL);
addIcode(Icode_GENERATOR_RETURN);
addUint16(lineNumber & 0xFFFF);
stackChange(-1);
}
} else {
if (child == null) {
addIcode(Icode_RETUNDEF);
} else {
visitExpression(child, ECF_TAIL);
addToken(Token.RETURN);
stackChange(-1);
}
}
break;
case Token.RETURN_RESULT:
updateLineNumber(node);
addToken(Token.RETURN_RESULT);
break;
case Token.ENUM_INIT_KEYS:
case Token.ENUM_INIT_VALUES:
case Token.ENUM_INIT_ARRAY:
case Token.ENUM_INIT_VALUES_IN_ORDER:
visitExpression(child, 0);
addIndexOp(type, getLocalBlockRef(node));
stackChange(-1);
break;
case Icode_GENERATOR:
break;
default:
throw badTree(node);
}
if (stackDepth != initialStackDepth) {
throw Kit.codeBug();
}
}
private void visitExpression(Node node, int contextFlags) {
int type = node.getType();
Node child = node.getFirstChild();
int savedStackDepth = stackDepth;
switch (type) {
case Token.FUNCTION:
{
int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
FunctionNode fn = scriptOrFn.getFunctionNode(fnIndex);
// See comments in visitStatement for Token.FUNCTION case
if (fn.getFunctionType() != FunctionNode.FUNCTION_EXPRESSION
&& fn.getFunctionType() != FunctionNode.ARROW_FUNCTION) {
throw Kit.codeBug();
}
addIndexOp(Icode_CLOSURE_EXPR, fnIndex);
stackChange(1);
}
break;
case Token.LOCAL_LOAD:
{
int localIndex = getLocalBlockRef(node);
addIndexOp(Token.LOCAL_LOAD, localIndex);
stackChange(1);
}
break;
case Token.COMMA:
{
Node lastChild = node.getLastChild();
while (child != lastChild) {
visitExpression(child, 0);
addIcode(Icode_POP);
stackChange(-1);
child = child.getNext();
}
// Preserve tail context flag if any
visitExpression(child, contextFlags & ECF_TAIL);
}
break;
case Token.USE_STACK:
// Indicates that stack was modified externally,
// like placed catch object
stackChange(1);
break;
case Token.REF_CALL:
case Token.CALL:
case Token.NEW:
{
if (type == Token.NEW) {
visitExpression(child, 0);
} else {
generateCallFunAndThis(child);
}
int argCount = 0;
while ((child = child.getNext()) != null) {
visitExpression(child, 0);
++argCount;
}
int callType = node.getIntProp(Node.SPECIALCALL_PROP, Node.NON_SPECIALCALL);
if (type != Token.REF_CALL && callType != Node.NON_SPECIALCALL) {
// embed line number and source filename
addIndexOp(Icode_CALLSPECIAL, argCount);
addUint8(callType);
addUint8(type == Token.NEW ? 1 : 0);
addUint16(lineNumber & 0xFFFF);
} else {
// Only use the tail call optimization if we're not in a try
// or we're not generating debug info (since the
// optimization will confuse the debugger)
if (type == Token.CALL
&& (contextFlags & ECF_TAIL) != 0
&& !compilerEnv.isGenerateDebugInfo()
&& !itsInTryFlag) {
type = Icode_TAIL_CALL;
}
addIndexOp(type, argCount);
}
// adjust stack
if (type == Token.NEW) {
// new: f, args -> result
stackChange(-argCount);
} else {
// call: f, thisObj, args -> result
// ref_call: f, thisObj, args -> ref
stackChange(-1 - argCount);
}
if (argCount > itsData.itsMaxCalleeArgs) {
itsData.itsMaxCalleeArgs = argCount;
}
}
break;
case Token.AND:
case Token.OR:
{
visitExpression(child, 0);
addIcode(Icode_DUP);
stackChange(1);
int afterSecondJumpStart = iCodeTop;
int jump = (type == Token.AND) ? Token.IFNE : Token.IFEQ;
addGotoOp(jump);
stackChange(-1);
addIcode(Icode_POP);
stackChange(-1);
child = child.getNext();
// Preserve tail context flag if any
visitExpression(child, contextFlags & ECF_TAIL);
resolveForwardGoto(afterSecondJumpStart);
}
break;
case Token.HOOK:
{
Node ifThen = child.getNext();
Node ifElse = ifThen.getNext();
visitExpression(child, 0);
int elseJumpStart = iCodeTop;
addGotoOp(Token.IFNE);
stackChange(-1);
// Preserve tail context flag if any
visitExpression(ifThen, contextFlags & ECF_TAIL);
int afterElseJumpStart = iCodeTop;
addGotoOp(Token.GOTO);
resolveForwardGoto(elseJumpStart);
stackDepth = savedStackDepth;
// Preserve tail context flag if any
visitExpression(ifElse, contextFlags & ECF_TAIL);
resolveForwardGoto(afterElseJumpStart);
}
break;
case Token.GETPROP:
case Token.GETPROPNOWARN:
visitExpression(child, 0);
child = child.getNext();
addStringOp(type, child.getString());
break;
case Token.DELPROP:
boolean isName = child.getType() == Token.BINDNAME;
visitExpression(child, 0);
child = child.getNext();
visitExpression(child, 0);
if (isName) {
// special handling for delete name
addIcode(Icode_DELNAME);
} else {
addToken(Token.DELPROP);
}
stackChange(-1);
break;
case Token.GETELEM:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.ADD:
case Token.SUB:
case Token.MOD:
case Token.DIV:
case Token.MUL:
case Token.EXP:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
visitExpression(child, 0);
child = child.getNext();
visitExpression(child, 0);
addToken(type);
stackChange(-1);
break;
case Token.POS:
case Token.NEG:
case Token.NOT:
case Token.BITNOT:
case Token.TYPEOF:
case Token.VOID:
visitExpression(child, 0);
if (type == Token.VOID) {
addIcode(Icode_POP);
addIcode(Icode_UNDEF);
} else {
addToken(type);
}
break;
case Token.GET_REF:
case Token.DEL_REF:
visitExpression(child, 0);
addToken(type);
break;
case Token.SETPROP:
case Token.SETPROP_OP:
{
visitExpression(child, 0);
child = child.getNext();
String property = child.getString();
child = child.getNext();
if (type == Token.SETPROP_OP) {
addIcode(Icode_DUP);
stackChange(1);
addStringOp(Token.GETPROP, property);
// Compensate for the following USE_STACK
stackChange(-1);
}
visitExpression(child, 0);
addStringOp(Token.SETPROP, property);
stackChange(-1);
}
break;
case Token.SETELEM:
case Token.SETELEM_OP:
visitExpression(child, 0);
child = child.getNext();
visitExpression(child, 0);
child = child.getNext();
if (type == Token.SETELEM_OP) {
addIcode(Icode_DUP2);
stackChange(2);
addToken(Token.GETELEM);
stackChange(-1);
// Compensate for the following USE_STACK
stackChange(-1);
}
visitExpression(child, 0);
addToken(Token.SETELEM);
stackChange(-2);
break;
case Token.SET_REF:
case Token.SET_REF_OP:
visitExpression(child, 0);
child = child.getNext();
if (type == Token.SET_REF_OP) {
addIcode(Icode_DUP);
stackChange(1);
addToken(Token.GET_REF);
// Compensate for the following USE_STACK
stackChange(-1);
}
visitExpression(child, 0);
addToken(Token.SET_REF);
stackChange(-1);
break;
case Token.STRICT_SETNAME:
case Token.SETNAME:
{
String name = child.getString();
visitExpression(child, 0);
child = child.getNext();
visitExpression(child, 0);
addStringOp(type, name);
stackChange(-1);
}
break;
case Token.SETCONST:
{
String name = child.getString();
visitExpression(child, 0);
child = child.getNext();
visitExpression(child, 0);
addStringOp(Icode_SETCONST, name);
stackChange(-1);
}
break;
case Token.TYPEOFNAME:
{
int index = -1;
// use typeofname if an activation frame exists
// since the vars all exist there instead of in jregs
if (itsInFunctionFlag && !itsData.itsNeedsActivation)
index = scriptOrFn.getIndexForNameNode(node);
if (index == -1) {
addStringOp(Icode_TYPEOFNAME, node.getString());
stackChange(1);
} else {
addVarOp(Token.GETVAR, index);
stackChange(1);
addToken(Token.TYPEOF);
}
}
break;
case Token.BINDNAME:
case Token.NAME:
case Token.STRING:
addStringOp(type, node.getString());
stackChange(1);
break;
case Token.INC:
case Token.DEC:
visitIncDec(node, child);
break;
case Token.NUMBER:
{
double num = node.getDouble();
int inum = (int) num;
if (inum == num) {
if (inum == 0) {
addIcode(Icode_ZERO);
// Check for negative zero
if (1.0 / num < 0.0) {
addToken(Token.NEG);
}
} else if (inum == 1) {
addIcode(Icode_ONE);
} else if ((short) inum == inum) {
addIcode(Icode_SHORTNUMBER);
// write short as uin16 bit pattern
addUint16(inum & 0xFFFF);
} else {
addIcode(Icode_INTNUMBER);
addInt(inum);
}
} else {
int index = getDoubleIndex(num);
addIndexOp(Token.NUMBER, index);
}
stackChange(1);
}
break;
case Token.GETVAR:
{
if (itsData.itsNeedsActivation) Kit.codeBug();
int index = scriptOrFn.getIndexForNameNode(node);
addVarOp(Token.GETVAR, index);
stackChange(1);
}
break;
case Token.SETVAR:
{
if (itsData.itsNeedsActivation) Kit.codeBug();
int index = scriptOrFn.getIndexForNameNode(child);
child = child.getNext();
visitExpression(child, 0);
addVarOp(Token.SETVAR, index);
}
break;
case Token.SETCONSTVAR:
{
if (itsData.itsNeedsActivation) Kit.codeBug();
int index = scriptOrFn.getIndexForNameNode(child);
child = child.getNext();
visitExpression(child, 0);
addVarOp(Token.SETCONSTVAR, index);
}
break;
case Token.NULL:
case Token.THIS:
case Token.THISFN:
case Token.FALSE:
case Token.TRUE:
addToken(type);
stackChange(1);
break;
case Token.ENUM_NEXT:
case Token.ENUM_ID:
addIndexOp(type, getLocalBlockRef(node));
stackChange(1);
break;
case Token.BIGINT:
addBigInt(node.getBigInt());
stackChange(1);
break;
case Token.REGEXP:
{
int index = node.getExistingIntProp(Node.REGEXP_PROP);
addIndexOp(Token.REGEXP, index);
stackChange(1);
}
break;
case Token.ARRAYLIT:
case Token.OBJECTLIT:
visitLiteral(node, child);
break;
case Token.ARRAYCOMP:
visitArrayComprehension(node, child, child.getNext());
break;
case Token.REF_SPECIAL:
visitExpression(child, 0);
addStringOp(type, (String) node.getProp(Node.NAME_PROP));
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
int childCount = 0;
do {
visitExpression(child, 0);
++childCount;
child = child.getNext();
} while (child != null);
addIndexOp(type, memberTypeFlags);
stackChange(1 - childCount);
}
break;
case Token.DOTQUERY:
{
int queryPC;
updateLineNumber(node);
visitExpression(child, 0);
addIcode(Icode_ENTERDQ);
stackChange(-1);
queryPC = iCodeTop;
visitExpression(child.getNext(), 0);
addBackwardGoto(Icode_LEAVEDQ, queryPC);
}
break;
case Token.DEFAULTNAMESPACE:
case Token.ESCXMLATTR:
case Token.ESCXMLTEXT:
visitExpression(child, 0);
addToken(type);
break;
case Token.YIELD:
case Token.YIELD_STAR:
if (child != null) {
visitExpression(child, 0);
} else {
addIcode(Icode_UNDEF);
stackChange(1);
}
if (type == Token.YIELD) {
addToken(Token.YIELD);
} else {
addIcode(Icode_YIELD_STAR);
}
addUint16(node.getLineno() & 0xFFFF);
break;
case Token.WITHEXPR:
{
Node enterWith = node.getFirstChild();
Node with = enterWith.getNext();
visitExpression(enterWith.getFirstChild(), 0);
addToken(Token.ENTERWITH);
stackChange(-1);
visitExpression(with.getFirstChild(), 0);
addToken(Token.LEAVEWITH);
break;
}
case Token.TEMPLATE_LITERAL:
visitTemplateLiteral(node);
break;
default:
throw badTree(node);
}
if (savedStackDepth + 1 != stackDepth) {
Kit.codeBug();
}
}
private void generateCallFunAndThis(Node left) {
// Generate code to place on stack function and thisObj
int type = left.getType();
switch (type) {
case Token.NAME:
{
String name = left.getString();
// stack: ... -> ... function thisObj
addStringOp(Icode_NAME_AND_THIS, name);
stackChange(2);
break;
}
case Token.GETPROP:
case Token.GETELEM:
{
Node target = left.getFirstChild();
visitExpression(target, 0);
Node id = target.getNext();
if (type == Token.GETPROP) {
String property = id.getString();
// stack: ... target -> ... function thisObj
addStringOp(Icode_PROP_AND_THIS, property);
stackChange(1);
} else {
visitExpression(id, 0);
// stack: ... target id -> ... function thisObj
addIcode(Icode_ELEM_AND_THIS);
}
break;
}
default:
// Including Token.GETVAR
visitExpression(left, 0);
// stack: ... value -> ... function thisObj
addIcode(Icode_VALUE_AND_THIS);
stackChange(1);
break;
}
}
private void visitIncDec(Node node, Node child) {
int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP);
int childType = child.getType();
switch (childType) {
case Token.GETVAR:
{
if (itsData.itsNeedsActivation) Kit.codeBug();
int i = scriptOrFn.getIndexForNameNode(child);
addVarOp(Icode_VAR_INC_DEC, i);
addUint8(incrDecrMask);
stackChange(1);
break;
}
case Token.NAME:
{
String name = child.getString();
addStringOp(Icode_NAME_INC_DEC, name);
addUint8(incrDecrMask);
stackChange(1);
break;
}
case Token.GETPROP:
{
Node object = child.getFirstChild();
visitExpression(object, 0);
String property = object.getNext().getString();
addStringOp(Icode_PROP_INC_DEC, property);
addUint8(incrDecrMask);
break;
}
case Token.GETELEM:
{
Node object = child.getFirstChild();
visitExpression(object, 0);
Node index = object.getNext();
visitExpression(index, 0);
addIcode(Icode_ELEM_INC_DEC);
addUint8(incrDecrMask);
stackChange(-1);
break;
}
case Token.GET_REF:
{
Node ref = child.getFirstChild();
visitExpression(ref, 0);
addIcode(Icode_REF_INC_DEC);
addUint8(incrDecrMask);
break;
}
default:
{
throw badTree(node);
}
}
}
private void visitLiteral(Node node, Node child) {
int type = node.getType();
int count;
Object[] propertyIds = null;
if (type == Token.ARRAYLIT) {
count = 0;
for (Node n = child; n != null; n = n.getNext()) {
++count;
}
} else if (type == Token.OBJECTLIT) {
propertyIds = (Object[]) node.getProp(Node.OBJECT_IDS_PROP);
count = propertyIds == null ? 0 : propertyIds.length;
} else {
throw badTree(node);
}
addIndexOp(Icode_LITERAL_NEW, count);
stackChange(2);
while (child != null) {
int childType = child.getType();
if (childType == Token.GET) {
visitExpression(child.getFirstChild(), 0);
addIcode(Icode_LITERAL_GETTER);
} else if (childType == Token.SET) {
visitExpression(child.getFirstChild(), 0);
addIcode(Icode_LITERAL_SETTER);
} else if (childType == Token.METHOD) {
visitExpression(child.getFirstChild(), 0);
addIcode(Icode_LITERAL_SET);
} else {
visitExpression(child, 0);
addIcode(Icode_LITERAL_SET);
}
stackChange(-1);
child = child.getNext();
}
if (type == Token.ARRAYLIT) {
int[] skipIndexes = (int[]) node.getProp(Node.SKIP_INDEXES_PROP);
if (skipIndexes == null) {
addToken(Token.ARRAYLIT);
} else {
int index = literalIds.size();
literalIds.add(skipIndexes);
addIndexOp(Icode_SPARE_ARRAYLIT, index);
}
} else {
int index = literalIds.size();
literalIds.add(propertyIds);
addIndexOp(Token.OBJECTLIT, index);
}
stackChange(-1);
}
private void visitTemplateLiteral(Node node) {
int index = node.getExistingIntProp(Node.TEMPLATE_LITERAL_PROP);
addIndexOp(Icode_TEMPLATE_LITERAL_CALLSITE, index);
stackChange(1);
}
private void visitArrayComprehension(Node node, Node initStmt, Node expr) {
// A bit of a hack: array comprehensions are implemented using
// statement nodes for the iteration, yet they appear in an
// expression context. So we pass the current stack depth to
// visitStatement so it can check that the depth is not altered
// by statements.
visitStatement(initStmt, stackDepth);
visitExpression(expr, 0);
}
private static int getLocalBlockRef(Node node) {
Node localBlock = (Node) node.getProp(Node.LOCAL_BLOCK_PROP);
return localBlock.getExistingIntProp(Node.LOCAL_PROP);
}
private int getTargetLabel(Node target) {
int label = target.labelId();
if (label != -1) {
return label;
}
label = labelTableTop;
if (labelTable == null || label == labelTable.length) {
if (labelTable == null) {
labelTable = new int[MIN_LABEL_TABLE_SIZE];
} else {
int[] tmp = new int[labelTable.length * 2];
System.arraycopy(labelTable, 0, tmp, 0, label);
labelTable = tmp;
}
}
labelTableTop = label + 1;
labelTable[label] = -1;
target.labelId(label);
return label;
}
private void markTargetLabel(Node target) {
int label = getTargetLabel(target);
if (labelTable[label] != -1) {
// Can mark label only once
Kit.codeBug();
}
labelTable[label] = iCodeTop;
}
private void addGoto(Node target, int gotoOp) {
int label = getTargetLabel(target);
if (!(label < labelTableTop)) Kit.codeBug();
int targetPC = labelTable[label];
if (targetPC != -1) {
addBackwardGoto(gotoOp, targetPC);
} else {
int gotoPC = iCodeTop;
addGotoOp(gotoOp);
int top = fixupTableTop;
if (fixupTable == null || top == fixupTable.length) {
if (fixupTable == null) {
fixupTable = new long[MIN_FIXUP_TABLE_SIZE];
} else {
long[] tmp = new long[fixupTable.length * 2];
System.arraycopy(fixupTable, 0, tmp, 0, top);
fixupTable = tmp;
}
}
fixupTableTop = top + 1;
fixupTable[top] = ((long) label << 32) | gotoPC;
}
}
private void fixLabelGotos() {
for (int i = 0; i < fixupTableTop; i++) {
long fixup = fixupTable[i];
int label = (int) (fixup >> 32);
int jumpSource = (int) fixup;
int pc = labelTable[label];
if (pc == -1) {
// Unlocated label
throw Kit.codeBug();
}
resolveGoto(jumpSource, pc);
}
fixupTableTop = 0;
}
private void addBackwardGoto(int gotoOp, int jumpPC) {
int fromPC = iCodeTop;
// Ensure that this is a jump backward
if (fromPC <= jumpPC) throw Kit.codeBug();
addGotoOp(gotoOp);
resolveGoto(fromPC, jumpPC);
}
private void resolveForwardGoto(int fromPC) {
// Ensure that forward jump skips at least self bytecode
if (iCodeTop < fromPC + 3) throw Kit.codeBug();
resolveGoto(fromPC, iCodeTop);
}
private void resolveGoto(int fromPC, int jumpPC) {
int offset = jumpPC - fromPC;
// Ensure that jumps do not overlap
if (0 <= offset && offset <= 2) throw Kit.codeBug();
int offsetSite = fromPC + 1;
if (offset != (short) offset) {
if (itsData.longJumps == null) {
itsData.longJumps = new UintMap();
}
itsData.longJumps.put(offsetSite, jumpPC);
offset = 0;
}
byte[] array = itsData.itsICode;
array[offsetSite] = (byte) (offset >> 8);
array[offsetSite + 1] = (byte) offset;
}
private void addToken(int token) {
if (!Icode.validTokenCode(token)) throw Kit.codeBug();
addUint8(token);
}
private void addIcode(int icode) {
if (!Icode.validIcode(icode)) throw Kit.codeBug();
// Write negative icode as uint8 bits
addUint8(icode & 0xFF);
}
private void addUint8(int value) {
if ((value & ~0xFF) != 0) throw Kit.codeBug();
byte[] array = itsData.itsICode;
int top = iCodeTop;
if (top == array.length) {
array = increaseICodeCapacity(1);
}
array[top] = (byte) value;
iCodeTop = top + 1;
}
private void addUint16(int value) {
if ((value & ~0xFFFF) != 0) throw Kit.codeBug();
byte[] array = itsData.itsICode;
int top = iCodeTop;
if (top + 2 > array.length) {
array = increaseICodeCapacity(2);
}
array[top] = (byte) (value >>> 8);
array[top + 1] = (byte) value;
iCodeTop = top + 2;
}
private void addInt(int i) {
byte[] array = itsData.itsICode;
int top = iCodeTop;
if (top + 4 > array.length) {
array = increaseICodeCapacity(4);
}
array[top] = (byte) (i >>> 24);
array[top + 1] = (byte) (i >>> 16);
array[top + 2] = (byte) (i >>> 8);
array[top + 3] = (byte) i;
iCodeTop = top + 4;
}
private int getDoubleIndex(double num) {
int index = doubleTableTop;
if (index == 0) {
itsData.itsDoubleTable = new double[64];
} else if (itsData.itsDoubleTable.length == index) {
double[] na = new double[index * 2];
System.arraycopy(itsData.itsDoubleTable, 0, na, 0, index);
itsData.itsDoubleTable = na;
}
itsData.itsDoubleTable[index] = num;
doubleTableTop = index + 1;
return index;
}
private void addGotoOp(int gotoOp) {
byte[] array = itsData.itsICode;
int top = iCodeTop;
if (top + 3 > array.length) {
array = increaseICodeCapacity(3);
}
array[top] = (byte) gotoOp;
// Offset would written later
iCodeTop = top + 1 + 2;
}
private void addVarOp(int op, int varIndex) {
switch (op) {
case Token.SETCONSTVAR:
if (varIndex < 128) {
addIcode(Icode_SETCONSTVAR1);
addUint8(varIndex);
return;
}
addIndexOp(Icode_SETCONSTVAR, varIndex);
return;
case Token.GETVAR:
case Token.SETVAR:
if (varIndex < 128) {
addIcode(op == Token.GETVAR ? Icode_GETVAR1 : Icode_SETVAR1);
addUint8(varIndex);
return;
}
// fallthrough
case Icode_VAR_INC_DEC:
addIndexOp(op, varIndex);
return;
}
throw Kit.codeBug();
}
private void addStringOp(int op, String str) {
addStringPrefix(str);
if (Icode.validIcode(op)) {
addIcode(op);
} else {
addToken(op);
}
}
private void addIndexOp(int op, int index) {
addIndexPrefix(index);
if (Icode.validIcode(op)) {
addIcode(op);
} else {
addToken(op);
}
}
private void addStringPrefix(String str) {
int index = strings.get(str, -1);
if (index == -1) {
index = strings.size();
strings.put(str, index);
}
if (index < 4) {
addIcode(Icode_REG_STR_C0 - index);
} else if (index <= 0xFF) {
addIcode(Icode_REG_STR1);
addUint8(index);
} else if (index <= 0xFFFF) {
addIcode(Icode_REG_STR2);
addUint16(index);
} else {
addIcode(Icode_REG_STR4);
addInt(index);
}
}
private void addBigInt(BigInteger n) {
int index = bigInts.get(n, -1);
if (index == -1) {
index = bigInts.size();
bigInts.put(n, index);
}
if (index < 4) {
addIcode(Icode_REG_BIGINT_C0 - index);
} else if (index <= 0xFF) {
addIcode(Icode_REG_BIGINT1);
addUint8(index);
} else if (index <= 0xFFFF) {
addIcode(Icode_REG_BIGINT2);
addUint16(index);
} else {
addIcode(Icode_REG_BIGINT4);
addInt(index);
}
addToken(Token.BIGINT);
}
private void addIndexPrefix(int index) {
if (index < 0) Kit.codeBug();
if (index < 6) {
addIcode(Icode_REG_IND_C0 - index);
} else if (index <= 0xFF) {
addIcode(Icode_REG_IND1);
addUint8(index);
} else if (index <= 0xFFFF) {
addIcode(Icode_REG_IND2);
addUint16(index);
} else {
addIcode(Icode_REG_IND4);
addInt(index);
}
}
private void addExceptionHandler(
int icodeStart,
int icodeEnd,
int handlerStart,
boolean isFinally,
int exceptionObjectLocal,
int scopeLocal) {
int top = exceptionTableTop;
int[] table = itsData.itsExceptionTable;
if (table == null) {
if (top != 0) Kit.codeBug();
table = new int[Interpreter.EXCEPTION_SLOT_SIZE * 2];
itsData.itsExceptionTable = table;
} else if (table.length == top) {
table = new int[table.length * 2];
System.arraycopy(itsData.itsExceptionTable, 0, table, 0, top);
itsData.itsExceptionTable = table;
}
table[top + Interpreter.EXCEPTION_TRY_START_SLOT] = icodeStart;
table[top + Interpreter.EXCEPTION_TRY_END_SLOT] = icodeEnd;
table[top + Interpreter.EXCEPTION_HANDLER_SLOT] = handlerStart;
table[top + Interpreter.EXCEPTION_TYPE_SLOT] = isFinally ? 1 : 0;
table[top + Interpreter.EXCEPTION_LOCAL_SLOT] = exceptionObjectLocal;
table[top + Interpreter.EXCEPTION_SCOPE_SLOT] = scopeLocal;
exceptionTableTop = top + Interpreter.EXCEPTION_SLOT_SIZE;
}
private byte[] increaseICodeCapacity(int extraSize) {
int capacity = itsData.itsICode.length;
int top = iCodeTop;
if (top + extraSize <= capacity) throw Kit.codeBug();
capacity *= 2;
if (top + extraSize > capacity) {
capacity = top + extraSize;
}
byte[] array = new byte[capacity];
System.arraycopy(itsData.itsICode, 0, array, 0, top);
itsData.itsICode = array;
return array;
}
private void stackChange(int change) {
if (change <= 0) {
stackDepth += change;
} else {
int newDepth = stackDepth + change;
if (newDepth > itsData.itsMaxStack) {
itsData.itsMaxStack = newDepth;
}
stackDepth = newDepth;
}
}
private int allocLocal() {
int localSlot = localTop;
++localTop;
if (localTop > itsData.itsMaxLocals) {
itsData.itsMaxLocals = localTop;
}
return localSlot;
}
private void releaseLocal(int localSlot) {
--localTop;
if (localSlot != localTop) Kit.codeBug();
}
}