请在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)。

看起来在lockingmonitor-entermonitor-exit和try/finally块的组合创建出ART标记为未平衡的monitorenter/monitorexit字节码的路径。特别是,monitorenter和monitorexit自身可能抛出异常(在null锁定对象上)。Java字节码对异常表进行了一些复杂的处理以覆盖这些情况,这(根据我的知识)在没有修改Clojure编译器的情况下是不可能的。

方法:一个可能的方法是让锁定在Java的同步块中调用传递的body。这通过将复杂的字节码问题交给Java来解决,但具有未知的性能影响。

补丁: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 字节码)。因此,所有针对它的上诉都是无关紧要的。我们 试图 使该 JVMS 关于 Dex 字节码的兼容性,但如果你的源代码不是 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测试基础设施,我将非常乐意。

在javac发出synchronized时,它安装一个异常类型为"任何类型"的catch处理器。链接的commit中捕获了Exception。如果需要,我们可以通过调用GeneratorAdapter.visitTryCatch并用null作为目标类来发出“任何类型”处理器。

0
发表于

评论者:adamclements

你尝试过应用现有的clj-1472-2.patch来查看是否修复了GraalVM吗?我认为我们最初达到了人们对该补丁满意的地方(如果我有误,Alex请纠正我),但决定JVM实现是王者,所以问题是Android不符合那个而不是Clojure。如果同样的问题在其他地方再次出现(我仍然想看到这个补丁),那么可能是需要重新考虑的决定。

这已经是很久以前的事情了,但我记得曾尝试过所有我想到的不同组合的 Clojure try/catch/finally/throw,但还是无法生成符合 synchronized 规范的字节码,而修改 Clojure 的代码生成器也太深了,不实用——所以上面提到的补丁依赖于 javac 在同步处生成字节码,而不是使用 monitor-enter 和 monitor-exit。我对你的链接实现有点担心,它可能仍然会在某些情况下生成错误的字节码,尽管它在 Clojure 代码级别上看起来完美无瑕并且显然正确。

0
by

评论者:gshayban

我没有尝试过,但我确信 clj-1472-2.patch 在可行性方面是可行的。但它有一个性能劣势:它可能导致 JVM 的“分析污染”,并混淆锁优化。(大多数锁无争用)

我更愿意修复输出,使其更像 javac,如果它适用于 Android。

0
by

评论者:eraserhd

我尝试了 Ghadi 链接的 diff,但有一些问题(包括丢弃被锁定表达式的值)。我fix了那部分,但经过研究,我意识到
1. 这并不能覆盖由 monitor-exit 本身抛出的异常,这似乎是必须的。我们不能在 catch 中递归,也不能捕获 'any',因此我们无法仅使用 Clojure 构造生成这种字节码。
2. Clojure 的 finally 生成的字节码包括一个 store 和一个 load,这些在异常处理中被覆盖了,所以我们不能只在 monitor-exit 代码中添加异常处理。我非常确信我们不能移除 store 和 load。

所以,除非我遗漏了什么,否则通过结构化锁定检查的唯一方法是使锁定成为一个特殊形式。

0
by

评论者:adamclements

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

在我于2014年进行的大量测试和对生成字节码的比较之后,这是我唯一找到的可行方法,它不需要指导新的特殊形式来重实现 poorly documented 规范,以精确生成正确的字节码,这对我来说感觉比较脆弱。

我非常想看到一些分析数据,看看这种声称的性能影响有多大,尤其是鉴于在 Clojure 中实际使用 locking 宏是非常罕见的(在测试中,我在整个生态系统中只找到了几个例子。大多数人使用其他并发原语或 Java 互操作)。

0
by

评论者:alexmiller

-3 修补程序基于 master 权重,无语义更改,归因保留

...