请分享您的想法,参加 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 字节码对这些情况进行了一些复杂的异常表处理,这在 Clojure 编译器中(afaict)没有可能做到,除非修改 Clojure 编译器。

方法: 一种可能的方法是让锁定在 Java 的 synchronized 块上下文中调用传 pip 的体。这样可以避免通过交付给 Java 来处理复杂的字节码问题,但性能影响是未知的。

补丁: clj-1472-3.patch

另请参阅: 将字节码与 java synchronized 块的对比,显示出一些差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

筛选: Alex Miller - 我认为这是一种可行的解决方案,可以解决这个问题,由于其使用频率不高,我不太担心这是一个性能问题。我认为另一种处理方法是使 locking 成为具有编译器支持的专用形式,但我不确定这是否值得去做,所以我会留给 Rich 决定。

带有以下注释关闭: fixed

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(Java语言规范),但不遵循JVM规范(我们怎么能呢,因为我们不运行Java字节码)。因此,所有针对它的上诉都是无关紧要的。我们尝试在Dex字节码方面与JVM规范的精神保持兼容,但如果你的源代码不是Java,没有任何保证。

现在,验证器(可能仍然是)不正确,甚至不符合我们的(相当糟糕)规范,而且遗憾的是,我们并不非常一致。例如,在Marshmallow中,许多我们无法正确验证与结构化锁定相关的代码被拒绝为VerifyError,这与JVM规范的精神不符。在下一个版本中,这将得到缓解,并将推迟到代码运行时进行实际检查。

遗憾的是,我们无法对旧版本作出任何回应,你必须千方百计解决任何问题。-(- 我会设法在你找到时间时看看你的类。
{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,如果这个修复方案可行,我 interessato di un patch per questo.

0

评论者:gshayban

如果有人可以帮助我测试Android,我将很乐意。

在javac输出synchronized时,它会安装一个异常类型为"any"的异常处理器。相关的提交中捕获了Exception。如果需要,我们可以通过调用GeneratorAdapter.visitTryCatch并使用null作为目标类来输出any处理器(我不知道这确切意味着什么--Throwable +未来可能的其他东西?)。

0

评论者:adamclements

您尝试过应用现有的clj-1472-2.patch来查看是否能修复GraalVM吗?我想我们最初达成了一个让人们对附带的补丁满意的阶段(Alex,如果我说错了请纠正我),但后来决定JVM实现才是王者,问题在于Android没有遵守这一规范,而不是Clojure的问题。如果同样的问题在其他地方再次出现,那么那个决定可能需要重新考虑(我仍然想看到这个补丁被应用)。

这已经是很久以前的事了,但我想起试图使用了所有能想到的clojure try/catch/finally/throw组合,但无法让生成的字节码符合synchronised规范,而不改变Clojure的代码生成,而这太深了,不切实际——因此产生了上面的补丁,该补丁依靠javac在synchronisation周围生成字节码,而不是使用monitor-enter和monitor-exit。我对您链接到的实现表示担忧,担心它在某些情况下仍然可能会生成错误的字节码,尽管它在Clojure代码级别上看起来完美无瑕且明显正确。

0

评论者:gshayban

我没有尝试,但我确信从可行性角度来看,clj-1472-2.patch应该会工作。它有一个性能缺点:它可能会引起JVM的“配置污染”并混淆锁优化。(大多数锁都是无争的)

如果对Android有效,我宁愿修复称为javac的生成方式。

0

评论者:eraserhd

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

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

0

评论者:adamclements

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

在2014年进行的大量测试和生成的字节码比较后,这是我找到的唯一可行的方案,该方案不需要指示一个新特别形式,该形式重新实现了不透明的规范,以生成确切的正确字节码,这在我看来很脆弱。

我很想看到一些性能分析数据,以便了解这种宣称的性能影响有多大,特别是在考虑到在Clojure中使用锁宏实际上是很稀有的时候(我在测试整个生态系统时只找到了几个例子。大多数人使用其他并发原语或Java交互操作)

0

评论者:alexmiller

-3 批量重推到 master,没有语义变化,保留归属

...