/* * Copyright (c) 1999, 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 com.sun.tools.javac.jvm; import com.sun.tools.javac.code.*; import com.sun.tools.javac.code.Symbol.*; import com.sun.tools.javac.code.Types.UniqueType; import com.sun.tools.javac.util.*; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import static com.sun.tools.javac.code.TypeTag.BOT; import static com.sun.tools.javac.code.TypeTag.INT; import static com.sun.tools.javac.jvm.ByteCodes.*; import static com.sun.tools.javac.jvm.UninitializedType.*; import static com.sun.tools.javac.jvm.ClassWriter.StackMapTableFrame; /** An internal structure that corresponds to the code attribute of * methods in a classfile. The class also provides some utility operations to * generate bytecode instructions. * *

This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice. */ public class Code { public final boolean debugCode; public final boolean needStackMap; public enum StackMapFormat { NONE, CLDC { Name getAttributeName(Names names) { return names.StackMap; } }, JSR202 { Name getAttributeName(Names names) { return names.StackMapTable; } }; Name getAttributeName(Names names) { return names.empty; } } final Types types; final Symtab syms; /*---------- classfile fields: --------------- */ /** The maximum stack size. */ public int max_stack = 0; /** The maximum number of local variable slots. */ public int max_locals = 0; /** The code buffer. */ public byte[] code = new byte[64]; /** the current code pointer. */ public int cp = 0; /** Check the code against VM spec limits; if * problems report them and return true. */ public boolean checkLimits(DiagnosticPosition pos, Log log) { if (cp > ClassFile.MAX_CODE) { log.error(pos, "limit.code"); return true; } if (max_locals > ClassFile.MAX_LOCALS) { log.error(pos, "limit.locals"); return true; } if (max_stack > ClassFile.MAX_STACK) { log.error(pos, "limit.stack"); return true; } return false; } /** A buffer for expression catch data. Each enter is a vector * of four unsigned shorts. */ ListBuffer catchInfo = new ListBuffer(); /** A buffer for line number information. Each entry is a vector * of two unsigned shorts. */ List lineInfo = List.nil(); // handled in stack fashion /** The CharacterRangeTable */ public CRTable crt; /*---------- internal fields: --------------- */ /** Are we generating code with jumps ≥ 32K? */ public boolean fatcode; /** Code generation enabled? */ private boolean alive = true; /** The current machine state (registers and stack). */ State state; /** Is it forbidden to compactify code, because something is * pointing to current location? */ private boolean fixedPc = false; /** The next available register. */ public int nextreg = 0; /** A chain for jumps to be resolved before the next opcode is emitted. * We do this lazily to avoid jumps to jumps. */ Chain pendingJumps = null; /** The position of the currently statement, if we are at the * start of this statement, NOPOS otherwise. * We need this to emit line numbers lazily, which we need to do * because of jump-to-jump optimization. */ int pendingStatPos = Position.NOPOS; /** Set true when a stackMap is needed at the current PC. */ boolean pendingStackMap = false; /** The stack map format to be generated. */ StackMapFormat stackMap; /** Switch: emit variable debug info. */ boolean varDebugInfo; /** Switch: emit line number info. */ boolean lineDebugInfo; /** Emit line number info if map supplied */ Position.LineMap lineMap; /** The constant pool of the current class. */ final Pool pool; final MethodSymbol meth; /** Construct a code object, given the settings of the fatcode, * debugging info switches and the CharacterRangeTable. */ public Code(MethodSymbol meth, boolean fatcode, Position.LineMap lineMap, boolean varDebugInfo, StackMapFormat stackMap, boolean debugCode, CRTable crt, Symtab syms, Types types, Pool pool) { this.meth = meth; this.fatcode = fatcode; this.lineMap = lineMap; this.lineDebugInfo = lineMap != null; this.varDebugInfo = varDebugInfo; this.crt = crt; this.syms = syms; this.types = types; this.debugCode = debugCode; this.stackMap = stackMap; switch (stackMap) { case CLDC: case JSR202: this.needStackMap = true; break; default: this.needStackMap = false; } state = new State(); lvar = new LocalVar[20]; this.pool = pool; } /* ************************************************************************** * Typecodes & related stuff ****************************************************************************/ /** Given a type, return its type code (used implicitly in the * JVM architecture). */ public static int typecode(Type type) { switch (type.getTag()) { case BYTE: return BYTEcode; case SHORT: return SHORTcode; case CHAR: return CHARcode; case INT: return INTcode; case LONG: return LONGcode; case FLOAT: return FLOATcode; case DOUBLE: return DOUBLEcode; case BOOLEAN: return BYTEcode; case VOID: return VOIDcode; case CLASS: case ARRAY: case METHOD: case BOT: case TYPEVAR: case UNINITIALIZED_THIS: case UNINITIALIZED_OBJECT: return OBJECTcode; default: throw new AssertionError("typecode " + type.getTag()); } } /** Collapse type code for subtypes of int to INTcode. */ public static int truncate(int tc) { switch (tc) { case BYTEcode: case SHORTcode: case CHARcode: return INTcode; default: return tc; } } /** The width in bytes of objects of the type. */ public static int width(int typecode) { switch (typecode) { case LONGcode: case DOUBLEcode: return 2; case VOIDcode: return 0; default: return 1; } } public static int width(Type type) { return type == null ? 1 : width(typecode(type)); } /** The total width taken up by a vector of objects. */ public static int width(List types) { int w = 0; for (List l = types; l.nonEmpty(); l = l.tail) w = w + width(l.head); return w; } /** Given a type, return its code for allocating arrays of that type. */ public static int arraycode(Type type) { switch (type.getTag()) { case BYTE: return 8; case BOOLEAN: return 4; case SHORT: return 9; case CHAR: return 5; case INT: return 10; case LONG: return 11; case FLOAT: return 6; case DOUBLE: return 7; case CLASS: return 0; case ARRAY: return 1; default: throw new AssertionError("arraycode " + type); } } /* ************************************************************************** * Emit code ****************************************************************************/ /** The current output code pointer. */ public int curPc() { if (pendingJumps != null) resolvePending(); if (pendingStatPos != Position.NOPOS) markStatBegin(); fixedPc = true; return cp; } /** Emit a byte of code. */ private void emit1(int od) { if (!alive) return; code = ArrayUtils.ensureCapacity(code, cp); code[cp++] = (byte)od; } /** Emit two bytes of code. */ private void emit2(int od) { if (!alive) return; if (cp + 2 > code.length) { emit1(od >> 8); emit1(od); } else { code[cp++] = (byte)(od >> 8); code[cp++] = (byte)od; } } /** Emit four bytes of code. */ public void emit4(int od) { if (!alive) return; if (cp + 4 > code.length) { emit1(od >> 24); emit1(od >> 16); emit1(od >> 8); emit1(od); } else { code[cp++] = (byte)(od >> 24); code[cp++] = (byte)(od >> 16); code[cp++] = (byte)(od >> 8); code[cp++] = (byte)od; } } /** Emit an opcode. */ private void emitop(int op) { if (pendingJumps != null) resolvePending(); if (alive) { if (pendingStatPos != Position.NOPOS) markStatBegin(); if (pendingStackMap) { pendingStackMap = false; emitStackMap(); } if (debugCode) System.err.println("emit@" + cp + " stack=" + state.stacksize + ": " + mnem(op)); emit1(op); } } void postop() { Assert.check(alive || state.stacksize == 0); } /** Emit a ldc (or ldc_w) instruction, taking into account operand size */ public void emitLdc(int od) { if (od <= 255) { emitop1(ldc1, od); } else { emitop2(ldc2, od); } } /** Emit a multinewarray instruction. */ public void emitMultianewarray(int ndims, int type, Type arrayType) { emitop(multianewarray); if (!alive) return; emit2(type); emit1(ndims); state.pop(ndims); state.push(arrayType); } /** Emit newarray. */ public void emitNewarray(int elemcode, Type arrayType) { emitop(newarray); if (!alive) return; emit1(elemcode); state.pop(1); // count state.push(arrayType); } /** Emit anewarray. */ public void emitAnewarray(int od, Type arrayType) { emitop(anewarray); if (!alive) return; emit2(od); state.pop(1); state.push(arrayType); } /** Emit an invokeinterface instruction. */ public void emitInvokeinterface(int meth, Type mtype) { int argsize = width(mtype.getParameterTypes()); emitop(invokeinterface); if (!alive) return; emit2(meth); emit1(argsize + 1); emit1(0); state.pop(argsize + 1); state.push(mtype.getReturnType()); } /** Emit an invokespecial instruction. */ public void emitInvokespecial(int meth, Type mtype) { int argsize = width(mtype.getParameterTypes()); emitop(invokespecial); if (!alive) return; emit2(meth); Symbol sym = (Symbol)pool.pool[meth]; state.pop(argsize); if (sym.isConstructor()) state.markInitialized((UninitializedType)state.peek()); state.pop(1); state.push(mtype.getReturnType()); } /** Emit an invokestatic instruction. */ public void emitInvokestatic(int meth, Type mtype) { int argsize = width(mtype.getParameterTypes()); emitop(invokestatic); if (!alive) return; emit2(meth); state.pop(argsize); state.push(mtype.getReturnType()); } /** Emit an invokevirtual instruction. */ public void emitInvokevirtual(int meth, Type mtype) { int argsize = width(mtype.getParameterTypes()); emitop(invokevirtual); if (!alive) return; emit2(meth); state.pop(argsize + 1); state.push(mtype.getReturnType()); } /** Emit an invokedynamic instruction. */ public void emitInvokedynamic(int desc, Type mtype) { // N.B. this format is under consideration by the JSR 292 EG int argsize = width(mtype.getParameterTypes()); int prevPos = pendingStatPos; try { //disable line number generation (we could have used 'emit1', that //bypasses stackmap generation - which is needed for indy calls) pendingStatPos = Position.NOPOS; emitop(invokedynamic); } finally { pendingStatPos = prevPos; } if (!alive) return; emit2(desc); emit2(0); state.pop(argsize); state.push(mtype.getReturnType()); } /** Emit an opcode with no operand field. */ public void emitop0(int op) { emitop(op); if (!alive) return; switch (op) { case aaload: { state.pop(1);// index Type a = state.stack[state.stacksize-1]; state.pop(1); //sometimes 'null type' is treated as a one-dimensional array type //see Gen.visitLiteral - we should handle this case accordingly Type stackType = a.hasTag(BOT) ? syms.objectType : types.erasure(types.elemtype(a)); state.push(stackType); } break; case goto_: markDead(); break; case nop: case ineg: case lneg: case fneg: case dneg: break; case aconst_null: state.push(syms.botType); break; case iconst_m1: case iconst_0: case iconst_1: case iconst_2: case iconst_3: case iconst_4: case iconst_5: case iload_0: case iload_1: case iload_2: case iload_3: state.push(syms.intType); break; case lconst_0: case lconst_1: case lload_0: case lload_1: case lload_2: case lload_3: state.push(syms.longType); break; case fconst_0: case fconst_1: case fconst_2: case fload_0: case fload_1: case fload_2: case fload_3: state.push(syms.floatType); break; case dconst_0: case dconst_1: case dload_0: case dload_1: case dload_2: case dload_3: state.push(syms.doubleType); break; case aload_0: state.push(lvar[0].sym.type); break; case aload_1: state.push(lvar[1].sym.type); break; case aload_2: state.push(lvar[2].sym.type); break; case aload_3: state.push(lvar[3].sym.type); break; case iaload: case baload: case caload: case saload: state.pop(2); state.push(syms.intType); break; case laload: state.pop(2); state.push(syms.longType); break; case faload: state.pop(2); state.push(syms.floatType); break; case daload: state.pop(2); state.push(syms.doubleType); break; case istore_0: case istore_1: case istore_2: case istore_3: case fstore_0: case fstore_1: case fstore_2: case fstore_3: case astore_0: case astore_1: case astore_2: case astore_3: case pop: case lshr: case lshl: case lushr: state.pop(1); break; case areturn: case ireturn: case freturn: Assert.check(state.nlocks == 0); state.pop(1); markDead(); break; case athrow: state.pop(1); markDead(); break; case lstore_0: case lstore_1: case lstore_2: case lstore_3: case dstore_0: case dstore_1: case dstore_2: case dstore_3: case pop2: state.pop(2); break; case lreturn: case dreturn: Assert.check(state.nlocks == 0); state.pop(2); markDead(); break; case dup: state.push(state.stack[state.stacksize-1]); break; case return_: Assert.check(state.nlocks == 0); markDead(); break; case arraylength: state.pop(1); state.push(syms.intType); break; case isub: case iadd: case imul: case idiv: case imod: case ishl: case ishr: case iushr: case iand: case ior: case ixor: state.pop(1); // state.pop(1); // state.push(syms.intType); break; case aastore: state.pop(3); break; case land: case lor: case lxor: case lmod: case ldiv: case lmul: case lsub: case ladd: state.pop(2); break; case lcmp: state.pop(4); state.push(syms.intType); break; case l2i: state.pop(2); state.push(syms.intType); break; case i2l: state.pop(1); state.push(syms.longType); break; case i2f: state.pop(1); state.push(syms.floatType); break; case i2d: state.pop(1); state.push(syms.doubleType); break; case l2f: state.pop(2); state.push(syms.floatType); break; case l2d: state.pop(2); state.push(syms.doubleType); break; case f2i: state.pop(1); state.push(syms.intType); break; case f2l: state.pop(1); state.push(syms.longType); break; case f2d: state.pop(1); state.push(syms.doubleType); break; case d2i: state.pop(2); state.push(syms.intType); break; case d2l: state.pop(2); state.push(syms.longType); break; case d2f: state.pop(2); state.push(syms.floatType); break; case tableswitch: case lookupswitch: state.pop(1); // the caller is responsible for patching up the state break; case dup_x1: { Type val1 = state.pop1(); Type val2 = state.pop1(); state.push(val1); state.push(val2); state.push(val1); break; } case bastore: state.pop(3); break; case int2byte: case int2char: case int2short: break; case fmul: case fadd: case fsub: case fdiv: case fmod: state.pop(1); break; case castore: case iastore: case fastore: case sastore: state.pop(3); break; case lastore: case dastore: state.pop(4); break; case dup2: if (state.stack[state.stacksize-1] != null) { Type value1 = state.pop1(); Type value2 = state.pop1(); state.push(value2); state.push(value1); state.push(value2); state.push(value1); } else { Type value = state.pop2(); state.push(value); state.push(value); } break; case dup2_x1: if (state.stack[state.stacksize-1] != null) { Type value1 = state.pop1(); Type value2 = state.pop1(); Type value3 = state.pop1(); state.push(value2); state.push(value1); state.push(value3); state.push(value2); state.push(value1); } else { Type value1 = state.pop2(); Type value2 = state.pop1(); state.push(value1); state.push(value2); state.push(value1); } break; case dup2_x2: if (state.stack[state.stacksize-1] != null) { Type value1 = state.pop1(); Type value2 = state.pop1(); if (state.stack[state.stacksize-1] != null) { // form 1 Type value3 = state.pop1(); Type value4 = state.pop1(); state.push(value2); state.push(value1); state.push(value4); state.push(value3); state.push(value2); state.push(value1); } else { // form 3 Type value3 = state.pop2(); state.push(value2); state.push(value1); state.push(value3); state.push(value2); state.push(value1); } } else { Type value1 = state.pop2(); if (state.stack[state.stacksize-1] != null) { // form 2 Type value2 = state.pop1(); Type value3 = state.pop1(); state.push(value1); state.push(value3); state.push(value2); state.push(value1); } else { // form 4 Type value2 = state.pop2(); state.push(value1); state.push(value2); state.push(value1); } } break; case dup_x2: { Type value1 = state.pop1(); if (state.stack[state.stacksize-1] != null) { // form 1 Type value2 = state.pop1(); Type value3 = state.pop1(); state.push(value1); state.push(value3); state.push(value2); state.push(value1); } else { // form 2 Type value2 = state.pop2(); state.push(value1); state.push(value2); state.push(value1); } } break; case fcmpl: case fcmpg: state.pop(2); state.push(syms.intType); break; case dcmpl: case dcmpg: state.pop(4); state.push(syms.intType); break; case swap: { Type value1 = state.pop1(); Type value2 = state.pop1(); state.push(value1); state.push(value2); break; } case dadd: case dsub: case dmul: case ddiv: case dmod: state.pop(2); break; case ret: markDead(); break; case wide: // must be handled by the caller. return; case monitorenter: case monitorexit: state.pop(1); break; default: throw new AssertionError(mnem(op)); } postop(); } /** Emit an opcode with a one-byte operand field. */ public void emitop1(int op, int od) { emitop(op); if (!alive) return; emit1(od); switch (op) { case bipush: state.push(syms.intType); break; case ldc1: state.push(typeForPool(pool.pool[od])); break; default: throw new AssertionError(mnem(op)); } postop(); } /** The type of a constant pool entry. */ private Type typeForPool(Object o) { if (o instanceof Integer) return syms.intType; if (o instanceof Float) return syms.floatType; if (o instanceof String) return syms.stringType; if (o instanceof Long) return syms.longType; if (o instanceof Double) return syms.doubleType; if (o instanceof ClassSymbol) return syms.classType; if (o instanceof Pool.MethodHandle) return syms.methodHandleType; if (o instanceof UniqueType) return typeForPool(((UniqueType)o).type); if (o instanceof Type) { Type ty = ((Type)o).unannotatedType(); if (ty instanceof Type.ArrayType) return syms.classType; if (ty instanceof Type.MethodType) return syms.methodTypeType; } throw new AssertionError("Invalid type of constant pool entry: " + o.getClass()); } /** Emit an opcode with a one-byte operand field; * widen if field does not fit in a byte. */ public void emitop1w(int op, int od) { if (od > 0xFF) { emitop(wide); emitop(op); emit2(od); } else { emitop(op); emit1(od); } if (!alive) return; switch (op) { case iload: state.push(syms.intType); break; case lload: state.push(syms.longType); break; case fload: state.push(syms.floatType); break; case dload: state.push(syms.doubleType); break; case aload: state.push(lvar[od].sym.type); break; case lstore: case dstore: state.pop(2); break; case istore: case fstore: case astore: state.pop(1); break; case ret: markDead(); break; default: throw new AssertionError(mnem(op)); } postop(); } /** Emit an opcode with two one-byte operand fields; * widen if either field does not fit in a byte. */ public void emitop1w(int op, int od1, int od2) { if (od1 > 0xFF || od2 < -128 || od2 > 127) { emitop(wide); emitop(op); emit2(od1); emit2(od2); } else { emitop(op); emit1(od1); emit1(od2); } if (!alive) return; switch (op) { case iinc: break; default: throw new AssertionError(mnem(op)); } } /** Emit an opcode with a two-byte operand field. */ public void emitop2(int op, int od) { emitop(op); if (!alive) return; emit2(od); switch (op) { case getstatic: state.push(((Symbol)(pool.pool[od])).erasure(types)); break; case putstatic: state.pop(((Symbol)(pool.pool[od])).erasure(types)); break; case new_: Symbol sym; if (pool.pool[od] instanceof UniqueType) { // Required by change in Gen.makeRef to allow // annotated types. // TODO: is this needed anywhere else? sym = ((UniqueType)(pool.pool[od])).type.tsym; } else { sym = (Symbol)(pool.pool[od]); } state.push(uninitializedObject(sym.erasure(types), cp-3)); break; case sipush: state.push(syms.intType); break; case if_acmp_null: case if_acmp_nonnull: case ifeq: case ifne: case iflt: case ifge: case ifgt: case ifle: state.pop(1); break; case if_icmpeq: case if_icmpne: case if_icmplt: case if_icmpge: case if_icmpgt: case if_icmple: case if_acmpeq: case if_acmpne: state.pop(2); break; case goto_: markDead(); break; case putfield: state.pop(((Symbol)(pool.pool[od])).erasure(types)); state.pop(1); // object ref break; case getfield: state.pop(1); // object ref state.push(((Symbol)(pool.pool[od])).erasure(types)); break; case checkcast: { state.pop(1); // object ref Object o = pool.pool[od]; Type t = (o instanceof Symbol) ? ((Symbol)o).erasure(types) : types.erasure((((UniqueType)o).type)); state.push(t); break; } case ldc2w: state.push(typeForPool(pool.pool[od])); break; case instanceof_: state.pop(1); state.push(syms.intType); break; case ldc2: state.push(typeForPool(pool.pool[od])); break; case jsr: break; default: throw new AssertionError(mnem(op)); } // postop(); } /** Emit an opcode with a four-byte operand field. */ public void emitop4(int op, int od) { emitop(op); if (!alive) return; emit4(od); switch (op) { case goto_w: markDead(); break; case jsr_w: break; default: throw new AssertionError(mnem(op)); } // postop(); } /** Align code pointer to next `incr' boundary. */ public void align(int incr) { if (alive) while (cp % incr != 0) emitop0(nop); } /** Place a byte into code at address pc. * Pre: {@literal pc + 1 <= cp }. */ private void put1(int pc, int op) { code[pc] = (byte)op; } /** Place two bytes into code at address pc. * Pre: {@literal pc + 2 <= cp }. */ private void put2(int pc, int od) { // pre: pc + 2 <= cp put1(pc, od >> 8); put1(pc+1, od); } /** Place four bytes into code at address pc. * Pre: {@literal pc + 4 <= cp }. */ public void put4(int pc, int od) { // pre: pc + 4 <= cp put1(pc , od >> 24); put1(pc+1, od >> 16); put1(pc+2, od >> 8); put1(pc+3, od); } /** Return code byte at position pc as an unsigned int. */ private int get1(int pc) { return code[pc] & 0xFF; } /** Return two code bytes at position pc as an unsigned int. */ private int get2(int pc) { return (get1(pc) << 8) | get1(pc+1); } /** Return four code bytes at position pc as an int. */ public int get4(int pc) { // pre: pc + 4 <= cp return (get1(pc) << 24) | (get1(pc+1) << 16) | (get1(pc+2) << 8) | (get1(pc+3)); } /** Is code generation currently enabled? */ public boolean isAlive() { return alive || pendingJumps != null; } /** Switch code generation on/off. */ public void markDead() { alive = false; } /** Declare an entry point; return current code pointer */ public int entryPoint() { int pc = curPc(); alive = true; pendingStackMap = needStackMap; return pc; } /** Declare an entry point with initial state; * return current code pointer */ public int entryPoint(State state) { int pc = curPc(); alive = true; this.state = state.dup(); Assert.check(state.stacksize <= max_stack); if (debugCode) System.err.println("entry point " + state); pendingStackMap = needStackMap; return pc; } /** Declare an entry point with initial state plus a pushed value; * return current code pointer */ public int entryPoint(State state, Type pushed) { int pc = curPc(); alive = true; this.state = state.dup(); Assert.check(state.stacksize <= max_stack); this.state.push(pushed); if (debugCode) System.err.println("entry point " + state); pendingStackMap = needStackMap; return pc; } /************************************************************************** * Stack map generation *************************************************************************/ /** An entry in the stack map. */ static class StackMapFrame { int pc; Type[] locals; Type[] stack; } /** A buffer of cldc stack map entries. */ StackMapFrame[] stackMapBuffer = null; /** A buffer of compressed StackMapTable entries. */ StackMapTableFrame[] stackMapTableBuffer = null; int stackMapBufferSize = 0; /** The last PC at which we generated a stack map. */ int lastStackMapPC = -1; /** The last stack map frame in StackMapTable. */ StackMapFrame lastFrame = null; /** The stack map frame before the last one. */ StackMapFrame frameBeforeLast = null; /** Emit a stack map entry. */ public void emitStackMap() { int pc = curPc(); if (!needStackMap) return; switch (stackMap) { case CLDC: emitCLDCStackMap(pc, getLocalsSize()); break; case JSR202: emitStackMapFrame(pc, getLocalsSize()); break; default: throw new AssertionError("Should have chosen a stackmap format"); } // DEBUG code follows if (debugCode) state.dump(pc); } private int getLocalsSize() { int nextLocal = 0; for (int i=max_locals-1; i>=0; i--) { if (state.defined.isMember(i) && lvar[i] != null) { nextLocal = i + width(lvar[i].sym.erasure(types)); break; } } return nextLocal; } /** Emit a CLDC stack map frame. */ void emitCLDCStackMap(int pc, int localsSize) { if (lastStackMapPC == pc) { // drop existing stackmap at this offset stackMapBuffer[--stackMapBufferSize] = null; } lastStackMapPC = pc; if (stackMapBuffer == null) { stackMapBuffer = new StackMapFrame[20]; } else { stackMapBuffer = ArrayUtils.ensureCapacity(stackMapBuffer, stackMapBufferSize); } StackMapFrame frame = stackMapBuffer[stackMapBufferSize++] = new StackMapFrame(); frame.pc = pc; frame.locals = new Type[localsSize]; for (int i=0; i 1) i++; } } frame.locals = new Type[localCount]; for (int i=0, j=0; i 1) i++; } int stackCount = 0; for (int i=0; i arg_types = ((MethodType)meth.externalType(types)).argtypes; int len = arg_types.length(); int count = 0; if (!meth.isStatic()) { Type thisType = meth.owner.type; frame.locals = new Type[len+1]; if (meth.isConstructor() && thisType != syms.objectType) { frame.locals[count++] = UninitializedType.uninitializedThis(thisType); } else { frame.locals[count++] = types.erasure(thisType); } } else { frame.locals = new Type[len]; } for (Type arg_type : arg_types) { frame.locals[count++] = types.erasure(arg_type); } frame.pc = -1; frame.stack = null; return frame; } /************************************************************************** * Operations having to do with jumps *************************************************************************/ /** A chain represents a list of unresolved jumps. Jump locations * are sorted in decreasing order. */ public static class Chain { /** The position of the jump instruction. */ public final int pc; /** The machine state after the jump instruction. * Invariant: all elements of a chain list have the same stacksize * and compatible stack and register contents. */ Code.State state; /** The next jump in the list. */ public final Chain next; /** Construct a chain from its jump position, stacksize, previous * chain, and machine state. */ public Chain(int pc, Chain next, Code.State state) { this.pc = pc; this.next = next; this.state = state; } } /** Negate a branch opcode. */ public static int negate(int opcode) { if (opcode == if_acmp_null) return if_acmp_nonnull; else if (opcode == if_acmp_nonnull) return if_acmp_null; else return ((opcode + 1) ^ 1) - 1; } /** Emit a jump instruction. * Return code pointer of instruction to be patched. */ public int emitJump(int opcode) { if (fatcode) { if (opcode == goto_ || opcode == jsr) { emitop4(opcode + goto_w - goto_, 0); } else { emitop2(negate(opcode), 8); emitop4(goto_w, 0); alive = true; pendingStackMap = needStackMap; } return cp - 5; } else { emitop2(opcode, 0); return cp - 3; } } /** Emit a branch with given opcode; return its chain. * branch differs from jump in that jsr is treated as no-op. */ public Chain branch(int opcode) { Chain result = null; if (opcode == goto_) { result = pendingJumps; pendingJumps = null; } if (opcode != dontgoto && isAlive()) { result = new Chain(emitJump(opcode), result, state.dup()); fixedPc = fatcode; if (opcode == goto_) alive = false; } return result; } /** Resolve chain to point to given target. */ public void resolve(Chain chain, int target) { boolean changed = false; State newState = state; for (; chain != null; chain = chain.next) { Assert.check(state != chain.state && (target > chain.pc || state.stacksize == 0)); if (target >= cp) { target = cp; } else if (get1(target) == goto_) { if (fatcode) target = target + get4(target + 1); else target = target + get2(target + 1); } if (get1(chain.pc) == goto_ && chain.pc + 3 == target && target == cp && !fixedPc) { // If goto the next instruction, the jump is not needed: // compact the code. cp = cp - 3; target = target - 3; if (chain.next == null) { // This is the only jump to the target. Exit the loop // without setting new state. The code is reachable // from the instruction before goto_. alive = true; break; } } else { if (fatcode) put4(chain.pc + 1, target - chain.pc); else if (target - chain.pc < Short.MIN_VALUE || target - chain.pc > Short.MAX_VALUE) fatcode = true; else put2(chain.pc + 1, target - chain.pc); Assert.check(!alive || chain.state.stacksize == newState.stacksize && chain.state.nlocks == newState.nlocks); } fixedPc = true; if (cp == target) { changed = true; if (debugCode) System.err.println("resolving chain state=" + chain.state); if (alive) { newState = chain.state.join(newState); } else { newState = chain.state; alive = true; } } } Assert.check(!changed || state != newState); if (state != newState) { setDefined(newState.defined); state = newState; pendingStackMap = needStackMap; } } /** Resolve chain to point to current code pointer. */ public void resolve(Chain chain) { Assert.check( !alive || chain==null || state.stacksize == chain.state.stacksize && state.nlocks == chain.state.nlocks); pendingJumps = mergeChains(chain, pendingJumps); } /** Resolve any pending jumps. */ public void resolvePending() { Chain x = pendingJumps; pendingJumps = null; resolve(x, cp); } /** Merge the jumps in of two chains into one. */ public static Chain mergeChains(Chain chain1, Chain chain2) { // recursive merge sort if (chain2 == null) return chain1; if (chain1 == null) return chain2; Assert.check( chain1.state.stacksize == chain2.state.stacksize && chain1.state.nlocks == chain2.state.nlocks); if (chain1.pc < chain2.pc) return new Chain( chain2.pc, mergeChains(chain1, chain2.next), chain2.state); return new Chain( chain1.pc, mergeChains(chain1.next, chain2), chain1.state); } /* ************************************************************************** * Catch clauses ****************************************************************************/ /** Add a catch clause to code. */ public void addCatch( char startPc, char endPc, char handlerPc, char catchType) { catchInfo.append(new char[]{startPc, endPc, handlerPc, catchType}); } public void compressCatchTable() { ListBuffer compressedCatchInfo = ListBuffer.lb(); List handlerPcs = List.nil(); for (char[] catchEntry : catchInfo) { handlerPcs = handlerPcs.prepend((int)catchEntry[2]); } for (char[] catchEntry : catchInfo) { int startpc = catchEntry[0]; int endpc = catchEntry[1]; if (startpc == endpc || (startpc == (endpc - 1) && handlerPcs.contains(startpc))) { continue; } else { compressedCatchInfo.append(catchEntry); } } catchInfo = compressedCatchInfo; } /* ************************************************************************** * Line numbers ****************************************************************************/ /** Add a line number entry. */ public void addLineNumber(char startPc, char lineNumber) { if (lineDebugInfo) { if (lineInfo.nonEmpty() && lineInfo.head[0] == startPc) lineInfo = lineInfo.tail; if (lineInfo.isEmpty() || lineInfo.head[1] != lineNumber) lineInfo = lineInfo.prepend(new char[]{startPc, lineNumber}); } } /** Mark beginning of statement. */ public void statBegin(int pos) { if (pos != Position.NOPOS) { pendingStatPos = pos; } } /** Force stat begin eagerly */ public void markStatBegin() { if (alive && lineDebugInfo) { int line = lineMap.getLineNumber(pendingStatPos); char cp1 = (char)cp; char line1 = (char)line; if (cp1 == cp && line1 == line) addLineNumber(cp1, line1); } pendingStatPos = Position.NOPOS; } /* ************************************************************************** * Simulated VM machine state ****************************************************************************/ class State implements Cloneable { /** The set of registers containing values. */ Bits defined; /** The (types of the) contents of the machine stack. */ Type[] stack; /** The first stack position currently unused. */ int stacksize; /** The numbers of registers containing locked monitors. */ int[] locks; int nlocks; State() { defined = new Bits(); stack = new Type[16]; } State dup() { try { State state = (State)super.clone(); state.defined = new Bits(defined); state.stack = stack.clone(); if (locks != null) state.locks = locks.clone(); if (debugCode) { System.err.println("duping state " + this); dump(); } return state; } catch (CloneNotSupportedException ex) { throw new AssertionError(ex); } } void lock(int register) { if (locks == null) { locks = new int[20]; } else { locks = ArrayUtils.ensureCapacity(locks, nlocks); } locks[nlocks] = register; nlocks++; } void unlock(int register) { nlocks--; Assert.check(locks[nlocks] == register); locks[nlocks] = -1; } void push(Type t) { if (debugCode) System.err.println(" pushing " + t); switch (t.getTag()) { case VOID: return; case BYTE: case CHAR: case SHORT: case BOOLEAN: t = syms.intType; break; default: break; } stack = ArrayUtils.ensureCapacity(stack, stacksize+2); stack[stacksize++] = t; switch (width(t)) { case 1: break; case 2: stack[stacksize++] = null; break; default: throw new AssertionError(t); } if (stacksize > max_stack) max_stack = stacksize; } Type pop1() { if (debugCode) System.err.println(" popping " + 1); stacksize--; Type result = stack[stacksize]; stack[stacksize] = null; Assert.check(result != null && width(result) == 1); return result; } Type peek() { return stack[stacksize-1]; } Type pop2() { if (debugCode) System.err.println(" popping " + 2); stacksize -= 2; Type result = stack[stacksize]; stack[stacksize] = null; Assert.check(stack[stacksize+1] == null && result != null && width(result) == 2); return result; } void pop(int n) { if (debugCode) System.err.println(" popping " + n); while (n > 0) { stack[--stacksize] = null; n--; } } void pop(Type t) { pop(width(t)); } /** Force the top of the stack to be treated as this supertype * of its current type. */ void forceStackTop(Type t) { if (!alive) return; switch (t.getTag()) { case CLASS: case ARRAY: int width = width(t); Type old = stack[stacksize-width]; Assert.check(types.isSubtype(types.erasure(old), types.erasure(t))); stack[stacksize-width] = t; break; default: } } void markInitialized(UninitializedType old) { Type newtype = old.initializedType(); for (int i=0; i=0; i--) { if (defined.isMember(i)) { lastLocal = i; break; } } if (lastLocal >= 0) System.err.println(" locals:"); for (int i=0; i<=lastLocal; i++) { System.err.print(" " + i + ": "); if (defined.isMember(i)) { LocalVar var = lvar[i]; if (var == null) { System.err.println("(none)"); } else if (var.sym == null) System.err.println("UNKNOWN!"); else System.err.println("" + var.sym + " of type " + var.sym.erasure(types)); } else { System.err.println("undefined"); } } if (nlocks != 0) { System.err.print(" locks:"); for (int i=0; i