2024 Clojure 状态调查!中分享您的想法。

欢迎!请参阅关于页面以了解更多关于如何使用本页的信息。

0
编译器

情景:给定两个文件

src/dispatch/core.clj

(ns dispatch.core (:require [dispatch.dispatch]))

src/dispatch/dispatch.clj

(ns dispatch.dispatch) (deftype T []) (def t (->T)) (println "T = (class t):" (= T (class t)))

首先编译 core,然后编译 dispatch

java -cp src:target/classes:clojure.jar -Dclojure.compile.path=target/classes clojure.main user=> (compile 'dispatch.core) T = (class t): true dispatch.core user=> (compile 'dispatch.dispatch) T = (class t): false ;; 预期 true dispatch.dispatch

这个情景在具有 {{:aot :all}} 的 leiningen 项目中更为常见。文件按字母顺序编译,在本例中,先编译 dispatch.core,然后编译 dispatch.dispatch。

原因

(compile 'dispatch.core)
- 递归编译 dispatch.dispatch
- 将 .class 文件写入 编译路径(它在类路径上)
- 断言通过

(compile 'dispatch.dispatch)
- 由于之前的编译,dispatch.dispatch__init 通过 appclassloader 加载
- ->T 构造函数将使用新的字节数组实例化一个 T 实例 - 这使用 appclassloader,从磁盘上的编译 T 加载
- 然而,T 类字面量是通过 RT.classForName 解析的,它检查动态类加载器缓存,所以使用旧的运行时版本 T,而不是磁盘上的版本

在 1.6 中,RT.classForName() 不会检查动态类加载器缓存,所以像实例一样从磁盘加载 T。此更改在 CLJ-979 中进行,以支持其他重定义和 AOT 混合使用。

方法

1) 按反向依赖顺序编译以避免重复编译。

可以在第一个例子中交换编译顺序或在 project.clj 中指定顺序

:aot [dispatch.dispatch dispatch.core]

这是一个短期解决方案。

2) 将 deftype 放入与它使用的命名空间不同的命名空间中,以便在第二次编译时不会被重新定义。这是一个短期解决方案。

3) 不要将 compile-path 放在类路径上(这违反了当前期望,但避免加载 dispatch__init)

(set! *compile-path* "foo") (compile 'dispatch.core) (compile 'dispatch.dispatch)

目前无法通过 Leiningen 设置。

4) 使用独立的 Clojure 运行时编译每个文件 - 避免在 DCL 中使用缓存的类字面量。

目前可能在 Leiningen 或其他地方这样做太麻烦了。

5) 使编译非透明。这类似于 CLJ-322,这是一个另一个问题。但这可能是我们应该走向的方向。

筛选:我不认为提议的补丁是一个好主意 - 它只是在症状上贴了一个补丁而没有解决根本原因。我认为我们需要重新评估与编译路径(#3)和透明性(CLJ-322)(#5)相关的编译方式,但这应该在 1.7 之后进行。 - Alex

也请参阅:CLJ-1650

7 答案

0

评论者:alexmiller

正在将代码拉入1.7版本以供考虑。

0

评论者:sfnelson

我在我的示例中添加了一个调试标志,该标志会导致打印出类型实例哈希码及其类加载器。

编译dispatch.core deftype => 652433136 (clojure.lang.DynamicClassLoader@23c30a20) defmethod => 652433136 (clojure.lang.DynamicClassLoader@23c30a20) instance => 652433136 (clojure.lang.DynamicClassLoader@23c30a20) dispatch: :pass 编译dispatch.dispatch deftype => 652433136 (clojure.lang.DynamicClassLoader@23c30a20) defmethod => 652433136 (clojure.lang.DynamicClassLoader@23c30a20) instance => 760357227 (sun.misc.Launcher$AppClassLoader@42a57993) dispatch: :fail

0

评论者:bronsa

当在classpath中同时存在clj文件和class文件时,编译器使用compile时具有奇怪的错误加载规则。

此错误发生是因为RT.load会在使用AOT类文件时重新绑定->Ctor以使用AOT deftype实例。

此问题的修复方法是在加载过程中使“加载的库”感知,以避免不必要的/有害的重载。

0

评论者:bronsa

附加的补丁通过跟踪已加载的文件和仅在必要时加载AOT类来修复此错误

0

评论者:alexmiller

原始描述(已替换)

使用错误类型实例定义类型调度多方法

当使用基于类型派发的多方法(如 print-dup/print-method)时,在 AOT 存在的情况下,传递给 {{addMethod}} 的类型引用在命名空间的第二次加载时是不正确的。这意味着如果命名空间已经被另一个文件作为依赖项加载,则在命名空间加载进行 AOT 编译的第二次加载将产生一个无法正确派发的多方法。

我已经创建了一个示例仓库
https://github.com/sfnelson/clj-mm-dispatch

要独立重现,创建一个包含一个 deftype 和在类型上派发的多方法的命名空间,以及另一个需要第一个并按字母顺序排在第一个之前的命名空间。分别对两个命名空间进行 AOT 编译。当通过 {{require}} 加载类型定义的命名空间时,它为 deftype 产生一个类文件。当它第二次加载进行 AOT 编译时,会给出与现有类文件相对应的类型给 defmethod,而不是由加载命名空间构建的新类。这导致多方法无法正确派发。

对我来说,这个问题似乎与 CLJ-979 类似:使用错误的类加载器检索传递给多方法的类型。这表明它可能比 AOT 和多方法派发有更广泛的影响。

0

评论者:bronsa

我刚刚意识到这个条目是 CLJ-1650 的重复

0
参考:https://clojure.atlassian.net/browse/CLJ-1741(由 alex+import 报告)
...