![]() |
|||
![]() ![]() |
![]() |
|
![]() |
SwitchingAlthough a switch statement is logically equivalent to a sequence of if-else if-else if- -else statements, Java provides special support to allow it to be executed more efficiently. The tableswitch instruction is used when the cases mostly cover a range of integers. For example: switch (j) { case 0: case 1: case 2: case 3: case 5: default: } The lookupswitch instruction is used when the cases are further apart. For example: switch (j) { case 0: case -121: case 236: case 342: case 5: default: } Both the tableswitch and lookupswitch instructions are variable-length instructions. They are the only such instructions in the Java virtual machine. Each of these instructions is followed in the code array by some padding and a table of locations of instructions to which they should branch. This table is called a jump table. The two instructions differ in how the jump table is stored. Each of these instructions requires that its table be aligned on a 4-byte boundary in the code array. In other words, the jump table always starts on byte 4, 8, 12, 16, 20, or some other multiple of four. Between zero and three null bytes are added as padding immediately following the switch instruction, to ensure that the jump table is 4-byte aligned. tableswitch Following the tableswitch instruction and the zero-to-three padding bytes are three 4-byte, signed int values. The first is called default; the second is called low; and the third is called high. After these three ints, theres a jump table of relative offsets. Each entry in the jump table is a signed, 4-byte int that is an address of an instruction to jump to relative to the tableswitch instruction. For example, if a jump table entry is 72 and the tableswitch is at instruction 17, then when that jump table entry is chosen, control jumps to instruction 72+17, which is instruction 89. There are always exactly high - low + 1 entries in the jump table. Because each jump table entry takes four bytes, the jump table is 4 * (high - low +1) bytes long. The index of the jump table entry to jump to is popped from the stack. If this number is between low and high inclusive, then the appropriate jump table entry is read, and control moves to the instruction indicated by that jump table entry. Otherwise, the default value is added to the address of the tableswitch instruction, and control jumps to that location. For example, consider the tswitch method: public int tswitch(int i) { int result = 0; switch (i) { case 0: result += 2; case 1: result += 3; case 2: result += 4; case 3: result += 6; case 5: result += 7; default: } return result; } Without optimization, tswitch() is compiled to the byte codes shown in Listing 5-3. The tableswitch instruction is byte 3. The next byte is byte 4, so no padding bytes are needed. Bytes 4 through 7 are the default value, in this case 52. Bytes 8 through 11 are the low value in the jump table, here 0. Bytes 12 through 15 are the high value in the jump table, here 5. Remember that the lowest value in the switch statement was 0 and the highest was 5. Therefore, there will be six entries in this jump table, entries 0 through 5. The jump table itself is stored in bytes 16 through 39. This is not a large method or a large switch statement, so its easy to read the different ints by just looking at the last byte in each 4-byte set. The jump table entries are 37, 40, 43, 46, 52, and 49 in that order. Thus, if the int value 0 is popped from the stack, control jumps to 37+3, that is instruction 40, iinc. If the int value 1 is popped from the stack, control jumps to instruction 40+3 or 43, a different iinc instruction. You may have noticed that the instructions for each jump are not disjointed. Once a jump takes place, all subsequent instructions are executed. Thats because I left break statements out of the cases to simplify the code. If 0 is passed into this version of tswitch(), 2+3+4+6+7=22 is returned. I add break statements in the next section. Listing 5-3 The compiled tswitch instruction public int tswitch(int) { 0 iconst_0 1 istore_2 2 iload_1 3 tableswitch 4 0 5 0 6 0 7 52 8 0 9 0 10 0 11 0 12 0 13 0 14 0 15 5 16 0 17 0 18 0 19 37 20 0 21 0 22 0 23 40 24 0 25 0 26 0 27 43 28 0 29 0 30 0 31 46 32 0 33 0 34 0 35 52 36 0 37 0 38 0 39 49 40 iinc 41 2 42 2 43 iinc 44 2 45 3 46 iinc 47 2 48 4 49 iinc 50 2 51 6 52 iinc 53 2 54 7 55 iload_2 56 ireturn } Control should never jump out of the current method, nor should the index that you use fall outside the jump table. Similarly, control should only jump to actual instructions, like iinc, not to data values like the 2 in byte 41. In general, the compiler should prevent this. If it does not, the classfile verifier should detect the problem and refuse to run the program.
|
![]() |
|