# A.2 子例程

除了上一节给出的字节代码指令,版本号低于或等于 V1_5 的类还可以包含 JSR 和 RET 指令,用于子例程(JSR 表示 Jump to SubRoutine,即跳转至子例程,RET 表示 RETurn from subroutine,,即从子例程返回)。版本高于或等于 V1_6 的类不能包含这些指令(移除它们就是为了 Java 6 中引入的新验证器体系结构;因为它们不是严格必需的,所以才可能删除它们)。

JSR 指令以一个标记为参数,无条件跳转到这个标记。但在跳转之前,它会在操作数栈中压入一个返回地址,它是紧跟在 JSR 之后的指令的索引。这个返回地址只能由诸如 POP、DUP 或SWAP 之类的栈指令、ASTORE 指令和 RET 指令处理。

RET 指令以一个局部变量索引为参数。它加载包含在这个槽中的返回地址,并无条件跳转至相应的指令。由于返回地址可以有几个可能值,所以 RET 指令可以返回到几个可能指令。 让我们用一个例子来说明。考虑以下代码:

JSR sub JSR sub RETURN
sub:
ASTORE 1
IINC 0 1
RET 1
1
2
3
4
5

第一条指令将第二条指令的索引作为返回地址压入栈中,并跳转到 ASTORE 指令。这个指令将返回地址存储局部变量 1 中。然后,局部变量 0 增 1。最后,RET 指令载入在局部变量 1 中包含的返回地址,并跳转到相应的指令,即第二条指令。

这个第二指令又是一个 JSR 指令:它将第三条指令的索引作为返回地址压入栈中,并跳转到 ASTORE 指令。当再次到达 RET 指令时,返回地址现在对应于 RETURN 指令,所以执行过程跳转到这个 RETURN,并停止。

sub 标记之后的指令定义了一个所谓的子例程。它有点像“方法”,可以从一个正常方法的不同地方“调用”。在 Java 6 之前,子例程用于编译 Java 中的 finally 块。但事实上,子例程并非必不可少的:实际上,有可能用相应子例程的主体来代替每个 JSR 指令。这种内联生成了重复代码,但删除了 JSR 和 RET 指令。对于上面的例子,结果非常简单:

IINC 0 1
IINC 0 1 RETURN
1
2

ASM 在 org.objectweb.asm.commons 包中提供了一个 JSRInlinerAdapter 类,它可以自动执行这一转换。可以用它来删除 JSR 和 RET 指令,以简化代码分析,或者将类从 1.5 或更低版本转换为 1.6 或更高版本。