0001: /*
0002:
0003: Derby - Class org.apache.derby.impl.services.bytecode.BCMethod
0004:
0005: Licensed to the Apache Software Foundation (ASF) under one or more
0006: contributor license agreements. See the NOTICE file distributed with
0007: this work for additional information regarding copyright ownership.
0008: The ASF licenses this file to you under the Apache License, Version 2.0
0009: (the "License"); you may not use this file except in compliance with
0010: the License. You may obtain a copy of the License at
0011:
0012: http://www.apache.org/licenses/LICENSE-2.0
0013:
0014: Unless required by applicable law or agreed to in writing, software
0015: distributed under the License is distributed on an "AS IS" BASIS,
0016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: See the License for the specific language governing permissions and
0018: limitations under the License.
0019:
0020: */
0021:
0022: package org.apache.derby.impl.services.bytecode;
0023:
0024: import org.apache.derby.iapi.services.compiler.ClassBuilder;
0025: import org.apache.derby.iapi.services.compiler.MethodBuilder;
0026: import org.apache.derby.iapi.services.classfile.ClassFormatOutput;
0027: import org.apache.derby.iapi.services.compiler.LocalField;
0028: import org.apache.derby.iapi.services.classfile.ClassHolder;
0029: import org.apache.derby.iapi.services.classfile.ClassMember;
0030:
0031: import org.apache.derby.iapi.services.sanity.SanityManager;
0032:
0033: import org.apache.derby.iapi.services.classfile.VMDescriptor;
0034: import org.apache.derby.iapi.services.classfile.VMOpcode;
0035:
0036: import java.lang.reflect.Modifier;
0037: import java.util.Vector;
0038: import java.io.IOException;
0039:
0040: /**
0041: * MethodBuilder is used to piece together a method when
0042: * building a java class definition.
0043: * <p>
0044: * When a method is first created, it has:
0045: * <ul>
0046: * <li> a return type
0047: * <li> modifiers
0048: * <li> a name
0049: * <li> an empty parameter list
0050: * <li> an empty throws list
0051: * <li> an empty statement block
0052: * </ul>
0053: * <p>
0054: * MethodBuilder implementations are required to supply a way for
0055: * Statements and Expressions to give them code. Most typically, they may have
0056: * a stream to which their contents writes the code that is of
0057: * the type to satisfy what the contents represent.
0058: * MethodBuilder implementations also have to have a way to supply
0059: * ClassBuilders with their code, that satisfies the type of class
0060: * builder they are implemented with. This is implementation-dependent,
0061: * so ClassBuilders, MethodBuilders, Statements, and Expressions all have
0062: * to be of the same implementation in order to interact to generate a class.
0063: * <p>
0064: * Method Builder implementation for generating bytecode.
0065: *
0066: */
0067: class BCMethod implements MethodBuilder {
0068:
0069: /**
0070: * Code length at which to split into sub-methods.
0071: * Normally set to the maximim code length the
0072: * JVM can support, but for testing the split code
0073: * it can be reduced so that the standard tests
0074: * cause some splitting. Tested with value set to 2000.
0075: */
0076: static final int CODE_SPLIT_LENGTH = VMOpcode.MAX_CODE_LENGTH;
0077:
0078: final BCClass cb;
0079: protected final ClassHolder modClass; // the class it is in (modifiable fmt)
0080: final String myReturnType;
0081:
0082: /**
0083: * The original name of the method, this
0084: * represents how any user would call this method.
0085: */
0086: private final String myName;
0087:
0088: /**
0089: * Fast access for the parametes, will be null
0090: * if the method has no parameters.
0091: */
0092: BCLocalField[] parameters;
0093:
0094: /**
0095: * List of parameter types with java language class names.
0096: * Can be null or zero length for no parameters.
0097: */
0098: private final String[] parameterTypes;
0099:
0100: Vector thrownExceptions; // expected to be names of Classes under Throwable
0101:
0102: CodeChunk myCode;
0103: protected ClassMember myEntry;
0104:
0105: private int currentVarNum;
0106: private int statementNum;
0107:
0108: /**
0109: * True if we are currently switching control
0110: * over to a sub method to avoid hitting the code generation
0111: * limit of 65535 bytes per method.
0112: */
0113: private boolean handlingOverflow;
0114:
0115: /**
0116: * How many sub-methods we have overflowed to.
0117: */
0118: private int subMethodCount;
0119:
0120: BCMethod(ClassBuilder cb, String returnType, String methodName,
0121: int modifiers, String[] parms, BCJava factory) {
0122:
0123: this .cb = (BCClass) cb;
0124: modClass = this .cb.modify();
0125: myReturnType = returnType;
0126: myName = methodName;
0127:
0128: if (SanityManager.DEBUG) {
0129: this .cb.validateType(returnType);
0130: }
0131:
0132: // if the method is not static, allocate for "this".
0133: if ((modifiers & Modifier.STATIC) == 0)
0134: currentVarNum = 1;
0135:
0136: String[] vmParamterTypes;
0137:
0138: if (parms != null && parms.length != 0) {
0139: int len = parms.length;
0140: vmParamterTypes = new String[len];
0141: parameters = new BCLocalField[len];
0142: for (int i = 0; i < len; i++) {
0143: Type t = factory.type(parms[i]);
0144: parameters[i] = new BCLocalField(t, currentVarNum);
0145: currentVarNum += t.width();
0146:
0147: // convert to vmname for the BCMethodDescriptor.get() call
0148: vmParamterTypes[i] = t.vmName();
0149: }
0150: } else
0151: vmParamterTypes = BCMethodDescriptor.EMPTY;
0152:
0153: // create a code attribute
0154: String sig = BCMethodDescriptor.get(vmParamterTypes, factory
0155: .type(returnType).vmName(), factory);
0156:
0157: // stuff the completed information into the class.
0158: myEntry = modClass.addMember(methodName, sig, modifiers);
0159:
0160: // get code chunk
0161: myCode = new CodeChunk(this .cb);
0162:
0163: parameterTypes = parms;
0164: }
0165:
0166: //
0167: // MethodBuilder interface
0168: //
0169:
0170: /**
0171: * Return the logical name of the method. The current
0172: * myEntry refers to the sub method we are currently
0173: * overflowing to. Those sub-methods are hidden from any caller.
0174: */
0175: public String getName() {
0176: return myName;
0177: }
0178:
0179: public void getParameter(int id) {
0180:
0181: int num = parameters[id].cpi;
0182: short typ = parameters[id].type.vmType();
0183: if (num < 4)
0184: myCode
0185: .addInstr((short) (CodeChunk.LOAD_VARIABLE_FAST[typ] + num));
0186: else
0187: myCode.addInstrWide(CodeChunk.LOAD_VARIABLE[typ], num);
0188:
0189: growStack(parameters[id].type);
0190: }
0191:
0192: /**
0193: * a throwable can be added to the end of
0194: * the list of thrownExceptions.
0195: */
0196: public void addThrownException(String exceptionClass) {
0197:
0198: // cannot add exceptions after code generation has started.
0199: // Allowing this would cause the method overflow/split to
0200: // break as the top-level method would not have the exception
0201: // added in the sub method.
0202: if (SanityManager.DEBUG) {
0203: if (myCode.getPC() != 0)
0204: SanityManager
0205: .THROWASSERT("Adding exception after code generation "
0206: + exceptionClass
0207: + " to method "
0208: + getName());
0209: }
0210:
0211: if (thrownExceptions == null)
0212: thrownExceptions = new Vector();
0213: thrownExceptions.addElement(exceptionClass);
0214: }
0215:
0216: /**
0217: * when the method has had all of its parameters
0218: * and thrown exceptions defined, and its statement
0219: * block has been completed, it can be completed and
0220: * its class file information generated.
0221: * <p>
0222: * further alterations of the method will not be
0223: * reflected in the code generated for it.
0224: */
0225: public void complete() {
0226:
0227: // myCode.getPC() gives the code length since
0228: // the program counter will be positioned after
0229: // the last instruction. Note this value can
0230: // be changed by the splitMethod call.
0231:
0232: if (myCode.getPC() > CODE_SPLIT_LENGTH)
0233: splitMethod();
0234:
0235: // write exceptions attribute info
0236: writeExceptions();
0237:
0238: // get the code attribute to put itself into the class
0239: // provide the final header information needed
0240: myCode.complete(this , modClass, myEntry, maxStack,
0241: currentVarNum);
0242: }
0243:
0244: /**
0245: * Attempt to split a large method by pushing code out to several
0246: * sub-methods. Performs a number of steps.
0247: * <OL>
0248: * <LI> Split at zero stack depth.
0249: * <LI> Split at non-zero stack depth (FUTURE)
0250: * </OL>
0251: *
0252: * If the class has already exceeded some limit in building the
0253: * class file format structures then don't attempt to split.
0254: * Most likely the number of constant pool entries has been exceeded
0255: * and thus the built class file no longer has integrity.
0256: * The split code relies on being able to read the in-memory
0257: * version of the class file in order to determine descriptors
0258: * for methods and fields.
0259: */
0260: private void splitMethod() {
0261:
0262: int split_pc = 0;
0263: boolean splittingZeroStack = true;
0264: for (int codeLength = myCode.getPC(); (cb.limitMsg == null)
0265: && (codeLength > CODE_SPLIT_LENGTH); codeLength = myCode
0266: .getPC()) {
0267: int lengthToCheck = codeLength - split_pc;
0268:
0269: int optimalMinLength;
0270: if (codeLength < CODE_SPLIT_LENGTH * 2) {
0271: // minimum required
0272: optimalMinLength = codeLength - CODE_SPLIT_LENGTH;
0273: } else {
0274: // try to split as much as possible
0275: // need one for the return instruction
0276: optimalMinLength = CODE_SPLIT_LENGTH - 1;
0277: }
0278:
0279: if (optimalMinLength > lengthToCheck)
0280: optimalMinLength = lengthToCheck;
0281:
0282: if (splittingZeroStack) {
0283: split_pc = myCode.splitZeroStack(this , modClass,
0284: split_pc, optimalMinLength);
0285: } else {
0286: // Note the split expression does not re-start split
0287: // at point left off by the previous split expression.
0288: // This could be done but would require some level
0289: // of stack depth history to be kept across calls.
0290: split_pc = myCode.splitExpressionOut(this , modClass,
0291: optimalMinLength, maxStack);
0292:
0293: }
0294:
0295: // Negative split point returned means that no split
0296: // was possible. Give up on this approach and goto
0297: // the next approach.
0298: if (split_pc < 0) {
0299: if (!splittingZeroStack)
0300: break;
0301: splittingZeroStack = false;
0302: split_pc = 0;
0303: }
0304:
0305: // success, continue on splitting after the call to the
0306: // sub-method if the method still execeeds the maximum length.
0307: }
0308:
0309: }
0310:
0311: /*
0312: * class interface
0313: */
0314:
0315: /**
0316: * In their giveCode methods, the parts of the method body will want to get
0317: * to the constant pool to add their constants. We really only want them
0318: * treating it like a constant pool inclusion mechanism, we could write a
0319: * wrapper to limit it to that.
0320: */
0321: ClassHolder constantPool() {
0322: return modClass;
0323: }
0324:
0325: //
0326: // Class implementation
0327: //
0328:
0329: /**
0330: * sets exceptionBytes to the attribute_info needed
0331: * for a method's Exceptions attribute.
0332: * The ClassUtilities take care of the header 6 bytes for us,
0333: * so they are not included here.
0334: * See The Java Virtual Machine Specification Section 4.7.5,
0335: * Exceptions attribute.
0336: */
0337: protected void writeExceptions() {
0338: if (thrownExceptions == null)
0339: return;
0340:
0341: int numExc = thrownExceptions.size();
0342:
0343: // don't write an Exceptions attribute if there are no exceptions.
0344: if (numExc != 0) {
0345:
0346: try {
0347: ClassFormatOutput eout = new ClassFormatOutput(
0348: (numExc * 2) + 2);
0349:
0350: eout.putU2(numExc); // number_of_exceptions
0351:
0352: for (int i = 0; i < numExc; i++) {
0353: // put each exception into the constant pool
0354: String e = thrownExceptions.elementAt(i).toString();
0355: int ei2 = modClass.addClassReference(e);
0356:
0357: // add constant pool index to exception attribute_info
0358: eout.putU2(ei2);
0359: }
0360:
0361: myEntry.addAttribute("Exceptions", eout);
0362:
0363: } catch (IOException ioe) {
0364: }
0365: }
0366: }
0367:
0368: /*
0369: ** New push compiler api.
0370: */
0371:
0372: /**
0373: * Array of the current types of the values on the stack.
0374: * A type that types up two words on the stack, e.g. double
0375: * will only occupy one element in this array.
0376: * This array is dynamically re-sized as needed.
0377: */
0378: private Type[] stackTypes = new Type[8];
0379:
0380: /**
0381: * Points to the next array offset in stackTypes
0382: * to be used. Really it's the number of valid entries
0383: * in stackTypes.
0384: */
0385: private int stackTypeOffset;
0386:
0387: /**
0388: * Maximum stack depth seen in this method, measured in words.
0389: * Corresponds to max_stack in the Code attribute of section 4.7.3
0390: * of the vm spec.
0391: */
0392: int maxStack;
0393:
0394: /**
0395: * Current stack depth in this method, measured in words.
0396: */
0397: private int stackDepth;
0398:
0399: private void growStack(int size, Type type) {
0400: stackDepth += size;
0401: if (stackDepth > maxStack)
0402: maxStack = stackDepth;
0403:
0404: if (stackTypeOffset >= stackTypes.length) {
0405:
0406: Type[] newStackTypes = new Type[stackTypes.length + 8];
0407: System.arraycopy(stackTypes, 0, newStackTypes, 0,
0408: stackTypes.length);
0409: stackTypes = newStackTypes;
0410: }
0411:
0412: stackTypes[stackTypeOffset++] = type;
0413:
0414: if (SanityManager.DEBUG) {
0415:
0416: int sum = 0;
0417: for (int i = 0; i < stackTypeOffset; i++) {
0418: sum += stackTypes[i].width();
0419: }
0420: if (sum != stackDepth) {
0421: SanityManager.THROWASSERT("invalid stack depth "
0422: + stackDepth + " calc " + sum);
0423: }
0424: }
0425: }
0426:
0427: private void growStack(Type type) {
0428: growStack(type.width(), type);
0429: }
0430:
0431: private Type popStack() {
0432: stackTypeOffset--;
0433: Type topType = stackTypes[stackTypeOffset];
0434: stackDepth -= topType.width();
0435: return topType;
0436:
0437: }
0438:
0439: private Type[] copyStack() {
0440: Type[] stack = new Type[stackTypeOffset];
0441: System.arraycopy(stackTypes, 0, stack, 0, stackTypeOffset);
0442: return stack;
0443: }
0444:
0445: public void pushThis() {
0446: myCode.addInstr(VMOpcode.ALOAD_0);
0447: growStack(1, cb.classType);
0448: }
0449:
0450: public void push(byte value) {
0451: push(value, Type.BYTE);
0452: }
0453:
0454: public void push(boolean value) {
0455: push(value ? 1 : 0, Type.BOOLEAN);
0456: }
0457:
0458: public void push(short value) {
0459: push(value, Type.SHORT);
0460: }
0461:
0462: public void push(int value) {
0463: push(value, Type.INT);
0464: }
0465:
0466: public void dup() {
0467: Type dup = popStack();
0468: myCode
0469: .addInstr(dup.width() == 2 ? VMOpcode.DUP2
0470: : VMOpcode.DUP);
0471: growStack(dup);
0472: growStack(dup);
0473:
0474: }
0475:
0476: public void swap() {
0477:
0478: // have A,B
0479: // want B,A
0480:
0481: Type wB = popStack();
0482: Type wA = popStack();
0483: growStack(wB);
0484: growStack(wA);
0485:
0486: if (wB.width() == 1) {
0487: // top value is one word
0488: if (wA.width() == 1) {
0489: myCode.addInstr(VMOpcode.SWAP);
0490: return;
0491: } else {
0492: myCode.addInstr(VMOpcode.DUP_X2);
0493: myCode.addInstr(VMOpcode.POP);
0494: }
0495: } else {
0496: // top value is two words
0497: if (wA.width() == 1) {
0498: myCode.addInstr(VMOpcode.DUP2_X1);
0499: myCode.addInstr(VMOpcode.POP2);
0500: } else {
0501: myCode.addInstr(VMOpcode.DUP2_X2);
0502: myCode.addInstr(VMOpcode.POP2);
0503: }
0504: }
0505:
0506: // all except the simple swap push an extra
0507: // copy of B which needs to be popped.
0508: growStack(wB);
0509: popStack();
0510:
0511: }
0512:
0513: /**
0514: * Push an integer value. Uses the special integer opcodes
0515: * for the constants -1 to 5, BIPUSH for values that fit in
0516: * a byte and SIPUSH for values that fit in a short. Otherwise
0517: * uses LDC with a constant pool entry.
0518: *
0519: * @param value Value to be pushed
0520: * @param type Final type of the value.
0521: */
0522: private void push(int value, Type type) {
0523:
0524: CodeChunk chunk = myCode;
0525:
0526: if (value >= -1 && value <= 5)
0527: chunk.addInstr((short) (VMOpcode.ICONST_0 + value));
0528: else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE)
0529: chunk.addInstrU1(VMOpcode.BIPUSH, value);
0530: else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)
0531: chunk.addInstrU2(VMOpcode.SIPUSH, value);
0532: else {
0533: int cpe = modClass.addConstant(value);
0534: addInstrCPE(VMOpcode.LDC, cpe);
0535: }
0536: growStack(type.width(), type);
0537:
0538: }
0539:
0540: /**
0541: * Push a long value onto the stack.
0542: * For the values zero and one the LCONST_0 and
0543: * LCONST_1 instructions are used.
0544: * For values betwee Short.MIN_VALUE and Short.MAX_VALUE
0545: * inclusive an byte/short/int value is pushed
0546: * using push(int, Type) followed by an I2L instruction.
0547: * This saves using a constant pool entry for such values.
0548: * All other values use a constant pool entry. For values
0549: * in the range of an Integer an integer constant pool
0550: * entry is created to allow sharing with integer constants
0551: * and to reduce constant pool slot entries.
0552: */
0553: public void push(long value) {
0554: CodeChunk chunk = myCode;
0555:
0556: if (value == 0L || value == 1L) {
0557: short opcode = value == 0L ? VMOpcode.LCONST_0
0558: : VMOpcode.LCONST_1;
0559: chunk.addInstr(opcode);
0560: } else if (value >= Integer.MIN_VALUE
0561: && value <= Integer.MAX_VALUE) {
0562: // the push(int, Type) method grows the stack for us.
0563: push((int) value, Type.LONG);
0564: chunk.addInstr(VMOpcode.I2L);
0565: return;
0566: } else {
0567: int cpe = modClass.addConstant(value);
0568: chunk.addInstrU2(VMOpcode.LDC2_W, cpe);
0569: }
0570: growStack(2, Type.LONG);
0571: }
0572:
0573: public void push(float value) {
0574:
0575: CodeChunk chunk = myCode;
0576:
0577: if (value == 0.0) {
0578: chunk.addInstr(VMOpcode.FCONST_0);
0579: } else if (value == 1.0) {
0580: chunk.addInstr(VMOpcode.FCONST_1);
0581: } else if (value == 2.0) {
0582: chunk.addInstr(VMOpcode.FCONST_2);
0583: } else {
0584: int cpe = modClass.addConstant(value);
0585: addInstrCPE(VMOpcode.LDC, cpe);
0586: }
0587: growStack(1, Type.FLOAT);
0588: }
0589:
0590: public void push(double value) {
0591: CodeChunk chunk = myCode;
0592:
0593: if (value == 0.0) {
0594: chunk.addInstr(VMOpcode.DCONST_0);
0595: } else {
0596: int cpe = modClass.addConstant(value);
0597: chunk.addInstrU2(VMOpcode.LDC2_W, cpe);
0598: }
0599: growStack(2, Type.DOUBLE);
0600: }
0601:
0602: public void push(String value) {
0603: int cpe = modClass.addConstant(value);
0604: addInstrCPE(VMOpcode.LDC, cpe);
0605: growStack(1, Type.STRING);
0606: }
0607:
0608: public void methodReturn() {
0609:
0610: short opcode;
0611: if (stackDepth != 0) {
0612: Type topType = popStack();
0613: opcode = CodeChunk.RETURN_OPCODE[topType.vmType()];
0614: } else {
0615: opcode = VMOpcode.RETURN;
0616: }
0617:
0618: myCode.addInstr(opcode);
0619:
0620: if (SanityManager.DEBUG) {
0621: if (stackDepth != 0)
0622: SanityManager.THROWASSERT("items left on stack "
0623: + stackDepth);
0624: }
0625: }
0626:
0627: public Object describeMethod(short opcode, String declaringClass,
0628: String methodName, String returnType) {
0629:
0630: Type rt = cb.factory.type(returnType);
0631:
0632: String methodDescriptor = BCMethodDescriptor.get(
0633: BCMethodDescriptor.EMPTY, rt.vmName(), cb.factory);
0634:
0635: if ((declaringClass == null)
0636: && (opcode != VMOpcode.INVOKESTATIC)) {
0637:
0638: Type dt = stackTypes[stackTypeOffset - 1];
0639:
0640: if (declaringClass == null)
0641: declaringClass = dt.javaName();
0642: }
0643:
0644: int cpi = modClass.addMethodReference(declaringClass,
0645: methodName, methodDescriptor,
0646: opcode == VMOpcode.INVOKEINTERFACE);
0647:
0648: return new BCMethodCaller(opcode, rt, cpi);
0649: }
0650:
0651: public int callMethod(Object methodDescriptor) {
0652:
0653: // pop the reference off the stack
0654: popStack();
0655:
0656: BCMethodCaller mc = (BCMethodCaller) methodDescriptor;
0657:
0658: int cpi = mc.cpi;
0659: short opcode = mc.opcode;
0660:
0661: if (opcode == VMOpcode.INVOKEINTERFACE) {
0662: myCode.addInstrU2U1U1(opcode, cpi, (short) 1, (short) 0);
0663: } else
0664: myCode.addInstrU2(opcode, cpi);
0665:
0666: // this is the return type of the method
0667: Type rt = mc.type;
0668: int rw = rt.width();
0669: if (rw != 0)
0670: growStack(rw, rt);
0671: else {
0672: if (stackDepth == 0)
0673: overflowMethodCheck();
0674: }
0675: return cpi;
0676: }
0677:
0678: public int callMethod(short opcode, String declaringClass,
0679: String methodName, String returnType, int numArgs) {
0680:
0681: Type rt = cb.factory.type(returnType);
0682:
0683: int initialStackDepth = stackDepth;
0684:
0685: // get the array of parameter types
0686:
0687: String[] debugParameterTypes = null;
0688: String[] vmParameterTypes;
0689: if (numArgs == 0) {
0690: vmParameterTypes = BCMethodDescriptor.EMPTY;
0691: } else {
0692: if (SanityManager.DEBUG) {
0693: debugParameterTypes = new String[numArgs];
0694: }
0695: vmParameterTypes = new String[numArgs];
0696: for (int i = numArgs - 1; i >= 0; i--) {
0697: Type at = popStack();
0698:
0699: vmParameterTypes[i] = at.vmName();
0700: if (SanityManager.DEBUG) {
0701: debugParameterTypes[i] = at.javaName();
0702: }
0703: }
0704: }
0705:
0706: String methodDescriptor = BCMethodDescriptor.get(
0707: vmParameterTypes, rt.vmName(), cb.factory);
0708:
0709: Type dt = null;
0710: if (opcode != VMOpcode.INVOKESTATIC) {
0711:
0712: dt = popStack();
0713: }
0714: Type dtu = vmNameDeclaringClass(declaringClass);
0715: if (dtu != null)
0716: dt = dtu;
0717:
0718: int cpi = modClass.addMethodReference(dt.vmNameSimple,
0719: methodName, methodDescriptor,
0720: opcode == VMOpcode.INVOKEINTERFACE);
0721:
0722: if (opcode == VMOpcode.INVOKEINTERFACE) {
0723: short callArgCount = (short) (initialStackDepth - stackDepth);
0724: myCode.addInstrU2U1U1(opcode, cpi, callArgCount, (short) 0);
0725: } else
0726: myCode.addInstrU2(opcode, cpi);
0727:
0728: // this is the return type of the method
0729: int rw = rt.width();
0730: if (rw != 0)
0731: growStack(rw, rt);
0732: else {
0733: if (stackDepth == 0)
0734: overflowMethodCheck();
0735: }
0736: // Check the declared type of the method
0737: if (SanityManager.DEBUG) {
0738:
0739: d_BCValidate.checkMethod(opcode, dt, methodName,
0740: debugParameterTypes, rt);
0741: }
0742:
0743: return cpi;
0744: }
0745:
0746: private Type vmNameDeclaringClass(String declaringClass) {
0747: if (declaringClass == null)
0748: return null;
0749: return cb.factory.type(declaringClass);
0750: }
0751:
0752: public void callSuper() {
0753:
0754: pushThis();
0755: callMethod(VMOpcode.INVOKESPECIAL, cb.getSuperClassName(),
0756: "<init>", "void", 0);
0757: }
0758:
0759: public void pushNewStart(String className) {
0760:
0761: int cpi = modClass.addClassReference(className);
0762:
0763: // Use U2, not CPE, since only wide form exists.
0764: myCode.addInstrU2(VMOpcode.NEW, cpi);
0765: myCode.addInstr(VMOpcode.DUP);
0766:
0767: // Grow the stack twice as we are pushing
0768: // two instances of newly created reference
0769: Type nt = cb.factory.type(className);
0770: growStack(1, nt);
0771: growStack(1, nt);
0772: }
0773:
0774: public void pushNewComplete(int numArgs) {
0775: callMethod(VMOpcode.INVOKESPECIAL, (String) null, "<init>",
0776: "void", numArgs);
0777: }
0778:
0779: public void upCast(String className) {
0780: Type uct = cb.factory.type(className);
0781:
0782: stackTypes[stackTypeOffset - 1] = uct;
0783: //popStack();
0784: //growStack(1, uct);
0785: }
0786:
0787: public void cast(String className) {
0788:
0789: // Perform a simple optimization to not
0790: // insert a checkcast when the classname
0791: // of the cast exactly matches the type name
0792: // currently on the stack.
0793: // This can reduce the amount of generated code.
0794: // This compiler/class generator does not load
0795: // classes to check relationships or any other
0796: // information. Thus other optimizations where a cast
0797: // is not required are not implemented.
0798: Type tbc = stackTypes[stackTypeOffset - 1];
0799:
0800: short sourceType = tbc.vmType();
0801:
0802: if (sourceType == BCExpr.vm_reference) {
0803: // Simple optimize step
0804: if (className.equals(tbc.javaName())) {
0805: // do nothing, exact matching type
0806: return;
0807: }
0808: }
0809:
0810: Type ct = cb.factory.type(className);
0811: popStack();
0812:
0813: short targetType = ct.vmType();
0814:
0815: if (SanityManager.DEBUG) {
0816:
0817: if (!((sourceType == BCExpr.vm_reference && targetType == BCExpr.vm_reference) || (sourceType != BCExpr.vm_reference && targetType != BCExpr.vm_reference))) {
0818: SanityManager
0819: .THROWASSERT("Both or neither must be object types "
0820: + ct.javaName() + " " + tbc.javaName());
0821: }
0822: }
0823:
0824: // if it is an object type, do a checkcast on it.
0825: if (sourceType == BCExpr.vm_reference) {
0826:
0827: int cpi = modClass.addClassReference(ct.vmNameSimple);
0828: myCode.addInstrU2(VMOpcode.CHECKCAST, cpi);
0829: }
0830: // otherwise, try to convert it.
0831: else {
0832: short opcode = VMOpcode.NOP;
0833:
0834: // we use the conversionInfo array
0835: // to determine how to convert; if
0836: // the result type of the conversion
0837: // is not our target type, we are not done
0838: // yet. Make sure there are no
0839: // infinite loop possibilities in the
0840: // conversionInfo array!
0841: while (sourceType != targetType && opcode != VMOpcode.BAD) {
0842: short[] currentConversion = CodeChunk.CAST_CONVERSION_INFO[sourceType][targetType];
0843: sourceType = currentConversion[1];
0844: opcode = currentConversion[0];
0845: if (opcode != VMOpcode.NOP) {
0846: myCode.addInstr(opcode);
0847: }
0848: }
0849: if (SanityManager.DEBUG) {
0850: SanityManager.ASSERT(opcode != VMOpcode.BAD,
0851: "BAD VMOpcode not expected in cast");
0852: }
0853: }
0854: growStack(ct);
0855: }
0856:
0857: public void isInstanceOf(String className) {
0858: int cpi = modClass.addClassReference(className);
0859: myCode.addInstrU2(VMOpcode.INSTANCEOF, cpi);
0860: popStack();
0861: growStack(1, Type.BOOLEAN);
0862: }
0863:
0864: public void pushNull(String type) {
0865: myCode.addInstr(VMOpcode.ACONST_NULL);
0866: growStack(1, cb.factory.type(type));
0867: }
0868:
0869: public void getField(LocalField field) {
0870:
0871: BCLocalField lf = (BCLocalField) field;
0872: Type lt = lf.type;
0873:
0874: pushThis();
0875: myCode.addInstrU2(VMOpcode.GETFIELD, lf.cpi);
0876:
0877: popStack();
0878: growStack(lt);
0879:
0880: }
0881:
0882: public void getField(String declaringClass, String fieldName,
0883: String fieldType) {
0884: Type dt = popStack();
0885:
0886: Type dtu = vmNameDeclaringClass(declaringClass);
0887: if (dtu != null)
0888: dt = dtu;
0889:
0890: getField(VMOpcode.GETFIELD, dt.vmNameSimple, fieldName,
0891: fieldType);
0892: }
0893:
0894: /**
0895: Push the contents of the described static field onto the stack.
0896: */
0897: public void getStaticField(String declaringClass, String fieldName,
0898: String fieldType) {
0899: getField(VMOpcode.GETSTATIC, declaringClass, fieldName,
0900: fieldType);
0901: }
0902:
0903: private void getField(short opcode, String declaringClass,
0904: String fieldName, String fieldType) {
0905:
0906: Type ft = cb.factory.type(fieldType);
0907: int cpi = modClass.addFieldReference(
0908: vmNameDeclaringClass(declaringClass).vmNameSimple,
0909: fieldName, ft.vmName());
0910: myCode.addInstrU2(opcode, cpi);
0911:
0912: growStack(ft);
0913: }
0914:
0915: /**
0916: * Set the field but don't duplicate its value so
0917: * nothing is left on the stack after this call.
0918: */
0919: public void setField(LocalField field) {
0920: BCLocalField lf = (BCLocalField) field;
0921: Type lt = lf.type;
0922:
0923: putField(lf.type, lf.cpi, false);
0924:
0925: if (stackDepth == 0)
0926: overflowMethodCheck();
0927: }
0928:
0929: /**
0930: Upon entry the top word(s) on the stack is
0931: the value to be put into the field. Ie.
0932: we have
0933: <PRE>
0934: word
0935: </PRE>
0936:
0937: Before the call we need
0938: <PRE>
0939: word
0940: this
0941: word
0942: </PRE>
0943: word2,word1 -> word2, word1, word2
0944:
0945: So that we are left with word after the put.
0946:
0947: */
0948: public void putField(LocalField field) {
0949: BCLocalField lf = (BCLocalField) field;
0950: Type lt = lf.type;
0951:
0952: putField(lf.type, lf.cpi, true);
0953: }
0954:
0955: /**
0956: Pop the top stack value and store it in the instance field of this class.
0957: */
0958: public void putField(String fieldName, String fieldType) {
0959:
0960: Type ft = cb.factory.type(fieldType);
0961: int cpi = modClass.addFieldReference(cb.classType.vmNameSimple,
0962: fieldName, ft.vmName());
0963:
0964: putField(ft, cpi, true);
0965: }
0966:
0967: private void putField(Type fieldType, int cpi, boolean dup) {
0968:
0969: // now have ...,value
0970: if (dup) {
0971: myCode.addInstr(fieldType.width() == 2 ? VMOpcode.DUP2
0972: : VMOpcode.DUP);
0973: growStack(fieldType);
0974: }
0975: // now have
0976: // dup true: ...,value,value
0977: // dup false: ...,value,
0978:
0979: pushThis();
0980: // now have
0981: // dup true: ...,value,value,this
0982: // dup false: ...,value,this
0983:
0984: swap();
0985: // now have
0986: // dup true: ...,value,this,value
0987: // dup false: ...,this,value
0988:
0989: myCode.addInstrU2(VMOpcode.PUTFIELD, cpi);
0990: popStack(); // the value
0991: popStack(); // this
0992:
0993: // now have
0994: // dup true: ...,value
0995: // dup false: ...
0996: }
0997:
0998: /**
0999: Pop the top stack value and store it in the field.
1000: This call requires the instance to be pushed by the caller.
1001: */
1002: public void putField(String declaringClass, String fieldName,
1003: String fieldType) {
1004: Type vt = popStack();
1005: Type dt = popStack();
1006:
1007: if (SanityManager.DEBUG) {
1008: if (dt.width() != 1)
1009: SanityManager
1010: .THROWASSERT("reference expected for field access - is "
1011: + dt.javaName());
1012: }
1013:
1014: // have objectref,value
1015: // need value,objectref,value
1016:
1017: myCode.addInstr(vt.width() == 2 ? VMOpcode.DUP2_X1
1018: : VMOpcode.DUP_X1);
1019: growStack(vt);
1020: growStack(dt);
1021: growStack(vt);
1022:
1023: Type dtu = vmNameDeclaringClass(declaringClass);
1024: if (dtu != null)
1025: dt = dtu;
1026:
1027: Type ft = cb.factory.type(fieldType);
1028: int cpi = modClass.addFieldReference(dt.vmNameSimple,
1029: fieldName, ft.vmName());
1030: myCode.addInstrU2(VMOpcode.PUTFIELD, cpi);
1031:
1032: popStack(); // value
1033: popStack(); // reference
1034: }
1035:
1036: public void conditionalIfNull() {
1037:
1038: conditionalIf(VMOpcode.IFNONNULL);
1039: }
1040:
1041: public void conditionalIf() {
1042: conditionalIf(VMOpcode.IFEQ);
1043: }
1044:
1045: private Conditional condition;
1046:
1047: private void conditionalIf(short opcode) {
1048: popStack();
1049:
1050: // Save the stack upon entry to the 'then' block of the
1051: // 'if' so that we can set up the 'else' block with the
1052: // correct stack on entry.
1053:
1054: condition = new Conditional(condition, myCode, opcode,
1055: copyStack());
1056: }
1057:
1058: public void startElseCode() {
1059:
1060: // start the else code
1061: Type[] entryStack = condition.startElse(this , myCode,
1062: copyStack());
1063:
1064: for (int i = stackDepth = 0; i < entryStack.length; i++) {
1065: stackDepth += (stackTypes[i] = entryStack[i]).width();
1066: }
1067: this .stackTypeOffset = entryStack.length;
1068:
1069: }
1070:
1071: public void completeConditional() {
1072: condition = condition.end(this , myCode, stackTypes,
1073: stackTypeOffset);
1074: }
1075:
1076: public void pop() {
1077: if (SanityManager.DEBUG) {
1078: if (stackDepth == 0)
1079: SanityManager.THROWASSERT("pop when stack is empty!");
1080: }
1081: Type toPop = popStack();
1082:
1083: myCode.addInstr(toPop.width() == 2 ? VMOpcode.POP2
1084: : VMOpcode.POP);
1085:
1086: if (stackDepth == 0)
1087: overflowMethodCheck();
1088: }
1089:
1090: public void endStatement() {
1091: if (stackDepth != 0) {
1092: pop();
1093: }
1094:
1095: //if (SanityManager.DEBUG) {
1096: // if (stackDepth != 0)
1097: // SanityManager.THROWASSERT("items left on stack " + stackDepth);
1098: // }
1099: }
1100:
1101: /**
1102: */
1103: public void getArrayElement(int element) {
1104:
1105: push(element);
1106: popStack(); // int just pushed will be popped by array access
1107:
1108: Type arrayType = popStack();
1109:
1110: String arrayJava = arrayType.javaName();
1111: String componentString = arrayJava.substring(0, arrayJava
1112: .length() - 2);
1113:
1114: Type componentType = cb.factory.type(componentString);
1115:
1116: short typ = componentType.vmType();
1117:
1118: // boolean has a type id of integer, here it needs to be byte.
1119: if ((typ == BCExpr.vm_int)
1120: && (componentType.vmName().equals("Z")))
1121: typ = BCExpr.vm_byte;
1122: myCode.addInstr(CodeChunk.ARRAY_ACCESS[typ]);
1123:
1124: growStack(componentType);
1125:
1126: }
1127:
1128: // come in with ref, value
1129:
1130: public void setArrayElement(int element) {
1131:
1132: // ref, value
1133:
1134: push(element);
1135:
1136: // ref, value, index
1137: swap();
1138:
1139: Type componentType = popStack(); // value
1140: popStack(); // int just pushed will be popped by array access
1141:
1142: popStack(); // array ref.
1143:
1144: short typ = componentType.vmType();
1145:
1146: // boolean has a type id of integer, here it needs to be byte.
1147: if ((typ == BCExpr.vm_int)
1148: && (componentType.vmName().equals("Z")))
1149: typ = BCExpr.vm_byte;
1150:
1151: myCode.addInstr(CodeChunk.ARRAY_STORE[typ]);
1152: }
1153:
1154: /**
1155: this array maps the BCExpr vm_* constants 0..6 to
1156: the expected VM type constants for the newarray instruction.
1157: <p>
1158: Because boolean was mapped to integer for general instructions,
1159: it will have to be specially matched and mapped to its value
1160: directly (4).
1161: */
1162: private static final byte newArrayElementTypeMap[] = { 8, 9, 10,
1163: 11, 6, 7, 5 };
1164: static final byte T_BOOLEAN = 4;
1165:
1166: /**
1167: Create an array instance
1168:
1169: Stack ... =>
1170: ...,arrayref
1171: */
1172: public void pushNewArray(String className, int size) {
1173:
1174: push(size);
1175: popStack(); // int just pushed will be popped by array creation
1176:
1177: Type elementType = cb.factory.type(className);
1178:
1179: // determine the instruction to use based on the element type
1180: if (elementType.vmType() == BCExpr.vm_reference) {
1181:
1182: // For an array of Java class/interface elements, generate:
1183: // ANEWARRAY #cpei ; where cpei is a constant pool index for the class
1184:
1185: int cpi = modClass
1186: .addClassReference(elementType.javaName());
1187: // Use U2, not CPE, since only wide form exists.
1188: myCode.addInstrU2(VMOpcode.ANEWARRAY, cpi);
1189: } else {
1190: byte atype;
1191:
1192: // get the argument for the array type
1193: // if the element type is boolean, we can't use the map
1194: // because the type id will say integer.
1195: // but we can use vm_int test to weed out some tests
1196: if (elementType.vmType() == BCExpr.vm_int
1197: && VMDescriptor.C_BOOLEAN == elementType.vmName()
1198: .charAt(0))
1199: atype = T_BOOLEAN;
1200: else
1201: atype = newArrayElementTypeMap[elementType.vmType()];
1202:
1203: // For an array of Java builtin type elements, generate:
1204: // NEWARRAY #atype ; where atype is a constant for the builtin type
1205:
1206: myCode.addInstrU1(VMOpcode.NEWARRAY, atype);
1207: }
1208:
1209: // an array reference is an object, hence width of 1
1210: growStack(1, cb.factory.type(className.concat("[]")));
1211: }
1212:
1213: /**
1214: * Write a instruction that uses a constant pool entry
1215: * as an operand, add a limit exceeded message if
1216: * the number of constant pool entries has exceeded
1217: * the limit.
1218: */
1219: private void addInstrCPE(short opcode, int cpe) {
1220: if (cpe >= VMOpcode.MAX_CONSTANT_POOL_ENTRIES)
1221: cb.addLimitExceeded(this , "constant_pool_count",
1222: VMOpcode.MAX_CONSTANT_POOL_ENTRIES, cpe);
1223:
1224: myCode.addInstrCPE(opcode, cpe);
1225: }
1226:
1227: /**
1228: Tell if statement number in this method builder hits limit. This
1229: method builder keeps a counter of how many statements are added to it.
1230: Caller should call this function every time it tries to add a statement
1231: to this method builder (counter is increased by 1), then the function
1232: returns whether the accumulated statement number hits a limit.
1233: The reason of doing this is that Java compiler has a limit of 64K code
1234: size for each method. We might hit this limit if an extremely long
1235: insert statement is issued, for example (see beetle 4293). Counting
1236: statement number is an approximation without too much overhead.
1237: */
1238: public boolean statementNumHitLimit(int noStatementsAdded) {
1239: if (statementNum > 2048) // 2K limit
1240: {
1241: return true;
1242: } else {
1243: statementNum = statementNum + noStatementsAdded;
1244: return false;
1245: }
1246: }
1247:
1248: /**
1249: * Check to see if the current method byte code is nearing the
1250: * limit of 65535. If it is start overflowing to a new method.
1251: * <P>
1252: * Overflow is handled for a method named e23 as:
1253: * <CODE>
1254: public Object e23()
1255: {
1256: ... existing code
1257: // split point
1258: return e23_0();
1259: }
1260: private Object e23_0()
1261: {
1262: ... first set overflowed code
1263: // split point
1264: return e23_1();
1265: }
1266: private Object e23_1()
1267: {
1268: ... second set overflowed code
1269: // method complete
1270: return result;
1271: }
1272: </CODE>
1273: <P>
1274:
1275: These overflow methods are hidden from the code using this MethodBuilder,
1276: it continues to think that it is building a single method with the
1277: original name.
1278:
1279:
1280: * <BR> Restrictions:
1281: * <UL>
1282: * <LI> Only handles methods with no arguments
1283: * <LI> Stack depth must be zero
1284: * </UL>
1285: *
1286: *
1287: */
1288: private void overflowMethodCheck() {
1289: if (handlingOverflow)
1290: return;
1291:
1292: // don't sub method in the middle of a conditional
1293: if (condition != null)
1294: return;
1295:
1296: int currentCodeSize = myCode.getPC();
1297:
1298: // Overflow at >= 55,000 bytes which is someway
1299: // below the limit of 65,535. Ideally overflow
1300: // would occur at 65535 minus the few bytes needed
1301: // to call the sub-method, but the issue is at this level
1302: // we don't know frequently we are called given the restriction
1303: // of only being called when the stack depth is zero.
1304: // Thus split earlier to try ensure most cases are caught.
1305: // Only downside is that we may split into N methods when N-1 would suffice.
1306: if (currentCodeSize < 55000)
1307: return;
1308:
1309: // only handle no-arg methods at the moment.
1310: if (parameters != null) {
1311: if (parameters.length != 0)
1312: return;
1313: }
1314:
1315: BCMethod subMethod = getNewSubMethod(myReturnType, false);
1316:
1317: // stop any recursion
1318: handlingOverflow = true;
1319:
1320: // in this method make a call to the sub method we will
1321: // be transferring control to.
1322: callSubMethod(subMethod);
1323:
1324: // and return its value, works just as well for a void method!
1325: this .methodReturn();
1326: this .complete();
1327:
1328: handlingOverflow = false;
1329:
1330: // now the tricky bit, make this object take over the
1331: // code etc. from the sub method. This is done so
1332: // that any code that has a reference to this MethodBuilder
1333: // will continue to work. They will be writing code into the
1334: // new sub method.
1335:
1336: this .myEntry = subMethod.myEntry;
1337: this .myCode = subMethod.myCode;
1338: this .currentVarNum = subMethod.currentVarNum;
1339: this .statementNum = subMethod.statementNum;
1340:
1341: // copy stack info
1342: this .stackTypes = subMethod.stackTypes;
1343: this .stackTypeOffset = subMethod.stackTypeOffset;
1344: this .maxStack = subMethod.maxStack;
1345: this .stackDepth = subMethod.stackDepth;
1346: }
1347:
1348: /**
1349: * Create a sub-method from this method to allow the code builder to split a
1350: * single logical method into multiple methods to avoid the 64k per-method
1351: * code size limit. The sub method with inherit the thrown exceptions of
1352: * this method.
1353: *
1354: * @param returnType
1355: * Return type of the new method
1356: * @param withParameters
1357: * True to define the method with matching parameters false to
1358: * define it with no parameters.
1359: * @return A valid empty sub method.
1360: */
1361: final BCMethod getNewSubMethod(String returnType,
1362: boolean withParameters) {
1363: int modifiers = myEntry.getModifier();
1364:
1365: // the sub-method can be private to ensure that no-one
1366: // can call it accidentally from outside the class.
1367: modifiers &= ~(Modifier.PROTECTED | Modifier.PUBLIC);
1368: modifiers |= Modifier.PRIVATE;
1369:
1370: String subMethodName = myName + "_s"
1371: + Integer.toString(subMethodCount++);
1372: BCMethod subMethod = (BCMethod) cb.newMethodBuilder(modifiers,
1373: returnType, subMethodName,
1374: withParameters ? parameterTypes : null);
1375: subMethod.thrownExceptions = this .thrownExceptions;
1376:
1377: return subMethod;
1378: }
1379:
1380: /**
1381: * Call a sub-method created by getNewSubMethod handling parameters
1382: * correctly.
1383: */
1384: final void callSubMethod(BCMethod subMethod) {
1385: // in this method make a call to the sub method we will
1386: // be transferring control to.
1387: short op;
1388: if ((myEntry.getModifier() & Modifier.STATIC) == 0) {
1389: op = VMOpcode.INVOKEVIRTUAL;
1390: this .pushThis();
1391: } else {
1392: op = VMOpcode.INVOKESTATIC;
1393: }
1394:
1395: int parameterCount = subMethod.parameters == null ? 0
1396: : subMethod.parameters.length;
1397:
1398: // push my parameter values for the call.
1399: for (int pi = 0; pi < parameterCount; pi++)
1400: this.getParameter(pi);
1401:
1402: this.callMethod(op, modClass.getName(), subMethod.getName(),
1403: subMethod.myReturnType, parameterCount);
1404: }
1405: }
|