Top Description Fields Constructors Methods
org.apache.derby.impl.services.bytecode

pack-priv final Class CodeChunk

extends Object
Class Inheritance
Imports
org.apache.derby.iapi.services.classfile.CONSTANT_Index_info, .CONSTANT_Utf8_info, .ClassFormatOutput, .ClassHolder, .ClassMember, .VMDescriptor, .VMOpcode, org.apache.derby.shared.common.sanity.SanityManager, org.apache.derby.iapi.services.io.ArrayOutputStream, java.io.IOException, java.lang.reflect.Modifier, java.util.Arrays

This class represents a chunk of code in a CodeAttribute. Typically, a CodeAttribute represents the code in a method. If there is a try/catch block, each catch block will get its own code chunk. This allows the catch blocks to all be put at the end of the generated code for a method, which eliminates the need to generate a jump around each catch block, which would be a forward reference.

Field Summary

Modifier and TypeField and Description
pack-priv static final short[]
pack-priv static final short[]
pack-priv static final short[][][]
pack-priv final BCClass
cb

The class we are generating code for, used to indicate that some limit was hit during code generation.

private static final int
CODE_OFFSET

Starting point of the byte code stream in the underlying stream/array.

private final ClassFormatOutput
pack-priv static final short[]
pack-priv static final short[]
private static final byte[]
NS

Constant used by OPCODE_ACTION to the opcode is not yet supported.

private static final byte[][]
OPCODE_ACTION

Array that provides two pieces of information about each VM opcode.

private final int
pcDelta

The delta between cout.size() and the pc.

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.

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.

pack-priv static final short[]
pack-priv static final short[]
pack-priv static final short[]
private static final byte
VARIABLE_STACK

Value for OPCODE_ACTION[opcode][0] to represent the number of words popped or pushed in variable.

Constructor Summary

AccessConstructor and Description
pack-priv
private
CodeChunk(CodeChunk main, int pc, int byteCount)

Return a CodeChunk that has limited visibility into this CodeChunk.

Method Summary

Modifier and TypeMethod and Description
pack-priv void
addInstr(short opcode)

Add an instruction that has no operand.

pack-priv 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.

pack-priv void
addInstrU1(short opcode, int operand)

Add an instruction that has an 8 bit operand.

pack-priv void
addInstrU2(short opcode, int operand)

Add an instruction that has a 16 bit operand.

pack-priv void
addInstrU2U1U1(short opcode, int operand1, short operand2, short operand3)

For adding an instruction with 3 operands, a U2 and two U1's.

pack-priv void
addInstrU4(short opcode, int operand)

Add an instruction that has a 32 bit operand.

pack-priv void
addInstrWide(short opcode, int varNum)

This takes an instruction that can be wrapped in a wide for large variable #s and does so.

pack-priv 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[]

Returns:

Null if the opcode is not the start of a conditional otherwise the array of values.
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(String vmDescriptor)

Get the word count for a type descriptor in the format of the virual machine.

pack-priv short
getOpcode(int pc)

Return the opcode at the given pc.

pack-priv int
getPC()

Get the current program counter

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.

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.

pack-priv 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

Returns:

true for is a return instruction, false otherwise.
isReturn
(short
opcode to be checked
opcode
)

See if the opcode is a return instruction.

private void
limitHit(IOException ioe)

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
My method
mb
,
ClassHolder
My class
ch
,
BCMethod
Sub-method code was pushed into
subMethod
,
final int
Program counter the split started at
split_pc
,
final int
Length of code split
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
My method
mb
,
ClassHolder
My class
ch
,
BCMethod
Sub-method code was pushed into
subMethod
,
final int
Program counter the split started at
split_pc
,
final int
Length of code split
splitLength
)

Split a block of code from this method into a sub-method and call it.

