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字节码对这些情况进行了复杂的异常表处理,这在(afaict)不修改Clojure编译器的情况下不可能实现。

方法:一个可能的方法是让锁在Java的synchronized块上下文中调用传入的主体。这通过将处理交给Java避免了复杂的字节码问题,但性能影响未可知。

补丁:clj-1472-3.patch

另请参阅:与Java synchronized块的字节码比较显示出许多差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

筛选员:Alex Miller - 我认为这是一个可行的解决方案,它解决了问题,并且由于使用频率不高,我并不真的担心它会成为性能问题。我认为另一种处理方式是将locking作为特殊形式与编译器支持一起使用,但我不确定这是否值得做,因此我将留待Rich决定。

注:问题已关闭,已修复

41 个回答

0

评论者:alexmiller

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

0

评论者:gshayban

alexf,你是否上传了正确的补丁?

0

评论者:alexmiller

我搞砸了,我会修复的。

0

评论者:bronsa

Alex,你可能想将其改为 ^:once fn**

0

评论者:gshayban

来自 Android
{quote}
Android 不运行 Java 字节码,而是运行 Dex 字节码。你类转换后的 dexdump 输出很有意义。

Java 虚拟机规范(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 时,它安装了一个捕获处理程序,异常类型为 "any"。相关提交已捕获 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

评论者:adamclements

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

在2014年进行了大量测试和生成的字节码比较之后,这是我唯一能找到的可行方法,它不需要命令新的特殊形式来重新实现文档不佳的规范,以确保生成完全正确的字节码,这似乎很脆弱。

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

0
by

评论者:alexmiller

-3 修订对master的回退,没有语义变化,保留归功

...