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,因为它失败了编译时验证(声明 'clojure.core.server$stop_server' 出现在 /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本身可以抛出(在null锁定对象上)。Java字节码对这些情况进行了一些复杂的异常表处理,这(据我所知)是不可能在不修改Clojure编译器的情况下完成的。

方法:一种可能的方法是让锁定在Java同步块的环境中调用传入的主体。这避免了通过将问题交由Java处理而出现复杂的字节码问题,但具有未知的影响。

补丁:clj-1472-3.patch

另见:与java同步块的字节码对比分析显示了一些差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

审核人:Alex Miller - 我认为这是一个可行的解决方案,可以修复这个问题。由于它的使用频率不高,我不太担心它会成为一个性能问题。我将标记我认为另一种处理方法是使locking成为一个特殊形式并具备编译器支持,但我不确定这是否值得这样做,所以我会留待Rich决定。

已关闭,备注: 已修复

41 个回答

0
by

评论发布者:alexmiller

我添加了一个新的clj-1472.patch来解决一些我不满意的前一个补丁的细节。然而,这个更改基本上仍是相同的,所以我保留了原始作者的归属。

0
by

评论发布者:gshayban

alexis你上传了正确的补丁吗?

0
by

评论发布者:alexmiller

啊,弄错了,会修复的。

0
by

评论发布者:bronsa

Alex,很可能需要将其改为^:once fn**

0
by

评论发布者:gshayban

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

JVMS(Java虚拟机规范)也不是很有兴趣。Android不是Java虚拟机。我们遵循JLS(Java语言规范),但不遵循JVMS(我们怎么能呢,因为我们不运行Java字节码)。因此,所有针对它的上诉都是无关紧要的。我们尽力与 JVMS 的精神保持兼容(针对Dex字节码),但如果你的源代码不是Java,则没有任何保证。

现在,验证器(可能仍然是)有问题的,甚至与我们的(相当糟糕的)规范相比也是如此,且我们并不是非常一致。例如,在Marshmallow中,我们很多关于结构化锁定的代码无法正确验证,并以 VerifyError 的形式被拒绝,这与 JVMS 的精神不符。然而,在下一个版本中,这将得到放宽,并将推迟到实际运行代码时进行检查。

遗憾的是,我们无法处理老版本的发布,您将不得不解决任何问题。 :-( 当我有时间时,我会尝试查看您的课程。
{quote}

听起来在Clojure中制作一个 workaround 是最坏的选择。

0

评论发布者:alexmiller

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

0

评论发布者:alexmiller

我们目前的看法是安卓(Android)存在问题,因此目前将其放在待处理事项中。

0

评论发布者:gshayban

GraalVM/native-image 也对 monitorenter/exit 平衡提出了抱怨。通过模仿 javac 所作: https://github.com/ghadishayban/clojure/commit/8acb995853761bc48b62190fe7005b70da692510

0

评论发布者:alexmiller

如果那个解决方案可行,我很感兴趣看到那个补丁。

0

评论发布者:gshayban

如果有人可以协助我进行 Android 测试基础设施,我会干的。

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。我担心您提供的实现可能会在某些情况下生成错误的字节码,就像现有实现那样,即使在 Clojure 代码层面看起来完美无瑕并且明显正确。

0

评论发布者:gshayban

我没有尝试过,但我确信从可行性角度看,clj-1472-2.patch 应该是可行的。但它有一个性能劣势:它可能会造成 JVM 的“性能分析污染”,并导致锁优化混淆。(大多数锁是不会竞争的)

如果这样的话,我宁愿修复输出,让它更像 javac。

0

评论者:eraserhd

我尝试了 Ghadi 链接的 diff,但存在一些问题(包括丢弃了已锁定的表达式值)。我修复了这个问题,但经过研究,我发现:
1. 这不包括由 monitor-exit 本身抛出的异常,这似乎是必要的。我们无法在 catch 中递归,也无法捕获 'any',因此无法仅使用 Clojure 构造生成此字节码。
Clojure 的 finally 生成的字节码包含一些未被异常处理覆盖的存储和加载操作,因此我们无法简单地在 monitor-exit 代码中添加异常处理。我很确定我们不能移除存储和加载操作。

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

0

评论者:adamclements

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

在我2014年进行了大量的测试和生成交集字节码比较之后,这是我找到的唯一可行的方案,它不需要指令新的特殊形式来实现重新实现了 Trie 文档的规范,以精确地生成正确的字节码,这在我看来非常脆弱。

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

0

评论发布者:alexmiller

-3次在master上打补丁,没有语义变化,保持归属

...