Class CodeChunk

java.lang.Object
org.apache.derby.impl.services.bytecode.CodeChunk

final class CodeChunk extends 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 final short[]
     
    (package private) static final short[]
     
    (package private) static final short[][][]
     
    (package private) final BCClass
    The class we are generating code for, used to indicate that some limit was hit during code generation.
    private static final int
    Starting point of the byte code stream in the underlying stream/array.
    private final ClassFormatOutput
     
    (package private) static final short[]
     
    (package private) static final short[]
     
    private static final byte[]
    Constant used by OPCODE_ACTION to the opcode is not yet supported.
    private static final byte[][]
    Array that provides two pieces of information about each VM opcode.
    private final int
    The delta between cout.size() and the pc.
    private static final byte[]
    Constant used by OPCODE_ACTION to represent the common action of push one word, 1 byte for the instruction.
    private static final byte[]
    Constant used by OPCODE_ACTION to represent the common action of push two words, 1 byte for the instruction.
    (package private) static final short[]
     
    (package private) static final short[]
     
    (package private) static final short[]
     
    private static final byte
    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)
     
    private
    CodeChunk(CodeChunk main, int pc, int byteCount)
    Return a CodeChunk that has limited visibility into this CodeChunk.
  • Method Summary

    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
    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
    Get the current program counter
    private String
    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
    Assume an IOException means some limit of the class file format was hit
    private static int
    parameterWordCount(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) final 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
    Minimum split length for a sub-method.
    (package private) 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.
    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, 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 Details

    • CODE_OFFSET

      private static final int CODE_OFFSET
      Starting point of the byte code stream in the underlying stream/array.
      See Also:
    • 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:
    • 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:
    • cb

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

      private final ClassFormatOutput cout
  • Constructor Details

    • 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 Details

    • limitHit

      private void limitHit(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 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(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(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, 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.