實例分析Erlang的彙編指令

《Erlang虛擬機(VM)簡介》一文中介紹的寄存器和指令參數類型是學習Erlang VM相關知識的基礎,也是理解本文內容的前提,這裏就不贅述了。

先看將要分析的實例的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值以上的寄存器,這樣就不會覆蓋正在使用的寄存器值,使用這種方法,也省去了使用棧。但是,這種方法不會導致寄存器不夠用嗎?或許只有簡單函數的調用才做了這樣的優化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章