`
844604778
  • 浏览: 550485 次
文章分类
社区版块
存档分类
最新评论

实例分析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值以上的寄存器,这样就不会覆盖正在使用的寄存器值,使用这种方法,也省去了使用栈。但是,这种方法不会导致寄存器不够用吗?或许只有简单函数的调用才做了这样的优化。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics