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

欢迎!请访问关于页面获取有关如何使用本站的一些更多信息。

0
Clojure

(set! *warn-on-reflection* true)
(fn [] (loop [b 0] (recur (loop [a 1] a)))))


生成以下警告


recur参数为原始局部变量:b与原始类型不匹配,现有:Object,所需:long
自动装箱loop参数:b


这有几个有趣的原因。其中之一是,如果{{recur}}的参数是一个{{let}}形式,则不会有警告


(fn [] (loop [b 0] (recur (let [a 1] a))))


编译器似乎也很好地理解{{loop}}表达式的返回类型


(use '[clojure.contrib.repl-utils :only [expression-info]])
(expression-info '(loop [a 1] a))
;=> {:class long, :primitive? true}


当然,可以通过在{{loop}}表达式中使用显式转换来解决这个问题


(fn [] (loop [b 0] (recur (long (loop [a 1] a)))))


由leafw在IRC报告:http://clojure-log.n01se.net/date/2011-01-03.html#10:31

*另请参阅:* CLJ-1422

*补丁:* 0001-CLJ-701-add-HoistedMethod-to-the-compiler-for-hoisti-v2.patch

47 个答案

0

_评论者:a_strangeguy

问题是,if表达式中的'loop形式会被转换为匿名fn并立即调用,在循环处于表达式上下文时(例如,其返回值需要,但不是方法/fn的返回值)。

所以

(fn [] (loop [b 0] (recur (loop [a 1] a)))))

转换成

(fn [] (loop [b 0] (recur ((fn [] (loop [a 1] a))))))

请参阅编译器中的代码
http://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L5572

此转换已经在有可变字段的deftype中想要在循环中'ami'它们时困扰过你

http://dev.clojure.org/jira/browse/CLJ-274

0

评论者:cgrand

由于Hotspot不适优化它们,所以表达式上下文中的循环会被提升为函数。
这导致了一些问题

*  type inference doesn't propagate outside of the loop(link: 1) 
*  the return value is never a primitive
  • 不可访问可变字段
    • 每次循环进入时都会意外分配一个闭包对象。

解决所有这些问题并不容易。
可以计算循环的类型并列出类型提示,但这仅适用于引用类型。要让其在原始类型上生效,原始函数不足以返回仅长/双精度值:您必须显式进行类型转换。
因此,解决前两个问题可以通过相当局部的方案完成。
其余两个点需要更多的重大更改,目标是生成方法而不是函数。这意味着至少需要更改ObjExpr并添加一个新的ObjMethod子类。

(链接:1) 测试时请注意CLJ-1111。

0

评论者:alexmiller

我认为这不会出现在1.6中,所以弃用了1.6标签。

0

评论者:hiredman

一个可能立即解决这个问题的方法是使用编译器生成的具有指定返回类型的特定非infer类型将循环提升出来,该类型使用带调用方法,该方法返回表达式的getJavaClass()类型,这将给我们代码提升出来的简化好处,同时摆脱ifn的Object语义。

0

评论者:hiredman

我已附加了一个包含hoistedmethod-pass-1.diff的3个部分的补丁

3ed6fed8添加了一个新的ObjMethod类型,用于在封装类上表示提升到自己的方法中的表达式

9c39cac1使用HoistedMethod来编译非返回上下文中的循环

901e4505将try表达式提升出来,并使try返回一个原始表达式成为可能(这也许属于http://dev.clojure.org/jira/browse/CLJ-1422

0

评论者:hiredman

使用hoistedmethod-pass-1.diff,示例代码生成的字节码如下

`
user=> (println (no.disassemble/disassemble (fn [] (loop [b 0] (recur (loop [a 1] a))))))
// 从form-init1272682692522767658.clj编译(版本1.5 : 49.0,超级位)
public final class user$eval1675$fn__1676 extends clojure.lang.AFunction {

// 字段描述符 #7 Ljava/lang/Object;
public static final java.lang.Object const__0;

// 字段描述符 #7 Ljava/lang/Object;
public static final java.lang.Object const__1;

// 方法描述符 #10 ()V
// 栈:2,局部变量:0
public static {};

 0  lconst_0
 1  invokestatic java.lang.Long.valueOf(long) : java.lang.Long [16]
 4  putstatic user$eval1675$fn__1676.const__0 : java.lang.Object [18]
 7  lconst_1
 8  invokestatic java.lang.Long.valueOf(long) : java.lang.Long [16]
11  putstatic user$eval1675$fn__1676.const__1 : java.lang.Object [20]
14  return
  Line numbers:
    [pc: 0, line: 1]

// 方法描述符 #10 ()V
// 栈:1,局部变量:1
public user$eval1675$fn__1676();

0  aload_0 [this]
1  invokespecial clojure.lang.AFunction() [23]
4  return
  Line numbers:
    [pc: 0, line: 1]

// 方法描述符 #25 ()Ljava/lang/Object;
// 栈:3,局部变量:3
public java.lang.Object invoke();

 0  lconst_0
 1  lstore_1 [b]
 2  aload_0 [this]
 3  lload_1 [b]
 4  invokevirtual user$eval1675$fn__1676.__hoisted1677(long) : long [29]
 7  lstore_1 [b]
 8  goto 2
11  areturn
  Line numbers:
    [pc: 0, line: 1]
  Local variable table:
    [pc: 2, pc: 11] local: b index: 1 type: long
    [pc: 0, pc: 11] local: this index: 0 type: java.lang.Object

// 方法描述符 #27 (J)J
// 栈:2,局部变量:5
public long __hoisted1677(long b);

0  lconst_1
1  lstore_3 [a]
2  lload_3
3  lreturn
  Line numbers:
    [pc: 0, line: 1]
  Local variable table:
    [pc: 2, pc: 3] local: a index: 3 type: long
    [pc: 0, pc: 3] local: this index: 0 type: java.lang.Object
    [pc: 0, pc: 3] local: b index: 1 type: java.lang.Object

}
nil
user=>

`

方法__hoisted1677的主体是内部循环

为参考,此处粘贴了同样函数编译于1.6.0版本的字节码部分 https://gist.github.com/hiredman/f178a690718bde773ba0 内部循环的主体缺失,因为它实现为一个自己的IFn类,该类被实例化后立即执行。它捕获数字的装箱版,并返回装箱版

0

评论者:hiredman

hoistedmethod-pass-2.diff用f0a405e3替换901e4505,这修复了MaybePrimitiveExpr for TryExpr的实现

使用hoistedmethod-pass-2.diff,访问最快的Clojure项目(53kloc)已编译并通过所有测试

0

评论者:alexmiller

感谢您在这方面的工作!

0

评论者:hiredman

我已经在运行与hoistedmethod-pass-2.diff的所有贡献项目的测试过程中工作,编译data.json时有字节码验证错误,其他地方也有错误,所以还有工作要做

0

评论者:hiredman

hoistedmethod-pass-3.diff

49782161 将HoistedMethod添加到编译器,以提升类型良好的方法外的表达式
e60e6907
如果需要,提升循环
547ba069 * 使TryExpr MaybePrimitive,并按需提升尝试

所有通过master测试的贡献项目也通过此补丁。

在该补丁中,从 hoistedmethod-pass-2.diff 变更来看,是增加了一些对占据一个以上槽位的参数的记账。

0
by

评论者:bronsa

Kevin,关于长双精度浮点数处理的bug仍在。
在提交49782161的diff文件第101行,如果表达式处于STATEMENT位置,你会发出gen.pop(),如果e.getReturnType是long.class或double.class,你需要发出gen.pop2()。

测试用例

user=> (fn [] (try 1 (finally)) 2) VerifyError (class: user$eval1$fn__2, method: invoke signature: ()Ljava/lang/Object;) 在栈上尝试分割长或双精度浮点数 user/eval1 (NO_SOURCE_FILE:1)

0
by

评论者:hiredman

哎,所有这些工作都是为了解决我不能正确解决的问题,当然我忽略了最初就知道的事情。我想移除 TryExpr 之间 emit 和 emitUnboxed 的编码重复,完成这个后,我会修复 pop。

0
by

评论者:hiredman

hoistedmethod-pass-4.diff 在逻辑上有相同的三个commits,但解决了 pop 与 pop2 的问题,并重写了 TryExpr 的 emit 和 emitUnboxed 以共享大部分代码。

0
by

评论者:hiredman

hoistedmethod-pass-5.diff 修复了 hoistedmethod-pass-4.diff 中测试的愚蠢错误。

0
by

评论者:bronsa

(链接: ~hiredman) 顺便说一下,这个补丁不再适用。

...