先看將要分析的實例的Erlang源代碼:
%% mytest.erl
-module(mytest).
-compile(export_all).
%% 返回atom
t1() ->
ok.
%% 調用BIF
t2(A) ->
integer_to_list(A).
%% 加法運算
t3(A) ->
A + 10.
%% 調用內部函數
t4(A) ->
t3(A).
%% 函數分支
t5(0) -> 0;
t5(A) -> A + 1.
%% 尾遞歸
t6([], Result) -> Result;
t6([H | T], Result) ->
H1 = H + 1,
t6(T, [H1 | Result]).
erlc +"'S'" test.erl
生成彙編文件:
%% test.S
{module, mytest}. %% version = 0
{exports, [{module_info,0},
{module_info,1},
{t1,0},
{t2,1},
{t3,1},
{t4,1},
{t5,1},
{t6,2}]}.
{attributes, []}.
{labels, 19}.
%% 返回atom
{function, t1, 0, 2}.
%% label就是標號,或者稱爲代碼標籤,
%% 在跳轉時,這是一個程序的入口點
{label,1}.
{line,[{location,"mytest.erl",5}]}.
{func_info,{atom,mytest},{atom,t1},0}.
{label,2}.
%% move指令,將原子“ok”送寄存器0
{move,{atom,ok},{x,0}}.
%% 返回
return.
%% 調用BIF
%% t2(A) ->
%% integer_to_list(A).
{function, t2, 1, 4}.
{label,3}.
{line,[{location,"mytest.erl",9}]}.
{func_info,{atom,mytest},{atom,t2},1}.
{label,4}.
{line,[{location,"mytest.erl",10}]}.
%% 調用BIF integer_to_list
{call_ext_only,1,{extfunc,erlang,integer_to_list,1}}.
%% 加法運算
%% t3(A) ->
%% A + 10.
{function, t3, 1, 6}.
{label,5}.
{line,[{location,"mytest.erl",13}]}.
{func_info,{atom,mytest},{atom,t3},1}.
{label,6}.
{line,[{location,"mytest.erl",14}]}.
%% 調用Guard BIF,+操作符也是一個BIF函數,
%% 參數爲[{x,0},{integer,10}],然後把結果送R0,即R0累加立即數10,
%% 如果運行出錯,則跳轉到label 0(這裏是拋出錯誤)
%% {f,0}後面的1是幹什麼用的?(見注1)
{gc_bif,'+',{f,0},1,[{x,0},{integer,10}],{x,0}}.
return.
%% 調用內部函數
{function, t4, 1, 8}.
{label,7}.
{line,[{location,"mytest.erl",17}]}.
{func_info,{atom,mytest},{atom,t4},1}.
{label,8}.
%% 跳轉到label 6,即調用函數t3
{call_only,1,{f,6}}.
%% 函數分支
%% t5(0) -> 0;
%% t5(A) -> A + 1.
{function, t5, 1, 10}.
{label,9}.
{line,[{location,"mytest.erl",21}]}.
{func_info,{atom,mytest},{atom,t5},1}.
{label,10}.
%% 測試R0是否等於0
{test,is_eq_exact,{f,11},[{x,0},{integer,0}]}.
%% 如果測試成功則返回,否則跳到label 11
return.
{label,11}.
{line,[{location,"mytest.erl",22}]}.
%% 執行加法運算
{gc_bif,'+',{f,0},1,[{x,0},{integer,1}],{x,0}}.
return.
%% 尾遞歸
%% t6([], Result) -> Result;
%% t6([H | T], Result) ->
%% H1 = H + 1,
%% t6(T, [H1 | Result]).
{function, t6, 2, 13}.
{label,12}.
{line,[{location,"mytest.erl",25}]}.
{func_info,{atom,mytest},{atom,t6},2}.
{label,13}.
%% 測試R0是否是一個非空list,如果不是則跳轉到{label,14}
{test,is_nonempty_list,{f,14},[{x,0}]}.
%% R0保存的是一個list指針
%% 從list(R0)頭部取出一個元素送R2,剩餘的送R3,
%% 相當於:[H|T] = R0, R2 = H, R3 = T
{get_list,{x,0},{x,2},{x,3}}.
{line,[{location,"mytest.erl",27}]}.
%% R2 + 1 結果送 R0
%% {f,0}後面的4是幹什麼用的?(見注1)
{gc_bif,'+',{f,0},4,[{x,2},{integer,1}],{x,0}}.
%% 測試堆空間至少有2 words,4爲Live
%% 這裏傳入的Live值有什麼作用?爲了GC嗎?
{test_heap,2,4}.
%% HTOP[0] = R0, HTOP[1] = R1, R1 = &HTOP[0]
{put_list,{x,0},{x,1},{x,1}}.
%% R3送R0,R3即是上面剩餘的list
{move,{x,3},{x,0}}.
%% 目前已經爲下一次的調用準備好了R0和R1這兩個參數
%% 調用自已{label,13}
%% 生成opcode時,call_only會合並上面的move一起生成move_call_only_xrf x(3) x(0) mytest:t6/2 ???
{call_only,2,{f,13}}.
{label,14}.
%% 測試R0是否爲空list,如果不爲空則跳轉到{label,12}報錯
{test,is_nil,{f,12},[{x,0}]}.
%% 將R1送R0,這是函數的最後返回結果
{move,{x,1},{x,0}}.
return.
%% 以下兩個函數,你懂的,不解釋。
{function, module_info, 0, 16}.
{label,15}.
{line,[]}.
{func_info,{atom,mytest},{atom,module_info},0}.
{label,16}.
{move,{atom,mytest},{x,0}}.
{line,[]}.
{call_ext_only,1,{extfunc,erlang,get_module_info,1}}.
{function, module_info, 1, 18}.
{label,17}.
{line,[]}.
{func_info,{atom,mytest},{atom,module_info},1}.
{label,18}.
{move,{x,0},{x,1}}.
{move,{atom,mytest},{x,0}}.
{line,[]}.
{call_ext_only,2,{extfunc,erlang,get_module_info,2}}.
注1:很多函數調用的指令後面都會有一個數字,不太理解這個數字是幹什麼用的。Google了一會,沒找到答案。可能它是一個地址值,作爲被調用函數的運行空間的起始地址。但這只是一個猜測,即使是這樣,這個值又是怎麼樣計算出來的?
現以加法爲例查找一下這個數字的來源。
在ops.tab中查找gc_bif,有如下結果:
#
# Optimize addition and subtraction of small literals using
# the i_increment/4 instruction (in bodies, not in guards).
#
gc_bif2 p Live u$bif:erlang:splus/2 Int=i Reg=d Dst => \
gen_increment(Reg, Int, Live, Dst)
gc_bif2 p Live u$bif:erlang:splus/2 Reg=d Int=i Dst => \
gen_increment(Reg, Int, Live, Dst)
再從beam_load.c中找到如下結果:
static GenOp* gen_increment(LoaderState* stp, GenOpArg Reg, GenOpArg Integer, GenOpArg Live, GenOpArg Dst) { GenOp* op; NEW_GENOP(stp, op); op->op = genop_i_increment_4; op->arity = 4; op->next = NULL; op->a[0] = Reg; op->a[1].type = TAG_u; op->a[1].val = Integer.val; op->a[2] = Live; // 找的就是你!!! op->a[3] = Dst; return op; }
原來它是一個Live值。
被調用的函數只能使用Live值以上的寄存器,這樣就不會覆蓋正在使用的寄存器值,使用這種方法,也省去了使用棧。但是,這種方法不會導致寄存器不夠用嗎?或許只有簡單函數的調用才做了這樣的優化。