pack-priv final int
splitExpressionOut(final BCMethod mb, final ClassHolder ch, final int optimalMinLength, final 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.

pack-priv final int
splitZeroStack(BCMethod
Method for this chunk.
mb
,
ClassHolder
Class definition
ch
,
final int split_pc, final int
minimum length required for split
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.

Inherited from java.lang.Object:
cloneequalsfinalizegetClasshashCodenotifynotifyAlltoStringwaitwaitwait

Field Detail

ARRAY_ACCESSback to summary
pack-priv static final short[] ARRAY_ACCESS
ARRAY_STOREback to summary
pack-priv static final short[] ARRAY_STORE
CAST_CONVERSION_INFOback to summary
pack-priv static final short[][][] CAST_CONVERSION_INFO
cbback to summary
pack-priv final BCClass cb

The class we are generating code for, used to indicate that some limit was hit during code generation.

CODE_OFFSETback to summary
private static final int CODE_OFFSET

Starting point of the byte code stream in the underlying stream/array.

coutback to summary
private final ClassFormatOutput cout
LOAD_VARIABLEback to summary
pack-priv static final short[] LOAD_VARIABLE
LOAD_VARIABLE_FASTback to summary
pack-priv static final short[] LOAD_VARIABLE_FAST
NSback to summary
private static final byte[] NS

Constant used by OPCODE_ACTION to the opcode is not yet supported.

OPCODE_ACTIONback to summary
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.

pcDeltaback to summary
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
push1_1iback to summary
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_1iback to summary
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.

RETURN_OPCODEback to summary
pack-priv static final short[] RETURN_OPCODE
STORE_VARIABLEback to summary
pack-priv static final short[] STORE_VARIABLE
STORE_VARIABLE_FASTback to summary
pack-priv static final short[] STORE_VARIABLE_FAST
VARIABLE_STACKback to summary
private static final byte VARIABLE_STACK

Value for OPCODE_ACTION[opcode][0] to represent the number of words popped or pushed in variable.

Constructor Detail

CodeChunkback to summary
pack-priv CodeChunk(BCClass cb)
CodeChunkback to summary
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.

Method Detail

addInstrback to summary
pack-priv void addInstr(short opcode)

Add an instruction that has no operand. All opcodes are 1 byte large.

addInstrCPEback to summary
pack-priv 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.

addInstrU1back to summary
pack-priv void addInstrU1(short opcode, int operand)

Add an instruction that has an 8 bit operand.

addInstrU2back to summary
pack-priv void addInstrU2(short opcode, int operand)

Add an instruction that has a 16 bit operand.

addInstrU2U1U1back to summary
pack-priv 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.

addInstrU4back to summary
pack-priv void addInstrU4(short opcode, int operand)

Add an instruction that has a 32 bit operand.

addInstrWideback to summary
pack-priv void addInstrWide(short opcode, int varNum)

This takes an instruction that can be wrapped in a wide for large variable #s and does so.

completeback to summary
pack-priv 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.

findConditionalPCsback to summary
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:int[]

Null if the opcode is not the start of a conditional otherwise the array of values.

findMaxStackback to summary
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.

fixLengthsback to summary
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
getDescriptorWordCountback to summary
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.

getOpcodeback to summary
pack-priv short getOpcode(int pc)

Return the opcode at the given pc.

getPCback to summary
pack-priv int getPC()

Get the current program counter

getTypeDescriptorback to summary
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.

getU2back to summary
private int getU2(int pc)

Get the unsigned short value for the opcode at the program counter pc.

getU4back to summary
private int getU4(int pc)

Get the unsigned 32 bit value for the opcode at the program counter pc.

getVariableStackDeltaback to summary
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.

insertCodeSpaceback to summary
pack-priv 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.

instructionLengthback to summary
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.

isReturnback to summary
private static boolean isReturn(short opcode)

See if the opcode is a return instruction.

Parameters
opcode:short

opcode to be checked

Returns:boolean

true for is a return instruction, false otherwise.

limitHitback to summary
private void limitHit(IOException ioe)

Assume an IOException means some limit of the class file format was hit

parameterWordCountback to summary
private static int parameterWordCount(String methodDescriptor)

Calculate the number of stack words in the arguments pushed for this method descriptor.

removePushedCodeback to summary
private int removePushedCode(BCMethod mb, ClassHolder ch, BCMethod subMethod, final int split_pc, final 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:BCMethod

My method

ch:ClassHolder

My class

subMethod:BCMethod

Sub-method code was pushed into

split_pc:int

Program counter the split started at

splitLength:int

Length of code split

splitCodeIntoSubMethodback to summary
private int splitCodeIntoSubMethod(BCMethod mb, ClassHolder ch, BCMethod subMethod, final int split_pc, final 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:BCMethod

My method

ch:ClassHolder

My class

subMethod:BCMethod

Sub-method code was pushed into

split_pc:int

Program counter the split started at

splitLength:int

Length of code split

splitExpressionOutback to summary
pack-priv final int splitExpressionOut(final BCMethod mb, final ClassHolder ch, final int optimalMinLength, final 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.

splitMinLengthback to summary
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.
splitZeroStackback to summary
pack-priv final int splitZeroStack(BCMethod mb, ClassHolder ch, final int split_pc, final 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:BCMethod

Method for this chunk.

ch:ClassHolder

Class definition

optimalMinLength:int

minimum length required for split

stackWordDeltaback to summary
private int stackWordDelta(ClassHolder ch, int pc, short opcode)

Return the number of stack words pushed (positive) or popped (negative) by this instruction.

startSubMethodback to summary
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.

usesParametersback to summary
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.