2442 lines
74 KiB
Java
2442 lines
74 KiB
Java
/*
|
|
* Copyright (c) 1999, 2015, 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.tree.JCTree;
|
|
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.
|
|
*
|
|
* <p><b>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.</b>
|
|
*/
|
|
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<char[]> catchInfo = new ListBuffer<char[]>();
|
|
|
|
/** A buffer for line number information. Each entry is a vector
|
|
* of two unsigned shorts.
|
|
*/
|
|
List<char[]> 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<Type> types) {
|
|
int w = 0;
|
|
for (List<Type> 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 curCP() {
|
|
/*
|
|
* This method has side-effects because calling it can indirectly provoke
|
|
* extra code generation, like goto instructions, depending on the context
|
|
* where it's called.
|
|
* Use with care or even better avoid using it.
|
|
*/
|
|
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) {
|
|
int argsize = width(mtype.getParameterTypes());
|
|
emitop(invokedynamic);
|
|
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 = curCP();
|
|
alive = true;
|
|
pendingStackMap = needStackMap;
|
|
return pc;
|
|
}
|
|
|
|
/** Declare an entry point with initial state;
|
|
* return current code pointer
|
|
*/
|
|
public int entryPoint(State state) {
|
|
int pc = curCP();
|
|
alive = true;
|
|
State newState = state.dup();
|
|
setDefined(newState.defined);
|
|
this.state = newState;
|
|
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 = curCP();
|
|
alive = true;
|
|
State newState = state.dup();
|
|
setDefined(newState.defined);
|
|
this.state = newState;
|
|
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 = curCP();
|
|
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<localsSize; i++) {
|
|
if (state.defined.isMember(i) && lvar[i] != null) {
|
|
Type vtype = lvar[i].sym.type;
|
|
if (!(vtype instanceof UninitializedType))
|
|
vtype = types.erasure(vtype);
|
|
frame.locals[i] = vtype;
|
|
}
|
|
}
|
|
frame.stack = new Type[state.stacksize];
|
|
for (int i=0; i<state.stacksize; i++)
|
|
frame.stack[i] = state.stack[i];
|
|
}
|
|
|
|
void emitStackMapFrame(int pc, int localsSize) {
|
|
if (lastFrame == null) {
|
|
// first frame
|
|
lastFrame = getInitialFrame();
|
|
} else if (lastFrame.pc == pc) {
|
|
// drop existing stackmap at this offset
|
|
stackMapTableBuffer[--stackMapBufferSize] = null;
|
|
lastFrame = frameBeforeLast;
|
|
frameBeforeLast = null;
|
|
}
|
|
|
|
StackMapFrame frame = new StackMapFrame();
|
|
frame.pc = pc;
|
|
|
|
int localCount = 0;
|
|
Type[] locals = new Type[localsSize];
|
|
for (int i=0; i<localsSize; i++, localCount++) {
|
|
if (state.defined.isMember(i) && lvar[i] != null) {
|
|
Type vtype = lvar[i].sym.type;
|
|
if (!(vtype instanceof UninitializedType))
|
|
vtype = types.erasure(vtype);
|
|
locals[i] = vtype;
|
|
if (width(vtype) > 1) i++;
|
|
}
|
|
}
|
|
frame.locals = new Type[localCount];
|
|
for (int i=0, j=0; i<localsSize; i++, j++) {
|
|
Assert.check(j < localCount);
|
|
frame.locals[j] = locals[i];
|
|
if (width(locals[i]) > 1) i++;
|
|
}
|
|
|
|
int stackCount = 0;
|
|
for (int i=0; i<state.stacksize; i++) {
|
|
if (state.stack[i] != null) {
|
|
stackCount++;
|
|
}
|
|
}
|
|
frame.stack = new Type[stackCount];
|
|
stackCount = 0;
|
|
for (int i=0; i<state.stacksize; i++) {
|
|
if (state.stack[i] != null) {
|
|
frame.stack[stackCount++] = types.erasure(state.stack[i]);
|
|
}
|
|
}
|
|
|
|
if (stackMapTableBuffer == null) {
|
|
stackMapTableBuffer = new StackMapTableFrame[20];
|
|
} else {
|
|
stackMapTableBuffer = ArrayUtils.ensureCapacity(
|
|
stackMapTableBuffer,
|
|
stackMapBufferSize);
|
|
}
|
|
stackMapTableBuffer[stackMapBufferSize++] =
|
|
StackMapTableFrame.getInstance(frame, lastFrame.pc, lastFrame.locals, types);
|
|
|
|
frameBeforeLast = lastFrame;
|
|
lastFrame = frame;
|
|
}
|
|
|
|
StackMapFrame getInitialFrame() {
|
|
StackMapFrame frame = new StackMapFrame();
|
|
List<Type> 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.
|
|
if (varDebugInfo) {
|
|
adjustAliveRanges(cp, -3);
|
|
}
|
|
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<char[]> compressedCatchInfo = new ListBuffer<>();
|
|
List<Integer> 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<stacksize; i++) {
|
|
if (stack[i] == old) stack[i] = newtype;
|
|
}
|
|
for (int i=0; i<lvar.length; i++) {
|
|
LocalVar lv = lvar[i];
|
|
if (lv != null && lv.sym.type == old) {
|
|
VarSymbol sym = lv.sym;
|
|
sym = sym.clone(sym.owner);
|
|
sym.type = newtype;
|
|
LocalVar newlv = lvar[i] = new LocalVar(sym);
|
|
newlv.aliveRanges = lv.aliveRanges;
|
|
}
|
|
}
|
|
}
|
|
|
|
State join(State other) {
|
|
defined.andSet(other.defined);
|
|
Assert.check(stacksize == other.stacksize
|
|
&& nlocks == other.nlocks);
|
|
for (int i=0; i<stacksize; ) {
|
|
Type t = stack[i];
|
|
Type tother = other.stack[i];
|
|
Type result =
|
|
t==tother ? t :
|
|
types.isSubtype(t, tother) ? tother :
|
|
types.isSubtype(tother, t) ? t :
|
|
error();
|
|
int w = width(result);
|
|
stack[i] = result;
|
|
if (w == 2) Assert.checkNull(stack[i+1]);
|
|
i += w;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
Type error() {
|
|
throw new AssertionError("inconsistent stack types at join point");
|
|
}
|
|
|
|
void dump() {
|
|
dump(-1);
|
|
}
|
|
|
|
void dump(int pc) {
|
|
System.err.print("stackMap for " + meth.owner + "." + meth);
|
|
if (pc == -1)
|
|
System.out.println();
|
|
else
|
|
System.out.println(" at " + pc);
|
|
System.err.println(" stack (from bottom):");
|
|
for (int i=0; i<stacksize; i++)
|
|
System.err.println(" " + i + ": " + stack[i]);
|
|
|
|
int lastLocal = 0;
|
|
for (int i=max_locals-1; 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<nlocks; i++) {
|
|
System.err.print(" " + locks[i]);
|
|
}
|
|
System.err.println();
|
|
}
|
|
}
|
|
}
|
|
|
|
static final Type jsrReturnValue = new JCPrimitiveType(INT, null);
|
|
|
|
|
|
/* **************************************************************************
|
|
* Local variables
|
|
****************************************************************************/
|
|
|
|
/** A live range of a local variable. */
|
|
static class LocalVar {
|
|
final VarSymbol sym;
|
|
final char reg;
|
|
|
|
class Range {
|
|
char start_pc = Character.MAX_VALUE;
|
|
char length = Character.MAX_VALUE;
|
|
|
|
Range() {}
|
|
|
|
Range(char start) {
|
|
this.start_pc = start;
|
|
}
|
|
|
|
Range(char start, char length) {
|
|
this.start_pc = start;
|
|
this.length = length;
|
|
}
|
|
|
|
boolean closed() {
|
|
return start_pc != Character.MAX_VALUE && length != Character.MAX_VALUE;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
int currentStartPC = start_pc;
|
|
int currentLength = length;
|
|
return "startpc = " + currentStartPC + " length " + currentLength;
|
|
}
|
|
}
|
|
|
|
java.util.List<Range> aliveRanges = new java.util.ArrayList<>();
|
|
|
|
LocalVar(VarSymbol v) {
|
|
this.sym = v;
|
|
this.reg = (char)v.adr;
|
|
}
|
|
public LocalVar dup() {
|
|
return new LocalVar(sym);
|
|
}
|
|
|
|
Range firstRange() {
|
|
return aliveRanges.isEmpty() ? null : aliveRanges.get(0);
|
|
}
|
|
|
|
Range lastRange() {
|
|
return aliveRanges.isEmpty() ? null : aliveRanges.get(aliveRanges.size() - 1);
|
|
}
|
|
|
|
void removeLastRange() {
|
|
Range lastRange = lastRange();
|
|
if (lastRange != null) {
|
|
aliveRanges.remove(lastRange);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
if (aliveRanges == null) {
|
|
return "empty local var";
|
|
}
|
|
StringBuilder sb = new StringBuilder().append(sym)
|
|
.append(" in register ").append((int)reg).append(" \n");
|
|
for (Range r : aliveRanges) {
|
|
sb.append(" starts at pc=").append(Integer.toString(((int)r.start_pc)))
|
|
.append(" length=").append(Integer.toString(((int)r.length)))
|
|
.append("\n");
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
public void openRange(char start) {
|
|
if (!hasOpenRange()) {
|
|
aliveRanges.add(new Range(start));
|
|
}
|
|
}
|
|
|
|
public void closeRange(char length) {
|
|
if (isLastRangeInitialized() && length > 0) {
|
|
Range range = lastRange();
|
|
if (range != null) {
|
|
if (range.length == Character.MAX_VALUE) {
|
|
range.length = length;
|
|
}
|
|
}
|
|
} else {
|
|
removeLastRange();
|
|
}
|
|
}
|
|
|
|
public boolean hasOpenRange() {
|
|
if (aliveRanges.isEmpty()) {
|
|
return false;
|
|
}
|
|
return lastRange().length == Character.MAX_VALUE;
|
|
}
|
|
|
|
public boolean isLastRangeInitialized() {
|
|
if (aliveRanges.isEmpty()) {
|
|
return false;
|
|
}
|
|
return lastRange().start_pc != Character.MAX_VALUE;
|
|
}
|
|
|
|
public Range getWidestRange() {
|
|
if (aliveRanges.isEmpty()) {
|
|
return new Range();
|
|
} else {
|
|
Range firstRange = firstRange();
|
|
Range lastRange = lastRange();
|
|
char length = (char)(lastRange.length + (lastRange.start_pc - firstRange.start_pc));
|
|
return new Range(firstRange.start_pc, length);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
/** Local variables, indexed by register. */
|
|
LocalVar[] lvar;
|
|
|
|
/** Add a new local variable. */
|
|
private void addLocalVar(VarSymbol v) {
|
|
int adr = v.adr;
|
|
lvar = ArrayUtils.ensureCapacity(lvar, adr+1);
|
|
Assert.checkNull(lvar[adr]);
|
|
if (pendingJumps != null) {
|
|
resolvePending();
|
|
}
|
|
lvar[adr] = new LocalVar(v);
|
|
state.defined.excl(adr);
|
|
}
|
|
|
|
void adjustAliveRanges(int oldCP, int delta) {
|
|
for (LocalVar localVar: lvar) {
|
|
if (localVar != null) {
|
|
for (LocalVar.Range range: localVar.aliveRanges) {
|
|
if (range.closed() && range.start_pc + range.length >= oldCP) {
|
|
range.length += delta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the size of the LocalVariableTable.
|
|
*/
|
|
public int getLVTSize() {
|
|
int result = varBufferSize;
|
|
for (int i = 0; i < varBufferSize; i++) {
|
|
LocalVar var = varBuffer[i];
|
|
result += var.aliveRanges.size() - 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** Set the current variable defined state. */
|
|
public void setDefined(Bits newDefined) {
|
|
if (alive && newDefined != state.defined) {
|
|
Bits diff = new Bits(state.defined).xorSet(newDefined);
|
|
for (int adr = diff.nextBit(0);
|
|
adr >= 0;
|
|
adr = diff.nextBit(adr+1)) {
|
|
if (adr >= nextreg)
|
|
state.defined.excl(adr);
|
|
else if (state.defined.isMember(adr))
|
|
setUndefined(adr);
|
|
else
|
|
setDefined(adr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Mark a register as being (possibly) defined. */
|
|
public void setDefined(int adr) {
|
|
LocalVar v = lvar[adr];
|
|
if (v == null) {
|
|
state.defined.excl(adr);
|
|
} else {
|
|
state.defined.incl(adr);
|
|
if (cp < Character.MAX_VALUE) {
|
|
v.openRange((char)cp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Mark a register as being undefined. */
|
|
public void setUndefined(int adr) {
|
|
state.defined.excl(adr);
|
|
if (adr < lvar.length &&
|
|
lvar[adr] != null &&
|
|
lvar[adr].isLastRangeInitialized()) {
|
|
LocalVar v = lvar[adr];
|
|
char length = (char)(curCP() - v.lastRange().start_pc);
|
|
if (length < Character.MAX_VALUE) {
|
|
lvar[adr] = v.dup();
|
|
v.closeRange(length);
|
|
putVar(v);
|
|
} else {
|
|
v.removeLastRange();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** End the scope of a variable. */
|
|
private void endScope(int adr) {
|
|
LocalVar v = lvar[adr];
|
|
if (v != null) {
|
|
if (v.isLastRangeInitialized()) {
|
|
char length = (char)(curCP() - v.lastRange().start_pc);
|
|
if (length < Character.MAX_VALUE) {
|
|
v.closeRange(length);
|
|
putVar(v);
|
|
fillLocalVarPosition(v);
|
|
}
|
|
}
|
|
/** the call to curCP() can implicitly adjust the current cp, if so
|
|
* the alive range of local variables may be modified. Thus we need
|
|
* all of them. For this reason assigning null to the given address
|
|
* should be the last action to do.
|
|
*/
|
|
lvar[adr] = null;
|
|
}
|
|
state.defined.excl(adr);
|
|
}
|
|
|
|
private void fillLocalVarPosition(LocalVar lv) {
|
|
if (lv == null || lv.sym == null || !lv.sym.hasTypeAnnotations())
|
|
return;
|
|
for (Attribute.TypeCompound ta : lv.sym.getRawTypeAttributes()) {
|
|
TypeAnnotationPosition p = ta.position;
|
|
LocalVar.Range widestRange = lv.getWidestRange();
|
|
p.lvarOffset = new int[] { (int)widestRange.start_pc };
|
|
p.lvarLength = new int[] { (int)widestRange.length };
|
|
p.lvarIndex = new int[] { (int)lv.reg };
|
|
p.isValidOffset = true;
|
|
}
|
|
}
|
|
|
|
// Method to be called after compressCatchTable to
|
|
// fill in the exception table index for type
|
|
// annotations on exception parameters.
|
|
public void fillExceptionParameterPositions() {
|
|
for (int i = 0; i < varBufferSize; ++i) {
|
|
LocalVar lv = varBuffer[i];
|
|
if (lv == null || lv.sym == null
|
|
|| !lv.sym.hasTypeAnnotations()
|
|
|| !lv.sym.isExceptionParameter())
|
|
continue;
|
|
|
|
for (Attribute.TypeCompound ta : lv.sym.getRawTypeAttributes()) {
|
|
TypeAnnotationPosition p = ta.position;
|
|
// At this point p.type_index contains the catch type index.
|
|
// Use that index to determine the exception table index.
|
|
// We can afterwards discard the type_index.
|
|
// A TA position is shared for all type annotations in the
|
|
// same location; updating one is enough.
|
|
// Use -666 as a marker that the exception_index was already updated.
|
|
if (p.type_index != -666) {
|
|
p.exception_index = findExceptionIndex(p.type_index);
|
|
p.type_index = -666;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private int findExceptionIndex(int catchType) {
|
|
if (catchType == Integer.MIN_VALUE) {
|
|
// We didn't set the catch type index correctly.
|
|
// This shouldn't happen.
|
|
// TODO: issue error?
|
|
return -1;
|
|
}
|
|
List<char[]> iter = catchInfo.toList();
|
|
int len = catchInfo.length();
|
|
for (int i = 0; i < len; ++i) {
|
|
char[] catchEntry = iter.head;
|
|
iter = iter.tail;
|
|
char ct = catchEntry[3];
|
|
if (catchType == ct) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/** Put a live variable range into the buffer to be output to the
|
|
* class file.
|
|
*/
|
|
void putVar(LocalVar var) {
|
|
// Keep local variables if
|
|
// 1) we need them for debug information
|
|
// 2) it is an exception type and it contains type annotations
|
|
boolean keepLocalVariables = varDebugInfo ||
|
|
(var.sym.isExceptionParameter() && var.sym.hasTypeAnnotations());
|
|
if (!keepLocalVariables) return;
|
|
//don't keep synthetic vars, unless they are lambda method parameters
|
|
boolean ignoredSyntheticVar = (var.sym.flags() & Flags.SYNTHETIC) != 0 &&
|
|
((var.sym.owner.flags() & Flags.LAMBDA_METHOD) == 0 ||
|
|
(var.sym.flags() & Flags.PARAMETER) == 0);
|
|
if (ignoredSyntheticVar) return;
|
|
if (varBuffer == null)
|
|
varBuffer = new LocalVar[20];
|
|
else
|
|
varBuffer = ArrayUtils.ensureCapacity(varBuffer, varBufferSize);
|
|
varBuffer[varBufferSize++] = var;
|
|
}
|
|
|
|
/** Previously live local variables, to be put into the variable table. */
|
|
LocalVar[] varBuffer;
|
|
int varBufferSize;
|
|
|
|
/** Create a new local variable address and return it.
|
|
*/
|
|
private int newLocal(int typecode) {
|
|
int reg = nextreg;
|
|
int w = width(typecode);
|
|
nextreg = reg + w;
|
|
if (nextreg > max_locals) max_locals = nextreg;
|
|
return reg;
|
|
}
|
|
|
|
private int newLocal(Type type) {
|
|
return newLocal(typecode(type));
|
|
}
|
|
|
|
public int newLocal(VarSymbol v) {
|
|
int reg = v.adr = newLocal(v.erasure(types));
|
|
addLocalVar(v);
|
|
return reg;
|
|
}
|
|
|
|
/** Start a set of fresh registers.
|
|
*/
|
|
public void newRegSegment() {
|
|
nextreg = max_locals;
|
|
}
|
|
|
|
/** End scopes of all variables with registers ≥ first.
|
|
*/
|
|
public void endScopes(int first) {
|
|
int prevNextReg = nextreg;
|
|
nextreg = first;
|
|
for (int i = nextreg; i < prevNextReg; i++) endScope(i);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* static tables
|
|
*************************************************************************/
|
|
|
|
public static String mnem(int opcode) {
|
|
return Mneumonics.mnem[opcode];
|
|
}
|
|
|
|
private static class Mneumonics {
|
|
private final static String[] mnem = new String[ByteCodeCount];
|
|
static {
|
|
mnem[nop] = "nop";
|
|
mnem[aconst_null] = "aconst_null";
|
|
mnem[iconst_m1] = "iconst_m1";
|
|
mnem[iconst_0] = "iconst_0";
|
|
mnem[iconst_1] = "iconst_1";
|
|
mnem[iconst_2] = "iconst_2";
|
|
mnem[iconst_3] = "iconst_3";
|
|
mnem[iconst_4] = "iconst_4";
|
|
mnem[iconst_5] = "iconst_5";
|
|
mnem[lconst_0] = "lconst_0";
|
|
mnem[lconst_1] = "lconst_1";
|
|
mnem[fconst_0] = "fconst_0";
|
|
mnem[fconst_1] = "fconst_1";
|
|
mnem[fconst_2] = "fconst_2";
|
|
mnem[dconst_0] = "dconst_0";
|
|
mnem[dconst_1] = "dconst_1";
|
|
mnem[bipush] = "bipush";
|
|
mnem[sipush] = "sipush";
|
|
mnem[ldc1] = "ldc1";
|
|
mnem[ldc2] = "ldc2";
|
|
mnem[ldc2w] = "ldc2w";
|
|
mnem[iload] = "iload";
|
|
mnem[lload] = "lload";
|
|
mnem[fload] = "fload";
|
|
mnem[dload] = "dload";
|
|
mnem[aload] = "aload";
|
|
mnem[iload_0] = "iload_0";
|
|
mnem[lload_0] = "lload_0";
|
|
mnem[fload_0] = "fload_0";
|
|
mnem[dload_0] = "dload_0";
|
|
mnem[aload_0] = "aload_0";
|
|
mnem[iload_1] = "iload_1";
|
|
mnem[lload_1] = "lload_1";
|
|
mnem[fload_1] = "fload_1";
|
|
mnem[dload_1] = "dload_1";
|
|
mnem[aload_1] = "aload_1";
|
|
mnem[iload_2] = "iload_2";
|
|
mnem[lload_2] = "lload_2";
|
|
mnem[fload_2] = "fload_2";
|
|
mnem[dload_2] = "dload_2";
|
|
mnem[aload_2] = "aload_2";
|
|
mnem[iload_3] = "iload_3";
|
|
mnem[lload_3] = "lload_3";
|
|
mnem[fload_3] = "fload_3";
|
|
mnem[dload_3] = "dload_3";
|
|
mnem[aload_3] = "aload_3";
|
|
mnem[iaload] = "iaload";
|
|
mnem[laload] = "laload";
|
|
mnem[faload] = "faload";
|
|
mnem[daload] = "daload";
|
|
mnem[aaload] = "aaload";
|
|
mnem[baload] = "baload";
|
|
mnem[caload] = "caload";
|
|
mnem[saload] = "saload";
|
|
mnem[istore] = "istore";
|
|
mnem[lstore] = "lstore";
|
|
mnem[fstore] = "fstore";
|
|
mnem[dstore] = "dstore";
|
|
mnem[astore] = "astore";
|
|
mnem[istore_0] = "istore_0";
|
|
mnem[lstore_0] = "lstore_0";
|
|
mnem[fstore_0] = "fstore_0";
|
|
mnem[dstore_0] = "dstore_0";
|
|
mnem[astore_0] = "astore_0";
|
|
mnem[istore_1] = "istore_1";
|
|
mnem[lstore_1] = "lstore_1";
|
|
mnem[fstore_1] = "fstore_1";
|
|
mnem[dstore_1] = "dstore_1";
|
|
mnem[astore_1] = "astore_1";
|
|
mnem[istore_2] = "istore_2";
|
|
mnem[lstore_2] = "lstore_2";
|
|
mnem[fstore_2] = "fstore_2";
|
|
mnem[dstore_2] = "dstore_2";
|
|
mnem[astore_2] = "astore_2";
|
|
mnem[istore_3] = "istore_3";
|
|
mnem[lstore_3] = "lstore_3";
|
|
mnem[fstore_3] = "fstore_3";
|
|
mnem[dstore_3] = "dstore_3";
|
|
mnem[astore_3] = "astore_3";
|
|
mnem[iastore] = "iastore";
|
|
mnem[lastore] = "lastore";
|
|
mnem[fastore] = "fastore";
|
|
mnem[dastore] = "dastore";
|
|
mnem[aastore] = "aastore";
|
|
mnem[bastore] = "bastore";
|
|
mnem[castore] = "castore";
|
|
mnem[sastore] = "sastore";
|
|
mnem[pop] = "pop";
|
|
mnem[pop2] = "pop2";
|
|
mnem[dup] = "dup";
|
|
mnem[dup_x1] = "dup_x1";
|
|
mnem[dup_x2] = "dup_x2";
|
|
mnem[dup2] = "dup2";
|
|
mnem[dup2_x1] = "dup2_x1";
|
|
mnem[dup2_x2] = "dup2_x2";
|
|
mnem[swap] = "swap";
|
|
mnem[iadd] = "iadd";
|
|
mnem[ladd] = "ladd";
|
|
mnem[fadd] = "fadd";
|
|
mnem[dadd] = "dadd";
|
|
mnem[isub] = "isub";
|
|
mnem[lsub] = "lsub";
|
|
mnem[fsub] = "fsub";
|
|
mnem[dsub] = "dsub";
|
|
mnem[imul] = "imul";
|
|
mnem[lmul] = "lmul";
|
|
mnem[fmul] = "fmul";
|
|
mnem[dmul] = "dmul";
|
|
mnem[idiv] = "idiv";
|
|
mnem[ldiv] = "ldiv";
|
|
mnem[fdiv] = "fdiv";
|
|
mnem[ddiv] = "ddiv";
|
|
mnem[imod] = "imod";
|
|
mnem[lmod] = "lmod";
|
|
mnem[fmod] = "fmod";
|
|
mnem[dmod] = "dmod";
|
|
mnem[ineg] = "ineg";
|
|
mnem[lneg] = "lneg";
|
|
mnem[fneg] = "fneg";
|
|
mnem[dneg] = "dneg";
|
|
mnem[ishl] = "ishl";
|
|
mnem[lshl] = "lshl";
|
|
mnem[ishr] = "ishr";
|
|
mnem[lshr] = "lshr";
|
|
mnem[iushr] = "iushr";
|
|
mnem[lushr] = "lushr";
|
|
mnem[iand] = "iand";
|
|
mnem[land] = "land";
|
|
mnem[ior] = "ior";
|
|
mnem[lor] = "lor";
|
|
mnem[ixor] = "ixor";
|
|
mnem[lxor] = "lxor";
|
|
mnem[iinc] = "iinc";
|
|
mnem[i2l] = "i2l";
|
|
mnem[i2f] = "i2f";
|
|
mnem[i2d] = "i2d";
|
|
mnem[l2i] = "l2i";
|
|
mnem[l2f] = "l2f";
|
|
mnem[l2d] = "l2d";
|
|
mnem[f2i] = "f2i";
|
|
mnem[f2l] = "f2l";
|
|
mnem[f2d] = "f2d";
|
|
mnem[d2i] = "d2i";
|
|
mnem[d2l] = "d2l";
|
|
mnem[d2f] = "d2f";
|
|
mnem[int2byte] = "int2byte";
|
|
mnem[int2char] = "int2char";
|
|
mnem[int2short] = "int2short";
|
|
mnem[lcmp] = "lcmp";
|
|
mnem[fcmpl] = "fcmpl";
|
|
mnem[fcmpg] = "fcmpg";
|
|
mnem[dcmpl] = "dcmpl";
|
|
mnem[dcmpg] = "dcmpg";
|
|
mnem[ifeq] = "ifeq";
|
|
mnem[ifne] = "ifne";
|
|
mnem[iflt] = "iflt";
|
|
mnem[ifge] = "ifge";
|
|
mnem[ifgt] = "ifgt";
|
|
mnem[ifle] = "ifle";
|
|
mnem[if_icmpeq] = "if_icmpeq";
|
|
mnem[if_icmpne] = "if_icmpne";
|
|
mnem[if_icmplt] = "if_icmplt";
|
|
mnem[if_icmpge] = "if_icmpge";
|
|
mnem[if_icmpgt] = "if_icmpgt";
|
|
mnem[if_icmple] = "if_icmple";
|
|
mnem[if_acmpeq] = "if_acmpeq";
|
|
mnem[if_acmpne] = "if_acmpne";
|
|
mnem[goto_] = "goto_";
|
|
mnem[jsr] = "jsr";
|
|
mnem[ret] = "ret";
|
|
mnem[tableswitch] = "tableswitch";
|
|
mnem[lookupswitch] = "lookupswitch";
|
|
mnem[ireturn] = "ireturn";
|
|
mnem[lreturn] = "lreturn";
|
|
mnem[freturn] = "freturn";
|
|
mnem[dreturn] = "dreturn";
|
|
mnem[areturn] = "areturn";
|
|
mnem[return_] = "return_";
|
|
mnem[getstatic] = "getstatic";
|
|
mnem[putstatic] = "putstatic";
|
|
mnem[getfield] = "getfield";
|
|
mnem[putfield] = "putfield";
|
|
mnem[invokevirtual] = "invokevirtual";
|
|
mnem[invokespecial] = "invokespecial";
|
|
mnem[invokestatic] = "invokestatic";
|
|
mnem[invokeinterface] = "invokeinterface";
|
|
mnem[invokedynamic] = "invokedynamic";
|
|
mnem[new_] = "new_";
|
|
mnem[newarray] = "newarray";
|
|
mnem[anewarray] = "anewarray";
|
|
mnem[arraylength] = "arraylength";
|
|
mnem[athrow] = "athrow";
|
|
mnem[checkcast] = "checkcast";
|
|
mnem[instanceof_] = "instanceof_";
|
|
mnem[monitorenter] = "monitorenter";
|
|
mnem[monitorexit] = "monitorexit";
|
|
mnem[wide] = "wide";
|
|
mnem[multianewarray] = "multianewarray";
|
|
mnem[if_acmp_null] = "if_acmp_null";
|
|
mnem[if_acmp_nonnull] = "if_acmp_nonnull";
|
|
mnem[goto_w] = "goto_w";
|
|
mnem[jsr_w] = "jsr_w";
|
|
mnem[breakpoint] = "breakpoint";
|
|
}
|
|
}
|
|
}
|