当前位置:文档之家› Javac (J2SE) 编译器代码生成介绍

Javac (J2SE) 编译器代码生成介绍

Javac (J2SE) 编译器代码生成介绍
Javac (J2SE) 编译器代码生成介绍

9代码生成

9 代码生成 (1)

9.1 指令的编码 (2)

9.2 生成代码的管理 (5)

9.3 指令的发射 (6)

9.4 存储管理 (10)

9.5 为Java语言的各种结构生成代码 (12)

9.5.1 为Java方法生成代码 (12)

9.5.2 为方法的调用生成代码 (14)

9.5.3 为循环结构生成代码 (15)

9.5.4 为条件语句生成代码 (17)

9.5.5 为异常捕获部分生成代码 (18)

9.6 代码生成运行实例分析 (22)

9.7 小结 (25)

代码生成作为编译器的最后一个阶段,要将某种形式的中间表示翻译成最终的目标代码。一般来讲代码生成器要负责存储的管理,与编译器的前端协作完成将源程序中的名字映射为运行时存储器中数据对象的地址。并且,它要充分利用目标处理器的资源,生成高效的代码。对于CISC结构的处理器,要利用体系结构支持的丰富的寻址模式,生成简洁的代码,这就需要使用基于树的模式匹配指令选择算法。对于面向寄存器的指令集,要进行寄存器分配。这些都是编译器中代码生成程序需要完成的功能。

从抽象语法树生成代码,需要对抽象语法树进行遍历。基本的算法可以用下面的递归过程描述(采用二叉树,但容易推广到节点子树多于2的情况):

procedure genCode(T :treenode)

begin

if T is not nil then

generate code to prepare for code of the left child of T;

genCode(left child of T);

generate code to prepare for the code of the right child of T;

genCode(right child of T)

generate code to implement the action of T;

end;

一遍的代码生成算法,需要解决的一个主要问题是:当生成一条跳转指令时,我们可能并不知道需要跳转到的目标地址。为解决这个问题,需要使用回填技术(backpatching)。我们可以生成一系列未指定目标地址的跳转指令。这些指令被放入到一个列表中,该列表中元素的地址在标号确定后进行回填。这项技术被广泛应用于各种高级语言的控制结构生成代码,如循环,跳转,条件语句等等。

寄存器分配也是代码生成的一项重要内容。在处理器中,寄存器是比存储器要快很多,而且这种差距有着拉大的趋势。为了加快程序的执行速度,就需要让程序生成的代码尽可能多的使用寄存器。寄存器的分配算法,从简单的基于静态引用计数的寄存器分配算法,到复

杂的基于图着色的寄存器分配算法,学术界和工程界已经进行了多年的研究,这方面的技术已经广泛应用到各种编译器之中。基于静态引用计数的寄存器分配算法根据对程序结构的静态分析,来推测变量被引用的次数,一次读或者写操作引用计数加一,对于循环结构中的引用,根据循环嵌套层数在基本的引用计数乘以一个加权因子后进行累加,对于一层循环和两层循环这个加权因子一般为10,100,以此类推。最后,为引用计数最多的变量分配寄存器。在生成代码时,如果对分配了寄存器的变量进行操作,就采用寄存器操作指令,相反,如果对没有分配寄存器的变量操作,就采用内存操作指令。基于图着色的寄存器分配算法,也是一种被广泛使用的寄存器分配算法,使用这种算法可以生成高质量的代码。由于算法比较复杂,这里不进行介绍,有兴趣的读者请参考相关文献[4]。

本书以Java语言的编译器GJC作为背景,由于Java虚拟机的指令集是面向堆栈的指令集,所以本书实例分析中没有涉及基于树的模式匹配的指令选择技术和寄存器分配技术。

在GJC中代码生成遍的主要任务是:

指令翻译:从Java虚拟机的指令集中选择合适的指令,对源程序的各种结构,如方法、表达式、循环、跳转、异常的抛出和捕获进行翻译。

存储管理:将源程序中的局部变量映射为运行时局部数据区中数据对象的地址。并且将源程序中的静态变量映射为Java类文件格式中常量池表项的索引。

字节码生成:将指令逐条按照Java虚拟机指令集的规定进行编码,生成最终的字节码文件。

9.1 指令的编码

一般来说,为了完成指令的编码任务,编译器需要完成两个映射:一个是字节码的助记符到字节码编码值的映射;另一个是字节码的助记符到字节码名字的映射。例如对于ByteCode第一条指令的助记符为nop,编码值为0,字节码名字为“nop”。为了输出字节码名字在程序中保存为字符串形式的“nop”。第一个映射的作用是显而易见的,它使编译器在代码生成的过程中只需要指定指令的助记符,通过这个映射对助记符进行编码。第二个映射的作用也很重要,它可以输出程序员能够识别的辅助信息,方便编译器的开发和调试。

在GJC中,从字节码助记符到字节码编码值的映射,是由Code/ByteCode.java中的程序来完成。

