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本身可能会抛出异常(在null锁定对象上)。Java字节码对这些情况进行了某些复杂的异常表处理,这对于(据我所知)没有修改Clojure编译器而不可能实现。

方法:一种可能的方法是让锁定在Java的synchronized块上下文中调用传递的body。这样做通过将其移交给Java避免了复杂的字节码问题,但具有未知的性能影响。

补丁:clj-1472-3.patch

另请参阅:将字节码与java synchronized块进行比较,可以显示一些差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

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

已关闭,备注为:已修复

41 个答案

0

评论者:alexmiller

我为之前补丁中的一些细节增加了新的patch,修正了一些我不喜欢的细节。然而,更改本质上仍然是相同的,所以我保留了原始作者的归属。

0

评论者:gshayban

alexmiller,你上传的是正确的补丁吗?

0

评论者:alexmiller

啊,搞错了。我会修复的。

0

评论者:bronsa

Alex,可能值得将那个更改为 ^:once fn**

0

评论者:gshayban

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

JVM规范(JVMS)也不相关。Android不是一个Java虚拟机。我们遵循JLS,但不遵循JVMS(因为我们不运行Java字节码,我们怎么可能呢)。因此,对此的所有上诉都是不相关的。我们努力与JVMS的精神兼容以Dex字节码相关,但如果您的源代码不是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,如果这是一种可行的修复方案,我对一个补丁很感兴趣。

0

评论者:gshayban

如果有人能帮助我在Android测试环境中进行一些工作,我会乐意成为游戏的一部分。

在`javac`生成synchronized时,它安装了一个异常类型的catch处理器,异常类型为"任何"。链接提交的代码捕获了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 中递归,也无法捕获 '任何',所以我们不能仅使用 Clojure 结构生成这种字节码。
Clojure 的 finally 中发出的字节码包括由异常处理未覆盖的存储和加载,所以我们不能在 monitor-exit 代码中简单地添加异常处理。我非常确信我们不能删除存储和加载。

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

0
by

评论作者:adamclements

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

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

我想查看一些性能数字,以了解这一声明的性能影响有多大,尤其是考虑到锁定宏在实际 Clojure 中的使用频率并不高(我在测试整个生态系统时只能找到几个例子。大多数人使用其他的并发原语或 Java 互操作)

0
by

评论者:alexmiller

修订版-3的补丁基于 master,没有语义变更,保留了归属权。

...