blob: e84a0d7d3b8e46e9b85a49af458697b0f5da7abc [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.services.bytecode.BCMethod
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.compiler.ClassBuilder;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.classfile.ClassFormatOutput;
import org.apache.derby.iapi.services.compiler.LocalField;
import org.apache.derby.iapi.services.classfile.ClassHolder;
import org.apache.derby.iapi.services.classfile.ClassMember;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.services.classfile.VMDescriptor;
import org.apache.derby.iapi.services.classfile.VMOpcode;
import java.lang.reflect.Modifier;
import java.util.Vector;
import java.io.IOException;
/**
* MethodBuilder is used to piece together a method when
* building a java class definition.
* <p>
* When a method is first created, it has:
* <ul>
* <li> a return type
* <li> modifiers
* <li> a name
* <li> an empty parameter list
* <li> an empty throws list
* <li> an empty statement block
* </ul>
* <p>
* MethodBuilder implementations are required to supply a way for
* Statements and Expressions to give them code. Most typically, they may have
* a stream to which their contents writes the code that is of
* the type to satisfy what the contents represent.
* MethodBuilder implementations also have to have a way to supply
* ClassBuilders with their code, that satisfies the type of class
* builder they are implemented with. This is implementation-dependent,
* so ClassBuilders, MethodBuilders, Statements, and Expressions all have
* to be of the same implementation in order to interact to generate a class.
* <p>
* Method Builder implementation for generating bytecode.
*
*/
class BCMethod implements MethodBuilder {
/**
* Code length at which to split into sub-methods.
* Normally set to the maximim code length the
* JVM can support, but for testing the split code
* it can be reduced so that the standard tests
* cause some splitting. Tested with value set to 2000.
*/
static final int CODE_SPLIT_LENGTH = VMOpcode.MAX_CODE_LENGTH;
final BCClass cb;
protected final ClassHolder modClass; // the class it is in (modifiable fmt)
final String myReturnType;
/**
* The original name of the method, this
* represents how any user would call this method.
*/
private final String myName;
/**
* Fast access for the parametes, will be null
* if the method has no parameters.
*/
BCLocalField[] parameters;
/**
* List of parameter types with java language class names.
* Can be null or zero length for no parameters.
*/
private final String[] parameterTypes;
Vector<String> thrownExceptions; // expected to be names of Classes under Throwable
CodeChunk myCode;
protected ClassMember myEntry;
private int currentVarNum;
private int statementNum;
/**
* True if we are currently switching control
* over to a sub method to avoid hitting the code generation
* limit of 65535 bytes per method.
*/
private boolean handlingOverflow;
/**
* How many sub-methods we have overflowed to.
*/
private int subMethodCount;
BCMethod(ClassBuilder cb,
String returnType,
String methodName,
int modifiers,
String[] parms,
BCJava factory) {
this.cb = (BCClass) cb;
modClass = this.cb.modify();
myReturnType = returnType;
myName = methodName;
if (SanityManager.DEBUG) {
this.cb.validateType(returnType);
}
// if the method is not static, allocate for "this".
if ((modifiers & Modifier.STATIC) == 0 )
currentVarNum = 1;
String[] vmParamterTypes;
if (parms != null && parms.length != 0) {
int len = parms.length;
vmParamterTypes = new String[len];
parameters = new BCLocalField[len];
for (int i = 0; i < len; i++) {
Type t = factory.type(parms[i]);
parameters[i] = new BCLocalField(t, currentVarNum);
currentVarNum += t.width();
// convert to vmname for the BCMethodDescriptor.get() call
vmParamterTypes[i] = t.vmName();
}
}
else
vmParamterTypes = BCMethodDescriptor.EMPTY;
// create a code attribute
String sig = BCMethodDescriptor.get(vmParamterTypes, factory.type(returnType).vmName(), factory);
// stuff the completed information into the class.
myEntry = modClass.addMember(methodName, sig, modifiers);
// get code chunk
myCode = new CodeChunk(this.cb);
parameterTypes = parms;
}
//
// MethodBuilder interface
//
/**
* Return the logical name of the method. The current
* myEntry refers to the sub method we are currently
* overflowing to. Those sub-methods are hidden from any caller.
*/
public String getName() {
return myName;
}
public void getParameter(int id) {
int num = parameters[id].cpi;
short typ = parameters[id].type.vmType();
if (num < 4)
myCode.addInstr((short) (CodeChunk.LOAD_VARIABLE_FAST[typ]+num));
else
myCode.addInstrWide(CodeChunk.LOAD_VARIABLE[typ], num);
growStack(parameters[id].type);
}
/**
* a throwable can be added to the end of
* the list of thrownExceptions.
*/
public void addThrownException(String exceptionClass) {
// cannot add exceptions after code generation has started.
// Allowing this would cause the method overflow/split to
// break as the top-level method would not have the exception
// added in the sub method.
if (SanityManager.DEBUG)
{
if (myCode.getPC() != 0)
SanityManager.THROWASSERT("Adding exception after code generation " + exceptionClass
+ " to method " + getName());
}
if (thrownExceptions == null)
thrownExceptions = new Vector<String>();
thrownExceptions.add(exceptionClass);
}
/**
* when the method has had all of its parameters
* and thrown exceptions defined, and its statement
* block has been completed, it can be completed and
* its class file information generated.
* <p>
* further alterations of the method will not be
* reflected in the code generated for it.
*/
public void complete() {
// myCode.getPC() gives the code length since
// the program counter will be positioned after
// the last instruction. Note this value can
// be changed by the splitMethod call.
if (myCode.getPC() > CODE_SPLIT_LENGTH)
splitMethod();
// write exceptions attribute info
writeExceptions();
// get the code attribute to put itself into the class
// provide the final header information needed
myCode.complete(this, modClass, myEntry, maxStack, currentVarNum);
}
/**
* Attempt to split a large method by pushing code out to several
* sub-methods. Performs a number of steps.
* <OL>
* <LI> Split at zero stack depth.
* <LI> Split at non-zero stack depth (FUTURE)
* </OL>
*
* If the class has already exceeded some limit in building the
* class file format structures then don't attempt to split.
* Most likely the number of constant pool entries has been exceeded
* and thus the built class file no longer has integrity.
* The split code relies on being able to read the in-memory
* version of the class file in order to determine descriptors
* for methods and fields.
*/
private void splitMethod() {
int split_pc = 0;
boolean splittingZeroStack = true;
for (int codeLength = myCode.getPC();
(cb.limitMsg == null) &&
(codeLength > CODE_SPLIT_LENGTH);
codeLength = myCode.getPC())
{
int lengthToCheck = codeLength - split_pc;
int optimalMinLength;
if (codeLength < CODE_SPLIT_LENGTH * 2) {
// minimum required
optimalMinLength = codeLength - CODE_SPLIT_LENGTH;
} else {
// try to split as much as possible
// need one for the return instruction
optimalMinLength = CODE_SPLIT_LENGTH - 1;
}
if (optimalMinLength > lengthToCheck)
optimalMinLength = lengthToCheck;
if (splittingZeroStack)
{
split_pc = myCode.splitZeroStack(this, modClass, split_pc,
optimalMinLength);
}
else
{
// Note the split expression does not re-start split
// at point left off by the previous split expression.
// This could be done but would require some level
// of stack depth history to be kept across calls.
split_pc = myCode.splitExpressionOut(this, modClass,
optimalMinLength, maxStack);
}
// Negative split point returned means that no split
// was possible. Give up on this approach and goto
// the next approach.
if (split_pc < 0) {
if (!splittingZeroStack)
break;
splittingZeroStack = false;
split_pc = 0;
}
// success, continue on splitting after the call to the
// sub-method if the method still execeeds the maximum length.
}
}
/*
* class interface
*/
/**
* In their giveCode methods, the parts of the method body will want to get
* to the constant pool to add their constants. We really only want them
* treating it like a constant pool inclusion mechanism, we could write a
* wrapper to limit it to that.
*/
ClassHolder constantPool() {
return modClass;
}
//
// Class implementation
//
/**
* sets exceptionBytes to the attribute_info needed
* for a method's Exceptions attribute.
* The ClassUtilities take care of the header 6 bytes for us,
* so they are not included here.
* See The Java Virtual Machine Specification Section 4.7.5,
* Exceptions attribute.
*/
protected void writeExceptions() {
if (thrownExceptions == null)
return;
int numExc = thrownExceptions.size();
// don't write an Exceptions attribute if there are no exceptions.
if (numExc != 0) {
try{
ClassFormatOutput eout = new ClassFormatOutput((numExc * 2) + 2);
eout.putU2(numExc); // number_of_exceptions
for (int i = 0; i < numExc; i++) {
// put each exception into the constant pool
String e = thrownExceptions.get(i).toString();
int ei2 = modClass.addClassReference(e);
// add constant pool index to exception attribute_info
eout.putU2(ei2);
}
myEntry.addAttribute("Exceptions", eout);
} catch (IOException ioe) {
}
}
}
/*
** New push compiler api.
*/
/**
* Array of the current types of the values on the stack.
* A type that types up two words on the stack, e.g. double
* will only occupy one element in this array.
* This array is dynamically re-sized as needed.
*/
private Type[] stackTypes = new Type[8];
/**
* Points to the next array offset in stackTypes
* to be used. Really it's the number of valid entries
* in stackTypes.
*/
private int stackTypeOffset;
/**
* Maximum stack depth seen in this method, measured in words.
* Corresponds to max_stack in the Code attribute of section 4.7.3
* of the vm spec.
*/
int maxStack;
/**
* Current stack depth in this method, measured in words.
*/
private int stackDepth;
private void growStack(int size, Type type) {
stackDepth += size;
if (stackDepth > maxStack)
maxStack = stackDepth;
if (stackTypeOffset >= stackTypes.length) {
Type[] newStackTypes = new Type[stackTypes.length + 8];
System.arraycopy(stackTypes, 0, newStackTypes, 0, stackTypes.length);
stackTypes = newStackTypes;
}
stackTypes[stackTypeOffset++] = type;
if (SanityManager.DEBUG) {
int sum = 0;
for (int i = 0 ; i < stackTypeOffset; i++) {
sum += stackTypes[i].width();
}
if (sum != stackDepth) {
SanityManager.THROWASSERT("invalid stack depth " + stackDepth + " calc " + sum);
}
}
}
private void growStack(Type type) {
growStack(type.width(), type);
}
private Type popStack() {
stackTypeOffset--;
Type topType = stackTypes[stackTypeOffset];
stackDepth -= topType.width();
return topType;
}
private Type[] copyStack()
{
Type[] stack = new Type[stackTypeOffset];
System.arraycopy(stackTypes, 0, stack, 0, stackTypeOffset);
return stack;
}
public void pushThis() {
myCode.addInstr(VMOpcode.ALOAD_0);
growStack(1, cb.classType);
}
public void push(byte value) {
push(value, Type.BYTE);
}
public void push(boolean value) {
push(value ? 1 : 0, Type.BOOLEAN);
}
public void push(short value) {
push(value, Type.SHORT);
}
public void push(int value) {
push(value, Type.INT);
}
public void dup() {
Type dup = popStack();
myCode.addInstr(dup.width() == 2 ? VMOpcode.DUP2 : VMOpcode.DUP);
growStack(dup);
growStack(dup);
}
public void swap() {
// have A,B
// want B,A
Type wB = popStack();
Type wA = popStack();
growStack(wB);
growStack(wA);
if (wB.width() == 1) {
// top value is one word
if (wA.width() == 1) {
myCode.addInstr(VMOpcode.SWAP);
return;
} else {
myCode.addInstr(VMOpcode.DUP_X2);
myCode.addInstr(VMOpcode.POP);
}
} else {
// top value is two words
if (wA.width() == 1) {
myCode.addInstr(VMOpcode.DUP2_X1);
myCode.addInstr(VMOpcode.POP2);
} else {
myCode.addInstr(VMOpcode.DUP2_X2);
myCode.addInstr(VMOpcode.POP2);
}
}
// all except the simple swap push an extra
// copy of B which needs to be popped.
growStack(wB);
popStack();
}
/**
* Push an integer value. Uses the special integer opcodes
* for the constants -1 to 5, BIPUSH for values that fit in
* a byte and SIPUSH for values that fit in a short. Otherwise
* uses LDC with a constant pool entry.
*
* @param value Value to be pushed
* @param type Final type of the value.
*/
private void push(int value, Type type) {
CodeChunk chunk = myCode;
if (value >= -1 && value <= 5)
chunk.addInstr((short)(VMOpcode.ICONST_0+value));
else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE)
chunk.addInstrU1(VMOpcode.BIPUSH,value);
else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)
chunk.addInstrU2(VMOpcode.SIPUSH,value);
else {
int cpe = modClass.addConstant(value);
addInstrCPE(VMOpcode.LDC, cpe);
}
growStack(type.width(), type);
}
/**
* Push a long value onto the stack.
* For the values zero and one the LCONST_0 and
* LCONST_1 instructions are used.
* For values betwee Short.MIN_VALUE and Short.MAX_VALUE
* inclusive an byte/short/int value is pushed
* using push(int, Type) followed by an I2L instruction.
* This saves using a constant pool entry for such values.
* All other values use a constant pool entry. For values
* in the range of an Integer an integer constant pool
* entry is created to allow sharing with integer constants
* and to reduce constant pool slot entries.
*/
public void push(long value) {
CodeChunk chunk = myCode;
if (value == 0L || value == 1L) {
short opcode = value == 0L ? VMOpcode.LCONST_0 : VMOpcode.LCONST_1;
chunk.addInstr(opcode);
} else if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
// the push(int, Type) method grows the stack for us.
push((int) value, Type.LONG);
chunk.addInstr(VMOpcode.I2L);
return;
} else {
int cpe = modClass.addConstant(value);
chunk.addInstrU2(VMOpcode.LDC2_W, cpe);
}
growStack(2, Type.LONG);
}
public void push(float value) {
CodeChunk chunk = myCode;
if (value == 0.0)
{
chunk.addInstr(VMOpcode.FCONST_0);
}
else if (value == 1.0)
{
chunk.addInstr(VMOpcode.FCONST_1);
}
else if (value == 2.0)
{
chunk.addInstr(VMOpcode.FCONST_2);
}
else
{
int cpe = modClass.addConstant(value);
addInstrCPE(VMOpcode.LDC, cpe);
}
growStack(1, Type.FLOAT);
}
public void push(double value) {
CodeChunk chunk = myCode;
if (value == 0.0) {
chunk.addInstr(VMOpcode.DCONST_0);
}
else {
int cpe = modClass.addConstant(value);
chunk.addInstrU2(VMOpcode.LDC2_W, cpe);
}
growStack(2, Type.DOUBLE);
}
public void push(String value) {
int cpe = modClass.addConstant(value);
addInstrCPE(VMOpcode.LDC, cpe);
growStack(1, Type.STRING);
}
public void methodReturn() {
short opcode;
if (stackDepth != 0) {
Type topType = popStack();
opcode = CodeChunk.RETURN_OPCODE[topType.vmType()];
} else {
opcode = VMOpcode.RETURN;
}
myCode.addInstr(opcode);
if (SanityManager.DEBUG) {
if (stackDepth != 0)
SanityManager.THROWASSERT("items left on stack " + stackDepth);
}
}
public Object describeMethod(short opcode, String declaringClass, String methodName, String returnType) {
Type rt = cb.factory.type(returnType);
String methodDescriptor = BCMethodDescriptor.get(BCMethodDescriptor.EMPTY, rt.vmName(), cb.factory);
if ((declaringClass == null) && (opcode != VMOpcode.INVOKESTATIC)) {
Type dt = stackTypes[stackTypeOffset - 1];
if (declaringClass == null)
declaringClass = dt.javaName();
}
int cpi = modClass.addMethodReference(declaringClass, methodName,
methodDescriptor, opcode == VMOpcode.INVOKEINTERFACE);
return new BCMethodCaller(opcode, rt, cpi);
}
public int callMethod(Object methodDescriptor) {
// pop the reference off the stack
popStack();
BCMethodCaller mc = (BCMethodCaller) methodDescriptor;
int cpi = mc.cpi;
short opcode = mc.opcode;
if (opcode == VMOpcode.INVOKEINTERFACE) {
myCode.addInstrU2U1U1(opcode, cpi, (short) 1, (short) 0);
}
else
myCode.addInstrU2(opcode, cpi);
// this is the return type of the method
Type rt = mc.type;
int rw = rt.width();
if (rw != 0)
growStack(rw, rt);
else
{
overflowMethodCheck();
}
return cpi;
}
public int callMethod(short opcode, String declaringClass, String methodName,
String returnType, int numArgs) {
Type rt = cb.factory.type(returnType);
int initialStackDepth = stackDepth;
// get the array of parameter types
String [] debugParameterTypes = null;
String[] vmParameterTypes;
if (numArgs == 0) {
vmParameterTypes = BCMethodDescriptor.EMPTY;
} else {
if (SanityManager.DEBUG) {
debugParameterTypes = new String[numArgs];
}
vmParameterTypes = new String[numArgs];
for (int i = numArgs - 1; i >= 0; i--) {
Type at = popStack();
vmParameterTypes[i] = at.vmName();
if (SanityManager.DEBUG) {
debugParameterTypes[i] = at.javaName();
}
}
}
String methodDescriptor = BCMethodDescriptor.get(vmParameterTypes, rt.vmName(), cb.factory);
Type dt = null;
if (opcode != VMOpcode.INVOKESTATIC) {
dt = popStack();
}
Type dtu = vmNameDeclaringClass(declaringClass);
if (dtu != null)
dt = dtu;
int cpi = modClass.addMethodReference(dt.vmNameSimple, methodName,
methodDescriptor, opcode == VMOpcode.INVOKEINTERFACE);
if (opcode == VMOpcode.INVOKEINTERFACE) {
short callArgCount = (short) (initialStackDepth - stackDepth);
myCode.addInstrU2U1U1(opcode, cpi, callArgCount, (short) 0);
}
else
myCode.addInstrU2(opcode, cpi);
// this is the return type of the method
int rw = rt.width();
if (rw != 0)
growStack(rw, rt);
else
{
overflowMethodCheck();
}
// Check the declared type of the method
if (SanityManager.DEBUG) {
d_BCValidate.checkMethod(opcode, dt, methodName, debugParameterTypes, rt);
}
return cpi;
}
private Type vmNameDeclaringClass(String declaringClass) {
if (declaringClass == null)
return null;
return cb.factory.type(declaringClass);
}
public void callSuper() {
pushThis();
callMethod(VMOpcode.INVOKESPECIAL, cb.getSuperClassName(), "<init>", "void", 0);
}
public void pushNewStart(String className) {
int cpi = modClass.addClassReference(className);
// Use U2, not CPE, since only wide form exists.
myCode.addInstrU2(VMOpcode.NEW, cpi);
myCode.addInstr(VMOpcode.DUP);
// Grow the stack twice as we are pushing
// two instances of newly created reference
Type nt = cb.factory.type(className);
growStack(1, nt);
growStack(1, nt);
}
public void pushNewComplete(int numArgs) {
callMethod(VMOpcode.INVOKESPECIAL, (String) null, "<init>", "void", numArgs);
}
public void upCast(String className) {
Type uct = cb.factory.type(className);
stackTypes[stackTypeOffset - 1] = uct;
//popStack();
//growStack(1, uct);
}
public void cast(String className) {
// Perform a simple optimization to not
// insert a checkcast when the classname
// of the cast exactly matches the type name
// currently on the stack.
// This can reduce the amount of generated code.
// This compiler/class generator does not load
// classes to check relationships or any other
// information. Thus other optimizations where a cast
// is not required are not implemented.
Type tbc = stackTypes[stackTypeOffset - 1];
short sourceType = tbc.vmType();
if (sourceType == BCExpr.vm_reference)
{
// Simple optimize step
if (className.equals(tbc.javaName()))
{
// do nothing, exact matching type
return;
}
}
Type ct = cb.factory.type(className);
popStack();
short targetType = ct.vmType();
if (SanityManager.DEBUG) {
if (!((sourceType == BCExpr.vm_reference &&
targetType == BCExpr.vm_reference) ||
(sourceType != BCExpr.vm_reference &&
targetType != BCExpr.vm_reference))) {
SanityManager.THROWASSERT("Both or neither must be object types " + ct.javaName() + " " + tbc.javaName());
}
}
// if it is an object type, do a checkcast on it.
if (sourceType == BCExpr.vm_reference) {
int cpi = modClass.addClassReference(ct.vmNameSimple);
myCode.addInstrU2(VMOpcode.CHECKCAST, cpi);
}
// otherwise, try to convert it.
else {
short opcode = VMOpcode.NOP;
// we use the conversionInfo array
// to determine how to convert; if
// the result type of the conversion
// is not our target type, we are not done
// yet. Make sure there are no
// infinite loop possibilities in the
// conversionInfo array!
while (sourceType!=targetType && opcode!=VMOpcode.BAD) {
short[] currentConversion =
CodeChunk.CAST_CONVERSION_INFO[sourceType][targetType];
sourceType = currentConversion[1];
opcode = currentConversion[0];
if (opcode != VMOpcode.NOP) {
myCode.addInstr(opcode);
}
}
if (SanityManager.DEBUG) {
SanityManager.ASSERT(opcode != VMOpcode.BAD,
"BAD VMOpcode not expected in cast");
}
}
growStack(ct);
}
public void isInstanceOf(String className) {
int cpi = modClass.addClassReference(className);
myCode.addInstrU2(VMOpcode.INSTANCEOF, cpi);
popStack();
growStack(1, Type.BOOLEAN);
}
public void pushNull(String type) {
myCode.addInstr(VMOpcode.ACONST_NULL);
growStack(1, cb.factory.type(type));
}
public void getField(LocalField field) {
BCLocalField lf = (BCLocalField) field;
Type lt = lf.type;
pushThis();
myCode.addInstrU2(VMOpcode.GETFIELD, lf.cpi);
popStack();
growStack(lt);
}
public void getField(String declaringClass, String fieldName, String fieldType) {
Type dt = popStack();
Type dtu = vmNameDeclaringClass(declaringClass);
if (dtu != null)
dt = dtu;
getField(VMOpcode.GETFIELD, dt.vmNameSimple, fieldName, fieldType);
}
/**
Push the contents of the described static field onto the stack.
*/
public void getStaticField(String declaringClass, String fieldName, String fieldType) {
getField(VMOpcode.GETSTATIC, declaringClass, fieldName, fieldType);
}
private void getField(short opcode, String declaringClass, String fieldName, String fieldType) {
Type ft = cb.factory.type(fieldType);
int cpi = modClass.addFieldReference(vmNameDeclaringClass(declaringClass).vmNameSimple, fieldName, ft.vmName());
myCode.addInstrU2(opcode, cpi);
growStack(ft);
}
/**
* Set the field but don't duplicate its value so
* nothing is left on the stack after this call.
*/
public void setField(LocalField field) {
BCLocalField lf = (BCLocalField) field;
putField(lf.type, lf.cpi, false);
overflowMethodCheck();
}
/**
Upon entry the top word(s) on the stack is
the value to be put into the field. Ie.
we have
<PRE>
word
</PRE>
Before the call we need
<PRE>
word
this
word
</PRE>
word2,word1 -&gt; word2, word1, word2
So that we are left with word after the put.
*/
public void putField(LocalField field) {
BCLocalField lf = (BCLocalField) field;
putField(lf.type, lf.cpi, true);
}
/**
Pop the top stack value and store it in the instance field of this class.
*/
public void putField(String fieldName, String fieldType) {
Type ft = cb.factory.type(fieldType);
int cpi = modClass.addFieldReference(cb.classType.vmNameSimple, fieldName, ft.vmName());
putField(ft, cpi, true);
}
private void putField(Type fieldType, int cpi, boolean dup) {
// now have ...,value
if (dup)
{
myCode.addInstr(fieldType.width() == 2 ? VMOpcode.DUP2 : VMOpcode.DUP);
growStack(fieldType);
}
// now have
// dup true: ...,value,value
// dup false: ...,value,
pushThis();
// now have
// dup true: ...,value,value,this
// dup false: ...,value,this
swap();
// now have
// dup true: ...,value,this,value
// dup false: ...,this,value
myCode.addInstrU2(VMOpcode.PUTFIELD, cpi);
popStack(); // the value
popStack(); // this
// now have
// dup true: ...,value
// dup false: ...
}
/**
Pop the top stack value and store it in the field.
This call requires the instance to be pushed by the caller.
*/
public void putField(String declaringClass, String fieldName, String fieldType) {
Type vt = popStack();
Type dt = popStack();
if (SanityManager.DEBUG) {
if (dt.width() != 1)
SanityManager.THROWASSERT("reference expected for field access - is " + dt.javaName());
}
// have objectref,value
// need value,objectref,value
myCode.addInstr(vt.width() == 2 ? VMOpcode.DUP2_X1 : VMOpcode.DUP_X1);
growStack(vt);
growStack(dt);
growStack(vt);
Type dtu = vmNameDeclaringClass(declaringClass);
if (dtu != null)
dt = dtu;
Type ft = cb.factory.type(fieldType);
int cpi = modClass.addFieldReference(dt.vmNameSimple, fieldName, ft.vmName());
myCode.addInstrU2(VMOpcode.PUTFIELD, cpi);
popStack(); // value
popStack(); // reference
}
public void conditionalIfNull() {
conditionalIf(VMOpcode.IFNONNULL);
}
public void conditionalIf() {
conditionalIf(VMOpcode.IFEQ);
}
private Conditional condition;
private void conditionalIf(short opcode) {
popStack();
// Save the stack upon entry to the 'then' block of the
// 'if' so that we can set up the 'else' block with the
// correct stack on entry.
condition = new Conditional(condition, myCode, opcode, copyStack());
}
public void startElseCode() {
// start the else code
Type[] entryStack = condition.startElse(this, myCode, copyStack());
for (int i = stackDepth = 0; i < entryStack.length; i++)
{
stackDepth += (stackTypes[i] = entryStack[i]).width();
}
this.stackTypeOffset = entryStack.length;
}
public void completeConditional() {
condition = condition.end(this, myCode, stackTypes, stackTypeOffset);
}
public void pop() {
if (SanityManager.DEBUG) {
if (stackDepth == 0)
SanityManager.THROWASSERT("pop when stack is empty!");
}
Type toPop = popStack();
myCode.addInstr(toPop.width() == 2 ? VMOpcode.POP2 : VMOpcode.POP);
overflowMethodCheck();
}
public void endStatement() {
if (stackDepth != 0) {
pop();
}
//if (SanityManager.DEBUG) {
// if (stackDepth != 0)
// SanityManager.THROWASSERT("items left on stack " + stackDepth);
// }
}
/**
*/
public void getArrayElement(int element) {
push(element);
popStack(); // int just pushed will be popped by array access
Type arrayType = popStack();
String arrayJava = arrayType.javaName();
String componentString = arrayJava.substring(0,arrayJava.length()-2);
Type componentType = cb.factory.type(componentString);
short typ = componentType.vmType();
// boolean has a type id of integer, here it needs to be byte.
if ((typ == BCExpr.vm_int) && (componentType.vmName().equals("Z")))
typ = BCExpr.vm_byte;
myCode.addInstr(CodeChunk.ARRAY_ACCESS[typ]);
growStack(componentType);
}
// come in with ref, value
public void setArrayElement(int element) {
// ref, value
push(element);
// ref, value, index
swap();
Type componentType = popStack(); // value
popStack(); // int just pushed will be popped by array access
popStack(); // array ref.
short typ = componentType.vmType();
// boolean has a type id of integer, here it needs to be byte.
if ((typ == BCExpr.vm_int) && (componentType.vmName().equals("Z")))
typ = BCExpr.vm_byte;
myCode.addInstr(CodeChunk.ARRAY_STORE[typ]);
}
/**
this array maps the BCExpr vm_* constants 0..6 to
the expected VM type constants for the newarray instruction.
<p>
Because boolean was mapped to integer for general instructions,
it will have to be specially matched and mapped to its value
directly (4).
*/
private static final byte newArrayElementTypeMap[] = { 8, 9, 10, 11, 6, 7, 5 };
static final byte T_BOOLEAN = 4;
/**
Create an array instance
Stack ... =&gt;
...,arrayref
*/
public void pushNewArray(String className, int size) {
push(size);
popStack(); // int just pushed will be popped by array creation
Type elementType = cb.factory.type(className);
// determine the instruction to use based on the element type
if (elementType.vmType() == BCExpr.vm_reference) {
// For an array of Java class/interface elements, generate:
// ANEWARRAY #cpei ; where cpei is a constant pool index for the class
int cpi = modClass.addClassReference(elementType.javaName());
// Use U2, not CPE, since only wide form exists.
myCode.addInstrU2(VMOpcode.ANEWARRAY, cpi);
} else {
byte atype;
// get the argument for the array type
// if the element type is boolean, we can't use the map
// because the type id will say integer.
// but we can use vm_int test to weed out some tests
if (elementType.vmType() == BCExpr.vm_int &&
VMDescriptor.C_BOOLEAN == elementType.vmName().charAt(0))
atype = T_BOOLEAN;
else
atype = newArrayElementTypeMap[elementType.vmType()];
// For an array of Java builtin type elements, generate:
// NEWARRAY #atype ; where atype is a constant for the builtin type
myCode.addInstrU1(VMOpcode.NEWARRAY, atype);
}
// an array reference is an object, hence width of 1
growStack(1, cb.factory.type(className.concat("[]")));
}
/**
* Write a instruction that uses a constant pool entry
* as an operand, add a limit exceeded message if
* the number of constant pool entries has exceeded
* the limit.
*/
private void addInstrCPE(short opcode, int cpe)
{
if (cpe >= VMOpcode.MAX_CONSTANT_POOL_ENTRIES)
cb.addLimitExceeded(this, "constant_pool_count",
VMOpcode.MAX_CONSTANT_POOL_ENTRIES, cpe);
myCode.addInstrCPE(opcode, cpe);
}
/**
Tell if statement number in this method builder hits limit. This
method builder keeps a counter of how many statements are added to it.
Caller should call this function every time it tries to add a statement
to this method builder (counter is increased by 1), then the function
returns whether the accumulated statement number hits a limit.
The reason of doing this is that Java compiler has a limit of 64K code
size for each method. We might hit this limit if an extremely long
insert statement is issued, for example (see beetle 4293). Counting
statement number is an approximation without too much overhead.
*/
public boolean statementNumHitLimit(int noStatementsAdded)
{
if (statementNum > 2048) // 2K limit
{
return true;
}
else
{
statementNum = statementNum + noStatementsAdded;
return false;
}
}
/**
* Check to see if the current method byte code is nearing the
* limit of 65535. If it is start overflowing to a new method.
* <P>
* Overflow is handled for a method named e23 as:
* <CODE>
public Object e23()
{
... existing code
// split point
return e23_0();
}
private Object e23_0()
{
... first set overflowed code
// split point
return e23_1();
}
private Object e23_1()
{
... second set overflowed code
// method complete
return result;
}
</CODE>
<P>
These overflow methods are hidden from the code using this MethodBuilder,
it continues to think that it is building a single method with the
original name.
* <BR> Restrictions:
* <UL>
* <LI> Only handles methods with no arguments
* <LI> Stack depth must be zero
* </UL>
*
*
*/
private void overflowMethodCheck()
{
if (stackDepth != 0) {
// Can only overflow to new method if the stack is empty.
return;
}
if (handlingOverflow)
return;
// don't sub method in the middle of a conditional
if (condition != null)
return;
int currentCodeSize = myCode.getPC();
// Overflow at >= 55,000 bytes which is someway
// below the limit of 65,535. Ideally overflow
// would occur at 65535 minus the few bytes needed
// to call the sub-method, but the issue is at this level
// we don't know frequently we are called given the restriction
// of only being called when the stack depth is zero.
// Thus split earlier to try ensure most cases are caught.
// Only downside is that we may split into N methods when N-1 would suffice.
if (currentCodeSize < 55000)
return;
// only handle no-arg methods at the moment.
if (parameters != null)
{
if (parameters.length != 0)
return;
}
BCMethod subMethod = getNewSubMethod(myReturnType, false);
// stop any recursion
handlingOverflow = true;
// in this method make a call to the sub method we will
// be transferring control to.
callSubMethod(subMethod);
// and return its value, works just as well for a void method!
this.methodReturn();
this.complete();
handlingOverflow = false;
// now the tricky bit, make this object take over the
// code etc. from the sub method. This is done so
// that any code that has a reference to this MethodBuilder
// will continue to work. They will be writing code into the
// new sub method.
this.myEntry = subMethod.myEntry;
this.myCode = subMethod.myCode;
this.currentVarNum = subMethod.currentVarNum;
this.statementNum = subMethod.statementNum;
// copy stack info
this.stackTypes = subMethod.stackTypes;
this.stackTypeOffset = subMethod.stackTypeOffset;
this.maxStack = subMethod.maxStack;
this.stackDepth = subMethod.stackDepth;
}
/**
* Create a sub-method from this method to allow the code builder to split a
* single logical method into multiple methods to avoid the 64k per-method
* code size limit. The sub method with inherit the thrown exceptions of
* this method.
*
* @param returnType
* Return type of the new method
* @param withParameters
* True to define the method with matching parameters false to
* define it with no parameters.
* @return A valid empty sub method.
*/
final BCMethod getNewSubMethod(String returnType, boolean withParameters) {
int modifiers = myEntry.getModifier();
// the sub-method can be private to ensure that no-one
// can call it accidentally from outside the class.
modifiers &= ~(Modifier.PROTECTED | Modifier.PUBLIC);
modifiers |= Modifier.PRIVATE;
String subMethodName = myName + "_s"
+ Integer.toString(subMethodCount++);
BCMethod subMethod = (BCMethod) cb.newMethodBuilder(modifiers,
returnType, subMethodName, withParameters ? parameterTypes
: null);
subMethod.thrownExceptions = this.thrownExceptions;
return subMethod;
}
/**
* Call a sub-method created by getNewSubMethod handling parameters
* correctly.
*/
final void callSubMethod(BCMethod subMethod) {
// in this method make a call to the sub method we will
// be transferring control to.
short op;
if ((myEntry.getModifier() & Modifier.STATIC) == 0) {
op = VMOpcode.INVOKEVIRTUAL;
this.pushThis();
} else {
op = VMOpcode.INVOKESTATIC;
}
int parameterCount = subMethod.parameters == null ? 0
: subMethod.parameters.length;
// push my parameter values for the call.
for (int pi = 0; pi < parameterCount; pi++)
this.getParameter(pi);
this.callMethod(op, modClass.getName(), subMethod.getName(),
subMethod.myReturnType, parameterCount);
}
}