我们从compile:file/1函数入手,先梳理下编译的流程。
下面是抽丝剥茧后一些核心的代码:
file(File) -> file(File, [verbose,report_errors,report_warnings]).
//file会调用到
internal({file,File}, Opts) ->
{Ext,Ps} = passes(file, Opts), //操作列表
Compile = #compile{options=Opts,mod_options=Opts},
internal_comp(Ps, none, File, Ext, Compile).// 执行操作列表中的操作
先解析internal_comp的实现,看看都支持些什么操作
internal_comp(Passes, Code0, File, Suffix, St0) ->
//此处省去St1初始化的代码 大致就是填充#compile结构
Opts = St1#compile.options,
Run0 = case member(time, Opts) of
//如果Opts设定了time,则返回run_tc
true ->
io:format("Compiling ~tp\n", [File]),
fun run_tc/3;
//run_tc 比起下面的fun函数,会打印出编译各阶段的内存和时间消耗
false ->
fun({_Name,Fun}, Code, St) ->
catch Fun(Code, St)
end
end,
//遍历解析和执行Passes(即操作列表)
fold_comp(Passes, Run0, Code0, St1).
Passes都包含些什么格式,以及fold_comp是如何解析和执行其中的操作的呢?
我们先看看fold_comp的代码:
fold_comp([{delay,Ps0}|Passes], Run, Code, #compile{options=Opts}=St) ->
//当指令为delay时,会对操作进行额外处理
Ps = select_passes(Ps0, Opts) ++ Passes,
fold_comp(Ps, Run, Code, St);
fold_comp([{Name,Test,Pass}|Ps], Run, Code, St) ->
//会先执行 Test(St),为True时才执行当前Pass,否则跳过;
fold_comp([{Name,Pass}|Ps], Run, Code0, St0) ->
//参考Run的实现,可得实际执行的是 Pass(Code0, St0)
case Run({Name,Pass}, Code0, St0) of
{ok,Code,St1} -> fold_comp(Ps, Run, Code, St1);
Error -> Error;
end;
fold_comp([], _Run, Code, St) -> {ok,Code,St}.
我们接着看看select_passes都干了些啥,了解后就对Passes的操作了然于胸了
select_passes([Command], Opts) -> [{Name,Function}]
Command可能是以下格式:
{pass,Mod}
会被拓展为对外部函数的调用, Mod:module(Code, Options)
return为:{ok,NewCode} 或者 {error,T}
{Name,Fun}
不做转换, 即执行 Fun(Code, St)
{Name,Test,Fun}
类似{Name,Fun},但是只有当Test(St)才会执行
{src_listing,Ext}
将当前的信息生成为以Ext为后缀的文件,并不再执行之后的Passes
{listing,Ext}
将当前的内部形式的term列表生成以Ext为后缀的文件,并不在执行之后的Passes
done
结束操作流程
{done,Ext}
结束操作流程,并将当前指令转换为 {unless,binary,{listing,Ext}}
{iff,Flag,Cmd}
如果给定了Flag标志,Cmd将被解释为Pass并执行,否则忽略
{unless,Flag,Cmd}
如果没有给定Flag标志,Cmd将被解释为Pass并执行,否则忽略
到此为止,我们已经知道Passes中的指令是如何处理的了,接下来我们看看编译过程中需要执行的Passes
//注此处为删减版,且不同版本的erl,Passes会有出入,但核心编译流程是基本一致的
-define(pass(P), {P,fun P/2}).
-define(pass(P,T), {P,fun T/1,fun P/2}).
Passes列表:
//compile:parse_module/2
//代码转化为抽象格式,抽象格式介绍见https://erlang.org/doc/apps/erts/absform.html
[?pass(parse_module)|standard_passes()]
standard_passes() ->
[?pass(transform_module),
//处理通过{parse_transform, Module}配置的转换规则
//如我们使用lager库时,配置的{parse_transform, lager_transform},就是在此处进行处理
{iff,'dpp',{listing,"pp"}},//若Opts中定义了dpp,则根据执行到此处的信息生成.pp文件
?pass(lint_module),
//此模块用于检查代码是否存在非法语法和其他错误,还会警告使用了不推荐的编码方法。
//检测的错误包括:
// 重定义和未定义的函数
// 未绑定和不安全的变量
// 非法使用record
//检测的警告包括:
// 未使用的函数和导入
// 未使用的变量
// 将变量导入匹配
// 从if / case / receive导出的变量
// 隐藏在fun和列表推导中的变量
//一些警告是可选的,可以通过指定适当的选项将其打开
{iff,'P',{src_listing,"P"}},//若Opts中定义了P,则根据执行到此处的信息生成.P文件
{iff,'to_pp',{done,"P"}},//若Opts中定义了to_pp,则根据执行到此处的信息生成.P文件
?pass(expand_records),//expand_records 展开record的相关操作,并将显示的模块名称添加到对BIF和导入函数(import)的调用中 如源码中的 is_list 会被扩展为 erlang:is_list
{iff,'dexp',{listing,"expand"}},//生成相关文件
{iff,'E',{src_listing,"E"}},//生成相关文件
{iff,'to_exp',{done,"E"}},//生成相关文件
?pass(core),//抽象格式转换为Core代码, Core码在R7B版本引入的
{iff,'dcore',{listing,"core"}}, //生成相关文件
{iff,'to_core0',{done,"core"}} //生成相关文件
| core_passes()].
core_passes() ->
[
{pass,sys_core_fold},
//sys_core_fold 优化内容有:
// 常量替换,如有语句 a() -> A = 10, A. 优化为 a()-> 10.
// [A]++ListB,优化为 [A|ListB]
// case语句优化,删除绝对无法匹配的分支,删除绝对会匹配的分支之后的分支
// 等
{pass,sys_core_alias},
// 避免重建已有的数据,如有
// f({A,B})-> [{A,B},A+B]. 优化前会分别绑定A和B,再使用A和B构建元组结构
// 优化后语句等同于 f({A,B}=C)-> [C,A+B]. 减少了新建元组的消耗
?pass(core_transforms),
//作用类似?pass(transform_module),差异在于core_transforms是对core码进行处理
{iff,'to_core',{done,"core"}}//生成相关文件
| kernel_passes()].
kernel_passes() ->
[{pass,sys_core_bsm},//添加一些批注,用于后续 codegen和beam_asm处理时优化binary的匹配
?pass(v3_kernel),//Core码转换为Kernel码, Kernel码在R6B版本引入的
{pass,beam_kernel_to_ssa},//Kernel码转换为SSA码,SSA码于18年1月开始着手引入
{pass,beam_ssa_bool},//优化保护语句中的布尔表达式
//如语句 when is_integer(V0), is_atom(V1) ->
//未经优化前:
// bif is_integer V0 => Bool0
// bif is_atom V1 => Bool1
// bif and Bool0 Bool1 => Bool
// test Bool =:= true else goto Fail
// ...
// Fail:
// ...
//优化后:
// test is_integer V0 else goto Fail
// test is_atom V1 else goto Fail
// ...
// Fail:
// ...
{pass,beam_ssa_share},//共享 ssa码switch或br语句中等效的分支代码
{pass,beam_ssa_bsm},
//优化位匹配 如:
//未经优化前:
// <<0,B/bits>> = A,
// <<1,C/bits>> = B,
// <> = C,
// D.
//优化后:
// <<0,1,D,_/bits>>=A,
// D.
{pass,beam_ssa_funs},
//优化仅用于执行的匿名函数
//如F = fun() -> skip end, F().
//编译时会给F起别名,如-test/1-fun-0-,若F仅用于本地调用的话,
//执行F(),会优化为调用 -test/1-fun-0-()
{pass,beam_ssa_opt},//不需要专门的pass操作的优化集合
{pass,beam_ssa_recv},//优化receive语句
{pass,beam_ssa_pre_codegen},
//为ssa_codegen做准备 如:
//为需要分配堆栈的指令,打上 {frame_size,Size}注释
//为需要存储到堆栈的变量,添加复制指令
//等
{pass,beam_ssa_codegen},//从ssa码生成beam汇编码
?pass(beam_validator_strong),
//验证,在生成的代码中查找可能导致模拟器崩溃或异常的代码
| asm_passes()].
asm_passes() ->
[{pass,beam_a}, //beam码优化的准备工作
{pass,beam_block},//将beam指令划分为不同的block
{pass,beam_jump},//优化跳转并删除无法访问的代码
{pass,beam_peep},//窥孔优化:编译器在一个基本块或者多个基本块中,结合当前CPU指令的特点,通过可能带来性能提升的转换规则或者整体的分析,通过指令转换,提升代码性能
{pass,beam_clean}, //清除不会用到的代码
{pass,beam_flatten}, //优化beam汇编码格式
{pass,beam_z}, //在beam_asm运行前进行最后的修复或清理。
{iff,'S',{listing,"S"}},//生成相关文件
{iff,'to_asm',{done,"S"}},//生成相关文件
?pass(beam_validator_weak),//类似beam_validator_strong
?pass(beam_asm)//beam汇编码转为beam二进制文件
| binary_passes()].
binary_passes() ->
[{iff,'to_dis',?pass(to_dis)},//生成相关文件
?pass(save_binary,not_werror)//存储beam二进制文件
].