diff options
Diffstat (limited to 'src/jdk/nashorn/internal/codegen/MethodEmitter.java')
-rw-r--r-- | src/jdk/nashorn/internal/codegen/MethodEmitter.java | 963 |
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; + } } |