blob: 5f94057d89c71eb87bb7baa41328bdb7ebe35351 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.services.bytecode.CodeChunk
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to you under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.apache.derby.impl.services.bytecode;
import org.apache.derby.iapi.services.classfile.CONSTANT_Index_info;
import org.apache.derby.iapi.services.classfile.CONSTANT_Utf8_info;
import org.apache.derby.iapi.services.classfile.ClassFormatOutput;
import org.apache.derby.iapi.services.classfile.ClassHolder;
import org.apache.derby.iapi.services.classfile.ClassMember;
import org.apache.derby.iapi.services.classfile.VMDescriptor;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.services.classfile.VMOpcode;
import org.apache.derby.iapi.services.io.ArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* This class represents a chunk of code in a CodeAttribute.
* Typically, a CodeAttribute represents the code in a method.
* If there is a try/catch block, each catch block will get its
* own code chunk. This allows the catch blocks to all be put at
* the end of the generated code for a method, which eliminates
* the need to generate a jump around each catch block, which
* would be a forward reference.
*/
final class CodeChunk {
/**
* Starting point of the byte code stream in the underlying stream/array.
*/
private static final int CODE_OFFSET = 8;
// The use of ILOAD for the non-integer types is correct.
// We have to assume that the appropriate checks/conversions
// are defined on math operation results to ensure that
// the type is preserved when/as needed.
static final short[] LOAD_VARIABLE = {
VMOpcode.ILOAD, /* vm_byte */
VMOpcode.ILOAD, /* vm_short */
VMOpcode.ILOAD, /* vm_int */
VMOpcode.LLOAD, /* vm_long */
VMOpcode.FLOAD, /* vm_float */
VMOpcode.DLOAD, /* vm_double */
VMOpcode.ILOAD, /* vm_char */
VMOpcode.ALOAD /* vm_reference */
};
static final short[] LOAD_VARIABLE_FAST = {
VMOpcode.ILOAD_0, /* vm_byte */
VMOpcode.ILOAD_0, /* vm_short */
VMOpcode.ILOAD_0, /* vm_int */
VMOpcode.LLOAD_0, /* vm_long */
VMOpcode.FLOAD_0, /* vm_float */
VMOpcode.DLOAD_0, /* vm_double */
VMOpcode.ILOAD_0, /* vm_char */
VMOpcode.ALOAD_0 /* vm_reference */
};
// The ISTOREs for non-int types are how things work.
// It assumes that the appropriate casts are done
// on operations on non-ints to ensure that the values
// remain in the valid ranges.
static final short[] STORE_VARIABLE = {
VMOpcode.ISTORE, /* vm_byte */
VMOpcode.ISTORE, /* vm_short */
VMOpcode.ISTORE, /* vm_int */
VMOpcode.LSTORE, /* vm_long */
VMOpcode.FSTORE, /* vm_float */
VMOpcode.DSTORE, /* vm_double */
VMOpcode.ISTORE, /* vm_char */
VMOpcode.ASTORE /* vm_reference */
};
static final short[] STORE_VARIABLE_FAST = {
VMOpcode.ISTORE_0, /* vm_byte */
VMOpcode.ISTORE_0, /* vm_short */
VMOpcode.ISTORE_0, /* vm_int */
VMOpcode.LSTORE_0, /* vm_long */
VMOpcode.FSTORE_0, /* vm_float */
VMOpcode.DSTORE_0, /* vm_double */
VMOpcode.ISTORE_0, /* vm_char */
VMOpcode.ASTORE_0 /* vm_reference */
};
static final short ARRAY_ACCESS[] = {
VMOpcode.BALOAD, /* vm_byte */
VMOpcode.SALOAD, /* vm_short */
VMOpcode.IALOAD, /* vm_int */
VMOpcode.LALOAD, /* vm_long */
VMOpcode.FALOAD, /* vm_float */
VMOpcode.DALOAD, /* vm_double */
VMOpcode.CALOAD, /* vm_char */
VMOpcode.AALOAD /* vm_reference */
};
static final short ARRAY_STORE[] = {
VMOpcode.BASTORE, /* vm_byte */
VMOpcode.SASTORE, /* vm_short */
VMOpcode.IASTORE, /* vm_int */
VMOpcode.LASTORE, /* vm_long */
VMOpcode.FASTORE, /* vm_float */
VMOpcode.DASTORE, /* vm_double */
VMOpcode.CASTORE, /* vm_char */
VMOpcode.AASTORE /* vm_reference */
};
static final short[] RETURN_OPCODE = {
VMOpcode.IRETURN, /* 0 = byte */
VMOpcode.IRETURN, /* 1 = short */
VMOpcode.IRETURN, /* 2 = int */
VMOpcode.LRETURN, /* 3 = long */
VMOpcode.FRETURN, /* 4 = float */
VMOpcode.DRETURN, /* 5 = double */
VMOpcode.IRETURN, /* 6 = char */
VMOpcode.ARETURN /* 7 = reference */
};
// the first dimension is the current vmTypeId
// the second dimension is the target vmTypeId
//
// the cells of the entry at [current,target] are:
// 0: operation
// 1: result type of operation
// if entry[1] = target, we are done. otherwise,
// you have to continue with entry[1] as the new current
// after generating the opcode listed (don't generate if it is NOP).
// if entry[0] = BAD, we can
static final short CAST_CONVERSION_INFO[][][] = {
/* current = vm_byte */
{
/* target = vm_byte */ { VMOpcode.NOP, BCExpr.vm_byte },
/* target = vm_short */ { VMOpcode.NOP, BCExpr.vm_short },
/* target = vm_int */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_long */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_float */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_double */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_char */ { VMOpcode.NOP, BCExpr.vm_char },
/* target = vm_reference */ { VMOpcode.BAD, BCExpr.vm_reference }
},
/* current = vm_short */
{
/* target = vm_byte */ { VMOpcode.NOP, BCExpr.vm_byte },
/* target = vm_short */ { VMOpcode.NOP, BCExpr.vm_short },
/* target = vm_int */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_long */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_float */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_double */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_char */ { VMOpcode.NOP, BCExpr.vm_char },
/* target = vm_reference */ { VMOpcode.BAD, BCExpr.vm_reference }
},
/* current = vm_int */
{
/* target = vm_byte */ { VMOpcode.I2B, BCExpr.vm_byte },
/* target = vm_short */ { VMOpcode.I2S, BCExpr.vm_short },
/* target = vm_int */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_long */ { VMOpcode.I2L, BCExpr.vm_long },
/* target = vm_float */ { VMOpcode.I2F, BCExpr.vm_float },
/* target = vm_double */ { VMOpcode.I2D, BCExpr.vm_double },
/* target = vm_char */ { VMOpcode.I2B, BCExpr.vm_char },
/* target = vm_reference */ { VMOpcode.BAD, BCExpr.vm_reference }
},
/* current = vm_long */
{
/* target = vm_byte */ { VMOpcode.L2I, BCExpr.vm_int },
/* target = vm_short */ { VMOpcode.L2I, BCExpr.vm_int },
/* target = vm_int */ { VMOpcode.L2I, BCExpr.vm_int },
/* target = vm_long */ { VMOpcode.NOP, BCExpr.vm_long },
/* target = vm_float */ { VMOpcode.L2F, BCExpr.vm_float },
/* target = vm_double */ { VMOpcode.L2D, BCExpr.vm_double },
/* target = vm_char */ { VMOpcode.L2I, BCExpr.vm_int },
/* target = vm_reference */ { VMOpcode.BAD, BCExpr.vm_reference }
},
/* current = vm_float */
{
/* target = vm_byte */ { VMOpcode.F2I, BCExpr.vm_int },
/* target = vm_short */ { VMOpcode.F2I, BCExpr.vm_int },
/* target = vm_int */ { VMOpcode.F2I, BCExpr.vm_int },
/* target = vm_long */ { VMOpcode.F2L, BCExpr.vm_long },
/* target = vm_float */ { VMOpcode.NOP, BCExpr.vm_float },
/* target = vm_double */ { VMOpcode.F2D, BCExpr.vm_double },
/* target = vm_char */ { VMOpcode.F2I, BCExpr.vm_int },
/* target = vm_reference */ { VMOpcode.BAD, BCExpr.vm_reference }
},
/* current = vm_double */
{
/* target = vm_byte */ { VMOpcode.D2I, BCExpr.vm_int },
/* target = vm_short */ { VMOpcode.D2I, BCExpr.vm_int },
/* target = vm_int */ { VMOpcode.D2I, BCExpr.vm_int },
/* target = vm_long */ { VMOpcode.D2L, BCExpr.vm_long },
/* target = vm_float */ { VMOpcode.D2F, BCExpr.vm_float },
/* target = vm_double */ { VMOpcode.NOP, BCExpr.vm_double },
/* target = vm_char */ { VMOpcode.D2I, BCExpr.vm_int },
/* target = vm_reference */ { VMOpcode.BAD, BCExpr.vm_reference }
},
/* current = vm_char */
{
/* target = vm_byte */ { VMOpcode.NOP, BCExpr.vm_byte },
/* target = vm_short */ { VMOpcode.NOP, BCExpr.vm_short },
/* target = vm_int */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_long */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_float */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_double */ { VMOpcode.NOP, BCExpr.vm_int },
/* target = vm_char */ { VMOpcode.NOP, BCExpr.vm_char },
/* target = vm_reference */ { VMOpcode.BAD, BCExpr.vm_reference }
},
/* current = vm_reference */
{
/* target = vm_byte */ { VMOpcode.BAD, BCExpr.vm_byte },
/* target = vm_short */ { VMOpcode.BAD, BCExpr.vm_short },
/* target = vm_int */ { VMOpcode.BAD, BCExpr.vm_int },
/* target = vm_long */ { VMOpcode.BAD, BCExpr.vm_long },
/* target = vm_float */ { VMOpcode.BAD, BCExpr.vm_float },
/* target = vm_double */ { VMOpcode.BAD, BCExpr.vm_double },
/* target = vm_char */ { VMOpcode.BAD, BCExpr.vm_char },
/* target = vm_reference */ { VMOpcode.NOP, BCExpr.vm_reference }
}
};
/**
* Constant used by OPCODE_ACTION to represent the
* common action of push one word, 1 byte
* for the instruction.
*/
private static final byte[] push1_1i = {1, 1};
/**
* Constant used by OPCODE_ACTION to represent the
* common action of push two words, 1 byte
* for the instruction.
*/
private static final byte[] push2_1i = {2, 1};
/**
* Constant used by OPCODE_ACTION to the opcode is
* not yet supported.
*/
private static final byte[] NS = {0, -1};
/**
* Value for OPCODE_ACTION[opcode][0] to represent
* the number of words popped or pushed in variable.
*/
private static final byte VARIABLE_STACK = -128;
/**
* Array that provides two pieces of information about
* each VM opcode. Each opcode has a two byte array.
* <P>
* The first element in the array [0] is the number of
* stack words (double/long count as two) pushed by the opcode.
* Will be negative if the opcode pops values.
*
* <P>
* The second element in the array [1] is the number of bytes
* in the instruction stream that this opcode's instruction
* takes up, including the opocode.
*/
private static final byte[][] OPCODE_ACTION =
{
/* NOP 0 */ { 0, 1 },
/* ACONST_NULL 1 */ push1_1i,
/* ICONST_M1 2 */ push1_1i,
/* ICONST_0 3 */ push1_1i,
/* ICONST_1 4 */ push1_1i,
/* ICONST_2 5 */ push1_1i,
/* ICONST_3 6 */ push1_1i,
/* ICONST_4 7 */ push1_1i,
/* ICONST_5 8 */ push1_1i,
/* LCONST_0 9 */ push2_1i,
/* LCONST_1 10 */ push2_1i,
/* FCONST_0 11 */ push1_1i,
/* FCONST_1 12 */ push1_1i,
/* FCONST_2 13 */ push1_1i,
/* DCONST_0 14 */ push2_1i,
/* DCONST_1 15 */ push2_1i,
/* BIPUSH 16 */ {1, 2},
/* SIPUSH 17 */ {1, 3},
/* LDC 18 */ {1, 2},
/* LDC_W 19 */ {1, 3},
/* LDC2_W 20 */ {2, 3},
/* ILOAD 21 */ { 1, 2 },
/* LLOAD 22 */ { 2, 2 },
/* FLOAD 23 */ { 1, 2 },
/* DLOAD 24 */ { 2, 2 },
/* ALOAD 25 */ { 1, 2 },
/* ILOAD_0 26 */ push1_1i,
/* ILOAD_1 27 */ push1_1i,
/* ILOAD_2 28 */ push1_1i,
/* ILOAD_3 29 */ push1_1i,
/* LLOAD_0 30 */ push2_1i,
/* LLOAD_1 31 */ push2_1i,
/* LLOAD_2 32 */ push2_1i,
/* LLOAD_3 33 */ push2_1i,
/* FLOAD_0 34 */ push1_1i,
/* FLOAD_1 35 */ push1_1i,
/* FLOAD_2 36 */ push1_1i,
/* FLOAD_3 37 */ push1_1i,
/* DLOAD_0 38 */ push2_1i,
/* DLOAD_1 39 */ push2_1i,
/* DLOAD_2 40 */ push2_1i,
/* DLOAD_3 41 */ push2_1i,
/* ALOAD_0 42 */ push1_1i,
/* ALOAD_1 43 */ push1_1i,
/* ALOAD_2 44 */ push1_1i,
/* ALOAD_3 45 */ push1_1i,
/* IALOAD 46 */ { -1, 1 },
/* LALOAD 47 */ { 0, 1 },
/* FALOAD 48 */ { -1, 1 },
/* DALOAD 49 */ { 0, 1 },
/* AALOAD 50 */ { -1, 1 },
/* BALOAD 51 */ { -1, 1 },
/* CALOAD 52 */ { -1, 1 },
/* SALOAD 53 */ { -1, 1 },
/* ISTORE 54 */ { -1, 2 },
/* LSTORE 55 */ { -2, 2 },
/* FSTORE 56 */ { -1, 2 },
/* DSTORE 57 */ { -2, 2 },
/* ASTORE 58 */ { -1, 2 },
/* ISTORE_0 59 */ { -1, 1 },
/* ISTORE_1 60 */ { -1, 1 },
/* ISTORE_2 61 */ { -1, 1 },
/* ISTORE_3 62 */ { -1, 1 },
/* LSTORE_0 63 */ { -2, 1 },
/* LSTORE_1 64 */ { -2, 1 },
/* LSTORE_2 65 */ { -2, 1 },
/* LSTORE_3 66 */ { -2, 1 },
/* FSTORE_0 67 */ { -1, 1 },
/* FSTORE_1 68 */ { -1, 1 },
/* FSTORE_2 69 */ { -1, 1 },
/* FSTORE_3 70 */ { -1, 1 },
/* DSTORE_0 71 */ { -2, 1 },
/* DSTORE_1 72 */ { -2, 1 },
/* DSTORE_2 73 */ { -2, 1 },
/* DSTORE_3 74 */ { -2, 1 },
/* ASTORE_0 75 */ { -1, 1 },
/* ASTORE_1 76 */ { -1, 1 },
/* ASTORE_2 77 */ { -1, 1 },
/* ASTORE_3 78 */ { -1, 1 },
/* IASTORE 79 */ { -3, 1 },
/* LASTORE 80 */ { -4, 1 },
/* FASTORE 81 */ { -3, 1 },
/* DASTORE 82 */ { -4, 1 },
/* AASTORE 83 */ { -3, 1 },
/* BASTORE 84 */ { -3, 1 },
/* CASTORE 85 */ { -3, 1 },
/* SASTORE 86 */ { -3, 1 },
/* POP 87 */ { -1, 1 },
/* POP2 88 */ { -2, 1 },
/* DUP 89 */ push1_1i,
/* DUP_X1 90 */ push1_1i,
/* DUP_X2 91 */ push1_1i,
/* DUP2 92 */ push2_1i,
/* DUP2_X1 93 */ push2_1i,
/* DUP2_X2 94 */ push2_1i,
/* SWAP 95 */ { 0, 1 },
/* IADD 96 */ NS,
/* LADD 97 */ NS,
/* FADD 98 */ { -1, 1 },
/* DADD 99 */ { -2, 1 },
/* ISUB 100 */ NS,
/* LSUB 101 */ NS,
/* FSUB 102 */ { -1, 1 },
/* DSUB 103 */ { -2, 1 },
/* IMUL 104 */ NS,
/* LMUL 105 */ NS,
/* FMUL 106 */ { -1, 1 },
/* DMUL 107 */ { -2, 1 },
/* IDIV 108 */ NS,
/* LDIV 109 */ NS,
/* FDIV 110 */ { -1, 1 },
/* DDIV 111 */ { -2, 1 },
/* IREM 112 */ { -1, 1 },
/* LREM 113 */ { -2, 1 },
/* FREM 114 */ { -1, 1 },
/* DREM 115 */ { -2, 1 },
/* INEG 116 */ { 0, 1 },
/* LNEG 117 */ { 0, 1 },
/* FNEG 118 */ { 0, 1 },
/* DNEG 119 */ { 0, 1 },
/* ISHL 120 */ { -1, 1 },
/* LSHL 121 */ NS,
/* ISHR 122 */ NS,
/* LSHR 123 */ NS,
/* IUSHR 124 */ NS,
/* LUSHR 125 */ NS,
/* IAND 126 */ { -1, 1 },
/* LAND 127 */ NS,
/* IOR 128 */ { -1, 1 },
/* LOR 129 */ NS,
/* IXOR 130 */ NS,
/* LXOR 131 */ NS,
/* IINC 132 */ NS,
/* I2L 133 */ push1_1i,
/* I2F 134 */ { 0, 1 },
/* I2D 135 */ push1_1i,
/* L2I 136 */ { -1, 1 },
/* L2F 137 */ { -1, 1 },
/* L2D 138 */ { 0, 1 },
/* F2I 139 */ { 0, 1 },
/* F2L 140 */ push2_1i,
/* F2D 141 */ push1_1i,
/* D2I 142 */ { -1, 1 },
/* D2L 143 */ { 0, 1 },
/* D2F 144 */ { -1, 1 },
/* I2B 145 */ { 0, 1 },
/* I2C 146 */ { 0, 1 },
/* I2S 147 */ { 0, 1 },
/* LCMP 148 */ NS,
/* FCMPL 149 */ { -1, 1 },
/* FCMPG 150 */ { -1, 1 },
/* DCMPL 151 */ { -3, 1 },
/* DCMPG 152 */ { -3, 1 },
/* IFEQ 153 */ { -1, VMOpcode.IF_INS_LENGTH },
/* IFNE 154 */ { -1, VMOpcode.IF_INS_LENGTH },
/* IFLT 155 */ { -1, VMOpcode.IF_INS_LENGTH },
/* IFGE 156 */ { -1, VMOpcode.IF_INS_LENGTH },
/* IFGT 157 */ { -1, VMOpcode.IF_INS_LENGTH },
/* IFLE 158 */ { -1, VMOpcode.IF_INS_LENGTH },
/* IF_ICMPEQ 159 */ NS,
/* IF_ICMPNE 160 */ NS,
/* IF_ICMPLT 161 */ NS,
/* IF_ICMPGE 162 */ NS,
/* IF_ICMPGT 163 */ NS,
/* IF_ICMPLE 164 */ NS,
/* IF_ACMPEQ 165 */ NS,
/* IF_ACMPNE 166 */ NS,
/* GOTO 167 */ { 0, VMOpcode.GOTO_INS_LENGTH },
/* JSR 168 */ NS,
/* RET 169 */ NS,
/* TABLESWITCH 170 */ NS,
/* LOOKUPSWITCH 171 */NS,
/* IRETURN 172 */ { -1, 1 }, // strictly speaking all words on the stack are popped.
/* LRETURN 173 */ { -2, 1 }, // strictly speaking all words on the stack are popped.
/* FRETURN 174 */ { -1, 1 }, // strictly speaking all words on the stack are popped.
/* DRETURN 175 */ { -2, 1 }, // strictly speaking all words on the stack are popped.
/* ARETURN 176 */ { -1, 1 }, // strictly speaking all words on the stack are popped.
/* RETURN 177 */ { 0, 1 }, // strictly speaking all words on the stack are popped.
/* GETSTATIC 178 */ {VARIABLE_STACK, 3 },
/* PUTSTATIC 179 */ {VARIABLE_STACK, 3 },
/* GETFIELD 180 */ {VARIABLE_STACK, 3 },
/* PUTFIELD 181 */ {VARIABLE_STACK, 3 },
/* INVOKEVIRTUAL 182 */ {VARIABLE_STACK, 3 },
/* INVOKESPECIAL 183 */ {VARIABLE_STACK, 3 },
/* INVOKESTATIC 184 */ {VARIABLE_STACK, 3 },
/* INVOKEINTERFACE 185 */ {VARIABLE_STACK, 5 },
/* XXXUNUSEDXXX 186 */ NS,
/* NEW 187 */ { 1, 3 },
/* NEWARRAY 188 */ { 0, 2 },
/* ANEWARRAY 189 */ { 0, 3 },
/* ARRAYLENGTH 190 */ { 0, 1 },
/* ATHROW 191 */ NS,
/* CHECKCAST 192 */ { 0, 3},
/* INSTANCEOF 193 */ { 0, 3 },
/* MONITORENTER 194 */ NS,
/* MONITOREXIT 195 */ NS,
/* WIDE 196 */ NS,
/* MULTIANEWARRAY 197 */ NS,
/* IFNULL 198 */ { -1, VMOpcode.IF_INS_LENGTH },
/* IFNONNULL 199 */ { -1, VMOpcode.IF_INS_LENGTH },
/* GOTO_W 200 */ {0, VMOpcode.GOTO_W_INS_LENGTH },
/* JSR_W 201 */ NS,
/* BREAKPOINT 202 */ NS,
};
/**
* Assume an IOException means some limit of the class file
* format was hit
*
*/
private void limitHit(IOException ioe)
{
cb.addLimitExceeded(ioe.toString());
}
/**
* Add an instruction that has no operand.
* All opcodes are 1 byte large.
*/
void addInstr(short opcode) {
try {
cout.putU1(opcode);
} catch (IOException ioe) {
limitHit(ioe);
}
if (SanityManager.DEBUG) {
if (OPCODE_ACTION[opcode][1] != 1)
SanityManager.THROWASSERT("Opcode " + opcode + " incorrect entry in OPCODE_ACTION -" +
" writing 1 byte - set as " + OPCODE_ACTION[opcode][1]);
}
}
/**
* Add an instruction that has a 16 bit operand.
*/
void addInstrU2(short opcode, int operand) {
try {
cout.putU1(opcode);
cout.putU2(operand);
} catch (IOException ioe) {
limitHit(ioe);
}
if (SanityManager.DEBUG) {
if (OPCODE_ACTION[opcode][1] != 3)
SanityManager.THROWASSERT("Opcode " + opcode + " incorrect entry in OPCODE_ACTION -" +
" writing 3 bytes - set as " + OPCODE_ACTION[opcode][1]);
}
}
/**
* Add an instruction that has a 32 bit operand.
*/
void addInstrU4(short opcode, int operand) {
try {
cout.putU1(opcode);
cout.putU4(operand);
} catch (IOException ioe) {
limitHit(ioe);
}
if (SanityManager.DEBUG) {
if (OPCODE_ACTION[opcode][1] != 5)
SanityManager.THROWASSERT("Opcode " + opcode + " incorrect entry in OPCODE_ACTION -" +
" writing 5 bytes - set as " + OPCODE_ACTION[opcode][1]);
}
}
/**
* Add an instruction that has an 8 bit operand.
*/
void addInstrU1(short opcode, int operand) {
try {
cout.putU1(opcode);
cout.putU1(operand);
} catch (IOException ioe) {
limitHit(ioe);
}
// Only debug code from here.
if (SanityManager.DEBUG) {
if (OPCODE_ACTION[opcode][1] != 2)
SanityManager.THROWASSERT("Opcode " + opcode + " incorrect entry in OPCODE_ACTION -" +
" writing 2 bytes - set as " + OPCODE_ACTION[opcode][1]);
}
}
/**
* This takes an instruction that has a narrow
* and a wide form for CPE access, and
* generates accordingly the right one.
* We assume the narrow instruction is what
* we were given, and that the wide form is
* the next possible instruction.
*/
void addInstrCPE(short opcode, int cpeNum) {
if (cpeNum < 256) {
addInstrU1(opcode, cpeNum);
}
else {
addInstrU2((short) (opcode+1), cpeNum);
}
}
/**
* This takes an instruction that can be wrapped in
* a wide for large variable #s and does so.
*/
void addInstrWide(short opcode, int varNum) {
if (varNum < 256) {
addInstrU1(opcode, varNum);
}
else {
addInstr(VMOpcode.WIDE);
addInstrU2(opcode, varNum);
}
}
/**
* For adding an instruction with 3 operands, a U2 and two U1's.
* So far, this is used by VMOpcode.INVOKEINTERFACE.
*/
void addInstrU2U1U1(short opcode, int operand1, short operand2,
short operand3) {
try {
cout.putU1(opcode);
cout.putU2(operand1);
cout.putU1(operand2);
cout.putU1(operand3);
} catch (IOException ioe) {
limitHit(ioe);
}
if (SanityManager.DEBUG) {
if (OPCODE_ACTION[opcode][1] != 5)
SanityManager.THROWASSERT("Opcode " + opcode + " incorrect entry in OPCODE_ACTION -" +
" writing 5 bytes - set as " + OPCODE_ACTION[opcode][1]);
}
}
/** Get the current program counter */
int getPC() {
return cout.size() + pcDelta;
}
/**
* Return the complete instruction length for the
* passed in opcode. This will include the space for
* the opcode and its operand.
*/
private static int instructionLength(short opcode)
{
int instructionLength = OPCODE_ACTION[opcode][1];
if (SanityManager.DEBUG)
{
if (instructionLength < 0)
SanityManager.THROWASSERT("Opcode without instruction length " + opcode);
}
return instructionLength;
}
/**
* The delta between cout.size() and the pc.
* For an initial code chunk this is -8 (CODE_OFFSET)
* since 8 bytes are written.
* For a nested CodeChunk return by insertCodeSpace the delta
* corresponds to the original starting pc.
* @see #insertCodeSpace
*/
private final int pcDelta;
/**
* The class we are generating code for, used to indicate that
* some limit was hit during code generation.
*/
final BCClass cb;
CodeChunk(BCClass cb) {
this.cb = cb;
cout = new ClassFormatOutput();
try {
cout.putU2(0); // max_stack, placeholder for now
cout.putU2(0); // max_locals, placeholder for now
cout.putU4(0); // code_length, placeholder 4 now
} catch (IOException ioe) {
limitHit(ioe);
}
pcDelta = - CodeChunk.CODE_OFFSET;
}
/**
* Return a CodeChunk that has limited visibility into
* this CodeChunk. Used when a caller needs to insert instructions
* into an existing stream.
* @param pc
* @param byteCount
*/
private CodeChunk(CodeChunk main, int pc, int byteCount)
{
this.cb = main.cb;
ArrayOutputStream aos =
new ArrayOutputStream(main.cout.getData());
try {
aos.setPosition(CODE_OFFSET + pc);
aos.setLimit(byteCount);
} catch (IOException e) {
limitHit(e);
}
cout = new ClassFormatOutput(aos);
pcDelta = pc;
}
private final ClassFormatOutput cout;
/**
* now that we have codeBytes, fix the lengths fields in it
* to reflect what was stored.
* Limits checked here are from these sections of the JVM spec.
* <UL>
* <LI> 4.7.3 The Code Attribute
* <LI> 4.10 Limitations of the Java Virtual Machine
* </UL>
*/
private void fixLengths(BCMethod mb, int maxStack, int maxLocals, int codeLength) {
byte[] codeBytes = cout.getData();
// max_stack is in bytes 0-1
if (mb != null && maxStack > 65535)
cb.addLimitExceeded(mb, "max_stack", 65535, maxStack);
codeBytes[0] = (byte)(maxStack >> 8 );
codeBytes[1] = (byte)(maxStack );
// max_locals is in bytes 2-3
if (mb != null && maxLocals > 65535)
cb.addLimitExceeded(mb, "max_locals", 65535, maxLocals);
codeBytes[2] = (byte)(maxLocals >> 8 );
codeBytes[3] = (byte)(maxLocals );
// code_length is in bytes 4-7
if (mb != null && codeLength > VMOpcode.MAX_CODE_LENGTH)
cb.addLimitExceeded(mb, "code_length",
VMOpcode.MAX_CODE_LENGTH, codeLength);
codeBytes[4] = (byte)(codeLength >> 24 );
codeBytes[5] = (byte)(codeLength >> 16 );
codeBytes[6] = (byte)(codeLength >> 8 );
codeBytes[7] = (byte)(codeLength );
}
/**
* wrap up the entry and stuff it in the class,
* now that it holds all of the instructions and
* the exception table.
*/
void complete(BCMethod mb, ClassHolder ch,
ClassMember method, int maxStack, int maxLocals) {
int codeLength = getPC();
ClassFormatOutput out = cout;
try {
out.putU2(0); // exception_table_length
if (SanityManager.DEBUG) {
if (SanityManager.DEBUG_ON("ClassLineNumbers")) {
// Add a single attribute - LineNumberTable
// This add fake line numbers that are the pc offset in the method.
out.putU2(1); // attributes_count
int cpiUTF = ch.addUtf8("LineNumberTable");
out.putU2(cpiUTF);
out.putU4((codeLength * 4) + 2);
out.putU2(codeLength);
for (int i = 0; i < codeLength; i++) {
out.putU2(i);
out.putU2(i);
}
} else {
out.putU2(0); // attributes_count
}
} else {
out.putU2(0); // attributes_count
// attributes is empty, a 0-element array.
}
} catch (IOException ioe) {
limitHit(ioe);
}
fixLengths(mb, maxStack, maxLocals, codeLength);
method.addAttribute("Code", out);
if (SanityManager.DEBUG)
{
// Only validate if the class file format is valid.
// Ok code length and guaranteed no errors building the class.
if ((codeLength <= VMOpcode.MAX_CODE_LENGTH)
&& (mb != null && mb.cb.limitMsg == null))
{
// Validate the alternate way to calculate the
// max stack agrees with the dynamic as the code
// is built way.
int walkedMaxStack = findMaxStack(ch, 0, codeLength);
if (walkedMaxStack != maxStack)
{
SanityManager.THROWASSERT("MAX STACK MISMATCH!! " +
maxStack + " <> " + walkedMaxStack);
}
}
}
}
/**
* Return the opcode at the given pc.
*/
short getOpcode(int pc)
{
return (short) (cout.getData()[CODE_OFFSET + pc] & 0xff);
}
/**
* Get the unsigned short value for the opcode at the program
* counter pc.
*/
private int getU2(int pc)
{
byte[] codeBytes = cout.getData();
int u2p = CODE_OFFSET + pc + 1;
return ((codeBytes[u2p] & 0xff) << 8) | (codeBytes[u2p+1] & 0xff);
}
/**
* Get the unsigned 32 bit value for the opcode at the program
* counter pc.
*/
private int getU4(int pc)
{
byte[] codeBytes = cout.getData();
int u4p = CODE_OFFSET + pc + 1;
return (((codeBytes[u4p] & 0xff) << 24) |
((codeBytes[u4p+1] & 0xff) << 16) |
((codeBytes[u4p+2] & 0xff) << 8) |
((codeBytes[u4p+3] & 0xff)));
}
/**
* Insert room for byteCount bytes after the instruction at pc
* and prepare to replace the instruction at pc. The instruction
* at pc is not modified by this call, space is allocated after it.
* The newly inserted space will be filled with NOP instructions.
*
* Returns a CodeChunk positioned at pc and available to write
* instructions upto (byteCode + length(existing instruction at pc) bytes.
*
* This chunk is left correctly positioned at the end of the code
* stream, ready to accept more code. Its pc will have increased by
* additionalBytes.
*
* It is the responsibility of the caller to patch up any
* branches or gotos.
*
* @param pc
* @param additionalBytes
*/
CodeChunk insertCodeSpace(int pc, int additionalBytes)
{
short existingOpcode = getOpcode(pc);
int lengthOfExistingInstruction
= instructionLength(existingOpcode);
if (additionalBytes > 0)
{
// Size of the current code after this pc.
int sizeToMove = (getPC() - pc) - lengthOfExistingInstruction;
// Increase the code by the number of bytes to be
// inserted. These NOPs will be overwritten by the
// moved code by the System.arraycopy below.
// It's assumed that the number of inserted bytes
// is small, one or two instructions worth, so it
// won't be a performance issue.
for (int i = 0; i < additionalBytes; i++)
addInstr(VMOpcode.NOP);
// Must get codeBytes here as the array might have re-sized.
byte[] codeBytes = cout.getData();
int byteOffset = CODE_OFFSET + pc + lengthOfExistingInstruction;
// Shift the existing code stream down
System.arraycopy(
codeBytes, byteOffset,
codeBytes, byteOffset + additionalBytes,
sizeToMove);
// Place NOPs in the space just freed by the move.
// This is not required, it ias assumed the caller
// will overwrite all the bytes they requested, but
// to be safe fill in with NOPs rather than leaving code
// that could break the verifier.
Arrays.fill(codeBytes, byteOffset, byteOffset + additionalBytes,
(byte) VMOpcode.NOP);
}
// The caller must overwrite the original instruction
// at pc, thus increase the range of the limit stream
// created to include those bytes.
additionalBytes += lengthOfExistingInstruction;
// Now the caller needs to fill in the instructions
// that make up the modified byteCount bytes of bytecode stream.
// Return a CodeChunk that can be used for this and
// is limited to only those bytes.
// The pc of the original code chunk is left unchanged.
return new CodeChunk(this, pc, additionalBytes);
}
/*
* * Methods related to splitting the byte code chunks into sections that
* fit in the JVM's limits for a single method.
*/
/**
* For a block of byte code starting at program counter pc for codeLength
* bytes return the maximum stack value, assuming a initial stack depth of
* zero.
*/
private int findMaxStack(ClassHolder ch, int pc, int codeLength) {
final int endPc = pc + codeLength;
int stack = 0;
int maxStack = 0;
for (; pc < endPc;) {
short opcode = getOpcode(pc);
int stackDelta = stackWordDelta(ch, pc, opcode);
stack += stackDelta;
if (stack > maxStack)
maxStack = stack;
int[] cond_pcs = findConditionalPCs(pc, opcode);
if (cond_pcs != null) {
// an else block exists.
if (cond_pcs[3] != -1) {
int blockMaxStack = findMaxStack(ch, cond_pcs[1],
cond_pcs[2]);
if ((stack + blockMaxStack) > maxStack)
maxStack = stack + blockMaxStack;
pc = cond_pcs[3];
continue;
}
}
pc += instructionLength(opcode);
}
return maxStack;
}
/**
* Return the number of stack words pushed (positive) or popped (negative)
* by this instruction.
*/
private int stackWordDelta(ClassHolder ch, int pc, short opcode) {
if (SanityManager.DEBUG) {
// this validates the OPCODE_ACTION entry
instructionLength(opcode);
}
int stackDelta = OPCODE_ACTION[opcode][0];
if (stackDelta == VARIABLE_STACK) {
stackDelta = getVariableStackDelta(ch, pc, opcode);
}
return stackDelta;
}
/**
* Get the type descriptor in the virtual machine format for the type
* defined by the constant pool index for the instruction at pc.
*/
private String getTypeDescriptor(ClassHolder ch, int pc) {
int cpi = getU2(pc);
// Field reference or method reference
CONSTANT_Index_info cii = (CONSTANT_Index_info) ch.getEntry(cpi);
// NameAndType reference
int nameAndType = cii.getI2();
cii = (CONSTANT_Index_info) ch.getEntry(nameAndType);
// UTF8 descriptor
int descriptor = cii.getI2();
CONSTANT_Utf8_info type = (CONSTANT_Utf8_info) ch.getEntry(descriptor);
String vmDescriptor = type.toString();
return vmDescriptor;
}
/**
* Get the word count for a type descriptor in the format of the virual
* machine. For a method this returns the the word count for the return
* type.
*/
private static int getDescriptorWordCount(String vmDescriptor) {
int width;
if (VMDescriptor.DOUBLE.equals(vmDescriptor))
width = 2;
else if (VMDescriptor.LONG.equals(vmDescriptor))
width = 2;
else if (vmDescriptor.charAt(0) == VMDescriptor.C_METHOD) {
switch (vmDescriptor.charAt(vmDescriptor.length() - 1)) {
case VMDescriptor.C_DOUBLE:
case VMDescriptor.C_LONG:
width = 2;
break;
case VMDescriptor.C_VOID:
width = 0;
break;
default:
width = 1;
break;
}
} else
width = 1;
return width;
}
/**
* Get the number of words pushed (positive) or popped (negative) by this
* instruction. The instruction is a get/put field or a method call, thus
* the size of the words is defined by the field or method being access.
*/
private int getVariableStackDelta(ClassHolder ch, int pc, int opcode) {
String vmDescriptor = getTypeDescriptor(ch, pc);
int width = CodeChunk.getDescriptorWordCount(vmDescriptor);
int stackDelta = 0;
// Stack delta depends on context.
switch (opcode) {
case VMOpcode.GETSTATIC:
stackDelta = width;
break;
case VMOpcode.GETFIELD:
stackDelta = width - 1; // one for popped object ref
break;
case VMOpcode.PUTSTATIC:
stackDelta = -width;
break;
case VMOpcode.PUTFIELD:
stackDelta = -width - 1; // one for pop object ref
break;
case VMOpcode.INVOKEVIRTUAL:
case VMOpcode.INVOKESPECIAL:
stackDelta = -1; // for instance reference for method call.
case VMOpcode.INVOKESTATIC:
stackDelta += (width - CodeChunk.parameterWordCount(vmDescriptor));
// System.out.println("invoked non-interface " + stackDelta);
break;
case VMOpcode.INVOKEINTERFACE:
// third byte contains the number of arguments to be popped
stackDelta = width - getOpcode(pc + 3);
// System.out.println("invoked interface " + stackDelta);
break;
default:
System.out.println("WHO IS THIS ");
break;
}
return stackDelta;
}
/**
* Calculate the number of stack words in the arguments pushed for this
* method descriptor.
*/
private static int parameterWordCount(String methodDescriptor) {
int wordCount = 0;
for (int i = 1;; i++) {
switch (methodDescriptor.charAt(i)) {
case VMDescriptor.C_ENDMETHOD:
return wordCount;
case VMDescriptor.C_DOUBLE:
case VMDescriptor.C_LONG:
wordCount += 2;
break;
case VMDescriptor.C_ARRAY:
// skip while there are array symbols.
do {
i++;
} while (methodDescriptor.charAt(i) == VMDescriptor.C_ARRAY);
if (methodDescriptor.charAt(i) != VMDescriptor.C_CLASS) {
// an array is a reference, even an array of doubles.
wordCount += 1;
break;
}
// fall through to skip the Lclassname; after the array.
case VMDescriptor.C_CLASS:
// skip until ;
do {
i++;
} while (methodDescriptor.charAt(i) != VMDescriptor.C_ENDCLASS);
wordCount += 1;
break;
default:
wordCount += 1;
break;
}
}
}
/**
* Find the limits of a conditional block starting at the instruction with
* the given opcode at the program counter pc.
* <P>
* Returns a six element integer array of program counters and lengths.
* <code> [0] - program counter of the IF opcode (passed in as pc) [1] -
* program counter of the start of the then block [2] - length of the then
* block [3] - program counter of the else block, -1 if no else block
* exists. [4] - length of of the else block, -1 if no else block exists.
* [5] - program counter of the common end point. </code>
*
* Looks for and handles conditionals that are written by the Conditional
* class.
*
* @return Null if the opcode is not the start of a conditional otherwise
* the array of values.
*/
private int[] findConditionalPCs(int pc, short opcode) {
switch (opcode) {
default:
return null;
case VMOpcode.IFNONNULL:
case VMOpcode.IFNULL:
case VMOpcode.IFEQ:
case VMOpcode.IFNE:
break;
}
int then_pc;
int else_pc;
int if_off = getU2(pc);
if ((if_off == 8)
&& (getOpcode(pc + VMOpcode.IF_INS_LENGTH) == VMOpcode.GOTO_W)) {
// 32 bit branch
then_pc = pc + VMOpcode.IF_INS_LENGTH + VMOpcode.GOTO_W_INS_LENGTH;
// Get else PC from the 32 bit offset within the GOTO_W
// instruction remembering to add it to the pc of that
// instruction, not the original IF.
else_pc = pc + VMOpcode.IF_INS_LENGTH
+ getU4(pc + VMOpcode.IF_INS_LENGTH);
} else {
then_pc = pc + VMOpcode.IF_INS_LENGTH;
else_pc = pc + if_off;
}
// Need to look for the goto or goto_w at the
// end of the then block. There might not be
// one for the case when there is no else block.
// In that case the then block will just run into
// what we currently think the else pc.
int end_pc = -1;
for (int tpc = then_pc; tpc < else_pc;) {
short opc = getOpcode(tpc);
// need to handle conditionals
int[] innerCond = findConditionalPCs(tpc, opc);
if (innerCond != null) {
// skip to the end of this conditional
tpc = innerCond[5]; // end_pc
continue;
}
if (opc == VMOpcode.GOTO) {
// not at the end of the then block
// so not our goto. Shouldn't see this
// with the current code due to the
// skipping of the conditional above.
// But safe defensive programming.
if (tpc != (else_pc - VMOpcode.GOTO_INS_LENGTH))
continue;
end_pc = tpc + getU2(tpc);
break;
} else if (opc == VMOpcode.GOTO_W) {
// not at the end of the then block
// so not our goto. SHouldn't see this
// with the current code due to the
// skipping of the conditional above.
// But safe defensive programming.
if (tpc != (else_pc - VMOpcode.GOTO_W_INS_LENGTH))
continue;
end_pc = tpc + getU4(tpc);
break;
}
tpc += instructionLength(opc);
}
int else_len;
int then_len;
if (end_pc == -1) {
// no else block;
end_pc = else_pc;
else_pc = -1;
then_len = end_pc - then_pc;
else_len = -1;
} else {
then_len = else_pc - then_pc;
else_len = end_pc - else_pc;
}
int[] ret = new int[6];
ret[0] = pc;
ret[1] = then_pc;
ret[2] = then_len;
ret[3] = else_pc;
ret[4] = else_len;
ret[5] = end_pc;
return ret;
}
/**
* Attempt to split the current method by pushing a chunk of
* its code into a sub-method. The starting point of the split
* (split_pc) must correspond to a stack depth of zero. It is the
* reponsibility of the caller to ensure this.
* Split is only made if there exists a chunk of code starting at
* pc=split_pc, whose stack depth upon termination is zero.
* The method will try to split a code section greater than
* optimalMinLength but may split earlier if no such block exists.
* <P>
* The method is aimed at splitting methods that contain
* many independent statements.
* <P>
* If a split is possible this method will perform the split and
* create a void sub method, and move the code into the sub-method
* and setup this method to call the sub-method before continuing.
* This method's max stack and current pc will be correctly set
* as though the method had just been created.
*
* @param mb Method for this chunk.
* @param ch Class definition
* @param optimalMinLength minimum length required for split
*/
final int splitZeroStack(BCMethod mb, ClassHolder ch, final int split_pc,
final int optimalMinLength) {
int splitMinLength = splitMinLength(mb);
int stack = 0;
// maximum possible split seen that is less than
// the minimum.
int possibleSplitLength = -1;
// do not split until at least this point (inclusive)
// used to ensure no split occurs in the middle of
// a conditional.
int outerConditionalEnd_pc = -1;
int end_pc = getPC(); // pc will be positioned at the end.
for (int pc = split_pc; pc < end_pc;) {
short opcode = getOpcode(pc);
int stackDelta = stackWordDelta(ch, pc, opcode);
stack += stackDelta;
// Cannot split a conditional but need to calculate
// the stack depth at the end of the conditional.
// Each path through the conditional will have the
// same stack depth.
int[] cond_pcs = findConditionalPCs(pc, opcode);
if (cond_pcs != null) {
// an else block exists, skip the then block.
if (cond_pcs[3] != -1) {
pc = cond_pcs[3];
continue;
}
if (SanityManager.DEBUG) {
if (outerConditionalEnd_pc != -1) {
if (cond_pcs[5] >= outerConditionalEnd_pc)
SanityManager.THROWASSERT("NESTED CONDITIONALS!");
}
}
if (outerConditionalEnd_pc == -1) {
outerConditionalEnd_pc = cond_pcs[5];
}
}
pc += instructionLength(opcode);
// Don't split in the middle of a conditional
if (outerConditionalEnd_pc != -1) {
if (pc > outerConditionalEnd_pc) {
// passed the outermost conditional
outerConditionalEnd_pc = -1;
}
continue;
}
if (stack != 0)
continue;
int splitLength = pc - split_pc;
if (splitLength < optimalMinLength) {
// record we do have a possible split.
possibleSplitLength = splitLength;
continue;
}
// no point splitting to a method bigger
// than the VM can handle. Save one for
// return instruction.
if (splitLength > BCMethod.CODE_SPLIT_LENGTH - 1) {
splitLength = -1;
}
else if (CodeChunk.isReturn(opcode))
{
// Don't handle a return in the middle of
// an instruction stream. Don't think this
// is generated, but be safe.
splitLength = -1;
}
// if splitLenth was set to -1 above then there
// is no possible split at this instruction.
if (splitLength == -1)
{
// no earlier split at all
if (possibleSplitLength == -1)
return -1;
// Decide if the earlier possible split is
// worth it.
if (possibleSplitLength <= splitMinLength)
return -1;
// OK go with the earlier split
splitLength = possibleSplitLength;
}
// Yes, we can split this big method into a smaller method!!
BCMethod subMethod = startSubMethod(mb, "void", split_pc,
splitLength);
return splitCodeIntoSubMethod(mb, ch, subMethod,
split_pc, splitLength);
}
return -1;
}
/**
* Start a sub method that we will split the portion of our current code to,
* starting from start_pc and including codeLength bytes of code.
*
* Return a BCMethod obtained from BCMethod.getNewSubMethod with the passed
* in return type and same parameters as mb if the code block to be moved
* uses parameters.
*/
private BCMethod startSubMethod(BCMethod mb, String returnType,
int split_pc, int blockLength) {
boolean needParameters = usesParameters(mb, split_pc, blockLength);
return mb.getNewSubMethod(returnType, needParameters);
}
/**
* Does a section of code use parameters.
* Any load, exception ALOAD_0 in an instance method, is
* seen as using parameters, as this complete byte code
* implementation does not use local variables.
*
*/
private boolean usesParameters(BCMethod mb, int pc, int codeLength) {
// does the method even have parameters?
if (mb.parameters == null)
return false;
boolean isStatic = (mb.myEntry.getModifier() & Modifier.STATIC) != 0;
int endPc = pc + codeLength;
for (; pc < endPc;) {
short opcode = getOpcode(pc);
switch (opcode) {
case VMOpcode.ILOAD_0:
case VMOpcode.LLOAD_0:
case VMOpcode.FLOAD_0:
case VMOpcode.DLOAD_0:
return true;
case VMOpcode.ALOAD_0:
if (isStatic)
return true;
break;
case VMOpcode.ILOAD_1:
case VMOpcode.LLOAD_1:
case VMOpcode.FLOAD_1:
case VMOpcode.DLOAD_1:
case VMOpcode.ALOAD_1:
return true;
case VMOpcode.ILOAD_2:
case VMOpcode.LLOAD_2:
case VMOpcode.FLOAD_2:
case VMOpcode.DLOAD_2:
case VMOpcode.ALOAD_2:
return true;
case VMOpcode.ILOAD_3:
case VMOpcode.LLOAD_3:
case VMOpcode.FLOAD_3:
case VMOpcode.DLOAD_3:
case VMOpcode.ALOAD_3:
return true;
case VMOpcode.ILOAD:
case VMOpcode.LLOAD:
case VMOpcode.FLOAD:
case VMOpcode.DLOAD:
case VMOpcode.ALOAD:
return true;
default:
break;
}
pc += instructionLength(opcode);
}
return false;
}
/**
* Split a block of code from this method into a sub-method
* and call it.
*
* Returns the pc of this method just after the call
* to the sub-method.
* @param mb My method
* @param ch My class
* @param subMethod Sub-method code was pushed into
* @param split_pc Program counter the split started at
* @param splitLength Length of code split
*/
private int splitCodeIntoSubMethod(BCMethod mb, ClassHolder ch,
BCMethod subMethod, final int split_pc, final int splitLength) {
CodeChunk subChunk = subMethod.myCode;
byte[] codeBytes = cout.getData();
// the code to be moved into the sub method
// as a block. This will correctly increase the
// program counter.
try {
subChunk.cout.write(codeBytes, CODE_OFFSET + split_pc, splitLength);
} catch (IOException ioe) {
limitHit(ioe);
}
// Just cause the sub-method to return,
// fix up its maxStack and then complete it.
if (subMethod.myReturnType.equals("void"))
subChunk.addInstr(VMOpcode.RETURN);
else
subChunk.addInstr(VMOpcode.ARETURN);
// Finding the max stack requires the class format to
// still be valid. If we have blown the number of constant
// pool entries then we can no longer guarantee that indexes
// into the constant pool in the code stream are valid.
if (cb.limitMsg != null)
return -1;
subMethod.maxStack = subChunk.findMaxStack(ch, 0, subChunk.getPC());
subMethod.complete();
return removePushedCode(mb, ch, subMethod, split_pc, splitLength);
}
/**
* Remove a block of code from this method that was pushed into a sub-method
* and call the sub-method.
*
* Returns the pc of this method just after the call to the sub-method.
*
* @param mb
* My method
* @param ch
* My class
* @param subMethod
* Sub-method code was pushed into
* @param split_pc
* Program counter the split started at
* @param splitLength
* Length of code split
*/
private int removePushedCode(BCMethod mb, ClassHolder ch,
BCMethod subMethod, final int split_pc, final int splitLength) {
// now need to fix up this method, create
// a new CodeChunk just to be clearer than
// trying to modify this chunk directly.
// total length of the code for this method before split
final int codeLength = getPC();
CodeChunk replaceChunk = new CodeChunk(mb.cb);
mb.myCode = replaceChunk;
mb.maxStack = 0;
byte[] codeBytes = cout.getData();
// write any existing code before the split point
// into the replacement chunk.
if (split_pc != 0) {
try {
replaceChunk.cout.write(codeBytes, CODE_OFFSET, split_pc);
} catch (IOException ioe) {
limitHit(ioe);
}
}
// Call the sub method, will write into replaceChunk.
mb.callSubMethod(subMethod);
int postSplit_pc = replaceChunk.getPC();
// Write the code remaining in this method into the replacement chunk
int remainingCodePC = split_pc + splitLength;
int remainingCodeLength = codeLength - splitLength - split_pc;
try {
replaceChunk.cout.write(codeBytes, CODE_OFFSET + remainingCodePC,
remainingCodeLength);
} catch (IOException ioe) {
limitHit(ioe);
}
// Finding the max stack requires the class format to
// still be valid. If we have blown the number of constant
// pool entries then we can no longer guarantee that indexes
// into the constant pool in the code stream are valid.
if (cb.limitMsg != null)
return -1;
mb.maxStack = replaceChunk.findMaxStack(ch, 0, replaceChunk.getPC());
return postSplit_pc;
}
/**
* Split an expression out of a large method into its own
* sub-method.
* <P>
* Method call expressions are of the form:
* <UL>
* <LI> expr.method(args) -- instance method call
* <LI> method(args) -- static method call
* </UL>
* Two special cases of instance method calls will be handled
* by the first incarnation of splitExpressionOut.
* three categories:
* <UL>
* <LI>this.method(args)
* <LI>this.getter().method(args)
* </UL>
* These calls are choosen as they are easier sub-cases
* and map to the code generated for SQL statements.
* Future coders can expand the method to cover more cases.
* <P>
* This method will split out such expressions in sub-methods
* and replace the original code with a call to that submethod.
* <UL>
* <LI>this.method(args) -&gt;&gt; this.sub1([parameters])
* <LI>this.getter().method(args) -&gt;&gt; this.sub1([parameters])
* </UL>
* The assumption is of course that the call to the sub-method
* is much smaller than the code it replaces.
* <P>
* Looking at the byte code for such calls they would look like
* (for an example three argument method):
* <code>
* this arg1 arg2 arg3 INVOKE // this.method(args)
* this INVOKE arg1 arg2 arg3 INVOKE // this.getter().metod(args)
* </code>
* The bytecode for the arguments can be arbitary long and
* consist of expressions, typical Derby code for generated
* queries is deeply nested method calls.
* <BR>
* If none of the arguments requred the parameters passed into
* the method, then in both cases the replacement bytecode
* would look like:
* <code>
* this.sub1();
* </code>
* Parameter handling is just as in the method splitZeroStack().
* <P>
* Because the VM is a stack machine the original byte code
* sequences are self contained. The stack at the start of
* is sequence is N and at the end (after the method call) will
* be:
* <UL>
* <LI> N - void method
* <LI> N + 1 - method returning a single word
* <LI> N + 2 - method returning a double word (java long or double)
* </UL>
* This code will handle the N+1 where the word is a reference,
* the typical case for generated code.
* <BR>
* The code is self contained because in general the byte code
* for the arguments will push and pop values but never drop
* below the stack value at the start of the byte code sequence.
* E.g. in the examples the stack before the first arg will be
* N+1 (the objectref for the method call) and at the end of the
* byte code for arg1 will be N+2 or N+3 depending on if arg1 is
* a single or double word argument. During the execution of
* the byte code the stack may have had many arguments pushed
* and popped, but will never have dropped below N+1. Thus the
* code for arg1 is independent of the stack's previous values
* and is self contained. This self-containment then extends to
* all the arguements, the method call itself and pushing the
* objectref for the method call, thus the complete
* sequence is self-contained.
* <BR>
* The self-containment breaks in a few cases, take the simple
* method call this.method(3), the byte code for this could be:
* <code>
* push3 this swap invoke
* </code>
* In this case the byte code for arg1 (swap) is not self-contained
* and relies on earlier stack values.
* <P>
* How to identify "self-contained blocks of code".
* <BR>
* We walk through the byte code and maintain a history of
* the program counter that indicates the start of the
* independent sequence each stack word depends on.
* Thus for a ALOAD_0 instruction which pushes 'this' the
* dependent pc is that of the this. If a DUP instruction followed
* then the top-word is now dependent on the previous word (this)
* and thus the dependence of it is equal to the dependence of
* the previous word. This information is kept in earliestIndepPC
* array as we process the instruction stream.
* <BR>
* When a INVOKE instruction is seen for an instance method
* that returns a single or double word, the dependence of
* the returned value is the dependence of the word in the
* stack that is the objectref for the call. This complete
* sequence from the pc the objectref depended on to the
* INVOKE instruction is then a self contained sequence
* and can be split into a sub-method.
* <BR>
* If the block is self-contained then it can be split, following
* similar logic to splitZeroStack().
*
* <P>
* WORK IN PROGRESS - Incremental development
* <BR>
* Currently walks the method maintaining the
* earliestIndepPC array and identifies potential blocks
* to splt, performs splits as required.
* Called by BCMethod but commented out in submitted code.
* Tested with local changes from calls in BCMethod.
* Splits generally work, though largeCodeGen shows
* a problem that will be fixed before the code in
* enabled for real.
*
*/
final int splitExpressionOut(final BCMethod mb, final ClassHolder ch,
final int optimalMinLength,
final int maxStack)
{
// Save the best block we have seen for splitting out.
int bestSplitPC = -1;
int bestSplitBlockLength = -1;
String bestSplitRT = null;
int splitMinLength = splitMinLength(mb);
// Program counter of the earliest instruction
// that the word in the current active stack
// at the given depth depends on.
//
// Some examples, N is the stack depth *after*
// the instruction.
// E.g.
// ALOAD_0 - pushes this, is an instruction that
// pushes an independent value, so the current
// stack word depends on the pc of current instruction.
// earliestIndepPC[N] = pc (that pushed the value).
//
// DUP - duplicates the top word, so the duplicated
// top word will depend on the same pc as the word
// it was duplicated from.
// I.e. earliestIndepPC[N]
// = earliestIndepPC[N-1];
//
// instance method call returning single word value.
// The top word will depend on the same pc as the
// objectref for the method call, which was at the
// same depth in this case.
// earliestIndepPC[N] unchanged
//
// at any time earliestIndepPC is only valid
// from 1 to N where N is the depth of the stack.
int[] earliestIndepPC = new int[maxStack+1];
int stack = 0;
//TODO: this conditional handling is copied from
//the splitZeroStack code, need to check to see
// how it fits with the expression logic.
// do not split until at least this point (inclusive)
// used to ensure no split occurs in the middle of
// a conditional.
int outerConditionalEnd_pc = -1;
int end_pc = getPC();
for (int pc = 0; pc < end_pc;) {
short opcode = getOpcode(pc);
int stackDelta = stackWordDelta(ch, pc, opcode);
stack += stackDelta;
// Cannot split a conditional but need to calculate
// the stack depth at the end of the conditional.
// Each path through the conditional will have the
// same stack depth.
int[] cond_pcs = findConditionalPCs(pc, opcode);
if (cond_pcs != null) {
// TODO: This conditional handling was copied
// from splitZeroStack, haven't looked in detail
// to see how a conditional should be handled
// with an expression split. So for the time
// being just bail.
if (true)
return -1;
// an else block exists, skip the then block.
if (cond_pcs[3] != -1) {
pc = cond_pcs[3];
continue;
}
if (SanityManager.DEBUG)
{
if (outerConditionalEnd_pc != -1)
{
if (cond_pcs[5] >= outerConditionalEnd_pc)
SanityManager.THROWASSERT("NESTED CONDITIONALS!");
}
}
if (outerConditionalEnd_pc == -1)
{
outerConditionalEnd_pc = cond_pcs[5];
}
}
pc += instructionLength(opcode);
// Don't split in the middle of a conditional
if (outerConditionalEnd_pc != -1) {
if (pc > outerConditionalEnd_pc) {
// passed the outermost conditional
outerConditionalEnd_pc = -1;
}
continue;
}
int opcode_pc = pc - instructionLength(opcode);
switch (opcode)
{
// Any instruction we don't have any information
// on, we simply clear all evidence of independent
// starting points, and start again.
default:
Arrays.fill(earliestIndepPC,
0, stack + 1, -1);
break;
// Independent instructions do not change the stack depth
// and the independence of the top word picks up
// the independence of the previous word at the same
// position. Ie. no change!
case VMOpcode.ARRAYLENGTH:
case VMOpcode.NOP:
case VMOpcode.CHECKCAST:
case VMOpcode.D2L:
case VMOpcode.DNEG:
case VMOpcode.F2I:
break;
// Independent instructions that push one word
case VMOpcode.ALOAD_0:
case VMOpcode.ALOAD_1:
case VMOpcode.ALOAD_2:
case VMOpcode.ALOAD_3:
case VMOpcode.ALOAD:
case VMOpcode.ACONST_NULL:
case VMOpcode.BIPUSH:
case VMOpcode.FCONST_0:
case VMOpcode.FCONST_1:
case VMOpcode.FCONST_2:
case VMOpcode.FLOAD:
case VMOpcode.ICONST_0:
case VMOpcode.ICONST_1:
case VMOpcode.ICONST_2:
case VMOpcode.ICONST_3:
case VMOpcode.ICONST_4:
case VMOpcode.ICONST_5:
case VMOpcode.ICONST_M1:
case VMOpcode.LDC:
case VMOpcode.LDC_W:
case VMOpcode.SIPUSH:
earliestIndepPC[stack] = opcode_pc;
break;
// Independent instructions that push two words
case VMOpcode.DCONST_0:
case VMOpcode.DCONST_1:
case VMOpcode.LCONST_0:
case VMOpcode.LCONST_1:
case VMOpcode.LDC2_W:
case VMOpcode.LLOAD:
case VMOpcode.LLOAD_0:
case VMOpcode.LLOAD_1:
case VMOpcode.LLOAD_2:
case VMOpcode.LLOAD_3:
earliestIndepPC[stack - 1] =
earliestIndepPC[stack] = opcode_pc;
break;
// nothing to do for pop, obviously no
// code will be dependent on the popped words.
case VMOpcode.POP:
case VMOpcode.POP2:
break;
case VMOpcode.SWAP:
earliestIndepPC[stack] = earliestIndepPC[stack -1];
break;
// push a value that depends on the previous value
case VMOpcode.I2L:
earliestIndepPC[stack] = earliestIndepPC[stack -1];
break;
case VMOpcode.GETFIELD:
{
String vmDescriptor = getTypeDescriptor(ch, opcode_pc);
int width = CodeChunk.getDescriptorWordCount(vmDescriptor);
if (width == 2)
earliestIndepPC[stack] = earliestIndepPC[stack -1];
break;
}
case VMOpcode.INVOKEINTERFACE:
case VMOpcode.INVOKEVIRTUAL:
{
// ...,objectref[,word]*
//
// => ...
// => ...,word
// => ...,word1,word2
// Width of the value returned by the method call.
String vmDescriptor = getTypeDescriptor(ch, opcode_pc);
int width = CodeChunk.getDescriptorWordCount(vmDescriptor);
// Independence of this block is the independence
// of the objectref that invokved the method.
int selfContainedBlockStart;
if (width == 0)
{
// objectref was at one more than the current depth
// no plan to split here though, as we are only
// splitting methods that return a reference.
selfContainedBlockStart = -1;
// earliestIndepPC[stack + 1];
}
else if (width == 1)
{
// stack is unchanged, objectref was at
// the current stack depth
selfContainedBlockStart = earliestIndepPC[stack];
}
else
{
// width == 2, objectref was one below the
// current stack depth.
// no plan to split here though, as we are only
// splitting methods that return a reference.
selfContainedBlockStart = -1;
// top two words depend on the objectref
// which was at the same depth of the first word
// of the 64 bit value.
earliestIndepPC[stack] =
earliestIndepPC[stack - 1];
}
if (selfContainedBlockStart != -1)
{
int blockLength = pc - selfContainedBlockStart;
if (blockLength <= splitMinLength)
{
// No point splitting, too small
}
else if (blockLength > (VMOpcode.MAX_CODE_LENGTH - 1))
{
// too big to split into a single method
// (one for the return opcode)
}
else
{
// Only split for a method that returns
// an class reference.
int me = vmDescriptor.lastIndexOf(')');
if (vmDescriptor.charAt(me+1) == 'L')
{
String rt = vmDescriptor.substring(me + 2,
vmDescriptor.length() - 1);
// convert to external format.
rt = rt.replace('/', '.');
if (blockLength >= optimalMinLength)
{
// Split now!
BCMethod subMethod = startSubMethod(mb,
rt, selfContainedBlockStart,
blockLength);
return splitCodeIntoSubMethod(mb, ch, subMethod,
selfContainedBlockStart, blockLength);
}
else if (blockLength > bestSplitBlockLength)
{
// Save it, may split at this point
// if nothing better seen.
bestSplitPC = selfContainedBlockStart;
bestSplitBlockLength = blockLength;
bestSplitRT = rt;
}
}
}
}
break;
}
}
}
if (bestSplitBlockLength != -1) {
BCMethod subMethod = startSubMethod(mb,
bestSplitRT, bestSplitPC,
bestSplitBlockLength);
return splitCodeIntoSubMethod(mb, ch, subMethod,
bestSplitPC, bestSplitBlockLength);
}
return -1;
}
/**
* See if the opcode is a return instruction.
* @param opcode opcode to be checked
* @return true for is a return instruction, false otherwise.
*/
private static boolean isReturn(short opcode)
{
switch (opcode)
{
case VMOpcode.RETURN:
case VMOpcode.ARETURN:
case VMOpcode.IRETURN:
case VMOpcode.FRETURN:
case VMOpcode.DRETURN:
case VMOpcode.LRETURN:
return true;
default:
return false;
}
}
/**
* Minimum split length for a sub-method. If the number of
* instructions to call the sub-method exceeds the length
* of the sub-method, then there's no point splitting.
* The number of bytes in the code stream to call
* a generated sub-method can take is based upon the number of method args.
* A method can have maximum of 255 words of arguments (section 4.10 JVM spec)
* which in the worst case would be 254 (one-word) parameters
* and this. For a sub-method the arguments will come from the
* parameters to the method, i.e. ALOAD, ILOAD etc.
* <BR>
* This leads to this number of instructions.
* <UL>
* <LI> 4 - 'this' and first 3 parameters have single byte instructions
* <LI> (N-4)*2 - Remaining parameters have two byte instructions
* <LI> 3 for the invoke instruction.
* </UL>
*/
private static int splitMinLength(BCMethod mb) {
int min = 1 + 3; // For ALOAD_0 (this) and invoke instruction
if (mb.parameters != null) {
int paramCount = mb.parameters.length;
min += paramCount;
if (paramCount > 3)
min += (paramCount - 3);
}
return min;
}
/*
final int splitNonZeroStack(BCMethod mb, ClassHolder ch,
final int codeLength, final int optimalMinLength,
int maxStack) {
// program counter for the instruction that
// made the stack reach the given stack depth.
int[] stack_pcs = new int[maxStack+1];
Arrays.fill(stack_pcs, -1);
int stack = 0;
// maximum possible split seen that is less than
// the minimum.
int possibleSplitLength = -1;
System.out.println("NZ SPLIT + " + mb.getName());
// do not split until at least this point (inclusive)
// used to ensure no split occurs in the middle of
// a conditional.
int outerConditionalEnd_pc = -1;
int end_pc = 0 + codeLength;
for (int pc = 0; pc < end_pc;) {
short opcode = getOpcode(pc);
int stackDelta = stackWordDelta(ch, pc, opcode);
stack += stackDelta;
// Cannot split a conditional but need to calculate
// the stack depth at the end of the conditional.
// Each path through the conditional will have the
// same stack depth.
int[] cond_pcs = findConditionalPCs(pc, opcode);
if (cond_pcs != null) {
// an else block exists, skip the then block.
if (cond_pcs[3] != -1) {
pc = cond_pcs[3];
continue;
}
if (SanityManager.DEBUG)
{
if (outerConditionalEnd_pc != -1)
{
if (cond_pcs[5] >= outerConditionalEnd_pc)
SanityManager.THROWASSERT("NESTED CONDITIONALS!");
}
}
if (outerConditionalEnd_pc == -1)
{
outerConditionalEnd_pc = cond_pcs[5];
}
}
pc += instructionLength(opcode);
// Don't split in the middle of a conditional
if (outerConditionalEnd_pc != -1) {
if (pc > outerConditionalEnd_pc) {
// passed the outermost conditional
outerConditionalEnd_pc = -1;
}
continue;
}
if (stackDelta == 0)
continue;
// Only split when the stack is having items popped
if (stackDelta > 0)
{
// pushing double word, clear out a
if (stackDelta == 2)
stack_pcs[stack - 1] = pc;
stack_pcs[stack] = pc;
continue;
}
int opcode_pc = pc - instructionLength(opcode);
// Look for specific opcodes that have the capability
// of having a significant amount of code in a self
// contained block.
switch (opcode)
{
// this.method(A) construct
// ... -- stack N
// push this -- stack N+1
// push args -- stack N+1+A
// call method -- stack N+R (R=0,1,2)
//
// stackDelta = (N+R) - (N+1+A) = R-(1+A)
// stack = N+R
// Need to determine N+1
//
//
//
// this.a(<i2>, <i2>, <i3>)
// returning int
//
// stackDelta = -3 (this & 3 args popped, ret pushed)
// initial depth N = 10
// pc - stack
// 100 ... - stack 10
// 101 push this - stack 11
// 109 push i1 - stack 12
// 125 push i2 - stack 13
// 156 push i3 - stack 14
// 157 call - stack 11
//
// need stack_pcs[11] = stack_pcs[11 + -3]
//
// ref.method(args).method(args) ... method(args)
//
case VMOpcode.INVOKEINTERFACE:
case VMOpcode.INVOKESPECIAL:
case VMOpcode.INVOKEVIRTUAL:
{
String vmDescriptor = getTypeDescriptor(ch, opcode_pc);
int r = CodeChunk.getDescriptorWordCount(vmDescriptor);
// PC of the opcode that pushed the reference for
// this method call.
int ref_pc = stack_pcs[stack - r + 1];
if (getOpcode(ref_pc) == VMOpcode.ALOAD_0) {
System.out.println("POSS SPLIT " + (pc - ref_pc) + " @ " + ref_pc);
}
break;
}
case VMOpcode.INVOKESTATIC:
String vmDescriptor = getTypeDescriptor(ch, opcode_pc);
int r = CodeChunk.getDescriptorWordCount(vmDescriptor);
int p1_pc = stack_pcs[stack - r + 1];
System.out.println("POSS STATIC SPLIT " + (pc - p1_pc) + " @ " + p1_pc);
}
stack_pcs[stack] = opcode_pc;
}
return -1;
}*/
}