in rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java [1016:1775]
private void generateExpression(Node node, Node parent) {
int type = node.getType();
Node child = node.getFirstChild();
switch (type) {
case Token.USE_STACK:
break;
case Token.FUNCTION:
if (fnCurrent != null || parent.getType() != Token.SCRIPT) {
int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex);
int t = ofn.fnode.getFunctionType();
if (t != FunctionNode.FUNCTION_EXPRESSION && t != FunctionNode.ARROW_FUNCTION) {
throw Codegen.badTree();
}
visitFunction(ofn, t);
}
break;
case Token.NAME:
{
cfw.addALoad(variableObjectLocal);
cfw.addALoad(contextLocal);
addDynamicInvoke("NAME:GET:" + node.getString(), Signatures.NAME_GET);
}
break;
case Token.CALL:
case Token.NEW:
{
int specialType = node.getIntProp(Node.SPECIALCALL_PROP, Node.NON_SPECIALCALL);
if (specialType == Node.NON_SPECIALCALL) {
OptFunctionNode target;
target = (OptFunctionNode) node.getProp(Node.DIRECTCALL_PROP);
if (target != null) {
visitOptimizedCall(node, target, type, child);
} else if (type == Token.CALL) {
visitStandardCall(node, child);
} else {
visitStandardNew(node, child);
}
} else {
visitSpecialCall(node, type, specialType, child);
}
}
break;
case Token.REF_CALL:
generateFunctionAndThisObj(child, node);
pushThisFromLastScriptable();
// stack: ... functionObj thisObj
child = child.getNext();
generateCallArgArray(node, child, false);
cfw.addALoad(contextLocal);
addScriptRuntimeInvoke(
"callRef",
"(Lorg/mozilla/javascript/Callable;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ "[Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ ")Lorg/mozilla/javascript/Ref;");
break;
case Token.NUMBER:
{
double num = node.getDouble();
if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
cfw.addPush(num);
} else {
codegen.pushNumberAsObject(cfw, num);
}
}
break;
case Token.BIGINT:
{
byte[] bytes = node.getBigInt().toByteArray();
cfw.add(ByteCode.NEW, "java/math/BigInteger");
cfw.add(ByteCode.DUP);
cfw.addPush(bytes.length);
cfw.add(ByteCode.NEWARRAY, ByteCode.T_BYTE);
for (int i = 0; i < bytes.length; i++) {
cfw.add(ByteCode.DUP);
cfw.addPush(i);
cfw.add(ByteCode.BIPUSH, bytes[i]);
cfw.add(ByteCode.BASTORE);
}
cfw.addInvoke(
ByteCode.INVOKESPECIAL, "java/math/BigInteger", "<init>", "([B)V");
}
break;
case Token.STRING:
cfw.addPush(node.getString());
break;
case Token.THIS:
cfw.addALoad(thisObjLocal);
break;
case Token.SUPER:
{
// See 9.1.1.3.5 GetSuperBase
// Read home object from activation, which we know we'll always have because we
// set "requiresActivation" to any function that mentions "super" in IRFactory
cfw.addALoad(variableObjectLocal);
cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/NativeCall");
cfw.addInvoke(
ByteCode.INVOKEVIRTUAL,
"org/mozilla/javascript/NativeCall",
"getHomeObject",
"()Lorg/mozilla/javascript/Scriptable;");
// If null, then put undefined
int after = cfw.acquireLabel();
int putPrototype = cfw.acquireLabel();
cfw.add(ByteCode.DUP);
cfw.add(ByteCode.IFNONNULL, putPrototype);
cfw.add(ByteCode.POP);
cfw.add(
ByteCode.GETSTATIC,
"org/mozilla/javascript/Undefined",
"instance",
"Ljava/lang/Object;");
cfw.add(ByteCode.GOTO, after);
// Otherwise put the prototype
cfw.markLabel(putPrototype);
cfw.addInvoke(
ByteCode.INVOKEINTERFACE,
"org/mozilla/javascript/Scriptable",
"getPrototype",
"()Lorg/mozilla/javascript/Scriptable;");
cfw.markLabel(after);
break;
}
case Token.THISFN:
cfw.add(ByteCode.ALOAD_0);
break;
case Token.NULL:
cfw.add(ByteCode.ACONST_NULL);
break;
case Token.TRUE:
cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
break;
case Token.FALSE:
cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;");
break;
case Token.REGEXP:
{
// Create a new wrapper around precompiled regexp
cfw.addALoad(contextLocal);
cfw.addALoad(variableObjectLocal);
int i = node.getExistingIntProp(Node.REGEXP_PROP);
cfw.add(
ByteCode.GETSTATIC,
codegen.mainClassName,
codegen.getCompiledRegexpName(scriptOrFn, i),
"Ljava/lang/Object;");
cfw.addInvoke(
ByteCode.INVOKESTATIC,
"org/mozilla/javascript/ScriptRuntime",
"wrapRegExp",
"(Lorg/mozilla/javascript/Context;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ "Ljava/lang/Object;"
+ ")Lorg/mozilla/javascript/Scriptable;");
}
break;
case Token.COMMA:
{
Node next = child.getNext();
while (next != null) {
generateExpression(child, node);
cfw.add(ByteCode.POP);
child = next;
next = next.getNext();
}
generateExpression(child, node);
break;
}
case Token.ENUM_NEXT:
case Token.ENUM_ID:
{
int local = getLocalBlockRegister(node);
cfw.addALoad(local);
cfw.addALoad(contextLocal);
if (type == Token.ENUM_NEXT) {
addScriptRuntimeInvoke(
"enumNext",
"(Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ ")Ljava/lang/Boolean;");
} else {
addScriptRuntimeInvoke(
"enumId",
"(Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ ")Ljava/lang/Object;");
}
break;
}
case Token.ARRAYLIT:
visitArrayLiteral(node, child, false);
break;
case Token.OBJECTLIT:
visitObjectLiteral(node, child, false);
break;
case Token.NOT:
{
int trueTarget = cfw.acquireLabel();
int falseTarget = cfw.acquireLabel();
int beyond = cfw.acquireLabel();
generateIfJump(child, node, trueTarget, falseTarget);
cfw.markLabel(trueTarget);
cfw.add(
ByteCode.GETSTATIC,
"java/lang/Boolean",
"FALSE",
"Ljava/lang/Boolean;");
cfw.add(ByteCode.GOTO, beyond);
cfw.markLabel(falseTarget);
cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
cfw.markLabel(beyond);
cfw.adjustStackTop(-1);
break;
}
case Token.BITNOT:
visitBitNot(node, child);
break;
case Token.VOID:
generateExpression(child, node);
cfw.add(ByteCode.POP);
Codegen.pushUndefined(cfw);
break;
case Token.TYPEOF:
generateExpression(child, node);
addScriptRuntimeInvoke("typeof", "(Ljava/lang/Object;" + ")Ljava/lang/String;");
break;
case Token.TYPEOFNAME:
visitTypeofname(node);
break;
case Token.INC:
case Token.DEC:
visitIncDec(node);
break;
case Token.OR:
case Token.AND:
{
generateExpression(child, node);
cfw.add(ByteCode.DUP);
addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN);
int falseTarget = cfw.acquireLabel();
if (type == Token.AND) cfw.add(ByteCode.IFEQ, falseTarget);
else cfw.add(ByteCode.IFNE, falseTarget);
cfw.add(ByteCode.POP);
generateExpression(child.getNext(), node);
cfw.markLabel(falseTarget);
}
break;
case Token.HOOK:
{
Node ifThen = child.getNext();
Node ifElse = ifThen.getNext();
generateExpression(child, node);
addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN);
int elseTarget = cfw.acquireLabel();
cfw.add(ByteCode.IFEQ, elseTarget);
int stack = cfw.getStackTop();
generateExpression(ifThen, node);
int afterHook = cfw.acquireLabel();
cfw.add(ByteCode.GOTO, afterHook);
cfw.markLabel(elseTarget, stack);
generateExpression(ifElse, node);
cfw.markLabel(afterHook);
}
break;
case Token.ADD:
{
generateExpression(child, node);
generateExpression(child.getNext(), node);
switch (node.getIntProp(Node.ISNUMBER_PROP, -1)) {
case Node.BOTH:
cfw.add(ByteCode.DADD);
break;
case Node.LEFT:
cfw.addALoad(contextLocal);
addOptRuntimeInvoke(
"add",
"(DLjava/lang/Object;Lorg/mozilla/javascript/Context;)Ljava/lang/Object;");
break;
case Node.RIGHT:
cfw.addALoad(contextLocal);
addOptRuntimeInvoke(
"add",
"(Ljava/lang/Object;DLorg/mozilla/javascript/Context;)Ljava/lang/Object;");
break;
default:
cfw.addALoad(contextLocal);
addDynamicInvoke("MATH:ADD", Signatures.MATH_ADD);
}
}
break;
case Token.SUB:
case Token.MUL:
case Token.DIV:
case Token.MOD:
visitArithmetic(node, type, child, parent);
break;
case Token.EXP:
visitExponentiation(node, child, parent);
break;
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.LSH:
case Token.RSH:
case Token.URSH:
visitBitOp(node, type, child);
break;
case Token.POS:
{
int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
generateExpression(child, node);
if (childNumberFlag == -1) {
addObjectToDouble();
addDoubleWrap();
}
break;
}
case Token.NEG:
{
int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
generateExpression(child, node);
if (childNumberFlag == -1) {
addObjectToNumeric();
addScriptRuntimeInvoke(
"negate", "(Ljava/lang/Number;" + ")Ljava/lang/Number;");
} else {
cfw.add(ByteCode.DNEG);
}
break;
}
case Token.TO_DOUBLE:
// cnvt to double (not Double)
generateExpression(child, node);
addObjectToDouble();
break;
case Token.TO_OBJECT:
{
// convert from double
int prop = -1;
if (child.getType() == Token.NUMBER) {
prop = child.getIntProp(Node.ISNUMBER_PROP, -1);
}
if (prop != -1) {
child.removeProp(Node.ISNUMBER_PROP);
generateExpression(child, node);
child.putIntProp(Node.ISNUMBER_PROP, prop);
} else {
generateExpression(child, node);
addDoubleWrap();
}
break;
}
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
{
int trueGOTO = cfw.acquireLabel();
int falseGOTO = cfw.acquireLabel();
visitIfJumpRelOp(node, child, trueGOTO, falseGOTO);
addJumpedBooleanWrap(trueGOTO, falseGOTO);
break;
}
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
{
int trueGOTO = cfw.acquireLabel();
int falseGOTO = cfw.acquireLabel();
visitIfJumpEqOp(node, child, trueGOTO, falseGOTO);
addJumpedBooleanWrap(trueGOTO, falseGOTO);
break;
}
case Token.GETPROP:
case Token.GETPROPNOWARN:
visitGetProp(node, child);
break;
case Token.GETELEM:
generateExpression(child, node); // object
if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
int getElem = cfw.acquireLabel();
int after = cfw.acquireLabel();
cfw.add(ByteCode.DUP);
addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
cfw.add(ByteCode.IFEQ, getElem);
cfw.add(ByteCode.POP);
cfw.add(
ByteCode.GETSTATIC,
"org/mozilla/javascript/Undefined",
"instance",
"Ljava/lang/Object;");
cfw.add(ByteCode.GOTO, after);
cfw.markLabel(getElem);
finishGetElemGeneration(node, child);
cfw.markLabel(after);
} else {
finishGetElemGeneration(node, child);
}
break;
case Token.GET_REF:
generateExpression(child, node); // reference
if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
int getRef = cfw.acquireLabel();
int after = cfw.acquireLabel();
cfw.add(ByteCode.DUP);
addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
cfw.add(ByteCode.IFEQ, getRef);
cfw.add(ByteCode.GOTO, after);
cfw.markLabel(getRef);
cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/Ref");
finishGetRefGeneration();
cfw.markLabel(after);
} else {
finishGetRefGeneration();
}
break;
case Token.GETVAR:
visitGetVar(node);
break;
case Token.SETVAR:
visitSetVar(node, child, true);
break;
case Token.SETNAME:
visitSetName(node, child);
break;
case Token.STRICT_SETNAME:
visitStrictSetName(node, child);
break;
case Token.SETCONST:
visitSetConst(node, child);
break;
case Token.SETCONSTVAR:
visitSetConstVar(node, child, true);
break;
case Token.SETPROP:
case Token.SETPROP_OP:
visitSetProp(type, node, child);
break;
case Token.SETELEM:
case Token.SETELEM_OP:
visitSetElem(type, node, child);
break;
case Token.SET_REF:
case Token.SET_REF_OP:
{
generateExpression(child, node);
child = child.getNext();
if (type == Token.SET_REF_OP) {
cfw.add(ByteCode.DUP);
cfw.addALoad(contextLocal);
addScriptRuntimeInvoke(
"refGet",
"(Lorg/mozilla/javascript/Ref;"
+ "Lorg/mozilla/javascript/Context;"
+ ")Ljava/lang/Object;");
}
generateExpression(child, node);
cfw.addALoad(contextLocal);
cfw.addALoad(variableObjectLocal);
addScriptRuntimeInvoke(
"refSet",
"(Lorg/mozilla/javascript/Ref;"
+ "Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ ")Ljava/lang/Object;");
}
break;
case Token.DEL_REF:
generateExpression(child, node);
cfw.addALoad(contextLocal);
addScriptRuntimeInvoke(
"refDel",
"(Lorg/mozilla/javascript/Ref;"
+ "Lorg/mozilla/javascript/Context;"
+ ")Ljava/lang/Object;");
break;
case Token.DELPROP:
boolean isName = child.getType() == Token.BINDNAME;
generateExpression(child, node);
child = child.getNext();
generateExpression(child, node);
if (node.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
// We have pushed `super` and the expression, but we need to remove them because
// we actually are just going to throw an error. However, delete is supposed to
// put a boolean on the stack and the class file writer would complain if we
// don't have only popped here. So we pop and the push 0 (false). Anyway, this
// is code that will always fail, so honestly no one will ever write something
// like this (delete super[foo]), so... even if this is not the most efficient
// bytecode, it's fine.
cfw.add(ByteCode.POP2);
cfw.addLoadConstant(0);
addScriptRuntimeInvoke("throwDeleteOnSuperPropertyNotAllowed", "()V");
} else {
cfw.addALoad(contextLocal);
cfw.addPush(isName);
addScriptRuntimeInvoke(
"delete",
"(Ljava/lang/Object;"
+ "Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ "Z)Ljava/lang/Object;");
}
break;
case Token.BINDNAME:
{
while (child != null) {
generateExpression(child, node);
child = child.getNext();
}
// Generate code for "ScriptRuntime.bind(varObj, "s")"
cfw.addALoad(variableObjectLocal);
cfw.addALoad(contextLocal);
addDynamicInvoke("NAME:BIND:" + node.getString(), Signatures.NAME_BIND);
}
break;
case Token.LOCAL_LOAD:
cfw.addALoad(getLocalBlockRegister(node));
break;
case Token.REF_SPECIAL:
{
generateExpression(child, node);
if (node.getIntProp(Node.OPTIONAL_CHAINING, 0) == 1) {
int getExpr = cfw.acquireLabel();
int after = cfw.acquireLabel();
cfw.add(ByteCode.DUP);
addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
cfw.add(ByteCode.IFEQ, getExpr);
cfw.add(ByteCode.POP);
cfw.add(
ByteCode.GETSTATIC,
"org/mozilla/javascript/Undefined",
"instance",
"Ljava/lang/Object;");
cfw.add(ByteCode.GOTO, after);
cfw.markLabel(getExpr);
finishRefSpecialGeneration(node);
cfw.markLabel(after);
} else {
finishRefSpecialGeneration(node);
}
}
break;
case Token.REF_MEMBER:
case Token.REF_NS_MEMBER:
case Token.REF_NAME:
case Token.REF_NS_NAME:
{
int memberTypeFlags = node.getIntProp(Node.MEMBER_TYPE_PROP, 0);
// generate possible target, possible namespace and member
do {
generateExpression(child, node);
child = child.getNext();
} while (child != null);
cfw.addALoad(contextLocal);
String methodName, signature;
switch (type) {
case Token.REF_MEMBER:
methodName = "memberRef";
signature =
"(Ljava/lang/Object;"
+ "Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ "I"
+ ")Lorg/mozilla/javascript/Ref;";
break;
case Token.REF_NS_MEMBER:
methodName = "memberRef";
signature =
"(Ljava/lang/Object;"
+ "Ljava/lang/Object;"
+ "Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ "I"
+ ")Lorg/mozilla/javascript/Ref;";
break;
case Token.REF_NAME:
methodName = "nameRef";
signature =
"(Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ "I"
+ ")Lorg/mozilla/javascript/Ref;";
cfw.addALoad(variableObjectLocal);
break;
case Token.REF_NS_NAME:
methodName = "nameRef";
signature =
"(Ljava/lang/Object;"
+ "Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ "I"
+ ")Lorg/mozilla/javascript/Ref;";
cfw.addALoad(variableObjectLocal);
break;
default:
throw Kit.codeBug();
}
cfw.addPush(memberTypeFlags);
addScriptRuntimeInvoke(methodName, signature);
}
break;
case Token.DOTQUERY:
visitDotQuery(node, child);
break;
case Token.ESCXMLATTR:
generateExpression(child, node);
cfw.addALoad(contextLocal);
addScriptRuntimeInvoke(
"escapeAttributeValue",
"(Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ ")Ljava/lang/String;");
break;
case Token.ESCXMLTEXT:
generateExpression(child, node);
cfw.addALoad(contextLocal);
addScriptRuntimeInvoke(
"escapeTextValue",
"(Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ ")Ljava/lang/String;");
break;
case Token.DEFAULTNAMESPACE:
generateExpression(child, node);
cfw.addALoad(contextLocal);
addScriptRuntimeInvoke(
"setDefaultNamespace",
"(Ljava/lang/Object;"
+ "Lorg/mozilla/javascript/Context;"
+ ")Ljava/lang/Object;");
break;
case Token.YIELD:
case Token.YIELD_STAR:
generateYieldPoint(node, true);
break;
case Token.WITHEXPR:
{
Node with = child.getNext();
Node leaveWith = with.getNext();
generateStatement(child);
generateExpression(with.getFirstChild(), with);
generateStatement(leaveWith);
break;
}
case Token.ARRAYCOMP:
{
Node expr = child.getNext();
generateStatement(child);
generateExpression(expr, node);
break;
}
case Token.TEMPLATE_LITERAL:
visitTemplateLiteral(node);
break;
case Token.NULLISH_COALESCING:
{
generateExpression(child, node); // left-hand side
int end = cfw.acquireLabel();
cfw.add(ByteCode.DUP);
addOptRuntimeInvoke("isNullOrUndefined", "(Ljava/lang/Object;)Z");
cfw.add(ByteCode.IFEQ, end);
cfw.add(ByteCode.POP);
generateExpression(child.getNext(), node); // right-hand side
cfw.markLabel(end);
break;
}
default:
throw new RuntimeException("Unexpected node type " + type);
}
}