erlang编译系列之流程概括

我们从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二进制文件
    ].

你可能感兴趣的