为JAVA虚拟机编译(第2部分)

3.1 算术运算

Java虚拟机通常基于操作数栈来进行算术运算(只有iinc指令例外,它直接对局部变量进行自增操作),算术运算使用到的操作数都是从操作数栈中弹出的,运算结果被压回操作数栈中。在内部运算时,中间运算(Arithmetic Subcomputations)的结果可以被当作操作数使用

譬如下面的align2grain()方法,它的作用是将int值对齐到2的指定幂次:

int align2grain(int i, int grain) { 
    return ((i + grain-1) & ~(grain-1)); 
}

编译代码如下:

Method int align2grain(int,int) 
0 iload_1 
1 iload_2 
2 iadd 
3 iconst_1 
4 isub 
5 iload_2 // Push grain
6 iconst_1 // Push int constant 1 立即操作数int数值1
7 isub // Subtract; push result
8 iconst_m1 // Push int constant −1
9 ixor // Do XOR; push result (因为~x == −1^x)
10 iand 
11 ireturn

3.4 访问运行时常量池

ldc和ldc_w指令用于访问运行时常量池中的对象 int类型 byte、char、short 类型,包括String实例

当使用的运行时常量池的项的数目过多时(多于256个,1个字节能表示的范围),需要使用ldc_w

ldc2_w 指令用于访问类型为double和long的运行时常量池项,这条指令没有非宽索引的版本(即没有ldc2指令)。

对于整型常量,包括byte、char、short和int使用bipushsipushiconst_<i>指令进行访问。某些浮点常量也可以编译进代码,使用fconst_<f>dconst_<d>指令进行访问。

void useManyNumeric() { 
    int i = 100; 
    int j = 1000000; 
    long l1 = 1; 
    long l2 = 0xffffffff; 
    double d = 2.2; 
    ...do some calculations... 
}

编译后代码如下:

Method void useManyNumeric() 
0 bipush 100 // Push a small int with bipush 
2 istore_1 
3 ldc #1 // Push int constant 1000000; a larger int 
// value uses ldc 
5 istore_2 
6 lconst_1 // A tiny long value uses short, fast lconst_1 
7 lstore_3 
8 ldc2_w #6 // Push long 0xffffffff (that is, an int −1); any 
// long constant value can be pushed using ldc2_w 
11 lstore 5 
13 ldc2_w #8 // Push double constant 2.200000; uncommon 
// double values are also pushed using ldc2_w 
16 dstore 7 
...do those calculations...

3.5 更多的控制结构示例

void whileInt() { 
    int i = 0; 
    while (i < 100) { 
        i++; 
    } 
}

编译后代码如下:

Method void whileInt() 
0 iconst_0 
1 istore_1 
2 goto 8 
5 iinc 1 1 
8 iload_1 
9 bipush 100 
11 if_icmplt 5
14 return

有两条比较指令:对于float类型是fcmplfcmpg指令,对于double是dcmpldcmpg指令 在接 ifltifle

int lessThan100(double d) { 
    if (d < 100.0) { 
        return 1; 
    } else { 
        return -1; 
    } 
}

编译后代码如下:

Method int lessThan100(double) 
0 dload_1 
1 ldc2_w #4 // Push double constant 100.0 
4 dcmpg // Push 1 if d is NaN or d > 100.0; 
// push 0 if d == 100.0 
5 ifge 10 // Branch on 0 or 1 
8 iconst_1 
9 ireturn 
10 iconst_m1 
11 ireturn

3.6 接收参数

传递了n个参数给某个实例方法,则当前栈帧会按照约定的顺序接收这些参数,将它们保存为方法的第1个至第n个局部变量之中

按照约定,实例方法需要传递一个自身实例的引用作为第0个局部变量。在Java语言中自身实例可以通过this关键字来访问。

类(static)方法不需要传递实例引用,所以它们不需要使用第0个局部变量来保存this关键字。所以从0开始 。

3.7 方法调用

int add12and13() { 
    return addTwo(12, 13); 
}

编译后代码如下:

Method int add12and13() 
0 aload_0 // Push local variable 0 (this) 
1 bipush 12 // Push int constant 12 
3 bipush 13 // Push int constant 13 
5 invokevirtual #4 // Method Example.addtwo(II)I 运行时常量池在该索引4含 内部二进制名称、方法名称和方法描述符
8 ireturn // Return int on top of operand stack; it is the int result of addTwo()   

编译器在方法调用时不会处理参数的类型转换问题,只是简单地将参数的压入操作数栈,且不改变其类型。

编译器在生成invokevirtual指令时,也会生成这条指令所引用的描述符,这个描述符提供了方法参数和返回值的信息。

说明