1 /**

2 * @(#)ByteCodes.java 1.1

3 03/01/23

3 *

4 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.

5 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.

6 */

7 package com.sun.tools.javac.v8.code;

8

9 /**

10 * Bytecode instruction codes, as well as typecodes used as

11 * instruction modifiers.

12 */

13 public interface ByteCodes {

14

16 * Byte code instruction codes.

17 */

18 int illegal = -1;

19

20 /**

21 * Byte code instruction codes.

22 */

23 int nop = 0;

24

25 /**

26 * Byte code instruction codes.

27 */

28 int aconst_null = 1;

29

30 /**

31 * Byte code instruction codes.

32 */

33 int iconst_m1 = 2;

34

35 /**

36 * Byte code instruction codes.

37 */

38 int iconst_0 = 3;

39

40 /**

41 * Byte code instruction codes.

42 */

43 int iconst_1 = 4;

44

。。。。。。

1039

1040 /**

1041 * Virtual instruction codes; used for constant folding. 1042 */

1043 int string_add = 256;

1044

1045 /**

1046 * Virtual instruction codes; used for constant folding. 1047 */

1048 int bool_not = 257;

1049

1050 /**

1051 * Virtual instruction codes; used for constant folding.

1053 int bool_and = 258;

1054

1055 /**

1056 * Virtual instruction codes; used for constant folding.

1057 */

1058 int bool_or = 259;

1059

1060 /**

1061 * Virtual opcodes; used for shifts with long shiftcount

1062 */

1063 int ishll = 270;

1064

1065 /**

1066 * Virtual opcodes; used for shifts with long shiftcount

1067 */

1068 int lshll = 271;

上面的程序断中值得注意的是1040行之后,编码值超过255的指令,这些并不是Java 虚拟机指令机中的指令,而是为了常量折叠(constant fold)等目的的需要引入的虚拟指令。

在GJC中,从字节码的助记符到字节码名字的映射,是由Code/Code.java中的Mneumonics类定义。

1105 private static class Mneumonics {

1106

1107 private Mneumonics() {

1108 super();

1109 }

1110 private static final String[] mnem = new String[ByteCodeCount]; 1111 static {

1112 mnem[nop] = "nop";

1113 mnem[aconst_null] = "aconst_null";

1114 mnem[iconst_m1] = "iconst_m1";

1115 mnem[iconst_0] = "iconst_0";

1116 mnem[iconst_1] = "iconst_1";

1117 mnem[iconst_2] = "iconst_2";

1118 mnem[iconst_3] = "iconst_3";

1119 mnem[iconst_4] = "iconst_4";

1120 mnem[iconst_5] = "iconst_5";

1121 mnem[lconst_0] = "lconst_0";

1122 mnem[lconst_1] = "lconst_1";

1123mnem[fconst_0] = "fconst_0";

。。。。。。

1305 mnem[instanceof_] = "instanceof_";

1306 mnem[monitorenter] = "monitorenter";

1307 mnem[monitorexit] = "monitorexit";

1308 mnem[wide] = "wide";

1309 mnem[multianewarray] = "multianewarray";

1310 mnem[if_acmp_null] = "if_acmp_null";

1311 mnem[if_acmp_nonnull] = "if_acmp_nonnull";

1312 mnem[goto_w] = "goto_w";

1313 mnem[jsr_w] = "jsr_w";

1314 mnem[breakpoint] = "breakpoint";

1315 }

1316 }

9.2 生成代码的管理

在GJC中生成代码的管理是在Code/Code.java中完成的,以下代码如无特殊说明均在Code/Code.java中。新生成的代码存放在代码缓冲区中,该缓冲区是Byte类型的一维数组,见34行。该数组的初始长度为64。当前的指令指针cp 被声明为整型变量,它是缓冲区的索引,见39行。

18 public class Code implements ByteCodes, TypeTags {

19 public static final boolean debugCode = false;

20

21 /**

22 * The maximum stack size.

23 */

24 public int max_stack = 0;

25

26 /**

27 * The maximum number of local variable slots.

28 */

29 public int max_locals = 0;

30

31 /**

32 * The code buffer.

33 */

34 public byte[] code = new byte[64];

35

36 /**

37 * the current code pointer.

38 */

39 public int cp = 0;

40

9.3 指令的发射

下面程序是发射一个字节代码的方法emit1和两个字节代码的方法emit2。emit1方法发射一个字节的代码只需要在当前指令指针处记录下要发射的字节,并且将指令指针向前移动一个字节,见307行。需要注意的是,由于最初申请的指令缓冲区的长度只有64字节,当这个长度不足时,需要增大指令缓冲区的大小。GJC的做法是每次增加一倍,并且要把原来指令缓冲区中已经生成的代码复制到新的指令缓冲区中区,见Code/Code.java,302-306行。emit2发射两个字节的代码,也是类似的,同样也需要注意指令缓冲区的长度问题,如果指令缓冲区已满,则调用有自动扩容功能的emit1()方法,分两次进行发射,见Code/Code.java,315-322。

297 /**

298 * Emit a byte of code.

299 */

300 public void emit1(int od) {

301 if (alive) {

302 if (cp == code.length) {

303 byte[] newcode = new byte[cp * 2];

304 System.arraycopy(code, 0, newcode, 0, cp);

305 code = newcode;

306 }

307 code[cp++] = (byte) od;

308 }

309 }

310

311 /**

312 * Emit two bytes of code.

313 */

314 public void emit2(int od) {

315 if (alive) {

316 if (cp + 2 > code.length) {

317 emit1(od >> 8);

318 emit1(od);

319 } else {

320 code[cp++] = (byte)(od >> 8);

321 code[cp++] = (byte) od;

322 }

323 }

324 }

在emitop方法中,首先解决没有确定的跳转,349-350行。调用emit1()来发射一个字节的操作符,见356行。之后进行必要的检查,使生成的代码满足Java虚拟机规范的要求。这些检查要保证操作数栈的长度为正数,并记录操作数栈的最大长度,见361-366行。对于某些指令,要求执行完毕后操作数栈的长度为0,见Code/Code.java,357-361行。

345 /**

346 * Emit an opcode, adjust stacksize by sdiff.

347 */

348 public void emitop(int op, int sdiff) {

349 if (pendingJumps != null)

350 resolvePending();

351 if (alive) {

352 if (pendingStatPos != Position.NOPOS)

353 markStatBegin();

354 if (debugCode)

355 System.err.println(cp + ":" + stacksize + ": " + mnem(op)); 356 emit1(op);

357 if (sdiff <= -1000) {

358 stacksize = stacksize + sdiff + 1000;

359 alive = false;

360 assert stacksize == 0;

361 } else {

362 stacksize = stacksize + sdiff;

363 assert stacksize >= 0;

364 if (stacksize > max_stack)

365 max_stack = stacksize;

366 }

367 }

368 }

369

上面程序中357-358行的-1000和1000两个数字让我们感到奇怪,它们在GJC中是做什么用途呢?从下面stackdiff数组的定义,1068-1087行,可以看到只有返回指令和异常抛出指令的diff值被定位在小于-1000的值。实际上,上面的程序只检查这些指令执行之后操作数栈的高度是否为零(见357行),这是Java虚拟机规范的要求。

1068 stackdiff[ireturn] = -1001;

1069 stackdiff[lreturn] = -1002;

1070 stackdiff[freturn] = -1001;

1071 stackdiff[dreturn] = -1002;

1072 stackdiff[areturn] = -1001;

1073stackdiff[return_] = -1000;

。。。。。。

1087stackdiff[athrow] = -1001;

下面方法emitop1发射操作符和其后1字节的操作数,它依次调用emitop(), emit1()来完成。类似的方法还有,emitop2(),emitop4()分别发射操作符和其后2个或者4个字节的操作数。

377 /**

378 * Emit an opcode with a one-byte operand field.

379 */

380 public void emitop1(int op, int od) {

381 emitop(op);

382 emit1(od);

383 }

384

跳转指令的发射比较复杂,GJC实现的过程中也用到好多技巧。造成跳转指令发射比其它指令复杂的原因是,在发射跳转指令时跳转的地址还没有确定,需要记录下当前跳转指令的位置,在适当的时候进行回填。Java虚拟机规范中定义的Java虚拟机指令集支持长跳转和短跳转,指令中偏移量分别为4个字节和2个字节。

Fatcode这个布尔变量是长跳转模式和短跳转模式的开关。当采用短跳转模式的时候,调用emitop2()发射跳转指令,并且设定操作符后面作为偏移量的2字节操作数值为0。并且把当前指令指针的值减3,作为跳转指令的本身地址,这是因为短跳转指令占用3个字节的空间。见569-572行。如果采用长跳转模式,见561-569行。对于goto_和jsr指令直接发射相应的长跳转指令goto_w和jsr_w。对于其余的条件转移指令由于没有对应的长跳转指令,要借助goto_w指令。首先发射一条相反的条件跳转指令,然后紧随其后发射一条长跳转指令goto_w。为了说清楚这个问题,下面试举一例说明。

对于下面的伪代码,其语义是如果大于的条件成立跳转到L1(长跳转)。

if_icmpgt L1

经过转化后生成如下的伪代码,如果小于等于的条件成立,跳转到L2,否则(大于的条件成立)滑落到下一条指令goto_w,跳转到L1。

if_icmple L2

goto_w L1

L2:

注意,下面程序565行中偏移量的值为8,这正是goto_w后面紧跟的一条指令的首地址。长跳转指令的偏移量在指令发射的时候还没有决定,所以要把它的首地址返回,适当的时候进行回填,因为长跳转指令占用5个字节所以返回cp-5,见Code/Code.java,568行。

556 /**

557 * Emit a jump instruction.

558 * Return code pointer of instruction to be patched.

559 */

560 public int emitJump(int opcode) {

561 if (fatcode) {

562 if (opcode == goto_ || opcode == jsr) {

563 emitop4(opcode + goto_w - goto_, 0);

564 } else {

565 emitop2(negate(opcode), 8);

566 emitop4(goto_w, 0);

567 }

568 return cp - 5;

569 } else {

570 emitop2(opcode, 0);

571 return cp - 3;

572 }

573 }

上面程序中的negate()(565行)方法的实现也很有技巧,它的源代码见下面程序中的547-554行。它完成从一个条件转移指令到与其相反的条件转移指令的映射。查阅Java虚拟机规范我们可以看到这些条件转移指令的编码是有一定规律,它满足两个条件: 相对应的一对指令的编码是相邻的,如ifeq和ifne的编码是153和154。

较小的指令编码是奇数,例如153。

只有一个例外就是ifnull和ifnonnull编码分别是198和199,不满足第二个条件。

153 (0x99)..................................... ifeq

154 (0x9a).................................... ifne

155 (0x9b)...................................... iflt

156 (0x9c)..................................... ifge

157 (0x9d)..................................... ifgt

158 (0x9e)...................................... ifle

159 (0x9f) ........................... if_icmpeq

160 (0xa0)........................... if_icmpne

161 (0xa1)............................ if_icmplt

162 (0xa2)........................... if_icmpge

163 (0xa3)........................... if_icmpgt

164 (0xa4)............................ if_icmple

165 (0xa5).......................... if_acmpeq

166 (0xa6).......................... if_acmpne

198 (0xc6) ................................. ifnull

199 (0xc7) ........................... ifnonnull

对于满足上面两个条件的一对指令,可以通过((opcode + 1) ^ 1)-1来求得对应的指令的编码,其中“^”表示异或操作。例如,((153+1)^1)-1=154,((154+1)^1)-1=153。有了这些说明就不难看懂Code/Code.java,547-554行negate()方法的定义了。

544 /**

545 * Negate a branch opcode.

546 */

547 public static int negate(int opcode) {

548 if (opcode == if_acmp_null)

549 return if_acmp_nonnull;

550 else if (opcode == if_acmp_nonnull)

551 return if_acmp_null;

552 else return ((opcode + 1) ^ 1)

553 - 1;

554 }

555

9.4 存储管理

在GJC中存储管理相对简单,对局部变量在局部变量区分配空间。Nextreg中保存的是当前可用的局部变量槽(local variable slot)的地址,每次只需返回这个地址,作为为下一个局部变量分配的空间,并根据当前数据的类型计算出下一个可用空间值,继续保存在nextreg中。见Code/Code.java,853-858行。

849 /**

850 * Create a new local variable address and return it.

851 */

852 public int newLocal(int typecode) {

853 int reg = nextreg;

854 int w = width(typecode);

855 nextreg = reg + w;

856 if (nextreg > max_locals)

857 max_locals = nextreg;

858 return reg;

859 }

860

对于类的静态域(static field),Java虚拟机规范中规定了使用getstatic和putstatic 指令来操作。而getstatic和putstatic指令的操作数为该静态域在常量池中的索引。所以GJC 的任务就是为静态域分配常量池表项,并返回该表项的索引,这部分功能在Code/Pool.java 中实现。下面33行定义的indices是一个哈希表,用来维护常量池中域和其对应的表项的索引值的映射。值得注意的是Java虚拟机规范中定义的常量池,其中存放很多内容,域的引用(field reference)仅是其中一项内容,还包括方法的引用(method reference)等等。

GJC的Pool类中最重要的方法就是85行定义的put()方法,public int put(Object value)。它的功能就是为每一个方法或者域分配一个常量池表项。如果put()方法参数中指定的域还没有在常量池中分配表项,那么put()方法就为该域在常量池中分配一个表项,将这个表项的索引值记录下来,并返回这索引值返回,91-102行。如果put()方法参数中指定的域已经在常量池中分配了表项,那么put()方法就返回这个表项的索引值,见下面Pool.java 的103行。

1 /**

2 * @(#)Pool.java 1.1

3 03/01/23

3 *

4 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.

5 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.

6 */

7 package com.sun.tools.javac.v8.code;

8 import com.sun.tools.javac.v8.util.*;

9

10 import com.sun.tools.javac.v8.code.Symbol.*;

11

12

13 /**

14 * An internal structure that corresponds to the constant pool of a classfile.

15 */

16 public class Pool {

17 public static final int MAX_ENTRIES = 65535;

18 public static final int MAX_STRING_LENGTH = 65535;

19

20 /**

21 * Index of next constant to be entered.

22 */

23 int pp;

24

25 /**

26 * The initial pool buffer.

27 */

28 Object[] pool;

29

30 /**

31 * A hashtable containing all constants in the pool.

32 */

33 Hashtable indices;

。。。。。。

80 /**

81 * Place an object in the pool, unless it is already there.

82 * If object is a symbol also enter its owner unless the owner is a

83 * package. Return the object's index in the pool.

84 */

85 public int put(Object value) {

86 if (value instanceof MethodSymbol)

87 value = new Method((MethodSymbol) value);

88 else if (value instanceof VarSymbol)

89 value = new Variable((VarSymbol) value);

90 Integer index = (Integer) indices.get(value);

91 if (index == null) {

92 index = new Integer(pp);

93 indices.put(value, index);

94 if (pp == pool.length)

95 doublePool();

96 pool[pp++] = value;

97 if (value instanceof Long || value instanceof Double) {

98 if (pp == pool.length)

99 doublePool();

100 pool[pp++] = null;

101 }

102 }

103 return index.intValue();

104 }

105

106 /**

107 * Return the given object's index in the pool,

108 * or -1 if object is not in there.

109 */

110 public int get(Object o) {

111 Integer n = (Integer) indices.get(o);

112 return n == null ? -1 : n.intValue();

113 }

9.5 为Java语言的各种结构生成代码

GJC代码生成部分主要在Comp/Gen.java中实现。GJC的代码生成阶段还使用了一个辅助类Items,该类的实现在源代码的Comp/Items.java中。项(Item)代表一个字节码中可以寻址的实体,有许多种类的项目,例如,局部变量、静态变量、堆栈的栈顶值、this指针等等。对于每一种项,Item的派生类中有固定的方法来完成相应的功能。

9.5.1为Java方法生成代码

我们首先要看看GJC是如何为Java语言中的方法生成代码。在genMethod中,生成代码之前,首先检查方法的参数是否超过了Class File规定的方法参数数目的限制。对于非静态的方法和构造方法,要注意有一个隐含的参数要统计在内,见Comp/Gen.java,796-800行。

方法是Java语言编译的最基本的单位,这是由Java虚拟机规范所决定的。在GJC中每一个方法采用一个单独的代码缓冲区,用来保存为它生成的代码,这部分已经在本章前面“生成代码的管理”一节进行了介绍。如果方法体不空,就要为之创建一个新的代码缓冲区,见Comp/Gen.java,801-804行。对于非静态方法,有一个隐含的this指针作为这个方法的一个参数,调用code.newLocal为这个隐式参数分配存储,见Comp/Gen.java,807-810行。对于所有的显式参数,逐个检查数组的维数(如果是数组的话),并且也调用code.newLocal为其分配存储,见Comp/Gen.java,810-815行。接着调用genStat()方法,为方法中语句部分生成代码。处理完语句部分检查堆栈的高度是否为0。如果生成代码没有结束,发射return指令,这是对应隐式的返回情况,见Comp/Gen.java,817-823行。

785 /**

786 * Generate code for a method.

787 * @param tree The tree representing the method definition. 788 * @param env The environment current for the method body. 789 * @param fatcode A flag that indicates whether all jumps are within 32K.

790 * We first invoke this method under the assumption 791 * that fatcode == false, i.e. all jumps are within

32K.

792 * If this fails, fatcode is set to true and we try again.

793 */

794 void genMethod(MethodDef tree, Env env, boolean fatcode) {

795 MethodSymbol meth = tree.sym;

796 if (Code.width(env.enclMethod.sym.type.erasure().argtypes()) + 797 (((tree.flags & STATIC) == 0 || meth.isConstructor()) ? 1 : 0) >

798 ClassFile.MAX_PARAMETERS) {

799 log.error(tree.pos, "limit.parameters");

800 nerrs++;

801 } else if (tree.body != null) {

802 meth.code = code = new Code(fatcode, lineDebugInfo, varDebugInfo,

803 genCrt ? new CRTable(tree,

env.toplevel.endPositions) : null);

804 items = new Items(pool, code, syms);

805 if (Code.debugCode)

806 System.err.println(meth);

807 if ((tree.flags & STATIC) == 0)

808 code.setDefined( code.newLocal(

809 new VarSymbol(FINAL, names._this,

meth.owner.type,

810 meth.owner)));

811 for (List l = tree.params; l.nonEmpty(); l = l.tail) {

812 checkDimension(((Tree.VarDef) l.head).pos,

813 ((Tree.VarDef) l.head).sym.type);

814 code.setDefined(code.newLocal(((Tree.VarDef)

l.head).sym));

815 }

816 int startpcCrt = genCrt ? code.curPc() : 0;

817 genStat(tree.body, env);

818 assert code.stacksize == 0;

819 if (code.isAlive()) {

820 code.statBegin(TreeInfo.endPos(tree.body));

821 if (env.enclMethod == null ||

822 env.enclMethod.sym.type.restype().tag == VOID) { 823 code.emitop(return_);

824 } else {

825 int startpc = code.entryPoint();

826 CondItem c = items.makeCondItem(goto_);

827 code.resolve(c.jumpTrue(), startpc);

828 }

829 }

830 if (genCrt) {

831 code.crt.put(tree.body, CRT_BLOCK, startpcCrt,

code.curPc());

832 }

833 code.endScopes(0);

834 if (code.checkLimits(tree.pos, log)) {

835 nerrs++;

836 return;

837 }

838 if (!fatcode && code.fatcode)

839 genMethod(tree, env, true);

840 }

841 }

842

9.5.2为方法的调用生成代码

GJC调用callMethod()方法为Java语言中方法的调用生成代码,对于静态方法和非静态方法分别创建Static Item和Member Item,之后分别调用invoke()方法,为方法的调用生成代码。

273 /**

274 * Generate code to call a non-private method or constructor.

275 * @param pos Position to be used for error reporting.

276 * @param site The type of which the method is a member. 277 * @param name The method's name.

278 * @param argtypes The method's argument types.

279 * @param isStatic A flag that indicates whether we call a

280 * static or instance method.

281 */

282 void callMethod(int pos, Type site, Name name, List argtypes, boolean isStatic) {

283 Symbol msym = rs.resolveInternalMethod(pos, attrEnv, site, name, argtypes);

284 if (isStatic)

285 items.makeStaticItem(msym).invoke();

286 else

287 items.makeMemberItem(msym, name == names.init).invoke();

288 }

289

StaticItem的invoke()方法在Comp/Items.java中定义。它首先计算调用前后操作数堆栈高度的差异,482-485行。发射invokestatic指令,486行。接着把方法放入常量池(参见参考文献[5]“JAVA虚拟机规范”中字节码文件格式的介绍)中,把索引值发射到字节码指令中,487行。

481 Item invoke() {

482 MethodType mtype = (MethodType) member.erasure();

483 int argsize = Code.width(mtype.argtypes);

484 int rescode = Code.typecode(mtype.restype);

485 int sdiff = Code.width(rescode) - argsize;

486 code.emitop(invokestatic, sdiff);

487 code.emit2(pool.put(member));

488 return stackItem[rescode];

489 }

MemberItem的invoke()方法也在Comp/Items.java中定义。与StaticItem的invoke()方法非常类似。这里不再详细介绍了,有兴趣的读者可以阅读下面的代码。

529 Item invoke() {

530 MethodType mtype = (MethodType) member.externalType();

531 int argsize = Code.width(mtype.argtypes);

532 int rescode = Code.typecode(mtype.restype);

533 int sdiff = Code.width(rescode) - argsize;

534 if ((member.owner.flags() & Flags.INTERFACE) != 0) {

535 code.emitop(invokeinterface, sdiff - 1);

536 code.emit2(pool.put(member));

537 code.emit1(argsize + 1);

538 code.emit1(0);

539 } else if (nonvirtual) {

540 code.emitop(invokespecial, sdiff - 1);

541 code.emit2(pool.put(member));

542 } else {

543 code.emitop(invokevirtual, sdiff - 1);

544 code.emit2(pool.put(member));

545 }

546 return stackItem[rescode];

547 }

548

9.5.3为循环结构生成代码

Java语言支持三种循环结构,分别是do循环,while循环和for 循环。其中while循环和for 循环是先判断条件是否成立,只有条件成立才执行循环体的控制结构。Do循环,与它们相反。在GJC中对于三种不同的循环结构都调用genLoop()方法来生成代码,其中最后一个布尔型的参数指定当前循环结构是属于先判断后执行,还是相反的,我们不妨称之为先验循环和后验循环。对于for循环,要先为循环初始化部分生成代码,并且允许循环内部定义局部变量。见Comp/Gen.java,867-880行。

867 public void visitDoLoop(DoLoop tree) {

868 genLoop(tree, tree.body, tree.cond, Tree.emptyList, false);

869 }

870

871 public void visitWhileLoop(WhileLoop tree) {

872 genLoop(tree, tree.body, tree.cond, Tree.emptyList, true);

873 }

874

875 public void visitForLoop(ForLoop tree) {

876 int limit = code.nextreg;

877 genStats(tree.init, env);

878 genLoop(tree, tree.body, tree.cond, tree.step, true);

879 code.endScopes(limit);

880 }

881

genLoop()方法大体上分为两部分,前一部分处理先验循环(Comp/Gen.java,896-909行),后一部分处理后验循环(Comp/Gen.java,911-923行)。无论先验循环还是后验循环,GJC都纪录循环开始处代码的位置,并且保存在startpc中,见894行。

对于先验循环,第一部分应该是判断循环是否执行的条件对应的代码,GJC调用genCond ()方法,为循环条件生成代码,见899行。如果条件不成立,应该直接跳出循环,但是循环结束的位置目前还不知道,也就是说需要跳转到何处现在还不知道,所以需要记录下当前条件不成立的跳转语句的位置,在后面适当的时候,用循环结束的位置回填这条跳转语句的偏移量部分。这个位置记录为loopDone,见903行。

对于条件成立的分支,应该执行循环体的代码,下面的部分就为循环体生成代码,所以使用下一条指令的地址,也就是循环体中第一条指令的地址,来回填条件成立的条件跳转语句的偏移量部分。见904行。

接着调用genStat()方法为循环体生成代码,见905行。调用genStat()方法为步长增量部分生成代码,见907行。生成一条跳转到循环最开始处的指令,见908行。这时,循环的代码已经全部生成了,下面的位置就是循环后面的代码了,所以现在可以确定loopDone的位置了,见909行。

对于后验循环,首先为循环体生成代码,然后为步长增量部分生成代码,最后为循环条件部分的代码,Comp/Gen.java,911-920行。如果条件成立则跳转到循环最开始位置执行下一遍循环,如果条件不成立跳出循环,下面的位置就是循环后面部分的代码,Comp/Gen.java,921-922行。

882 /**

883 * Generate code for a loop.

884 * @param loop The tree representing the loop.

885 * @param body The loop's body.

886 * @param cond The loop's controling condition.

887 * @param step "Step" statements to be inserted at end of 888 * each iteration.

889 * @param testFirst True if the loop test belongs before the body. 890 */

891 private void genLoop(Tree loop, Tree body, Tree cond, List step,

892 boolean testFirst) {

893 Env loopEnv = env.dup(loop, new GenContext());

894 int startpc = code.entryPoint();

895 if (testFirst) {

896 CondItem c;

897 if (cond != null) {

898 code.statBegin(cond.pos);

899 c = genCond(TreeInfo.skipParens(cond),

CRT_FLOW_CONTROLLER);

900 } else {

901 c = items.makeCondItem(goto_);

902 }

903 Chain loopDone = c.jumpFalse();

904 code.resolve(c.trueJumps);

905 genStat(body, loopEnv, CRT_STATEMENT | CRT_FLOW_TARGET); 906 code.resolve(((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).cont);

907 genStats(step, loopEnv);

908 code.resolve(code.branch(goto_), startpc);

909 code.resolve(loopDone);

910 } else {

911 genStat(body, loopEnv, CRT_STATEMENT | CRT_FLOW_TARGET); 912 code.resolve(((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).cont);

913 genStats(step, loopEnv);

914 CondItem c;

915 if (cond != null) {

916 code.statBegin(cond.pos);

917 c = genCond(TreeInfo.skipParens(cond),

CRT_FLOW_CONTROLLER);

918 } else {

919 c = items.makeCondItem(goto_);

920 }

921 code.resolve(c.jumpTrue(), startpc);

922 code.resolve(c.falseJumps);

923 }

924 code.resolve(((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).exit);

925 }

926

注意:906,912,924行回填的是,循环中如果有continue或者break语句时,应该跳转到的位置。

9.5.4为条件语句生成代码

visitIf是为条件语句生成代码。首先为条件部分生成代码,见1452行。记录下跳转到else部分的跳转指令为elseChain,该指令的偏移量部分现在还不能确定,将来适当的时候进行回填,见1453行。如果条件部分求值为真,为then部分生成代码,首先回填条件为真的转移语句的偏移量,使其跳转到then部分的最开始处,见1455行。1456行为then部分的语句生成代码。下面是生成跳转到条件语句结束位置的代码,由于当前跳转语句偏移量部分无法确定,因此将代码记录为thenExit,将来适当的时候进行回填,见1457行。

接下来为else部分生成代码,这时else部分的第一条指令地址已经确定,回填elseChain,见1459-1463行。最后全部条件语句的代码生成完毕,条件语句结束的位置已经确定,回填thenExit,见1464行。

1449 public void visitIf(If tree) {

1450 int limit = code.nextreg;

1451 Chain thenExit = null;

1452 CondItem c = genCond(TreeInfo.skipParens(tree.cond),

CRT_FLOW_CONTROLLER);

1453 Chain elseChain = c.jumpFalse();

1454 if (!c.isFalse()) {

1455 code.resolve(c.trueJumps);

1456 genStat(tree.thenpart, env, CRT_STATEMENT | CRT_FLOW_TARGET); 1457 thenExit = code.branch(goto_);

1458 }

1459 if (elseChain != null) {

1460 code.resolve(elseChain);

1461 if (tree.elsepart != null)

1462 genStat(tree.elsepart, env, CRT_STATEMENT |

CRT_FLOW_TARGET);

1463 }

1464 code.resolve(thenExit);

1465 code.endScopes(limit);

1466 }

1467

下面程序中的visitThrow为异常抛出部分生成代码。异常的抛出相对简单,只需要构造一个抛出的对象,并把这个对象的引用装载到操作数栈的栈顶,见1511行。然后发射athrow 指令即可。

1510 public void visitThrow(Throw tree) {

1511 genExpr(tree.expr, tree.expr.type).load();

1512 code.emitop(athrow);

1513 }

1514

9.5.5为异常捕获部分生成代码

为Java语言异常捕获部分生成代码是代码生成中最复杂的部分,生成的代码必须严格满足Java语言规范中try、catch和finally语句的语义。Java虚拟机规范中使用jsr指令为异常捕获部分生成代码。这方面的基本知识,请查看本书第8章中Java虚拟机指令集介绍或者参阅Java虚拟机规范。

GJC中在代码生成阶段使用visitTry()来访问抽象语法树,visitTry()又调用genTry()为异常捕获部分生成代码,见Code/Gen.java,1144行。visitTry()方法的另一个作用就是定义了一个匿名的内部类(annonymous inner class),并且创建了一个该类的对象,见Code/Gen.java,1109行。这个类有两个重要的成员方法gen()和genLast()。

Gen()方法(我们只关心使用jsr指令的情况),首先生成一条jsr跳转指令,跳转到

finally的代码部分,跳转的位置在目前还不能确定,首先记录下来,在将来适当的时候回填,并且记录下gap的首地址,见Code/Gen.java,1112-1125行。

GenLast()方法为finally部分生成代码,见Code/Gen.java,1134-1137行。

1104 public void visitTry(final Try tree) {

1105 final Env tryEnv = env.dup(tree, new GenContext());

1106 final Env oldEnv = env;

1107 final boolean useJsrLocally = jsrlimit <= 0 || jsrlimit < 100 && 1108 estimateCodeComplexity(tree.finalizer) > jsrlimit;

1109 ((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).finalize = new GenFinalizer() { 1110

1111

1112 void gen() {

1113 if (useJsrLocally) {

1114 if (tree.finalizer != null) {

1115 ((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).cont = 1116 new Chain(code.emitJump(jsr), 1117 code.stacksize + 1,

1118 ((Gen.GenContext)

https://www.doczj.com/doc/972660967.html,).cont,

1119 varDebugInfo ?

code.defined.dup() : null);

1120 }

1121 assert ((Gen.GenContext)

https://www.doczj.com/doc/972660967.html,).gaps.length()

1122 % 2 == 0;

1123 ((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).gaps.append( 1124 new Integer(code.curPc()));

1125 } else {

1126 assert ((Gen.GenContext)

https://www.doczj.com/doc/972660967.html,).gaps.length()

1127 % 2 == 0;

1128 ((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).gaps.append( 1129 new Integer(code.curPc()));

1130 genLast();

1131 }

1132 }

1133

1134 void genLast() {

1135 if (tree.finalizer != null)

1136 genStat(tree.finalizer, oldEnv, CRT_BLOCK); 1137 }

1138

1139 boolean hasFinalizer() {

1140 return tree.finalizer != null;

1141 }

1142 };

1143 ((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).gaps = new ListBuffer();

1144 genTry(tree.body, tree.catchers, tryEnv);

1145 }

1146

GJC中调用genTry()为异常捕获部分生成代码,首先记录当前代码缓冲区的指针作为try block的初始位置,1115行。接着为try block生成代码,并且纪录这时的代码缓冲区指针作为try block的结束位置,1157-1158行。

如果try block为空,其中没有代码,GJC直接调用genLast为finally部分生成代码, 见1161-1166行。否则调用genFinallizer(env),通过查看源代码,这也就是调用上面匿名内部类中的gen()方法,来生成一条跳转到finally部分代码的jsr指令, 见1170行。接着发射一条跳转goto指令,跳转到异常捕获代码结束后的指令。发射的jsr和goto指令连接起来的语义是,如果try block中没有发生异常,首先跳转到finally部分,因为是jsr跳转,当前地址被纪录在操作数栈中,可以用来从finally部分返回。返回后完成了所有异常捕获的操作,再跳过所有异常捕获部分的代码。

对于每个catch结构,调用genCatch()生成捕获某种类型的异常后的处理代码,见1177行。其后也分别发射一条jsr指令(见1178行)(跳转到finally部分),和发射一条goto 指令(见1181行),跳过所有异常捕获部分的代码。

接着genTry()方法为未被捕获的异常生成代码。纪录当前代码缓冲区的指针为所有未被捕获异常的处理代码的指针,1187行。首先发射指令,保存抛出的异常对象的指针,1200行。发射jsr跳转到finally部分,1201行。从finally部分返回后,装载先前保存的指针到操作数栈栈顶,1202行。再次向上层抛出异常,1206行。

最后,为finally部分生成代码。首先指定让先前发射的所有的jsr指令跳转到这里,1209行。把jsr指令保存在操作数栈栈顶的返回地址,保存到局部变量区,1212-1213行。调用genLast()方法,为finally部分生成代码,1214行。发射返回指令1215行。

1147 /**

1148 * Generate code for a try or synchronized statement

1149 * @param body The body of the try or synchronized statement. 1150 * @param catchers The lis of catch clauses.

1151 * @param env the environment current for the body.

1152 */

1153 void genTry(Tree body, List catchers, Env env) {

1154 int limit = code.nextreg;

1155 int startpc = code.curPc();

1156 Bits definedTry = varDebugInfo ? code.defined.dup() : null;

1157 genStat(body, env, CRT_BLOCK);

1158 int endpc = code.curPc();

1159 boolean hasFinalizer = ((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).finalize != null &&

1160 ((Gen.GenContext) https://www.doczj.com/doc/972660967.html,).finalize.hasFinalizer();

1161 if (startpc == endpc) {

1162 if (hasFinalizer) {

1163 code.statBegin(TreeInfo.finalizerPos(env.tree));

相关主题
文本预览
相关文档 最新文档