请在 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: 拒绝类 clojure.core.server$stop_server,因为它在编译时验证失败(在 /data/app/com.clojure_on_android-1/base.apk 中声明了'clojure.core.server$stop_server')

原因:根据一次关于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)。

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

方法:一个可能的方法是通过在Java的synchronized块中调用传递的body来使锁定调用。这通过将复杂的字节码问题转移到Java来避免,但有可能影响性能。

补丁:clj-1472-3.patch

另见:将字节码与Java的synchronized块进行比较,可以显示出许多差异
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输出很有趣。

JVM规范也不有趣。Android不是一个Java虚拟机。我们遵循JLS,但不遵循JVM规范(我们怎么能,因为我们不运行Java字节码)。因此,所有针对它的上诉都是无关的。我们试图在Dex字节码方面与JVM规范的精神相一致,但如果你的源代码不是Java,则没有任何保证。

现在,校验器(可能仍然是)在即使是对我们(相当糟糕)的规范的情况下也是损坏的,并且不幸的是我们不太一致。例如,在Marshmallow中,许多我们不能正确校验关于结构化锁定的代码被拒绝为VerifyError,这与JVM规范的精神不符。在下一个版本中,这将得到放松,并且将推迟到实际运行代码时进行检查。

遗憾的是,我们无法对旧版本分支做任何事情,你必须绕过任何问题。 :-( 当我有时间时,我会努力查看你的类。
{quote}

在Clojure中采取的变通方法似乎是所有方案中伤害最小的。

0
by

评论者:alexmiller

使用^:once添加了-2版本的修补程序。

0
by

评论者:alexmiller

我们目前认为这里是Android有问题,所以先将其放回等待处理。

0
by

评论者:gshayban

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

0
by

评论者:alexmiller

如果没有实际可行的解决方案,对这个问题我感兴趣。

0
by

评论者:gshayban

如果有人能帮我测试Android的测试基础设施,我会非常乐意。

在`javac`生成`synchronized`时,它会安装一个具有“任何”异常类型的捕获处理器。链接的提交捕获了Exception。如果需要,我们可以通过将`GeneratorAdapter.visitTryCatch`中的目标类设置为`null`来发出`any`处理器(我不太清楚这意味着什么——Throwable + 将来可能的任何其他东西?)

0
by

评论者:adamclements

您是否尝试将现有的clj-1472-2.patch应用到GraalVM上,看是否解决问题?我认为最初我们曾达成一个大家都有意见的补丁(如果我有错,请纠正我,Alex),但后来决定JVM实现为王,所以问题出在Android未能符合JVM标准,而不是Clojure的问题。如果同样的问题在其他地方再次出现,那么这个决定可能需要重新考虑(我仍然想看到这个补丁)。

这是很久以前的事情了,我记得尝试了所有可能想到的clojure的try/catch/finally/throw组合,但无法使生成的字节码符合同步规范,而又不改变Clojure的代码生成,因为这过于底层而不实用。所以,上述的补丁是依托javac生成同步相关的字节码,而不是使用monitor-enter和monitor-exit。我担心您的链接实现可能在某些情况下仍然生成错误的字节码,尽管它在Clojure代码层面上看起来完全正确且似乎正确。

0

评论者:gshayban

我没有试过,但相信从可实现性的角度来看,clj-1472-2.patch应该有效。但它的性能方面有缺点:它可能导致JVM的“剖面污染”并混淆锁优化(大多数锁都是无竞争的)。

我更愿意修复到更像javac的输出,如果这对Android有效的话。

0

评论者:eraserhd

我尝试了Ghadi提供的diff,但存在问题(包括丢弃了锁定表达式的值)。我修复了这个问题,但经过研究,我发现:
1. 这不包括monitor-exit本身抛出的异常,这似乎是必要的。我们不能在catch中进行递归,也不能捕获任何内容,因此我们不能仅使用Clojure结构生成这种字节码。
2. Clojure的finally生成的字节码包含存储和加载数据,这些数据未被异常处理覆盖,所以我们不能在monitor-exit代码中简单添加异常处理。我相当确定我们不能删除存储和加载数据。

所以,除非我忽略了一些东西,否则通过结构化锁定检查的唯一方法是使锁定成为特殊形式。

0

评论者:adamclements

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

在2014年进行的广泛测试和生成的字节码比较之后,这是我能找到的唯一可行方法,这种方法不需要引入新的特殊形式来重新实现不良记录的规范,以精确地生成正确的字节码,这在我看来很脆弱。

我非常想看看一些性能分析数据,来看一下声称的性能影响有多大,特别是在clojure中使用锁定宏实际上是多么罕见(我在整个生态系统中测试时只找到了几个例子。大多数人使用其他并发原语或Java互操作。)

0
by

评论者:alexmiller

-3 拉取请求在master上重新合并,没有语义变化,属性保留

...