https://www.surveymonkey.com/r/clojure2024上的2024年Clojure状态调查中分享您的想法!

欢迎!请参阅关于页面,了解更多关于如何使用本站的信息。

+52投票
Clojure
关闭

Android ART在字节码上执行编译时验证,并在使用锁宏的任何地方失败。错误类似于CLJ-1829中的(在这种情况下,clojure.core.server/stop-server调用了锁宏)

10-16 14:49:26.801 2008-2008/? E/AndroidRuntime: java.lang.VerifyError: Rejecting class clojure.core.server$stop_server because it failed compile-time verification ( declaration of 'clojure.core.server$stop_server' appears in /data/app/com.clojure_on_android-1/base.apk)

原因:从Android问题讨论(https://code.google.com/p/android/issues/detail?id=80823),这似乎是因为更严格地执行JVM规范中的“结构锁定”条款(https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10)。

locking中的monitor-enter、monitor-exit和try/finally块的混合似乎创建了ART标记为不平衡monitorenter/monitorexit字节码的路径。特别是,monitorenter和monitorexit本身可以抛出异常(在空锁定对象上)。Java字节码对这些情况进行了某些复杂的异常表处理,这(afaict)在不修改Clojure编译器的情况下不可能实现。

方法:一个可能的方法是让锁在Java的synchronized块上下文中调用传入的body。这避免了复杂的字节码问题,但性能影响未知。

补丁:clj-1472-3.patch

另见:将字节码与Java同步块进行比较,显示了多项差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

审核:由Alex Miller审核 - 我认为这是一种可行的解决方案,可以解决问题,并且由于其使用频率不高,我认为这不会是一个性能问题。我将标志我认为另一种处理方法是将locking变成一个特殊形式,并支持编译器,但不清楚这是否值得去做,所以我将留给Rich来决定。

以以下注释关闭:已修复

41 条回答

0

评论者:alexmiller

我为之前的补丁添加了一个新的clj-1472.patch,修复了我不太喜欢的一些小细节。然而,这个更改本质上还是一样的,因此我保留了原始作者的归属。

0

评论者:gshayban

alex,你上传了对的补丁吗?

0

评论者:alexmiller

啊,我混蛋了。我会修复的。

0

评论者:bronsa

Alex,你可能需要把它做成^:once fn**

0

评论者:gshayban

来自 Android
{quote}
Android不运行Java字节码,它运行Dex字节码。你的类转换出的dexdump输出很有趣。

JVMS也没有什么兴趣。Android不是一个Java虚拟机。我们遵循JLS,但不遵循JVMS(我们怎么能运行Java字节码呢)。因此,所有对此的诉求都是不相关的。我们尝试使Dex字节码方面与JVMS的精神兼容,但是如果你的源代码不是Java,那就没有保证。

现在,验证器(可能仍然是)有问题的,即使是针对我们(相当糟糕)的规范,而且我们并不非常一致。例如,在Marshmallow中,大量我们不能正确验证有关结构化锁定的代码被拒绝为VerifyError,这与JVMS的精神不符。在下一个版本中,这将得到放松,并推迟到实际运行代码时进行检查。

遗憾的是,我们无法对旧版本做出任何处理,你必须在工作时解决任何问题。:-) 当我有时间时,我会尝试查看你的类。
{quote}

似乎在Clojure中找到一种权宜之计是所有弊端中最低的。

0

评论者:alexmiller

添加了带有^:once的-2补丁。

0

评论者:alexmiller

我们目前认为这是Android的过错,所以现在将其归档。

0

评论者:gshayban

GraalVM/native-image也正在抱怨monitorenter/exit的平衡问题。我通过模仿javac的行为来修复了这个问题:[链接](https://github.com/ghadishayban/clojure/commit/8acb995853761bc48b62190fe7005b70da692510)

0

评论者:alexmiller

Ghadi,如果那是个可行的修复方案,我对那个补丁很感兴趣。

0

评论者:gshayban

如果能有人帮助我进行Android测试基础设施的工作,我将很乐意。

In javac的synchronized代码生成过程中,它安装了一个捕获异常类型为"任何"的处理程序。相应的提交捕获Exception。如果需要,可以通过调用GeneratorAdapter.visitTryCatch并传递null作为目标类来生成any处理程序(我还不确切知道那是什么意思——Throwable加未来的任何内容?)。

0

评论者:adamclements

您尝试过应用现有的clj-1472-2.patch来看它是否能修复GraalVM吗?我认为我们最初达到了一个人们对所附加补丁满意的局面(如果我没说错,Alex),但是决定JVM实现是王,所以问题是在Android不遵守这一点,而不是Clojure。如果同样的问题在其他地方再次出现,那么这个决定可能需要重新考虑(我仍希望看到这个补丁)。

现在已经是很久以前的事情了,但我想起曾尝试了所有我能想到的不同组合的 clojure try/catch/finally/throw 函数,但无法使生成了的字节码符合规格要求,以便同步,而不会修改 clojure 代码生成部分的实现,这个部分的实现太过底层,并不实用 - 因此,上述补丁依赖于 javac 来生成与同步相关的字节码,而不是使用 monitor-enter 和 monitor-exit。我对你的链接实现在某些情况下可能仍然会生成错误的字节码感到担忧,就像现有实现那样,尽管它看起来完美无缺,表面上看起来也是正确的。

0

评论者:gshayban

我没有尝试过,但我相信从可行性的角度来看,clj-1472-2.patch 会奏效。它有一个性能劣势:它可能会造成 JVM 的“性能数据污染”,并混淆锁优化。(大多数锁是无争用的)

我更愿意修改输出,使其更加类似于 javac,如果这适用于 Android 的话。

0

评论者:eraserhd

我已经尝试了 Ghadi 链接到的差异,但存在一些问题(包括丢弃已锁表达式的值)。我在修复了之后,但经过研究,我意识到以下问题:
1. 这并不能涵盖由 monitor-exit 本身抛出的异常,这似乎是必需的。我们不能在 catch 中递归,也不能捕获 '任何',所以仅仅使用 Clojure 构造函数是无法生成这种字节码的。
Clojure 的 finally 产生的字节码包括一个存储和一个通过异常处理未被覆盖的加载,所以我们不能仅仅在 monitor-exit 代码中添加异常处理。我相当确信我们无法删除这个存储和加载。

因此,除非我漏掉了什么,否则唯一通过结构锁检查的方法是使锁定成为特殊的格式。

0

评论者:adamclements

已经附加的 clj-1472-2.patch 通过了结构锁测试。

在我于 2014 年进行大量测试和比较生成的字节码之前,这是我找到的唯一可行的方案,这种方式不需要指导一个新的特殊形式来重新实现不良记录的规格,并且需要生成精确的字节码,这在我来看是脆弱的。

我想看看一些性能数据,看看所声称的性能影响有多大,尤其是在 clojure 中使用 locking 宏实际非常罕见的情况下(我在测试中只能在整个生态系统中发现几个例子。大多数人使用其他并发原语或 Java 交互操作)

0

评论者:alexmiller

-3 修复分支重置到 master,没有语义更改,保留归属

...