aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/codegen/MethodEmitter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/nashorn/internal/codegen/MethodEmitter.java')
-rw-r--r--src/jdk/nashorn/internal/codegen/MethodEmitter.java963
1 files changed, 763 insertions, 200 deletions
diff --git a/src/jdk/nashorn/internal/codegen/MethodEmitter.java b/src/jdk/nashorn/internal/codegen/MethodEmitter.java
index 91129ece..362a7363 100644
--- a/src/jdk/nashorn/internal/codegen/MethodEmitter.java
+++ b/src/jdk/nashorn/internal/codegen/MethodEmitter.java
@@ -43,6 +43,10 @@ import static jdk.internal.org.objectweb.asm.Opcodes.IFNULL;
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ACMPEQ;
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ACMPNE;
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPEQ;
+import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPGE;
+import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPGT;
+import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPLE;
+import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPLT;
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPNE;
import static jdk.internal.org.objectweb.asm.Opcodes.INSTANCEOF;
import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEINTERFACE;
@@ -64,11 +68,17 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup
import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
import static jdk.nashorn.internal.codegen.CompilerConstants.staticField;
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
+import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE;
+import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC;
+import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT;
import java.io.PrintStream;
import java.lang.reflect.Array;
+import java.util.Collection;
import java.util.EnumSet;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
import jdk.internal.dynalink.support.NameCodec;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.MethodVisitor;
@@ -81,17 +91,25 @@ import jdk.nashorn.internal.codegen.types.NumericType;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.IdentNode;
-import jdk.nashorn.internal.ir.LexicalContext;
+import jdk.nashorn.internal.ir.JoinPredecessor;
import jdk.nashorn.internal.ir.LiteralNode;
+import jdk.nashorn.internal.ir.LocalVariableConversion;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.Symbol;
+import jdk.nashorn.internal.ir.TryNode;
+import jdk.nashorn.internal.objects.Global;
+import jdk.nashorn.internal.objects.NativeArray;
import jdk.nashorn.internal.runtime.ArgumentSetter;
+import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.Debug;
-import jdk.nashorn.internal.runtime.DebugLogger;
import jdk.nashorn.internal.runtime.JSType;
-import jdk.nashorn.internal.runtime.ScriptEnvironment;
+import jdk.nashorn.internal.runtime.RewriteException;
+import jdk.nashorn.internal.runtime.Scope;
import jdk.nashorn.internal.runtime.ScriptObject;
+import jdk.nashorn.internal.runtime.ScriptRuntime;
+import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
+import jdk.nashorn.internal.runtime.logging.DebugLogger;
import jdk.nashorn.internal.runtime.options.Options;
/**
@@ -111,27 +129,34 @@ public class MethodEmitter implements Emitter {
/** The ASM MethodVisitor we are plugged into */
private final MethodVisitor method;
- /** Current type stack for current evaluation */
- private Label.Stack stack;
-
/** Parent classEmitter representing the class of this method */
private final ClassEmitter classEmitter;
/** FunctionNode representing this method, or null if none exists */
protected FunctionNode functionNode;
+ /** Current type stack for current evaluation */
+ private Label.Stack stack;
+
/** Check whether this emitter ever has a function return point */
private boolean hasReturn;
- /** The script environment */
- private final ScriptEnvironment env;
+ private boolean preventUndefinedLoad;
+
+ /**
+ * Map of live local variable definitions.
+ */
+ private final Map<Symbol, LocalVariableDef> localVariableDefs = new IdentityHashMap<>();
+
+ /** The context */
+ private final Context context;
/** Threshold in chars for when string constants should be split */
static final int LARGE_STRING_THRESHOLD = 32 * 1024;
/** Debug flag, should we dump all generated bytecode along with stacks? */
- private static final DebugLogger LOG = new DebugLogger("codegen", "nashorn.codegen.debug");
- private static final boolean DEBUG = LOG.isEnabled();
+ private final DebugLogger log;
+ private final boolean debug;
/** dump stack on a particular line, or -1 if disabled */
private static final int DEBUG_TRACE_LINE;
@@ -153,6 +178,9 @@ public class MethodEmitter implements Emitter {
/** Bootstrap for runtime node indy:s */
private static final Handle RUNTIMEBOOTSTRAP = new Handle(H_INVOKESTATIC, RuntimeCallSite.BOOTSTRAP.className(), RuntimeCallSite.BOOTSTRAP.name(), RuntimeCallSite.BOOTSTRAP.descriptor());
+ /** Bootstrap for array populators */
+ private static final Handle POPULATE_ARRAY_BOOTSTRAP = new Handle(H_INVOKESTATIC, RewriteException.BOOTSTRAP.className(), RewriteException.BOOTSTRAP.name(), RewriteException.BOOTSTRAP.descriptor());
+
/**
* Constructor - internal use from ClassEmitter only
* @see ClassEmitter#method
@@ -173,11 +201,13 @@ public class MethodEmitter implements Emitter {
* @param functionNode a function node representing this method
*/
MethodEmitter(final ClassEmitter classEmitter, final MethodVisitor method, final FunctionNode functionNode) {
- this.env = classEmitter.getEnv();
+ this.context = classEmitter.getContext();
this.classEmitter = classEmitter;
this.method = method;
this.functionNode = functionNode;
this.stack = null;
+ this.log = context.getLogger(CodeGenerator.class);
+ this.debug = log.isEnabled();
}
/**
@@ -203,6 +233,14 @@ public class MethodEmitter implements Emitter {
classEmitter.endMethod(this);
}
+ boolean isReachable() {
+ return stack != null;
+ }
+
+ private void doesNotContinueSequentially() {
+ stack = null;
+ }
+
private void newStack() {
stack = new Label.Stack();
}
@@ -216,7 +254,7 @@ public class MethodEmitter implements Emitter {
* Push a type to the existing stack
* @param type the type
*/
- private void pushType(final Type type) {
+ void pushType(final Type type) {
if (type != null) {
stack.push(type);
}
@@ -230,7 +268,7 @@ public class MethodEmitter implements Emitter {
* @return the type that was retrieved
*/
private Type popType(final Type expected) {
- final Type type = stack.pop();
+ final Type type = popType();
assert type.isObject() && expected.isObject() ||
type.isEquivalentTo(expected) : type + " is not compatible with " + expected;
return type;
@@ -246,26 +284,40 @@ public class MethodEmitter implements Emitter {
}
/**
- * Pop a type from the existing stack, ensuring that it is numeric,
- * assert if not
+ * Pop a type from the existing stack, ensuring that it is numeric. Boolean type is popped as int type.
*
* @return the type
*/
private NumericType popNumeric() {
- final Type type = stack.pop();
- assert type.isNumeric() : type + " is not numeric";
+ final Type type = popType();
+ if(type.isBoolean()) {
+ // Booleans are treated as int for purposes of arithmetic operations
+ return Type.INT;
+ }
+ assert type.isNumeric();
return (NumericType)type;
}
/**
* Pop a type from the existing stack, ensuring that it is an integer type
- * (integer or long), assert if not
+ * (integer or long). Boolean type is popped as int type.
*
* @return the type
*/
+ private BitwiseType popBitwise() {
+ final Type type = popType();
+ if(type == Type.BOOLEAN) {
+ return Type.INT;
+ }
+ return (BitwiseType)type;
+ }
+
private BitwiseType popInteger() {
- final Type type = stack.pop();
- assert type.isInteger() || type.isLong() : type + " is not an integer or long";
+ final Type type = popType();
+ if(type == Type.BOOLEAN) {
+ return Type.INT;
+ }
+ assert type == Type.INT;
return (BitwiseType)type;
}
@@ -276,7 +328,7 @@ public class MethodEmitter implements Emitter {
* @return the type
*/
private ArrayType popArray() {
- final Type type = stack.pop();
+ final Type type = popType();
assert type.isArray() : type;
return (ArrayType)type;
}
@@ -307,13 +359,14 @@ public class MethodEmitter implements Emitter {
* object type on the stack
*
* @param classDescriptor class descriptor for the object type
+ * @param type the type of the new object
*
* @return the method emitter
*/
- MethodEmitter _new(final String classDescriptor) {
+ MethodEmitter _new(final String classDescriptor, final Type type) {
debug("new", classDescriptor);
method.visitTypeInsn(NEW, classDescriptor);
- pushType(Type.OBJECT);
+ pushType(type);
return this;
}
@@ -326,7 +379,7 @@ public class MethodEmitter implements Emitter {
* @return the method emitter
*/
MethodEmitter _new(final Class<?> clazz) {
- return _new(className(clazz));
+ return _new(className(clazz), Type.typeFor(clazz));
}
/**
@@ -358,25 +411,40 @@ public class MethodEmitter implements Emitter {
debug("dup", depth);
switch (depth) {
- case 0:
+ case 0: {
+ final int l0 = stack.getTopLocalLoad();
pushType(peekType());
+ stack.markLocalLoad(l0);
break;
+ }
case 1: {
+ final int l0 = stack.getTopLocalLoad();
final Type p0 = popType();
+ final int l1 = stack.getTopLocalLoad();
final Type p1 = popType();
pushType(p0);
+ stack.markLocalLoad(l0);
pushType(p1);
+ stack.markLocalLoad(l1);
pushType(p0);
+ stack.markLocalLoad(l0);
break;
}
case 2: {
+ final int l0 = stack.getTopLocalLoad();
final Type p0 = popType();
+ final int l1 = stack.getTopLocalLoad();
final Type p1 = popType();
+ final int l2 = stack.getTopLocalLoad();
final Type p2 = popType();
pushType(p0);
+ stack.markLocalLoad(l0);
pushType(p2);
+ stack.markLocalLoad(l2);
pushType(p1);
+ stack.markLocalLoad(l1);
pushType(p0);
+ stack.markLocalLoad(l0);
break;
}
default:
@@ -398,13 +466,22 @@ public class MethodEmitter implements Emitter {
debug("dup2");
if (peekType().isCategory2()) {
+ final int l0 = stack.getTopLocalLoad();
pushType(peekType());
+ stack.markLocalLoad(l0);
} else {
- final Type type = get2();
- pushType(type);
- pushType(type);
- pushType(type);
- pushType(type);
+ final int l0 = stack.getTopLocalLoad();
+ final Type p0 = popType();
+ final int l1 = stack.getTopLocalLoad();
+ final Type p1 = popType();
+ pushType(p0);
+ stack.markLocalLoad(l0);
+ pushType(p1);
+ stack.markLocalLoad(l1);
+ pushType(p0);
+ stack.markLocalLoad(l0);
+ pushType(p1);
+ stack.markLocalLoad(l1);
}
method.visitInsn(DUP2);
return this;
@@ -454,35 +531,42 @@ public class MethodEmitter implements Emitter {
MethodEmitter swap() {
debug("swap");
+ final int l0 = stack.getTopLocalLoad();
final Type p0 = popType();
+ final int l1 = stack.getTopLocalLoad();
final Type p1 = popType();
p0.swap(method, p1);
pushType(p0);
+ stack.markLocalLoad(l0);
pushType(p1);
- debug("after ", p0, p1);
+ stack.markLocalLoad(l1);
return this;
}
- /**
- * Add a local variable. This is a nop if the symbol has no slot
- *
- * @param symbol symbol for the local variable
- * @param start start of scope
- * @param end end of scope
- */
- void localVariable(final Symbol symbol, final Label start, final Label end) {
- if (!symbol.hasSlot()) {
- return;
- }
-
- String name = symbol.getName();
-
- if (name.equals(THIS.symbolName())) {
- name = THIS_DEBUGGER.symbolName();
+ void pack() {
+ final Type type = peekType();
+ if (type.isInteger()) {
+ convert(PRIMITIVE_FIELD_TYPE);
+ } else if (type.isLong()) {
+ //nop
+ } else if (type.isNumber()) {
+ invokestatic("java/lang/Double", "doubleToRawLongBits", "(D)J");
+ } else {
+ assert false : type + " cannot be packed!";
}
+ //all others are nops, objects aren't packed
+ }
- method.visitLocalVariable(name, symbol.getSymbolType().getDescriptor(), null, start.getLabel(), end.getLabel(), symbol.getSlot());
+ /**
+ * Initializes a bytecode method parameter
+ * @param symbol the symbol for the parameter
+ * @param type the type of the parameter
+ * @param start the label for the start of the method
+ */
+ void initializeMethodParameter(final Symbol symbol, final Type type, final Label start) {
+ assert symbol.isBytecodeLocal();
+ localVariableDefs.put(symbol, new LocalVariableDef(start.getLabel(), type));
}
/**
@@ -550,8 +634,8 @@ public class MethodEmitter implements Emitter {
*/
MethodEmitter shr() {
debug("shr");
- popType(Type.INT);
- pushType(popInteger().shr(method));
+ popInteger();
+ pushType(popBitwise().shr(method));
return this;
}
@@ -563,21 +647,21 @@ public class MethodEmitter implements Emitter {
*/
MethodEmitter shl() {
debug("shl");
- popType(Type.INT);
- pushType(popInteger().shl(method));
+ popInteger();
+ pushType(popBitwise().shl(method));
return this;
}
/**
- * Pops two integer types from the stack, performs a bitwise arithetic shift right and pushes
+ * Pops two integer types from the stack, performs a bitwise arithmetic shift right and pushes
* the result. The shift count, the first element, must be INT.
*
* @return the method emitter
*/
MethodEmitter sar() {
debug("sar");
- popType(Type.INT);
- pushType(popInteger().sar(method));
+ popInteger();
+ pushType(popBitwise().sar(method));
return this;
}
@@ -586,9 +670,9 @@ public class MethodEmitter implements Emitter {
*
* @return the method emitter
*/
- MethodEmitter neg() {
+ MethodEmitter neg(final int programPoint) {
debug("neg");
- pushType(popNumeric().neg(method));
+ pushType(popNumeric().neg(method, programPoint));
return this;
}
@@ -599,20 +683,49 @@ public class MethodEmitter implements Emitter {
* @param recovery label pointing to start of catch block
*/
void _catch(final Label recovery) {
- stack.clear();
- stack.push(Type.OBJECT);
+ // While in JVM a catch block can be reached through normal control flow, our code generator never does this,
+ // so we might as well presume there's no stack on entry.
+ assert stack == null;
+ recovery.onCatch();
label(recovery);
+ beginCatchBlock();
}
/**
+ * Add any number of labels for the start of a catch block and push the exception to the
+ * stack
+ *
+ * @param recoveries labels pointing to start of catch block
+ */
+ void _catch(final Collection<Label> recoveries) {
+ assert stack == null;
+ for(final Label l: recoveries) {
+ label(l);
+ }
+ beginCatchBlock();
+ }
+
+ private void beginCatchBlock() {
+ // It can happen that the catch label wasn't marked as reachable. They are marked as reachable if there's an
+ // assignment in the try block, but it's possible that there was none.
+ if(!isReachable()) {
+ newStack();
+ }
+ pushType(Type.typeFor(Throwable.class));
+ }
+ /**
* Start a try/catch block.
*
* @param entry start label for try
* @param exit end label for try
* @param recovery start label for catch
* @param typeDescriptor type descriptor for exception
+ * @param isOptimismHandler true if this is a hander for {@code UnwarrantedOptimismException}. Normally joining on a
+ * catch handler kills temporary variables, but optimism handlers are an exception, as they need to capture
+ * temporaries as well, so they must remain live.
*/
- void _try(final Label entry, final Label exit, final Label recovery, final String typeDescriptor) {
+ private void _try(final Label entry, final Label exit, final Label recovery, final String typeDescriptor, final boolean isOptimismHandler) {
+ recovery.joinFromTry(entry.getStack(), isOptimismHandler);
method.visitTryCatchBlock(entry.getLabel(), exit.getLabel(), recovery.getLabel(), typeDescriptor);
}
@@ -625,7 +738,7 @@ public class MethodEmitter implements Emitter {
* @param clazz exception class
*/
void _try(final Label entry, final Label exit, final Label recovery, final Class<?> clazz) {
- method.visitTryCatchBlock(entry.getLabel(), exit.getLabel(), recovery.getLabel(), CompilerConstants.className(clazz));
+ _try(entry, exit, recovery, CompilerConstants.className(clazz), clazz == UnwarrantedOptimismException.class);
}
/**
@@ -636,9 +749,12 @@ public class MethodEmitter implements Emitter {
* @param recovery start label for catch
*/
void _try(final Label entry, final Label exit, final Label recovery) {
- _try(entry, exit, recovery, (String)null);
+ _try(entry, exit, recovery, (String)null, false);
}
+ void markLabelAsOptimisticCatchHandler(final Label label, final int liveLocalCount) {
+ label.markAsOptimisticCatchHandler(stack, liveLocalCount);
+ }
/**
* Load the constants array
@@ -665,6 +781,12 @@ public class MethodEmitter implements Emitter {
return this;
}
+ MethodEmitter loadForcedInitializer(final Type type) {
+ debug("load forced initializer ", type);
+ pushType(type.loadForcedInitializer(method));
+ return this;
+ }
+
/**
* Push the empty value for the given type, i.e. EMPTY.
*
@@ -805,22 +927,36 @@ public class MethodEmitter implements Emitter {
}
/**
- * Push a local variable to the stack. If the symbol representing
- * the local variable doesn't have a slot, this is a NOP
+ * Pushes the value of an identifier to the stack. If the identifier does not represent a local variable or a
+ * parameter, this will be a no-op.
+ *
+ * @param ident the identifier for the variable being loaded.
*
- * @param symbol the symbol representing the local variable.
+ * @return the method emitter
+ */
+ MethodEmitter load(final IdentNode ident) {
+ return load(ident.getSymbol(), ident.getType());
+ }
+
+ /**
+ * Pushes the value of the symbol to the stack with the specified type. No type conversion is being performed, and
+ * the type is only being used if the symbol addresses a local variable slot. The value of the symbol is loaded if
+ * it addresses a local variable slot, or it is a parameter (in which case it can also be loaded from a vararg array
+ * or the arguments object). If it is neither, the operation is a no-op.
*
+ * @param symbol the symbol addressing the value being loaded
+ * @param type the presumed type of the value when it is loaded from a local variable slot
* @return the method emitter
*/
- MethodEmitter load(final Symbol symbol) {
+ MethodEmitter load(final Symbol symbol, final Type type) {
assert symbol != null;
if (symbol.hasSlot()) {
- final int slot = symbol.getSlot();
- debug("load symbol", symbol.getName(), " slot=", slot);
- final Type type = symbol.getSymbolType().load(method, slot);
- pushType(type == Type.OBJECT && symbol.isThis() ? Type.THIS : type);
+ final int slot = symbol.getSlot(type);
+ debug("load symbol", symbol.getName(), " slot=", slot, "type=", type);
+ load(type, slot);
+ // _try(new Label("dummy"), new Label("dummy2"), recovery);
+ // method.visitTryCatchBlock(new Label(), arg1, arg2, arg3);
} else if (symbol.isParam()) {
- assert !symbol.isScope();
assert functionNode.isVarArg() : "Non-vararg functions have slotted parameters";
final int index = symbol.getFieldIndex();
if (functionNode.needsArguments()) {
@@ -841,7 +977,7 @@ public class MethodEmitter implements Emitter {
}
/**
- * Push a local variable to the stack, given an explicit bytecode slot
+ * Push a local variable to the stack, given an explicit bytecode slot.
* This is used e.g. for stub generation where we know where items like
* "this" and "scope" reside.
*
@@ -853,7 +989,11 @@ public class MethodEmitter implements Emitter {
MethodEmitter load(final Type type, final int slot) {
debug("explicit load", type, slot);
final Type loadType = type.load(method, slot);
+ assert loadType != null;
pushType(loadType == Type.OBJECT && isThisSlot(slot) ? Type.THIS : loadType);
+ assert !preventUndefinedLoad || (slot < stack.localVariableTypes.size() && stack.localVariableTypes.get(slot) != Type.UNKNOWN)
+ : "Attempted load of uninitialized slot " + slot + " (as type " + type + ")";
+ stack.markLocalLoad(slot);
return this;
}
@@ -861,7 +1001,7 @@ public class MethodEmitter implements Emitter {
if (functionNode == null) {
return slot == CompilerConstants.JAVA_THIS.slot();
}
- final int thisSlot = compilerConstant(THIS).getSlot();
+ final int thisSlot = getCompilerConstantSymbol(THIS).getSlot(Type.OBJECT);
assert !functionNode.needsCallee() || thisSlot == 1; // needsCallee -> thisSlot == 1
assert functionNode.needsCallee() || thisSlot == 0; // !needsCallee -> thisSlot == 0
return slot == thisSlot;
@@ -883,7 +1023,7 @@ public class MethodEmitter implements Emitter {
return this;
}
- private Symbol compilerConstant(final CompilerConstants cc) {
+ private Symbol getCompilerConstantSymbol(final CompilerConstants cc) {
return functionNode.getBody().getExistingSymbol(cc.symbolName());
}
@@ -893,22 +1033,46 @@ public class MethodEmitter implements Emitter {
* @return if this method has a slot allocated for the scope variable.
*/
boolean hasScope() {
- return compilerConstant(SCOPE).hasSlot();
+ return getCompilerConstantSymbol(SCOPE).hasSlot();
}
MethodEmitter loadCompilerConstant(final CompilerConstants cc) {
- final Symbol symbol = compilerConstant(cc);
+ return loadCompilerConstant(cc, null);
+ }
+
+ MethodEmitter loadCompilerConstant(final CompilerConstants cc, final Type type) {
if (cc == SCOPE && peekType() == Type.SCOPE) {
dup();
return this;
}
- return load(symbol);
+ return load(getCompilerConstantSymbol(cc), type != null ? type : getCompilerConstantType(cc));
+ }
+
+ MethodEmitter loadScope() {
+ return loadCompilerConstant(SCOPE).checkcast(Scope.class);
+ }
+
+ MethodEmitter setSplitState(final int state) {
+ return loadScope().load(state).invoke(Scope.SET_SPLIT_STATE);
}
void storeCompilerConstant(final CompilerConstants cc) {
- final Symbol symbol = compilerConstant(cc);
+ storeCompilerConstant(cc, null);
+ }
+
+ void storeCompilerConstant(final CompilerConstants cc, final Type type) {
+ final Symbol symbol = getCompilerConstantSymbol(cc);
+ if(!symbol.hasSlot()) {
+ return;
+ }
debug("store compiler constant ", symbol);
- store(symbol);
+ store(symbol, type != null ? type : getCompilerConstantType(cc));
+ }
+
+ private static Type getCompilerConstantType(final CompilerConstants cc) {
+ final Class<?> constantType = cc.type();
+ assert constantType != null;
+ return Type.typeFor(constantType);
}
/**
@@ -940,48 +1104,215 @@ public class MethodEmitter implements Emitter {
/**
* Pop a value from the stack and store it in a local variable represented
- * by the given symbol. If the symbol has no slot, this is a NOP
+ * by the given identifier. If the symbol has no slot, this is a NOP
*
- * @param symbol symbol to store stack to
+ * @param ident identifier to store stack to
*/
- void store(final Symbol symbol) {
+ void store(final IdentNode ident) {
+ final Type type = ident.getType();
+ final Symbol symbol = ident.getSymbol();
+ if(type == Type.UNDEFINED) {
+ assert peekType() == Type.UNDEFINED;
+ store(symbol, Type.OBJECT);
+ } else {
+ store(symbol, type);
+ }
+ }
+
+ /**
+ * Represents a definition of a local variable with a type. Used for local variable table building.
+ */
+ private static class LocalVariableDef {
+ // The start label from where this definition lives.
+ private final jdk.internal.org.objectweb.asm.Label label;
+ // The currently live type of the local variable.
+ private final Type type;
+
+ LocalVariableDef(final jdk.internal.org.objectweb.asm.Label label, final Type type) {
+ this.label = label;
+ this.type = type;
+ }
+
+ }
+
+ void closeLocalVariable(final Symbol symbol, final Label label) {
+ final LocalVariableDef def = localVariableDefs.get(symbol);
+ if(def != null) {
+ endLocalValueDef(symbol, def, label.getLabel());
+ }
+ if(isReachable()) {
+ markDeadLocalVariable(symbol);
+ }
+ }
+
+ void markDeadLocalVariable(final Symbol symbol) {
+ if(!symbol.isDead()) {
+ markDeadSlots(symbol.getFirstSlot(), symbol.slotCount());
+ }
+ }
+
+ void markDeadSlots(final int firstSlot, final int slotCount) {
+ stack.markDeadLocalVariables(firstSlot, slotCount);
+ }
+
+ private void endLocalValueDef(final Symbol symbol, final LocalVariableDef def, final jdk.internal.org.objectweb.asm.Label label) {
+ String name = symbol.getName();
+ if (name.equals(THIS.symbolName())) {
+ name = THIS_DEBUGGER.symbolName();
+ }
+ method.visitLocalVariable(name, def.type.getDescriptor(), null, def.label, label, symbol.getSlot(def.type));
+ }
+
+ void store(final Symbol symbol, final Type type) {
+ store(symbol, type, true);
+ }
+
+ /**
+ * Pop a value from the stack and store it in a variable denoted by the given symbol. The variable should be either
+ * a local variable, or a function parameter (and not a scoped variable). For local variables, this method will also
+ * do the bookeeping of the local variable table as well as mark values in all alternative slots for the symbol as
+ * dead. In this regard it differs from {@link #storeHidden(Type, int)}.
+ *
+ * @param symbol the symbol to store into.
+ * @param type the type to store
+ * @param onlySymbolLiveValue if true, this is the sole live value for the symbol. If false, currently live values should
+ * be kept live.
+ */
+ void store(final Symbol symbol, final Type type, final boolean onlySymbolLiveValue) {
assert symbol != null : "No symbol to store";
if (symbol.hasSlot()) {
- final int slot = symbol.getSlot();
- debug("store symbol", symbol.getName(), " slot=", slot);
- popType(symbol.getSymbolType()).store(method, slot);
+ final boolean isLiveType = symbol.hasSlotFor(type);
+ final LocalVariableDef existingDef = localVariableDefs.get(symbol);
+ if(existingDef == null || existingDef.type != type) {
+ final jdk.internal.org.objectweb.asm.Label here = new jdk.internal.org.objectweb.asm.Label();
+ if(isLiveType) {
+ final LocalVariableDef newDef = new LocalVariableDef(here, type);
+ localVariableDefs.put(symbol, newDef);
+ }
+ method.visitLabel(here);
+ if(existingDef != null) {
+ endLocalValueDef(symbol, existingDef, here);
+ }
+ }
+ if(isLiveType) {
+ final int slot = symbol.getSlot(type);
+ debug("store symbol", symbol.getName(), " type=", type, " slot=", slot);
+ storeHidden(type, slot, onlySymbolLiveValue);
+ } else {
+ if(onlySymbolLiveValue) {
+ markDeadLocalVariable(symbol);
+ }
+ debug("dead store symbol ", symbol.getName(), " type=", type);
+ pop();
+ }
} else if (symbol.isParam()) {
assert !symbol.isScope();
assert functionNode.isVarArg() : "Non-vararg functions have slotted parameters";
final int index = symbol.getFieldIndex();
if (functionNode.needsArguments()) {
+ convert(Type.OBJECT);
debug("store symbol", symbol.getName(), " arguments index=", index);
loadCompilerConstant(ARGUMENTS);
load(index);
ArgumentSetter.SET_ARGUMENT.invoke(this);
} else {
+ convert(Type.OBJECT);
// varargs without arguments object - just do array store to __varargs__
debug("store symbol", symbol.getName(), " array index=", index);
loadCompilerConstant(VARARGS);
load(index);
ArgumentSetter.SET_ARRAY_ELEMENT.invoke(this);
}
+ } else {
+ debug("dead store symbol ", symbol.getName(), " type=", type);
+ pop();
}
}
/**
- * Pop a value from the stack and store it in a given local variable
- * slot.
+ * Pop a value from the stack and store it in a local variable slot. Note that in contrast with
+ * {@link #store(Symbol, Type)}, this method does not adjust the local variable table, nor marks slots for
+ * alternative value types for the symbol as being dead. For that reason, this method is usually not called
+ * directly. Notable exceptions are temporary internal locals (e.g. quick store, last-catch-condition, etc.) that
+ * are not desired to show up in the local variable table.
*
* @param type the type to pop
* @param slot the slot
*/
- void store(final Type type, final int slot) {
+ void storeHidden(final Type type, final int slot) {
+ storeHidden(type, slot, true);
+ }
+
+ void storeHidden(final Type type, final int slot, final boolean onlyLiveSymbolValue) {
+ explicitStore(type, slot);
+ stack.onLocalStore(type, slot, onlyLiveSymbolValue);
+ }
+
+ void storeTemp(final Type type, final int slot) {
+ explicitStore(type, slot);
+ defineTemporaryLocalVariable(slot, slot + type.getSlots());
+ onLocalStore(type, slot);
+ }
+
+ void onLocalStore(final Type type, final int slot) {
+ stack.onLocalStore(type, slot, true);
+ }
+
+ private void explicitStore(final Type type, final int slot) {
+ assert slot != -1;
+ debug("explicit store", type, slot);
popType(type);
type.store(method, slot);
}
/**
+ * Marks a range of slots as belonging to a defined local variable. The slots will start out with no live value
+ * in them.
+ * @param fromSlot first slot, inclusive.
+ * @param toSlot last slot, exclusive.
+ */
+ void defineBlockLocalVariable(final int fromSlot, final int toSlot) {
+ stack.defineBlockLocalVariable(fromSlot, toSlot);
+ }
+
+ /**
+ * Marks a range of slots as belonging to a defined temporary local variable. The slots will start out with no
+ * live value in them.
+ * @param fromSlot first slot, inclusive.
+ * @param toSlot last slot, exclusive.
+ */
+ void defineTemporaryLocalVariable(final int fromSlot, final int toSlot) {
+ stack.defineTemporaryLocalVariable(fromSlot, toSlot);
+ }
+
+ /**
+ * Defines a new temporary local variable and returns its allocated index.
+ * @param width the required width (in slots) for the new variable.
+ * @return the bytecode slot index where the newly allocated local begins.
+ */
+ int defineTemporaryLocalVariable(final int width) {
+ return stack.defineTemporaryLocalVariable(width);
+ }
+
+ void undefineLocalVariables(final int fromSlot, final boolean canTruncateSymbol) {
+ if(isReachable()) {
+ stack.undefineLocalVariables(fromSlot, canTruncateSymbol);
+ }
+ }
+
+ List<Type> getLocalVariableTypes() {
+ return stack.localVariableTypes;
+ }
+
+ List<Type> getWidestLiveLocals(final List<Type> localTypes) {
+ return stack.getWidestLiveLocals(localTypes);
+ }
+
+ String markSymbolBoundariesInLvarTypesDescriptor(final String lvarDescriptor) {
+ return stack.markSymbolBoundariesInLvarTypesDescriptor(lvarDescriptor);
+ }
+
+ /**
* Increment/Decrement a local integer by the given value.
*
* @param slot the int slot
@@ -999,9 +1330,9 @@ public class MethodEmitter implements Emitter {
public void athrow() {
debug("athrow");
final Type receiver = popType(Type.OBJECT);
- assert receiver.isObject();
+ assert Throwable.class.isAssignableFrom(receiver.getTypeClass()) : receiver.getTypeClass();
method.visitInsn(ATHROW);
- stack = null;
+ doesNotContinueSequentially();
}
/**
@@ -1130,11 +1461,7 @@ public class MethodEmitter implements Emitter {
popType(Type.OBJECT);
}
- if (opcode == INVOKEINTERFACE) {
- method.visitMethodInsn(opcode, className, methodName, methodDescriptor, true);
- } else {
- method.visitMethodInsn(opcode, className, methodName, methodDescriptor, false);
- }
+ method.visitMethodInsn(opcode, className, methodName, methodDescriptor, opcode == INVOKEINTERFACE);
if (returnType != null) {
pushType(returnType);
@@ -1197,7 +1524,7 @@ public class MethodEmitter implements Emitter {
*
* @return the method emitter
*/
- MethodEmitter invokeStatic(final String className, final String methodName, final String methodDescriptor, final Type returnType) {
+ MethodEmitter invokestatic(final String className, final String methodName, final String methodDescriptor, final Type returnType) {
invokestatic(className, methodName, methodDescriptor);
popType();
pushType(returnType);
@@ -1235,8 +1562,9 @@ public class MethodEmitter implements Emitter {
*/
void lookupswitch(final Label defaultLabel, final int[] values, final Label... table) {//Collection<Label> table) {
debug("lookupswitch", peekType());
- popType(Type.INT);
+ adjustStackForSwitch(defaultLabel, table);
method.visitLookupSwitchInsn(defaultLabel.getLabel(), values, getLabels(table));
+ doesNotContinueSequentially();
}
/**
@@ -1248,8 +1576,17 @@ public class MethodEmitter implements Emitter {
*/
void tableswitch(final int lo, final int hi, final Label defaultLabel, final Label... table) {
debug("tableswitch", peekType());
- popType(Type.INT);
+ adjustStackForSwitch(defaultLabel, table);
method.visitTableSwitchInsn(lo, hi, defaultLabel.getLabel(), getLabels(table));
+ doesNotContinueSequentially();
+ }
+
+ private void adjustStackForSwitch(final Label defaultLabel, final Label... table) {
+ popType(Type.INT);
+ joinTo(defaultLabel);
+ for(final Label label: table) {
+ joinTo(label);
+ }
}
/**
@@ -1305,7 +1642,7 @@ public class MethodEmitter implements Emitter {
convert(type);
}
popType(type)._return(method);
- stack = null;
+ doesNotContinueSequentially();
}
/**
@@ -1322,18 +1659,7 @@ public class MethodEmitter implements Emitter {
debug("return [void]");
assert stack.isEmpty() : stack;
method.visitInsn(RETURN);
- stack = null;
- }
-
- /**
- * Goto, possibly when splitting is taking place. If
- * a splitNode exists, we need to handle the case that the
- * jump target is another method
- *
- * @param label destination label
- */
- void splitAwareGoto(final LexicalContext lc, final Label label) {
- _goto(label);
+ doesNotContinueSequentially();
}
/**
@@ -1359,7 +1685,7 @@ public class MethodEmitter implements Emitter {
assert peekType().isInteger() || peekType().isBoolean() || peekType().isObject() : "expecting integer type or object for jump, but found " + peekType();
popType();
}
- mergeStackTo(label);
+ joinTo(label);
method.visitJumpInsn(opcode, label.getLabel());
}
@@ -1454,6 +1780,16 @@ public class MethodEmitter implements Emitter {
}
/**
+ * Generate an if_icmplt
+ *
+ * @param label label to true case
+ */
+ void if_icmplt(final Label label) {
+ debug("if_icmplt", label);
+ jump(IF_ICMPLT, label, 2);
+ }
+
+ /**
* Generate an ifle
*
* @param label label to true case
@@ -1464,6 +1800,16 @@ public class MethodEmitter implements Emitter {
}
/**
+ * Generate an if_icmple
+ *
+ * @param label label to true case
+ */
+ void if_icmple(final Label label) {
+ debug("if_icmple", label);
+ jump(IF_ICMPLE, label, 2);
+ }
+
+ /**
* Generate an ifgt
*
* @param label label to true case
@@ -1474,6 +1820,16 @@ public class MethodEmitter implements Emitter {
}
/**
+ * Generate an if_icmpgt
+ *
+ * @param label label to true case
+ */
+ void if_icmpgt(final Label label) {
+ debug("if_icmpgt", label);
+ jump(IF_ICMPGT, label, 2);
+ }
+
+ /**
* Generate an ifge
*
* @param label label to true case
@@ -1484,24 +1840,58 @@ public class MethodEmitter implements Emitter {
}
/**
+ * Generate an if_icmpge
+ *
+ * @param label label to true case
+ */
+ void if_icmpge(final Label label) {
+ debug("if_icmpge", label);
+ jump(IF_ICMPGE, label, 2);
+ }
+
+ /**
* Unconditional jump to a label
*
* @param label destination label
*/
void _goto(final Label label) {
- //debug("goto", label);
+ debug("goto", label);
jump(GOTO, label, 0);
- stack = null; //whoever reaches the point after us provides the stack, because we don't
+ doesNotContinueSequentially(); //whoever reaches the point after us provides the stack, because we don't
+ }
+
+ /**
+ * Unconditional jump to the start label of a loop. It differs from ordinary {@link #_goto(Label)} in that it will
+ * preserve the current label stack, as the next instruction after the goto is loop body that the loop will come
+ * back to. Also used to jump at the start label of the continuation handler, as it behaves much like a loop test in
+ * the sense that after it is evaluated, it also jumps backwards.
+ *
+ * @param loopStart start label of a loop
+ */
+ void gotoLoopStart(final Label loopStart) {
+ debug("goto (loop)", loopStart);
+ jump(GOTO, loopStart, 0);
}
/**
- * Examine two stacks and make sure they are of the same size and their
- * contents are equivalent to each other
- * @param s0 first stack
- * @param s1 second stack
+ * Unconditional jump without any control flow and data flow testing. You should not normally use this method when
+ * generating code, except if you're very sure that you know what you're doing. Normally only used for the
+ * admittedly torturous control flow of continuation handler plumbing.
+ * @param target the target of the jump
+ */
+ void uncheckedGoto(final Label target) {
+ method.visitJumpInsn(GOTO, target.getLabel());
+ }
+
+ /**
+ * Potential transfer of control to a catch block.
*
- * @return true if stacks are equivalent, false otherwise
+ * @param catchLabel destination catch label
*/
+ void canThrow(final Label catchLabel) {
+ catchLabel.joinFromTry(stack, false);
+ }
+
/**
* A join in control flow - helper function that makes sure all entry stacks
* discovered for the join point so far are equivalent
@@ -1511,45 +1901,46 @@ public class MethodEmitter implements Emitter {
*
* @param label label
*/
- private void mergeStackTo(final Label label) {
- //sometimes we can do a merge stack without having a stack - i.e. when jumping ahead to dead code
- //see NASHORN-73. So far we had been saved by the line number nodes. This should have been fixed
- //by Lower removing everything after an unconditionally executed terminating statement OR a break
- //or continue in a block. Previously code left over after breaks and continues was still there
- //and caused bytecode to be generated - which crashed on stack not being there, as the merge
- //was not in fact preceeded by a visit. Furthermore, this led to ASM putting out its NOP NOP NOP
- //ATHROW sequences instead of no code being generated at all. This should now be fixed.
- assert stack != null : label + " entered with no stack. deadcode that remains?";
-
- final Label.Stack labelStack = label.getStack();
- if (labelStack == null) {
- label.setStack(stack.copy());
- return;
- }
- assert stack.isEquivalentTo(labelStack) : "stacks " + stack + " is not equivalent with " + labelStack + " at join point";
+ private void joinTo(final Label label) {
+ assert isReachable();
+ label.joinFrom(stack);
}
/**
* Register a new label, enter it here.
+ * @param label
+ */
+ void label(final Label label) {
+ breakLabel(label, -1);
+ }
+
+ /**
+ * Register a new break target label, enter it here.
*
* @param label the label
+ * @param liveLocals the number of live locals at this label
*/
- void label(final Label label) {
- /*
- * If stack == null, this means that we came here not through a fallthrough.
- * E.g. a label after an athrow. Then we create a new stack if one doesn't exist
- * for this location already.
- */
- if (stack == null) {
- stack = label.getStack();
- if (stack == null) {
- newStack();
- }
+ void breakLabel(final Label label, final int liveLocals) {
+ if (!isReachable()) {
+ // If we emit a label, and the label's stack is null, it must not be reachable.
+ assert (label.getStack() == null) != label.isReachable();
+ } else {
+ joinTo(label);
+ }
+ // Use label's stack as we might have no stack.
+ final Label.Stack labelStack = label.getStack();
+ stack = labelStack == null ? null : labelStack.clone();
+ if(stack != null && label.isBreakTarget() && liveLocals != -1) {
+ // This has to be done because we might not have another frame to provide us with its firstTemp if the label
+ // is only reachable through a break or continue statement; also in this case, the frame can actually
+ // give us a higher number of live locals, e.g. if it comes from a catch. Typical example:
+ // for(;;) { try{ throw 0; } catch(e) { break; } }.
+ // Since the for loop can only be exited through the break in the catch block, it'll bring with it the
+ // "e" as a live local, and we need to trim it off here.
+ assert stack.firstTemp >= liveLocals;
+ stack.firstTemp = liveLocals;
}
debug_label(label);
-
- mergeStackTo(label); //we have to merge our stack to whatever is in the label
-
method.visitLabel(label.getLabel());
}
@@ -1561,13 +1952,32 @@ public class MethodEmitter implements Emitter {
* @return the method emitter
*/
MethodEmitter convert(final Type to) {
- final Type type = peekType().convert(method, to);
+ final Type from = peekType();
+ final Type type = from.convert(method, to);
if (type != null) {
- if (!peekType().isEquivalentTo(to)) {
- debug("convert", peekType(), "->", to);
+ if (!from.isEquivalentTo(to)) {
+ debug("convert", from, "->", to);
+ }
+ if (type != from) {
+ final int l0 = stack.getTopLocalLoad();
+ popType();
+ pushType(type);
+ // NOTE: conversions from a primitive type are considered to preserve the "load" property of the value
+ // on the stack. Otherwise we could introduce temporary locals in a deoptimized rest-of (e.g. doing an
+ // "i < x.length" where "i" is int and ".length" gets deoptimized to long would end up converting i to
+ // long with "ILOAD i; I2L; LSTORE tmp; LLOAD tmp;"). Such additional temporary would cause an error
+ // when restoring the state of the function for rest-of execution, as the not-yet deoptimized variant
+ // would have the (now invalidated) assumption that "x.length" is an int, so it wouldn't have the I2L,
+ // and therefore neither the subsequent LSTORE tmp; LLOAD tmp;. By making sure conversions from a
+ // primitive type don't erase the "load" information, we don't introduce temporaries in the deoptimized
+ // rest-of that didn't exist in the more optimistic version that triggered the deoptimization.
+ // NOTE: as a more general observation, we could theoretically track the operations required to
+ // reproduce any stack value as long as they are all local loads, constant loads, and stack operations.
+ // We won't go there in the current system
+ if(!from.isObject()) {
+ stack.markLocalLoad(l0);
+ }
}
- popType();
- pushType(type);
}
return this;
}
@@ -1590,8 +2000,8 @@ public class MethodEmitter implements Emitter {
* @return common type
*/
private BitwiseType get2i() {
- final BitwiseType p0 = popInteger();
- final BitwiseType p1 = popInteger();
+ final BitwiseType p0 = popBitwise();
+ final BitwiseType p1 = popBitwise();
assert p0.isEquivalentTo(p1) : "expecting equivalent types on stack but got " + p0 + " and " + p1;
return p0;
}
@@ -1613,9 +2023,9 @@ public class MethodEmitter implements Emitter {
*
* @return the method emitter
*/
- MethodEmitter add() {
+ MethodEmitter add(final int programPoint) {
debug("add");
- pushType(get2().add(method));
+ pushType(get2().add(method, programPoint));
return this;
}
@@ -1624,9 +2034,9 @@ public class MethodEmitter implements Emitter {
*
* @return the method emitter
*/
- MethodEmitter sub() {
+ MethodEmitter sub(final int programPoint) {
debug("sub");
- pushType(get2n().sub(method));
+ pushType(get2n().sub(method, programPoint));
return this;
}
@@ -1635,9 +2045,9 @@ public class MethodEmitter implements Emitter {
*
* @return the method emitter
*/
- MethodEmitter mul() {
+ MethodEmitter mul(final int programPoint) {
debug("mul ");
- pushType(get2n().mul(method));
+ pushType(get2n().mul(method, programPoint));
return this;
}
@@ -1646,9 +2056,9 @@ public class MethodEmitter implements Emitter {
*
* @return the method emitter
*/
- MethodEmitter div() {
+ MethodEmitter div(final int programPoint) {
debug("div");
- pushType(get2n().div(method));
+ pushType(get2n().div(method, programPoint));
return this;
}
@@ -1657,9 +2067,9 @@ public class MethodEmitter implements Emitter {
*
* @return the method emitter
*/
- MethodEmitter rem() {
+ MethodEmitter rem(final int programPoint) {
debug("rem");
- pushType(get2n().rem(method));
+ pushType(get2n().rem(method, programPoint));
return this;
}
@@ -1670,13 +2080,23 @@ public class MethodEmitter implements Emitter {
* @return array of Types
*/
protected Type[] getTypesFromStack(final int count) {
- final Type[] types = new Type[count];
- int pos = 0;
- for (int i = count - 1; i >= 0; i--) {
- types[i] = stack.peek(pos++);
- }
+ return stack.getTopTypes(count);
+ }
+
+ int[] getLocalLoadsOnStack(final int from, final int to) {
+ return stack.getLocalLoads(from, to);
+ }
- return types;
+ int getStackSize() {
+ return stack.size();
+ }
+
+ int getFirstTemp() {
+ return stack.firstTemp;
+ }
+
+ int getUsedSlotsWithLiveTemporaries() {
+ return stack.getUsedSlotsWithLiveTemporaries();
}
/**
@@ -1693,7 +2113,14 @@ public class MethodEmitter implements Emitter {
int pos = 0;
for (int i = argCount - 1; i >= 0; i--) {
- paramTypes[i] = stack.peek(pos++);
+ Type pt = stack.peek(pos++);
+ // "erase" specific ScriptObject subtype info - except for NativeArray.
+ // NativeArray is used for array/List/Deque conversion for Java calls.
+ if (ScriptObject.class.isAssignableFrom(pt.getTypeClass()) &&
+ !NativeArray.class.isAssignableFrom(pt.getTypeClass())) {
+ pt = Type.SCRIPT_OBJECT;
+ }
+ paramTypes[i] = pt;
}
final String descriptor = Type.getMethodDescriptor(returnType, paramTypes);
for (int i = 0; i < argCount; i++) {
@@ -1703,6 +2130,20 @@ public class MethodEmitter implements Emitter {
return descriptor;
}
+ MethodEmitter invalidateSpecialName(final String name) {
+ switch (name) {
+ case "apply":
+ case "call":
+ debug("invalidate_name", "name=", name);
+ load("Function");
+ invoke(ScriptRuntime.INVALIDATE_RESERVED_BUILTIN_NAME);
+ break;
+ default:
+ break;
+ }
+ return this;
+ }
+
/**
* Generate a dynamic new
*
@@ -1712,6 +2153,7 @@ public class MethodEmitter implements Emitter {
* @return the method emitter
*/
MethodEmitter dynamicNew(final int argCount, final int flags) {
+ assert !isOptimistic(flags);
debug("dynamic_new", "argcount=", argCount);
final String signature = getDynamicSignature(Type.OBJECT, argCount);
method.visitInvokeDynamicInsn("dyn:new", signature, LINKERBOOTSTRAP, flags);
@@ -1738,6 +2180,14 @@ public class MethodEmitter implements Emitter {
return this;
}
+ MethodEmitter dynamicArrayPopulatorCall(final int argCount, final int startIndex) {
+ debug("populate_array", "args=", argCount, "startIndex=", startIndex);
+ final String signature = getDynamicSignature(Type.OBJECT_ARRAY, argCount);
+ method.visitInvokeDynamicInsn("populateArray", signature, POPULATE_ARRAY_BOOTSTRAP, startIndex);
+ pushType(Type.OBJECT_ARRAY);
+ return this;
+ }
+
/**
* Generate a dynamic call for a runtime node
*
@@ -1764,11 +2214,11 @@ public class MethodEmitter implements Emitter {
* @param name name of property
* @param flags call site flags
* @param isMethod should it prefer retrieving methods
- *
+ * @param isIndex is this an index operation?
* @return the method emitter
*/
- MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod) {
- debug("dynamic_get", name, valueType);
+ MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod, final boolean isIndex) {
+ debug("dynamic_get", name, valueType, getProgramPoint(flags));
Type type = valueType;
if (type.isObject() || type.isBoolean()) {
@@ -1776,11 +2226,10 @@ public class MethodEmitter implements Emitter {
}
popType(Type.SCOPE);
- method.visitInvokeDynamicInsn((isMethod ? "dyn:getMethod|getProp|getElem:" : "dyn:getProp|getElem|getMethod:") +
- NameCodec.encode(name), Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags);
+ method.visitInvokeDynamicInsn(dynGetOperation(isMethod, isIndex) + ':' + NameCodec.encode(name),
+ Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags);
pushType(type);
-
convert(valueType); //most probably a nop
return this;
@@ -1789,12 +2238,13 @@ public class MethodEmitter implements Emitter {
/**
* Generate dynamic setter. Pop receiver and property from stack.
*
- * @param valueType the type of the value to set
- * @param name name of property
- * @param flags call site flags
+ * @param name name of property
+ * @param flags call site flags
+ * @param isIndex is this an index operation?
*/
- void dynamicSet(final String name, final int flags) {
- debug("dynamic_set", name, peekType());
+ void dynamicSet(final String name, final int flags, final boolean isIndex) {
+ assert !isOptimistic(flags);
+ debug("dynamic_set", name, peekType());
Type type = peekType();
if (type.isObject() || type.isBoolean()) { //promote strings to objects etc
@@ -1804,7 +2254,8 @@ public class MethodEmitter implements Emitter {
popType(type);
popType(Type.SCOPE);
- method.visitInvokeDynamicInsn("dyn:setProp|setElem:" + NameCodec.encode(name), methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags);
+ method.visitInvokeDynamicInsn(dynSetOperation(isIndex) + ':' + NameCodec.encode(name),
+ methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags);
}
/**
@@ -1818,7 +2269,8 @@ public class MethodEmitter implements Emitter {
* @return the method emitter
*/
MethodEmitter dynamicGetIndex(final Type result, final int flags, final boolean isMethod) {
- debug("dynamic_get_index", peekType(1), "[", peekType(), "]");
+ assert result.getTypeClass().isPrimitive() || result.getTypeClass() == Object.class;
+ debug("dynamic_get_index", peekType(1), "[", peekType(), "]", getProgramPoint(flags));
Type resultType = result;
if (result.isBoolean()) {
@@ -1836,8 +2288,7 @@ public class MethodEmitter implements Emitter {
final String signature = Type.getMethodDescriptor(resultType, Type.OBJECT /*e.g STRING->OBJECT*/, index);
- method.visitInvokeDynamicInsn(isMethod ? "dyn:getMethod|getElem|getProp" : "dyn:getElem|getProp|getMethod",
- signature, LINKERBOOTSTRAP, flags);
+ method.visitInvokeDynamicInsn(dynGetOperation(isMethod, true), signature, LINKERBOOTSTRAP, flags);
pushType(resultType);
if (result.isBoolean()) {
@@ -1847,6 +2298,13 @@ public class MethodEmitter implements Emitter {
return this;
}
+ private static String getProgramPoint(final int flags) {
+ if((flags & CALLSITE_OPTIMISTIC) == 0) {
+ return "";
+ }
+ return "pp=" + String.valueOf((flags & (-1 << CALLSITE_PROGRAM_POINT_SHIFT)) >> CALLSITE_PROGRAM_POINT_SHIFT);
+ }
+
/**
* Dynamic setter for indexed structures. Pop value, index and receiver from
* stack, generate appropriate signature based on types
@@ -1854,6 +2312,7 @@ public class MethodEmitter implements Emitter {
* @param flags call site flags for setter
*/
void dynamicSetIndex(final int flags) {
+ assert !isOptimistic(flags);
debug("dynamic_set_index", peekType(2), "[", peekType(1), "] =", peekType());
Type value = peekType();
@@ -2009,10 +2468,9 @@ public class MethodEmitter implements Emitter {
* Register line number at a label
*
* @param line line number
- * @param label label
*/
void lineNumber(final int line) {
- if (env._debug_lines) {
+ if (context.getEnv()._debug_lines) {
debug_label("[LINE]", line);
final jdk.internal.org.objectweb.asm.Label l = new jdk.internal.org.objectweb.asm.Label();
method.visitLabel(l);
@@ -2020,6 +2478,56 @@ public class MethodEmitter implements Emitter {
}
}
+ void beforeJoinPoint(final JoinPredecessor joinPredecessor) {
+ LocalVariableConversion next = joinPredecessor.getLocalVariableConversion();
+ while(next != null) {
+ final Symbol symbol = next.getSymbol();
+ if(next.isLive()) {
+ emitLocalVariableConversion(next, true);
+ } else {
+ markDeadLocalVariable(symbol);
+ }
+ next = next.getNext();
+ }
+ }
+
+ void beforeTry(final TryNode tryNode, final Label recovery) {
+ LocalVariableConversion next = tryNode.getLocalVariableConversion();
+ while(next != null) {
+ if(next.isLive()) {
+ final Type to = emitLocalVariableConversion(next, false);
+ recovery.getStack().onLocalStore(to, next.getSymbol().getSlot(to), true);
+ }
+ next = next.getNext();
+ }
+ }
+
+ private static String dynGetOperation(final boolean isMethod, final boolean isIndex) {
+ if (isMethod) {
+ return isIndex ? "dyn:getMethod|getElem|getProp" : "dyn:getMethod|getProp|getElem";
+ } else {
+ return isIndex ? "dyn:getElem|getProp|getMethod" : "dyn:getProp|getElem|getMethod";
+ }
+ }
+
+ private static String dynSetOperation(final boolean isIndex) {
+ return isIndex ? "dyn:setElem|setProp" : "dyn:setProp|setElem";
+ }
+
+ private Type emitLocalVariableConversion(final LocalVariableConversion conversion, final boolean onlySymbolLiveValue) {
+ final Type from = conversion.getFrom();
+ final Type to = conversion.getTo();
+ final Symbol symbol = conversion.getSymbol();
+ assert symbol.isBytecodeLocal();
+ if(from == Type.UNDEFINED) {
+ loadUndefined(to);
+ } else {
+ load(symbol, from).convert(to);
+ }
+ store(symbol, to, onlySymbolLiveValue);
+ return to;
+ }
+
/*
* Debugging below
*/
@@ -2086,12 +2594,55 @@ public class MethodEmitter implements Emitter {
*
* @param args debug information to print
*/
+ @SuppressWarnings("unused")
private void debug(final Object... args) {
- if (DEBUG) {
+ if (debug) {
debug(30, args);
}
}
+ private void debug(final String arg) {
+ if (debug) {
+ debug(30, arg);
+ }
+ }
+
+ private void debug(final Object arg0, final Object arg1) {
+ if (debug) {
+ debug(30, new Object[] { arg0, arg1 });
+ }
+ }
+
+ private void debug(final Object arg0, final Object arg1, final Object arg2) {
+ if (debug) {
+ debug(30, new Object[] { arg0, arg1, arg2 });
+ }
+ }
+
+ private void debug(final Object arg0, final Object arg1, final Object arg2, final Object arg3) {
+ if (debug) {
+ debug(30, new Object[] { arg0, arg1, arg2, arg3 });
+ }
+ }
+
+ private void debug(final Object arg0, final Object arg1, final Object arg2, final Object arg3, final Object arg4) {
+ if (debug) {
+ debug(30, new Object[] { arg0, arg1, arg2, arg3, arg4 });
+ }
+ }
+
+ private void debug(final Object arg0, final Object arg1, final Object arg2, final Object arg3, final Object arg4, final Object arg5) {
+ if (debug) {
+ debug(30, new Object[] { arg0, arg1, arg2, arg3, arg4, arg5 });
+ }
+ }
+
+ private void debug(final Object arg0, final Object arg1, final Object arg2, final Object arg3, final Object arg4, final Object arg5, final Object arg6) {
+ if (debug) {
+ debug(30, new Object[] { arg0, arg1, arg2, arg3, arg4, arg5, arg6 });
+ }
+ }
+
/**
* Debug function that outputs generated bytecode and stack contents
* for a label - indentation is currently the only thing that differs
@@ -2099,13 +2650,13 @@ public class MethodEmitter implements Emitter {
* @param args debug information to print
*/
private void debug_label(final Object... args) {
- if (DEBUG) {
+ if (debug) {
debug(22, args);
}
}
private void debug(final int padConstant, final Object... args) {
- if (DEBUG) {
+ if (debug) {
final StringBuilder sb = new StringBuilder();
int pad;
@@ -2118,7 +2669,7 @@ public class MethodEmitter implements Emitter {
pad--;
}
- if (stack != null && !stack.isEmpty()) {
+ if (isReachable() && !stack.isEmpty()) {
sb.append("{");
sb.append(stack.size());
sb.append(":");
@@ -2148,7 +2699,10 @@ public class MethodEmitter implements Emitter {
} else {
sb.append(t.getDescriptor());
}
-
+ final int loadIndex = stack.localLoads[stack.sp - 1 - pos];
+ if(loadIndex != Label.Stack.NON_LOAD) {
+ sb.append('(').append(loadIndex).append(')');
+ }
if (pos + 1 < stack.size()) {
sb.append(' ');
}
@@ -2168,10 +2722,10 @@ public class MethodEmitter implements Emitter {
sb.append(' ');
}
- if (env != null) { //early bootstrap code doesn't have inited context yet
- LOG.info(sb);
+ if (context.getEnv() != null) { //early bootstrap code doesn't have inited context yet
+ log.info(sb);
if (DEBUG_TRACE_LINE == linePrefix) {
- new Throwable().printStackTrace(LOG.getOutputStream());
+ new Throwable().printStackTrace(log.getOutputStream());
}
}
}
@@ -2189,8 +2743,17 @@ public class MethodEmitter implements Emitter {
return hasReturn;
}
- List<Label> getExternalTargets() {
- return null;
+ /**
+ * Invoke to enforce assertions preventing load from a local variable slot that's known to not have been written to.
+ * Used by CodeGenerator, as it strictly enforces tracking of stores. Simpler uses of MethodEmitter, e.g. those
+ * for creating initializers for structure classes, array getters, etc. don't have strict tracking of stores,
+ * therefore they would fail if they had this assertion turned on.
+ */
+ void setPreventUndefinedLoad() {
+ this.preventUndefinedLoad = true;
}
+ private static boolean isOptimistic(final int flags) {
+ return (flags & CALLSITE_OPTIMISTIC) != 0;
+ }
}