Java字节码是基于栈的一种编码。这种编码方式十分方便解释器的设计,但同时不利于程序分析,因此一些高效的代码优化技术无法方便的Java字节码上实现。
先大体说说Java字节码的特点。目前版本的Java大概有200+的字节码指令,其中大部分都是1字节指令,这也是为什么叫做字节码。少部分指令是多字节或不定长指令。对于解释器来说,解释指令时一般都是在操作两个区域。一个是栈,一个是局部变量表。举例来说,iload1指令,就是从局部变量表的1号槽位的数据放入操作数栈中,即*stack++ = locals。
与C或者其他常用的编程语言不同的是,Java字节码的操作数类型是隐含的,操作的类型的显示的,而C语言中操作数类型都是显示的,但是操作是多态的。比如“+”,在C语言中“+”两边的操作数类型可以是int型,可以是double。尚学堂•百战程序员陈老师指出Java字节码中iadd指令明确表示了要操作相加的两个数一定是int型。但是当抛开iadd指令而直接观测操作数栈时,并不知道栈上操作数的类型。
Java字节码在每一条指令执行时,操作数栈的深度,局部变量表的大小,以及它们上面的操作数类型都是可以确定的。而且,无论从何种路径执行到某一条指令,操作数栈深度及操作数类型都是确定的,以一个简单的a=b+c的例子来说明这个翻译过程。
其中局部变量的类型都是已知的。可以看到s0,s1跟Java操作数栈的功能一样,是为了存放临时的计算结果。上面的代码完全可以化简为“l1 = l1 + l2”。但前期没有必要引入这种复杂性,这种化简完全可以由后续的各种优化完成。
上面的例子实际上的存在一些问题的。虽然Java操作数栈和局部变量表里面存放的数据都是有类型,但是栈和局部变量表本身只是一个存储空间罢了,并没有规定里面必须存放什么类型的数据。所以每次在给栈空间或者局部变量赋值的时候,我们有必要新声明一个局部变量。
通过数据流分析可以求出def-use,方便做上面的这种变量分裂,这里不详细说了。最近在研究Soot,由于Soot的目的是对字节码做优化,所以里面也有将字节码翻译为Jimple的逻辑。但是不明白Soot为什么需要类型推导,目前我感觉将Java字节码翻译为Jimple完全不需要推导类型。