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 ;; expected true dispatch.dispatch

这种情况在具有 {{:aot :all}} 的 leiningen 项目中更为常见。使用 :all 时,文件按字母顺序编译。在这种情况下,dispatch.core 将先编译,然后是 dispatch.dispatch。

原因

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

(compile 'dispatch.dispatch)
- 由于先前编译,通过应用类加载器加载 dispatch__init
- ->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) 不要在类路径上将编译路径(这违反了当前预期,但避免了加载 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

当在类路径中同时存在clj文件和class文件时,编译器使用了奇特的加载规则。

这个错误发生是因为RT.load会重新绑定->Ctor使用AOT deftype实例来加载AOT类文件。

对此问题的修复方法是让load "已加载库"感知,以避免不必要的/有害的重装。

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 报告)
...