为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使用bipush
、sipush
和iconst_<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类型是fcmpl
和fcmpg
指令,对于double是dcmpl
和dcmpg
指令
在接 iflt
或 ifle
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
指令时,也会生成这条指令所引用的描述符,这个描述符提供了方法参数和返回值的信息。
说明
《Java虚拟机规范(Java SE 7版)》作者:Tim Lindholm、Frank Yellin、Gilad Bracha、Alex Buckley 摘要:第三章 为JAVA虚拟机编译(第2部分)
你也可以访问:http://txidol.github.io 获取更多的信息