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: 列表 class 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.11.10)。

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

方法:一个可能的方法是在 Java 的一个同步块中执行锁定的传递体。这避免了由于将手头的问题移交给 Java 而产生的问题,但具有未知的影响。

补丁: clj-1472-3.patch

另见:与 java 同步块比较字节码检查发现了一些差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

审阅: Alex Miller - 我认为这是一个可行的解决方案,可以解决问题,并且由于其使用频率不高,所以我不太担心它的性能问题。我认为另一种处理方法是将 locking 配置为带有编译器支持的特设形式,但我不确定这样做是否值得,所以我把这个问题留给 Rich 决定。

修复 的注释关闭

41 个答复

0

评论者:alexmiller

我增加了一个新的 clj-1472.patch,修复了前一个补丁中我不喜欢的一些细节。然而,它仍然基本上是同一种更改,因此我保留了原始作者的归属。

0

评论者:gshayban

alexs,你上传了正确的补丁吗?

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生成中,它会对任何异常类型安装捕获处理程序。链接的提交会在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 的“分析污染”,并可能混淆锁优化(大多数锁都是无竞争的)。

如果这对 Android 生效的话,我宁愿修正代码生成使其更接近 javac。

0

评论者:eraserhd

我已经尝试了 Ghadi 链接到的差异,但存在问题(包括丢弃被锁的表达式值)。我修复了这个问题,但在研究之后,我发现:
1. 这没有涵盖由 monitor-exit 自身抛出的异常,这似乎是必需的。我们不能在 catch 中递归,也不能捕获 'any',因此我们不能仅使用 Clojure 构造来生成这种字节码。
Clojure 的 finally 代码生成的字节码包含一个 store 和一个 load,这是异常处理中未发现的,所以我们不能仅仅在 monitor-exit 代码中添加异常处理。我相当确定我们不能去掉 store 和 load。

因此,除非我漏掉了什么,否则要通过结构化锁定检查,唯一的办法是将锁定转换为特殊形式。

0

评论者:adamclements

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

在2014年我对生成的字节码进行了大量测试和比较后,这是我找到的唯一可行的方案,它不需要指导新的特殊形式来重新实现文档记载不佳的规范,以精确地发出正确的字节码,这对于我来说似乎很脆弱。

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

0

评论者:alexmiller

-3补丁基于master版本,没有语义变化,保留了归属权

...