【问题标题】:Generating JVM bytecode for a unary not expression为一元非表达式生成 JVM 字节码
【发布时间】:2012-10-14 21:18:45
【问题描述】:

假设您正在编写一个 Java(或 Java 子集)编译器,并且您想为一元非表达式 !E 生成字节码。您已经通过了类型检查,所以您知道 E 的类型为 boolean,也就是说,它会将 10 推入操作数堆栈。

一种方法是(在 Jasmin 语法中):

E
ifeq truelabel
iconst_0
goto stoplabel
truelabel:
iconst_1
stoplabel:

即如果堆栈上有 0 则推 1,否则推 0。另一种方法是利用 boolean 只是一个 int 的值 10 的事实,就是说 @ 987654331@并生成

E
iconst_1
iadd
iconst_2
irem

使用一个比另一个有优势吗?还是完全不同的东西?

【问题讨论】:

    标签: java compiler-construction jvm javac bytecode


    【解决方案1】:

    我曾经尝试写一个Java反编译器,所以我曾经知道javac生成了什么代码。我记得,javac 1.0.x 使用了!E = E ? false : true,而 javac 1.1 使用了!E = E ^ 1(按位异或)。

    【讨论】:

    • 没想到^ 1;这绝对比第二个好。你知道从前者切换到后者背后的基本原理,或者为什么一开始没有使用^ 1
    • 实际上我只是尝试使用javac 1.6.0_26,它生成了第一个(除了ifne 而不是ifeq),所以我猜他们切换回来了。我想我仍然想知道一个与另一个的好处。
    • 这只是一个疯狂的猜测,但也许他们将这些微优化转移到 JIT 中,这可能是他们所属的地方。
    【解决方案2】:

    我不会指望以下定义在字节码级别成立。

    true == 1
    

    在二进制级别(几乎与语言无关),布尔值通常定义为

    false == 0
    true != 0
    

    javac 编译器显然也遵循这个定义(我见过的 javac 字节码中的所有检查总是检查零,从不检查 ONE)。

    将这个定义用于布尔值而不是仅将 1 视为 true 是有意义的,C 也以这种方式定义它(true 只是 != 0,而不仅仅是 1),并且在汇编代码中这个约定也很常用。所以java也使用这个定义使得可以在没有任何特殊转换的情况下将java布尔值传递给其他代码。

    我怀疑您的第一个代码示例(使用 ifeq)是正确实现布尔非运算符的唯一方法。如果布尔值未严格表示为 0/1,则 ^1 方法(与 1 异或)将失败。任何其他 int 值都会导致表达式无法正常工作。

    【讨论】:

    • JVM 规范说:“Java 虚拟机使用 1 表示 true 和 0 表示 false 对布尔数组组件进行编码。其中 Java 编程语言布尔值由编译器映射到 Java 虚拟机类型的值int,编译器必须使用相同的编码。” docs.oracle.com/javase/specs/jvms/se7/html/…
    • @isbadawi 这描述了 JVM 如何编码布尔值的数组,以及 Java 编译器必须如何将布尔值编码为整数何时以及如果他们这样做。 它与问题没有直接关系。
    • @EJP 这对我来说似乎有点模棱两可。第一句话是关于布尔数组,但第二句是关于布尔值。不过我可能错了。
    • @isdabawi 我发现 JVM 规范中的措辞非常混乱。首先,它对 Oracle VM确实如何将 boolean[] 实现为 byte[] 进行了评论(但我认为此评论不是规范的一部分)。然后,最后两句话讨论了 java 编译器 在将 boolean[] 实现为 int[] 时应该做什么——同样这并没有真正说明 VM 必须如何实现 boolean。事实上,我没有看到任何说 VM 必须以某种方式实现它,它只给出了它如何解释编译器可能为 boolean[] 生成的某些构造的约束。
    • 我看起来并不困惑。 “Java 虚拟机编码布尔数组组件,使用 1 表示真,0 表示假”这句话清楚地针对 JVM。因此,这意味着即使特定实现在底层使用字节数组,这不得显示出来,即从布尔数组读取必须产生零或一,仅此而已。有趣的是,我试用的最新版本没有实现“非零表示真”规则,而是在使用除零或一以外的 int 值作为布尔值时截断除最低位之外的所有位。
    【解决方案3】:

    我听说模数运算可能非常慢。我没有它的来源,但考虑到添加比分割简单得多,这是有道理的。再说一次,在这种情况下,过多地跳转程序计数器可能会产生问题,在这种情况下,if/else 方法不会很好地工作。

    话虽如此,我怀疑 Neil 的 E ^ 1 是最快的,但这只是一种预感。您所要做的就是将数字通过一个逻辑电路,您就完成了!只需一次操作,而不是少数几项。

    【讨论】:

    • ^1 的计算量绝对比 div/rem 少很多,而且条件分支的成本也有点高(因为它们会导致大多数当前 CPU 架构的分支预测错误)。更大的问题是,假设用于实现布尔值的 int 是否被严格限制为值 0/1 是否有效。
    • 我相信 Java,确实如此。无论如何,如果您不知道,您可以直接使用(E & 1) ^ 1
    • (&1) ^ 1 将 0x2 转换为 0x1(例如)。 true == !true 并不是人们所期望的。我不知道有任何简单的表达式可以将 int 的所有 32 位的逻辑 OR 折叠成一个位。要么布尔值受到严格约束,要么不能使用 ^ 方法。
    猜你喜欢
    • 2012-12-26
    • 1970-01-01
    • 2014-03-05
    • 1970-01-01
    • 2012-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多