Julia 开发笔记

                     

贡献者: addis

   本文是关于 Julia 解释器的原理:它使用了哪些技术,可以使得它作为一门动态语言能达到编译语言的性能。

图
图 1:Julia JIT 流程

1. Parser 和 IR 等架构

2. Julia 的 AST 格式(Expr)

3. 把 Julia 代码编译成 LLVM IR 代码

   首先安装 using Pkg; Pkg.add("LLVM")(其实没必要)

   在 REPL 中输入如下代码

代码 1:julia
function add(x::Int, y::Int)::Int
    return x + y
end
lowered_ir = @code_lowered add(1, 2) # 生成 Lowered Julia IR
typed_ir = @code_typed add(1, 2) # 生成静态类型的 Julia IR
llvm_ir = @code_llvm add(1, 2) # 生成 LLVM IR

   也可以把函数定义写到 jl 脚本中,然后在 REPL 中 include("脚本名.jl"),再 @code_xxx add(1, 2)include() 相当于把脚本代码直接插入到该位置。如果找不到,就用 using InteractiveUtils; a = InteractiveUtils.@code_lowered add(1, 2);

   注意只会生成指定函数的 IR,不会递归生成它调用的其他函数的 IR。

代码 2:julia ir
julia> lowered_ir = @code_lowered add(1, 2)
CodeInfo(
1 ─ %1  = Main.Int
│   %2  = Main.:+
│   %3  = (%2)(x, y)   
│         @_4 = %3
│   %5  = @_4
│   %6  = %5 isa %1
└──       goto #3 if not %6
2 ─       goto #4
3 ─ %9  = @_4
│   %10 = Base.convert(%1, %9)
└──       @_4 = Core.typeassert(%10, %1)
4 ┄ %12 = @_4
└──       return %12
)

   生成的 LLVM IR 代码如下

图
图 2:LLVM IR

   注意返回的 lowered_ir 变量并不是一个字符串,而是结构化的信息。

如何读懂 Julia IR

   如果 Julia IR 中的东西不懂,文档也不完善,那就直接执行试试即可,Julia IR 中的函数都是可以在 REPL 中执行的。例如上面的 Main.Int 执行出来就是 Int64::DataType。找许多 Julia 源码和 IR 对照下来基本就能明白。

4. 循环的 IR

   代码

for n in 1:5
    c += n
end
对应的 IR 是
14 ┄ %77  = Main.:(:)                         ; for n in 1:5
                                              ;     % ==循环准备阶段==
│    %78  = (%77)(1, 5)                       ;     % 返回 ::UnitRange{Int64}
│           @_4 = Base.iterate(%78)           ;     % 初始化迭代器: (值, 循环指数)
                                              ;     % 类型 Tuple{Int64, Int64}
                                              ;     % , 当前为 (1, 1)
│    %80  = @_4
│    %81  = %80 === nothing
│    %82  = Base.not_int(%81)                 ;     % 非运算
└───        goto #17 if not %82
                                              ;     % ==循环体==
15 ┄ %84  = @_4
│           n = Core.getfield(%84, 1)
│    %86  = Core.getfield(%84, 2)             ;     % 循环指数(对 UnitRange{Int64}
                                              ;     %    未必从 1 开始也未必步长为 1)
│    %87  = Main.:+                           ;     c += n
│    %88  = c
│    %89  = n
│           c = (%87)(%88, %89)
│           @_4 = Base.iterate(%78, %86)      ;     % 迭代(nothing 表示结束)
│    %92  = @_4
│    %93  = %92 === nothing
│    %94  = Base.not_int(%93)
└───        goto #17 if not %94
16 ─        goto #15
                                              ; end

   代码

function foo()
a = 0
for i in [1, "123", 1+2im]
    a += 1
    println(i, a);
end
end
对应的 IR 是
CodeInfo(
1 ─       a = 0
                                         ; % ==循环准备==
│   %2  = Main.:+                        ; % 构造 [1, "123", 1+2im]
│   %3  = Main.:*
│   %4  = Main.im
│   %5  = (%3)(2, %4)
│   %6  = (%2)(1, %5)
│   %7  = Base.vect(1, "123", %6)
│         @_2 = Base.iterate(%7)         ; % 迭代器
│   %9  = @_2
│   %10 = %9 === nothing
│   %11 = Base.not_int(%10)
└──       goto #4 if not %11
2 ┄ %13 = @_2                            ; % ==循环体==
│         i = Core.getfield(%13, 1)      ; % 循环变量
│   %15 = Core.getfield(%13, 2)          ; % 循环指数
│   %16 = Main.:+
│   %17 = a
│         a = (%16)(%17, 1)
│   %19 = i
│   %20 = a
│         Main.println(%19, %20)
│         @_2 = Base.iterate(%7, %15)
│   %23 = @_2
│   %24 = %23 === nothing
│   %25 = Base.not_int(%24)
└──       goto #4 if not %25
3 ─       goto #2
4 ┄       return nothing
)

5. Core 模块

6. Base 模块

7. Main 模块

8. code_lowered 源码分析

9. code_typed

   源码分析

10. Julia 内核笔记(非 Julia 源码的部分)

数据结构

调用过程

11. jl_task_t 是什么

12. 执行语法树

13. 测试

14. flisp 如何与 C 工作

15. 函数分派

   那该怎么调试呢?


致读者: 小时百科一直以来坚持所有内容免费无广告,这导致我们处于严重的亏损状态。 长此以往很可能会最终导致我们不得不选择大量广告以及内容付费等。 因此,我们请求广大读者热心打赏 ,使网站得以健康发展。 如果看到这条信息的每位读者能慷慨打赏 20 元,我们一周就能脱离亏损, 并在接下来的一年里向所有读者继续免费提供优质内容。 但遗憾的是只有不到 1% 的读者愿意捐款, 他们的付出帮助了 99% 的读者免费获取知识, 我们在此表示感谢。

                     

友情链接: 超理论坛 | ©小时科技 保留一切权利