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 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.
/* -*- 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();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy