请在 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-path(它在类路径上)
- 断言通过

(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,这是一场另一场战争。但这也可能是我们应该走的方向。

筛选:我相信提出的补丁不是好主意 - 它只是在表面上掩盖了症状,而没有解决根本原因。我认为我们需要重新评估与 compile-path(#3)和传递性(CLJ-322)(#5)相关的编译方式,但我觉得我们应该在 1.7 之后做这件事 - Alex

另见:CLJ-1650

7个答案

0

评论人:alexmiller

将Pulling into 1.7 for consideration.

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

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

这个错误发生是因为RT.load会加载AOT类文件,将->Ctor rebound以使用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 报告)
...