计算Julia代码
Julia代码执行
以下是该过程的一般描述。
-
用户启动’julia'。
-
C函数’main()`从’cli/loader_exe调用。c'。 此函数通过填写`jl_options`结构并设置`ARGS`变量来处理命令行参数。 然后她初始化Julia(通过调用函数https://github.com/JuliaLang/julia/blob/master/src/init.c [`julia_init`in’init.c'],从而可以加载先前编译的系统映像, sysimg)。 最后,她把控制权交给了茱莉亚,打电话给她https://github.com/JuliaLang/julia/blob/master/base/client.jl ['基地。_start(’]。
-
当`_start()'获得控制权时,随后的命令序列取决于指定的命令行参数。 例如,如果指定了文件名,则将执行此文件。 否则,将启动交互式REPL循环。
-
省略有关REPL如何与用户交互的详细信息,我们只说程序以它想要执行的代码块结束。
-
如果要执行的代码块在文件中,则调用https://github.com/JuliaLang/julia/blob/master/src/toplevel.c [`jl_load(char*filename)']上传文件及其 分析。 然后将每段代码传递给’eval’执行。
-
每段代码(或AST)都传递给 `eval()'转换为结果。
-
'eval()
获取每个代码片段并尝试在https://github.com/JuliaLang/julia/blob/master/src/toplevel.c['jl_toplevel_eval_flex()]。
-
`jl_toplevel_eval_flex()`确定代码是否是函数内部不允许的顶级操作(例如,`using`或`module')。 如果是这种情况,则将代码传递给顶级解释器。
-
然后`jl_toplevel_eval_flex()` 展开代码以消除任何宏并降低AST以简化其执行。
-
之后,'jl_toplevel_eval_flex()它使用一些简单的启发式过程来决定是为AST执行JIT编译还是直接解释它。
-
解释代码的主要部分是由https://github.com/JuliaLang/julia/blob/master/src/interpreter.c [`eval`in’解释器。c']。
-
如果代码被编译,它会完成大部分工作。 "科德根。cpp'。 每当第一次使用给定的参数类型调用Julia函数时,它将被执行 类型推断。 此信息在代码生成阶段使用(codegen)来创建更快的代码。
-
最终,用户退出REPL或到达程序结束,并返回`_start()'方法。
-
就在退出之前,
main()'调用https://github.com/JuliaLang/julia/blob/master/src/init.c ['jl_atexit_hook(exit_code)
]。 这将调用函数’Base。_atexit()'(调用在atexit()'
内Julia)。 然后调用函数https://github.com/JuliaLang/julia/blob/master/src/gc.c ['jl_gc_run_all_finalizers()]。 因此,它正确地清除所有’libuv’处理程序并等待它们被重置并关闭。
分析
Julia分析器是一个用femtolisp语言编写的小型lisp程序,其源代码位于文件夹中的Julia内部https://github.com/JuliaLang/julia/tree/master/src/flisp [src/flisp]。
其接口函数主要定义在https://github.com/JuliaLang/julia/blob/master/src/jlfrontend.scm ['jlfrontend.scm']。 代码在https://github.com/JuliaLang/julia/blob/master/src/ast.c ['ast.C']在朱莉娅那边处理这种转移。
在此阶段的其他重要文件是https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm ['朱莉娅-解析器。scm'],它处理Julia代码的标记并将其转换为AST,以及https://github.com/JuliaLang/julia/blob/master/src/julia-syntax.scm ['朱莉娅-语法。scm'],其处理将复杂AST表示转换为更适合于分析和执行的简单缩减AST表示。
如果你想在不完全重建Julia的情况下测试分析器,你可以自己运行接口部分,如下所示。
$cd src $flisp/flisp >(加载"jlfrontend.scm") >(jl-解析-文件"<文件名>")
宏观扩张
当功能 eval()'
检测到一个宏,它在尝试评估表达式之前扩展这个AST节点。 宏观扩张涉及从 `eval()(在Julia中)到分析器函数`jl_macroexpand()`(写在’flisp’中)到Julia宏本身(写在Julia的某个地方)使用函数`fl_invoke_julia_macro()'并返回。
扩展通常被激活作为呼叫期间的第一步。 '元。lower()/'jl_expand()',虽然也可以通过调用直接发起 'macroexpand()/`jl_macroexpand()'。
类型推断
在Julia中,使用函数实现类型推断https://github.com/JuliaLang/julia/blob/master/base/compiler/typeinfer.jl 文件`compiler/typeinfer中的[`typeinf()'。jl']。 类型推断是检查Julia函数并确定其每个变量的类型边界以及函数返回值的类型边界的过程。 这允许实现许多未来的优化措施,例如解包已知的不可变值,以及在编译期间引发各种计算操作,例如计算字段偏移量和函数指针。 类型推断还可以包含常数传播和嵌入等其他步骤。
更多的定义
*JIT 在线编译(JIT编译)。 在需要时将自己的机器代码生成到内存中的过程。 *LLVM 一个低级虚拟机(编译器)。 Julia JIT编译器是一个名为libLLVM的程序或库。 Julia中的代码生成既指将Julia的AST转换为LLVM指令的过程,也指优化LLVM指令并将其转换为本机汇编指令的过程。 *C++ LLVM编译器实现的编程语言,这意味着代码生成也用这种语言实现。 Julia库的其余部分是用C实现的,部分原因是其较小的功能集使其更方便地用作多种语言的接口层。 *包装 此术语用于描述获取值并包装由垃圾收集器跟踪并用对象类型标记的数据的过程。 *拆箱 义的包装相反的术语。 当在编译时(通过类型推断)完全知道这些数据的类型时,此操作允许更有效的数据管理。 *通用功能 一个Julia函数,由几个方法组成,这些方法根据参数类型的签名被选择用于动态调度。 *匿名函数或方法 Julia函数没有名称,也没有类型调度功能。 *原始函数 在C中实现的函数,但在Julia中作为命名函数方法引入(尽管没有通用函数调度功能:类似于匿名函数)。 *内部功能 在Julia中作为函数呈现的低级操作。 这些伪函数用原始位实现操作,例如加法和符号扩展,这些操作不能以任何其他方式直接表示。 由于它们直接与位一起工作,因此必须将它们编译为函数并由"核心"调用包围。内在。箱(T,。..)'重写值的类型信息。 |
JIT代码生成
代码生成是将Julia的AST转换为本机机器代码的过程。
JIT环境通过提前调用进行初始化https://github.com/JuliaLang/julia/blob/master/src/codegen.cpp [`jl_init_codegen’在’codegen。cpp']。
根据请求,Julia方法使用函数`emit_function(jl_method_instance_t*)`转换为自己的函数。 (请注意,当使用MCJIT(在LLVM v3.4及更高版本中)时,每个函数都必须JIT到一个新模块中。)此函数递归调用’emit_expr()'函数,直到发出整个函数。
本文档的其余大部分内容专门讨论特定代码模式的各种手动优化。 例如,函数’emit_known_call(’知道如何嵌入许多基元函数(在https://github.com/JuliaLang/julia/blob/master/src/builtins.c ['建造。c'])的参数类型的各种组合。
代码生成过程的其他部分由各种辅助文件处理。
-
处理JIT函数的回溯
-
处理FFI ccall和llvmcall,以及各种’abi_*。cpp’档案
-
处理各种低级内部函数的输出
引导/引导
创建系统映像的过程称为引导。 |
这个词来自英文短语pulling yourself up by the bootstraps(实现自己的一切),意思是从一组非常有限的可用功能和定义开始,最终创建一个功能齐全的环境。
系统图像
系统映像是一组Julia文件的预编译存档。 文件’sys.ji’distributed with Julia是通过执行文件创建的这些系统映像之一https://github.com/JuliaLang/julia/blob/master/base/sysimg.jl ['sysimg.jl']并将生成的环境(包括类型,函数,模块和所有其他定义的值)序列化为文件。 因此,它包含"Main","Core"和"Base"模块的静态版本(以及引导结束时环境中的其他所有内容)。 此串行器或解串器是使用函数实现的https://github.com/JuliaLang/julia/blob/master/src/staticdata.c ['staticdata’中的`jl_save_system_image’或’jl_restore_system_image'。c’文件]。
如果sysimg文件丢失('jl_options.image_file==NULL'),这也意味着在命令行上指定了'--build’选项,因此最终结果应该是一个新的sysimg文件。 在Julia初始化期间,创建最小模块’Core’和’Main'。 然后是一个名为’boot.jl’从当前目录计算。 之后,Julia计算指定为命令行参数的任何文件,直到它到达末尾。 最后,它将生成的环境保存到sysimg文件中,以用作将来执行的起点。