/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.codegen; import static jdk.internal.org.objectweb.asm.Opcodes.ATHROW; import static jdk.internal.org.objectweb.asm.Opcodes.CHECKCAST; import static jdk.internal.org.objectweb.asm.Opcodes.DUP2; import static jdk.internal.org.objectweb.asm.Opcodes.GETFIELD; import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.GOTO; import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.IFEQ; import static jdk.internal.org.objectweb.asm.Opcodes.IFGE; import static jdk.internal.org.objectweb.asm.Opcodes.IFGT; import static jdk.internal.org.objectweb.asm.Opcodes.IFLE; import static jdk.internal.org.objectweb.asm.Opcodes.IFLT; import static jdk.internal.org.objectweb.asm.Opcodes.IFNE; import static jdk.internal.org.objectweb.asm.Opcodes.IFNONNULL; 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; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static jdk.internal.org.objectweb.asm.Opcodes.NEW; import static jdk.internal.org.objectweb.asm.Opcodes.PUTFIELD; import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS_DEBUGGER; import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; import static jdk.nashorn.internal.codegen.CompilerConstants.className; 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; import jdk.nashorn.internal.codegen.ClassEmitter.Flag; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.codegen.CompilerConstants.FieldAccess; import jdk.nashorn.internal.codegen.types.ArrayType; import jdk.nashorn.internal.codegen.types.BitwiseType; 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.JoinPredecessor; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LocalVariableConversion; 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.JSType; 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; /** * This is the main function responsible for emitting method code * in a class. It maintains a type stack and keeps track of control * flow to make sure that the registered instructions don't violate * byte code verification. * * Running Nashorn with -ea will assert as soon as a type stack * becomes corrupt, for easier debugging * * Running Nashorn with -Dnashorn.codegen.debug=true will print * all generated bytecode and labels to stderr, for easier debugging, * including bytecode stack contents */ public class MethodEmitter { /** The ASM MethodVisitor we are plugged into */ private final MethodVisitor method; /** 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; private boolean preventUndefinedLoad; /** * Map of live local variable definitions. */ private final Map 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 final DebugLogger log; private final boolean debug; /** dump stack on a particular line, or -1 if disabled */ private static final int DEBUG_TRACE_LINE; static { final String tl = Options.getStringProperty("nashorn.codegen.debug.trace", "-1"); int line = -1; try { line = Integer.parseInt(tl); } catch (final NumberFormatException e) { //fallthru } DEBUG_TRACE_LINE = line; } /** Bootstrap for normal indy:s */ private static final Handle LINKERBOOTSTRAP = new Handle(H_INVOKESTATIC, Bootstrap.BOOTSTRAP.className(), Bootstrap.BOOTSTRAP.name(), Bootstrap.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 * * @param classEmitter the class emitter weaving the class this method is in * @param method a method visitor */ MethodEmitter(final ClassEmitter classEmitter, final MethodVisitor method) { this(classEmitter, method, null); } /** * Constructor - internal use from ClassEmitter only * @see ClassEmitter#method * * @param classEmitter the class emitter weaving the class this method is in * @param method a method visitor * @param functionNode a function node representing this method */ MethodEmitter(final ClassEmitter classEmitter, final MethodVisitor method, final FunctionNode functionNode) { 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(); } /** * Begin a method */ public void begin() { classEmitter.beginMethod(this); newStack(); method.visitCode(); } /** * End a method */ public void end() { method.visitMaxs(0, 0); method.visitEnd(); classEmitter.endMethod(this); } boolean isReachable() { return stack != null; } private void doesNotContinueSequentially() { stack = null; } private void newStack() { stack = new Label.Stack(); } @Override public String toString() { return "methodEmitter: " + (functionNode == null ? method : functionNode.getName()).toString() + ' ' + Debug.id(this); } /** * Push a type to the existing stack * @param type the type */ void pushType(final Type type) { if (type != null) { stack.push(type); } } /** * Pop a type from the existing stack * * @param expected expected type - will assert if wrong * * @return the type that was retrieved */ private Type popType(final Type expected) { final Type type = popType(); assert type.isEquivalentTo(expected) : type + " is not compatible with " + expected; return type; } /** * Pop a type from the existing stack, no matter what it is. * * @return the type */ private Type popType() { return stack.pop(); } /** * 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 = 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). 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 = popType(); if(type == Type.BOOLEAN) { return Type.INT; } assert type == Type.INT; return (BitwiseType)type; } /** * Pop a type from the existing stack, ensuring that it is an array type, * assert if not * * @return the type */ private ArrayType popArray() { final Type type = popType(); assert type.isArray() : type; return (ArrayType)type; } /** * Peek a given number of slots from the top of the stack and return the * type in that slot * * @param pos the number of positions from the top, 0 is the top element * * @return the type at position "pos" on the stack */ final Type peekType(final int pos) { return stack.peek(pos); } /** * Peek at the type at the top of the stack * * @return the type at the top of the stack */ final Type peekType() { return stack.peek(); } /** * Generate code a for instantiating a new object and push the * 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, final Type type) { debug("new", classDescriptor); method.visitTypeInsn(NEW, classDescriptor); pushType(type); return this; } /** * Generate code a for instantiating a new object and push the * object type on the stack * * @param clazz class type to instatiate * * @return the method emitter */ MethodEmitter _new(final Class clazz) { return _new(className(clazz), Type.typeFor(clazz)); } /** * Generate code to call the empty constructor for a class * * @param clazz class type to instatiate * * @return the method emitter */ MethodEmitter newInstance(final Class clazz) { return invoke(constructorNoLookup(clazz)); } /** * Perform a dup, that is, duplicate the top element and * push the duplicate down a given number of positions * on the stack. This is totally type agnostic. * * @param depth the depth on which to put the copy * * @return the method emitter, or null if depth is illegal and * has no instruction equivalent. */ MethodEmitter dup(final int depth) { if (peekType().dup(method, depth) == null) { return null; } debug("dup", depth); switch (depth) { 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: assert false : "illegal dup depth = " + depth; return null; } return this; } /** * Perform a dup2, that is, duplicate the top element if it * is a category 2 type, or two top elements if they are category * 1 types, and push them on top of the stack * * @return the method emitter */ MethodEmitter dup2() { debug("dup2"); if (peekType().isCategory2()) { final int l0 = stack.getTopLocalLoad(); pushType(peekType()); stack.markLocalLoad(l0); } else { 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; } /** * Duplicate the top element on the stack and push it * * @return the method emitter */ MethodEmitter dup() { return dup(0); } /** * Pop the top element of the stack and throw it away * * @return the method emitter */ MethodEmitter pop() { debug("pop", peekType()); popType().pop(method); return this; } /** * Pop the top element of the stack if category 2 type, or the two * top elements of the stack if category 1 types * * @return the method emitter */ MethodEmitter pop2() { if (peekType().isCategory2()) { popType(); } else { get2n(); } return this; } /** * Swap the top two elements of the stack. This is totally * type agnostic and works for all types * * @return the method 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); stack.markLocalLoad(l1); return this; } 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!"; } } /** * 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)); } /** * Create a new string builder, call the constructor and push the instance to the stack. * * @return the method emitter */ MethodEmitter newStringBuilder() { return invoke(constructorNoLookup(StringBuilder.class)).dup(); } /** * Pop a string and a StringBuilder from the top of the stack and call the append * function of the StringBuilder, appending the string. Pushes the StringBuilder to * the stack when finished. * * @return the method emitter */ MethodEmitter stringBuilderAppend() { convert(Type.STRING); return invoke(virtualCallNoLookup(StringBuilder.class, "append", StringBuilder.class, String.class)); } /** * Pops two integer types from the stack, performs a bitwise and and pushes * the result * * @return the method emitter */ MethodEmitter and() { debug("and"); pushType(get2i().and(method)); return this; } /** * Pops two integer types from the stack, performs a bitwise or and pushes * the result * * @return the method emitter */ MethodEmitter or() { debug("or"); pushType(get2i().or(method)); return this; } /** * Pops two integer types from the stack, performs a bitwise xor and pushes * the result * * @return the method emitter */ MethodEmitter xor() { debug("xor"); pushType(get2i().xor(method)); return this; } /** * Pops two integer types from the stack, performs a bitwise logic shift right and pushes * the result. The shift count, the first element, must be INT. * * @return the method emitter */ MethodEmitter shr() { debug("shr"); popInteger(); pushType(popBitwise().shr(method)); return this; } /** * Pops two integer types from the stack, performs a bitwise shift left and and pushes * the result. The shift count, the first element, must be INT. * * @return the method emitter */ MethodEmitter shl() { debug("shl"); popInteger(); pushType(popBitwise().shl(method)); return this; } /** * 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"); popInteger(); pushType(popBitwise().sar(method)); return this; } /** * Pops a numeric type from the stack, negates it and pushes the result * * @return the method emitter */ MethodEmitter neg(final int programPoint) { debug("neg"); pushType(popNumeric().neg(method, programPoint)); return this; } /** * Add label for the start of a catch block and push the exception to the * stack * * @param recovery label pointing to start of catch block */ void _catch(final Label recovery) { // 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