Class CodeChunk


  • final class CodeChunk
    extends java.lang.Object
    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.
    • Field Summary

      Fields 
      Modifier and Type Field Description
      (package private) static short[] ARRAY_ACCESS  
      (package private) static short[] ARRAY_STORE  
      (package private) static short[][][] CAST_CONVERSION_INFO  
      (package private) BCClass cb
      The class we are generating code for, used to indicate that some limit was hit during code generation.
      private static int CODE_OFFSET
      Starting point of the byte code stream in the underlying stream/array.
      private ClassFormatOutput cout  
      (package private) static short[] LOAD_VARIABLE  
      (package private) static short[] LOAD_VARIABLE_FAST  
      private static byte[] NS
      Constant used by OPCODE_ACTION to the opcode is not yet supported.
      private static byte[][] OPCODE_ACTION
      Array that provides two pieces of information about each VM opcode.
      private int pcDelta
      The delta between cout.size() and the pc.
      private static byte[] push1_1i
      Constant used by OPCODE_ACTION to represent the common action of push one word, 1 byte for the instruction.
      private static byte[] push2_1i
      Constant used by OPCODE_ACTION to represent the common action of push two words, 1 byte for the instruction.
      (package private) static short[] RETURN_OPCODE  
      (package private) static short[] STORE_VARIABLE  
      (package private) static short[] STORE_VARIABLE_FAST  
      private static byte VARIABLE_STACK
      Value for OPCODE_ACTION[opcode][0] to represent the number of words popped or pushed in variable.
    • Constructor Summary

      Constructors 
      Modifier Constructor Description
      (package private) CodeChunk​(BCClass cb)  
      private CodeChunk​(CodeChunk main, int pc, int byteCount)
      Return a CodeChunk that has limited visibility into this CodeChunk.
    • Method Summary

      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and Type Method Description
      (package private) void addInstr​(short opcode)
      Add an instruction that has no operand.
      (package private) void addInstrCPE​(short opcode, int cpeNum)
      This takes an instruction that has a narrow and a wide form for CPE access, and generates accordingly the right one.
      (package private) void addInstrU1​(short opcode, int operand)
      Add an instruction that has an 8 bit operand.
      (package private) void addInstrU2​(short opcode, int operand)
      Add an instruction that has a 16 bit operand.
      (package private) void addInstrU2U1U1​(short opcode, int operand1, short operand2, short operand3)
      For adding an instruction with 3 operands, a U2 and two U1's.
      (package private) void addInstrU4​(short opcode, int operand)
      Add an instruction that has a 32 bit operand.
      (package private) void addInstrWide​(short opcode, int varNum)
      This takes an instruction that can be wrapped in a wide for large variable #s and does so.
      (package private) void complete​(BCMethod mb, ClassHolder ch, ClassMember method, int maxStack, int maxLocals)
      wrap up the entry and stuff it in the class, now that it holds all of the instructions and the exception table.
      private int[] findConditionalPCs​(int pc, short opcode)
      Find the limits of a conditional block starting at the instruction with the given opcode at the program counter pc.
      private int findMaxStack​(ClassHolder ch, int pc, int codeLength)
      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 void fixLengths​(BCMethod mb, int maxStack, int maxLocals, int codeLength)
      now that we have codeBytes, fix the lengths fields in it to reflect what was stored.
      private static int getDescriptorWordCount​(java.lang.String vmDescriptor)
      Get the word count for a type descriptor in the format of the virual machine.
      (package private) short getOpcode​(int pc)
      Return the opcode at the given pc.
      (package private) int getPC()
      Get the current program counter
      private java.lang.String getTypeDescriptor​(ClassHolder ch, int pc)
      Get the type descriptor in the virtual machine format for the type defined by the constant pool index for the instruction at pc.
      private int getU2​(int pc)
      Get the unsigned short value for the opcode at the program counter pc.
      private int getU4​(int pc)
      Get the unsigned 32 bit value for the opcode at the program counter pc.
      private int getVariableStackDelta​(ClassHolder ch, int pc, int opcode)
      Get the number of words pushed (positive) or popped (negative) by this instruction.
      (package private) CodeChunk insertCodeSpace​(int pc, int additionalBytes)
      Insert room for byteCount bytes after the instruction at pc and prepare to replace the instruction at pc.
      private static int instructionLength​(short opcode)
      Return the complete instruction length for the passed in opcode.
      private static boolean isReturn​(short opcode)
      See if the opcode is a return instruction.
      private void limitHit​(java.io.IOException ioe)
      Assume an IOException means some limit of the class file format was hit
      private static int parameterWordCount​(java.lang.String methodDescriptor)
      Calculate the number of stack words in the arguments pushed for this method descriptor.
      private int removePushedCode​(BCMethod mb, ClassHolder ch, BCMethod subMethod, int split_pc, int splitLength)
      Remove a block of code from this method that was pushed into a sub-method and call the sub-method.
      private int splitCodeIntoSubMethod​(BCMethod mb, ClassHolder ch, BCMethod subMethod, int split_pc, int splitLength)
      Split a block of code from this method into a sub-method and call it.
      (package private) int splitExpressionOut​(BCMethod mb, ClassHolder ch, int optimalMinLength, int maxStack)
      Split an expression out of a large method into its own sub-method.
      private static int splitMinLength​(BCMethod mb)
      Minimum split length for a sub-method.
      (package private) int splitZeroStack​(BCMethod mb, ClassHolder ch, int split_pc, int optimalMinLength)
      Attempt to split the current method by pushing a chunk of its code into a sub-method.
      private int stackWordDelta​(ClassHolder ch, int pc, short opcode)
      Return the number of stack words pushed (positive) or popped (negative) by this instruction.
      private BCMethod startSubMethod​(BCMethod mb, java.lang.String returnType, int split_pc, int blockLength)
      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.
      private boolean usesParameters​(BCMethod mb, int pc, int codeLength)
      Does a section of code use parameters.
      • Methods inherited from class java.lang.Object

        clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    • Field Detail

      • CODE_OFFSET

        private static final int CODE_OFFSET
        Starting point of the byte code stream in the underlying stream/array.
        See Also:
        Constant Field Values
      • LOAD_VARIABLE

        static final short[] LOAD_VARIABLE
      • LOAD_VARIABLE_FAST

        static final short[] LOAD_VARIABLE_FAST
      • STORE_VARIABLE

        static final short[] STORE_VARIABLE
      • STORE_VARIABLE_FAST

        static final short[] STORE_VARIABLE_FAST
      • ARRAY_ACCESS

        static final short[] ARRAY_ACCESS
      • ARRAY_STORE

        static final short[] ARRAY_STORE
      • RETURN_OPCODE

        static final short[] RETURN_OPCODE
      • CAST_CONVERSION_INFO

        static final short[][][] CAST_CONVERSION_INFO
      • push1_1i

        private static final byte[] push1_1i
        Constant used by OPCODE_ACTION to represent the common action of push one word, 1 byte for the instruction.
      • push2_1i

        private static final byte[] push2_1i
        Constant used by OPCODE_ACTION to represent the common action of push two words, 1 byte for the instruction.
      • NS

        private static final byte[] NS
        Constant used by OPCODE_ACTION to the opcode is not yet supported.
      • VARIABLE_STACK

        private static final byte VARIABLE_STACK
        Value for OPCODE_ACTION[opcode][0] to represent the number of words popped or pushed in variable.
        See Also:
        Constant Field Values
      • OPCODE_ACTION

        private static final byte[][] OPCODE_ACTION
        Array that provides two pieces of information about each VM opcode. Each opcode has a two byte array.

        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.

        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.

      • pcDelta

        private final int pcDelta
        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 Also:
        insertCodeSpace(int, int)
      • cb

        final BCClass cb
        The class we are generating code for, used to indicate that some limit was hit during code generation.
    • Constructor Detail

      • CodeChunk

        CodeChunk​(BCClass cb)
      • CodeChunk

        private CodeChunk​(CodeChunk main,
                          int pc,
                          int byteCount)
        Return a CodeChunk that has limited visibility into this CodeChunk. Used when a caller needs to insert instructions into an existing stream.
        Parameters:
        pc -
        byteCount -
    • Method Detail

      • limitHit

        private void limitHit​(java.io.IOException ioe)
        Assume an IOException means some limit of the class file format was hit
      • addInstr

        void addInstr​(short opcode)
        Add an instruction that has no operand. All opcodes are 1 byte large.
      • addInstrU2

        void addInstrU2​(short opcode,
                        int operand)
        Add an instruction that has a 16 bit operand.
      • addInstrU4

        void addInstrU4​(short opcode,
                        int operand)
        Add an instruction that has a 32 bit operand.
      • addInstrU1

        void addInstrU1​(short opcode,
                        int operand)
        Add an instruction that has an 8 bit operand.
      • addInstrCPE

        void addInstrCPE​(short opcode,
                         int cpeNum)
        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.
      • addInstrWide

        void addInstrWide​(short opcode,
                          int varNum)
        This takes an instruction that can be wrapped in a wide for large variable #s and does so.
      • addInstrU2U1U1

        void addInstrU2U1U1​(short opcode,
                            int operand1,
                            short operand2,
                            short operand3)
        For adding an instruction with 3 operands, a U2 and two U1's. So far, this is used by VMOpcode.INVOKEINTERFACE.
      • getPC

        int getPC()
        Get the current program counter
      • instructionLength

        private static int instructionLength​(short opcode)
        Return the complete instruction length for the passed in opcode. This will include the space for the opcode and its operand.
      • fixLengths

        private void fixLengths​(BCMethod mb,
                                int maxStack,
                                int maxLocals,
                                int codeLength)
        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.
        • 4.7.3 The Code Attribute
        • 4.10 Limitations of the Java Virtual Machine
      • complete

        void complete​(BCMethod mb,
                      ClassHolder ch,
                      ClassMember method,
                      int maxStack,
                      int maxLocals)
        wrap up the entry and stuff it in the class, now that it holds all of the instructions and the exception table.
      • getOpcode

        short getOpcode​(int pc)
        Return the opcode at the given pc.
      • getU2

        private int getU2​(int pc)
        Get the unsigned short value for the opcode at the program counter pc.
      • getU4

        private int getU4​(int pc)
        Get the unsigned 32 bit value for the opcode at the program counter pc.
      • insertCodeSpace

        CodeChunk insertCodeSpace​(int pc,
                                  int additionalBytes)
        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.
        Parameters:
        pc -
        additionalBytes -
      • findMaxStack

        private int findMaxStack​(ClassHolder ch,
                                 int pc,
                                 int codeLength)
        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.
      • stackWordDelta

        private int stackWordDelta​(ClassHolder ch,
                                   int pc,
                                   short opcode)
        Return the number of stack words pushed (positive) or popped (negative) by this instruction.
      • getTypeDescriptor

        private java.lang.String getTypeDescriptor​(ClassHolder ch,
                                                   int pc)
        Get the type descriptor in the virtual machine format for the type defined by the constant pool index for the instruction at pc.
      • getDescriptorWordCount

        private static int getDescriptorWordCount​(java.lang.String 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.
      • getVariableStackDelta

        private int getVariableStackDelta​(ClassHolder ch,
                                          int pc,
                                          int opcode)
        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.
      • parameterWordCount

        private static int parameterWordCount​(java.lang.String methodDescriptor)
        Calculate the number of stack words in the arguments pushed for this method descriptor.
      • findConditionalPCs

        private int[] findConditionalPCs​(int pc,
                                         short opcode)
        Find the limits of a conditional block starting at the instruction with the given opcode at the program counter pc.

        Returns a six element integer array of program counters and lengths. [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. Looks for and handles conditionals that are written by the Conditional class.

        Returns:
        Null if the opcode is not the start of a conditional otherwise the array of values.
      • splitZeroStack

        final int splitZeroStack​(BCMethod mb,
                                 ClassHolder ch,
                                 int split_pc,
                                 int optimalMinLength)
        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.

        The method is aimed at splitting methods that contain many independent statements.

        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.

        Parameters:
        mb - Method for this chunk.
        ch - Class definition
        optimalMinLength - minimum length required for split
      • startSubMethod

        private BCMethod startSubMethod​(BCMethod mb,
                                        java.lang.String returnType,
                                        int split_pc,
                                        int blockLength)
        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.
      • usesParameters

        private boolean usesParameters​(BCMethod mb,
                                       int pc,
                                       int codeLength)
        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.
      • splitCodeIntoSubMethod

        private int splitCodeIntoSubMethod​(BCMethod mb,
                                           ClassHolder ch,
                                           BCMethod subMethod,
                                           int split_pc,
                                           int splitLength)
        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.
        Parameters:
        mb - My method
        ch - My class
        subMethod - Sub-method code was pushed into
        split_pc - Program counter the split started at
        splitLength - Length of code split
      • removePushedCode

        private int removePushedCode​(BCMethod mb,
                                     ClassHolder ch,
                                     BCMethod subMethod,
                                     int split_pc,
                                     int 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.
        Parameters:
        mb - My method
        ch - My class
        subMethod - Sub-method code was pushed into
        split_pc - Program counter the split started at
        splitLength - Length of code split
      • splitExpressionOut

        final int splitExpressionOut​(BCMethod mb,
                                     ClassHolder ch,
                                     int optimalMinLength,
                                     int maxStack)
        Split an expression out of a large method into its own sub-method.

        Method call expressions are of the form:

        • expr.method(args) -- instance method call
        • method(args) -- static method call
        Two special cases of instance method calls will be handled by the first incarnation of splitExpressionOut. three categories:
        • this.method(args)
        • this.getter().method(args)
        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.

        This method will split out such expressions in sub-methods and replace the original code with a call to that submethod.

        • this.method(args) ->> this.sub1([parameters])
        • this.getter().method(args) ->> this.sub1([parameters])
        The assumption is of course that the call to the sub-method is much smaller than the code it replaces.

        Looking at the byte code for such calls they would look like (for an example three argument method): this arg1 arg2 arg3 INVOKE // this.method(args) this INVOKE arg1 arg2 arg3 INVOKE // this.getter().metod(args) The bytecode for the arguments can be arbitary long and consist of expressions, typical Derby code for generated queries is deeply nested method calls.
        If none of the arguments requred the parameters passed into the method, then in both cases the replacement bytecode would look like: this.sub1(); Parameter handling is just as in the method splitZeroStack().

        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:

        • N - void method
        • N + 1 - method returning a single word
        • N + 2 - method returning a double word (java long or double)
        This code will handle the N+1 where the word is a reference, the typical case for generated code.
        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.
        The self-containment breaks in a few cases, take the simple method call this.method(3), the byte code for this could be: push3 this swap invoke In this case the byte code for arg1 (swap) is not self-contained and relies on earlier stack values.

        How to identify "self-contained blocks of code".
        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.
        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.
        If the block is self-contained then it can be split, following similar logic to splitZeroStack().

        WORK IN PROGRESS - Incremental development
        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.

      • isReturn

        private static boolean isReturn​(short opcode)
        See if the opcode is a return instruction.
        Parameters:
        opcode - opcode to be checked
        Returns:
        true for is a return instruction, false otherwise.
      • splitMinLength

        private static int splitMinLength​(BCMethod mb)
        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.
        This leads to this number of instructions.
        • 4 - 'this' and first 3 parameters have single byte instructions
        • (N-4)*2 - Remaining parameters have two byte instructions
        • 3 for the invoke instruction.