为一元非表达式生成JVM字节码

9
假设您正在编写Java(或Java子集)编译器,并且想要为一元非运算符!E生成字节码。您已经完成了类型检查,因此知道E的类型为boolean,即它将在操作数栈上推送10
其中一种方法是使用以下类似的代码(以Jasmin语法表示):
E
ifeq truelabel
iconst_0
goto stoplabel
truelabel:
iconst_1
stoplabel:

即,如果栈上有0,则推入1,否则推入0。另一种方法是利用一个布尔值只是一个带有值为1或0的int类型,可以使用!E =(E + 1)% 2 来生成。

E
iconst_1
iadd
iconst_2
irem

有没有一种方法比另一种更有优势呢?还是完全有其他方法可行?
3个回答

5

我曾尝试编写Java反编译器,因此我了解javac生成的代码。据我回忆,javac 1.0.x使用!E = E ? false : true,而javac 1.1使用!E = E ^ 1(按位异或)。


我之前没有考虑过^ 1,这绝对比第二个更好。你知道为什么从前者切换到后者的原因吗?或者为什么一开始没有使用^ 1呢? - Ismail Badawi
实际上,我刚刚尝试了javac 1.6.0_26,它生成了第一个(除了ifne而不是ifeq),所以我猜他们又换回来了。我想我仍然在想其中一个与另一个的好处。 - Ismail Badawi
1
这只是一个猜测,但也许他们将这些微小的优化移动到JIT中,这可能是它们所属的地方。 - Neil

4
我不会指望以下定义在字节码级别上保持真实的情况。
true == 1

在二进制层面上(几乎与语言无关),布尔值通常被定义为:
false == 0
true != 0

javac编译器显然也遵循这个定义(我看到的所有javac字节码检查都只检查零,从不检查一)。

对于布尔值而言,使用这个定义是有意义的,而不仅仅将1视为真。C语言也是这样定义的(true只是!= 0,而不是简单的1),在汇编代码中也常常使用这种约定。所以,Java也采用了这个定义,使得可以将Java布尔值传递给其他代码而无需进行任何特殊转换。

我怀疑你的第一个代码示例(带有ifeq的那个)是实现布尔值非运算符的唯一正确方式。^1方法(与1异或)将在布尔值不严格表示为0/1时失败。任何其他int值都会导致表达式工作不正确。


JVM 规范表示:“Java虚拟机使用1来表示true,使用0来表示false以编码布尔数组组件。如果Java编程语言的布尔值被编译器映射为Java虚拟机类型int的值,则编译器必须使用相同的编码方式。” http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.3.4 - Ismail Badawi
@isbadawi 这描述了JVM如何编码布尔数组以及Java编译器必须将布尔值编码为整数(如果它们真的这样做的话)。这与问题并不直接相关。 - user207421
@EJP,对我来说这似乎有点模糊。第一句话是关于布尔数组的,但第二句话是关于布尔值的。不过我可能是错的。 - Ismail Badawi
@isdabawi,我觉得JVM规范中的措辞非常令人困惑。首先,它对Oracle VM如何将boolean[]实现为byte[]做出了评论(但我认为这个评论不是规范的一部分)。然后,最后两句话谈到了java编译器在将boolean[]实现为int[]时应该做什么 - 这并没有真正说明VM必须如何实现boolean。事实上,我没有看到任何规定VM必须以某种方式实现它,它只给出了一些约束条件,说明它将如何解释编译器可能为boolean[]生成的某些结构。 - Durandal
对我来说,这并不令人困惑。句子“Java虚拟机使用1表示true和0表示false编码布尔数组组件”明确地涉及JVM。因此,它意味着即使特定的实现在幕后使用字节数组,也不能显示出来,即从布尔数组中读取必须产生零或一,没有其他东西。有趣的是,我最近尝试的版本并没有实现“非零即真”的规则,而是在使用除零或一以外的int值作为布尔值时截断除最低位以外的所有位。 - Holger

0

我听说模数运算可能非常慢。我没有来源,但考虑到加法比除法简单得多,这是有道理的。然而,如果程序计数器跳来跳去太多,那么if/else方法可能不太适用。

话虽如此,我认为尼尔的E ^ 1是最快的,但这只是一种直觉。你只需要通过一个逻辑电路传递数字,就完成了!只需要一个操作而不是一堆操作。


^1 明显比 div/rem 更少的计算强度,条件分支也有一定的代价(因为它们可能会导致大多数当前 CPU 架构中的分支错误预测惩罚)。更重要的问题是是否可以假设用于实现布尔值的 int 严格限制为值 0/1。 - Durandal
我相信Java,无论如何,如果你不知道的话,你可以只做(E & 1) ^ 1 - Ryan Amos
(&1) ^ 1 将0x2转换为0x1(例如)。true == !true并不是人们真正期望的结果。我不知道有任何简单的表达式可以使int的所有32位的逻辑OR折叠成一个单独的位。布尔值要么严格受限,要么不能使用^方法。 - Durandal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接