[Erlang 0125] Know a little Erlang opcode



    Erlang源代碼編譯為beam文件,代碼要經過一系列的過程(見下面的簡圖),Core Erlang之前已經簡單介紹過了Core Erlang,代碼轉換為Core Erlang,就容易撥開一些語法糖的真面目了.下一階段就是將Core Erlang轉換為opcode,使用c(m,'S')生成的是.S文件可以看到反編譯的代碼.編譯器最終輸出的是Virtual Beam Code 但這還不是最終VM執行的代碼,在erts\emulator\beam\beam_load.c執行的過程中會完成指令優化,優化之后的代碼可以通過erts_debug:df(m)生成.dis文件查看,換句話說,這個方法執行成功必須要要求模塊已經加載.

 

下面的代碼里面,模塊t還沒有編譯,所以第一次執行erts_debug:df(t)返回值是{undef,t}

Eshell V6.0  (abort with ^G)
1> c(t,'S').
** Warning: No object file created - nothing loaded **
ok
2> erts_debug:df(t).
{undef,t}
3> c(t).
{ok,t}
4> erts_debug:df(t).
ok
5> q().
ok
6>

  

   之前有兩次使用過反匯編的代碼:

 

    [Erlang 0029] Erlang Inline編譯 [鏈接] 里面我們使用過反編譯的代碼觀察inline機制.
    [Erlang 0100] make_ref 與 Selective Receive [鏈接]通過反匯編代碼看到selective receive的優化處理.


    除了Erlang源代碼幾乎沒有什么資料可供參考.有兩個方法可以繼續跟蹤下去,一是通過erts_debug:instructions()輸出所有命令,再就是編寫erl代碼對照.S代碼去推測意義.霸爺有一篇<實驗Erlang語法對應的opcode 讓你對erlang理解更深>就是這樣做的,下面把我比較關注的一些地方做下測試.

 

Eshell V6.0  (abort with ^G)
1> rp(erts_debug:instructions()).
["allocate_tt","allocate_heap_tIt","allocate_heap_zero_tIt",
"allocate_init_tIy","allocate_zero_tt","apply_I",
"apply_bif","apply_last_IP","badarg_j","badmatch_r",
"badmatch_x","badmatch_y","bif1_fbsd","bif1_body_bsd",
"bs_context_to_binary_r","bs_context_to_binary_x",
"bs_context_to_binary_y","bs_init_writable",
"bs_put_string_II","bs_test_tail_imm2_frI",
"bs_test_tail_imm2_fxI","bs_test_unit_frI",
"bs_test_unit_fxI","bs_test_unit8_fr","bs_test_unit8_fx",
"bs_test_zero_tail2_fr","bs_test_zero_tail2_fx",
"call_bif_e","call_error_handler","call_nif","case_end_r",
"case_end_x","case_end_y","catch_yf","catch_end_y",
"continue_exit","deallocate_I","deallocate_return_Q",
"error_action_code","extract_next_element_x",
"extract_next_element_y","extract_next_element2_x",
"extract_next_element2_y","extract_next_element3_x",
"extract_next_element3_y","fclearerror","fconv_dl",
"fmove_ql","fmove_ld","fmove_dl","get_list_rrx",
"get_list_rry","get_list_rxr","get_list_rxx","get_list_rxy",
"get_list_ryr","get_list_ryx","get_list_ryy","get_list_xrx",
"get_list_xry","get_list_xxr","get_list_xxx","get_list_xxy",
"get_list_xyr","get_list_xyx","get_list_xyy","get_list_yrx",
"get_list_yry","get_list_yxr","get_list_yxx","get_list_yxy",
"get_list_yyr","get_list_yyx","get_list_yyy",
"hipe_call_count","hipe_trap_call","hipe_trap_call_closure",
"hipe_trap_resume","hipe_trap_return","hipe_trap_throw",
"i_apply","i_apply_fun","i_apply_fun_last_P",
"i_apply_fun_only","i_apply_last_P","i_apply_only",
"i_band_jId","i_bif2_fbd","i_bif2_body_bd","i_bor_jId",
"i_bs_add_jId","i_bs_append_jIIId",
"i_bs_get_binary2_frIsId","i_bs_get_binary2_fxIsId",
"i_bs_get_binary_all2_frIId","i_bs_get_binary_all2_fxIId",
"i_bs_get_binary_all_reuse_rfI",
"i_bs_get_binary_all_reuse_xfI",
"i_bs_get_binary_imm2_frIIId","i_bs_get_binary_imm2_fxIIId",
"i_bs_get_float2_frIsId","i_bs_get_float2_fxIsId",
"i_bs_get_integer_fIId","i_bs_get_integer_16_rfd",
"i_bs_get_integer_16_xfd","i_bs_get_integer_32_rfId",
"i_bs_get_integer_32_xfId","i_bs_get_integer_8_rfd",
"i_bs_get_integer_8_xfd","i_bs_get_integer_imm_rIIfId",
"i_bs_get_integer_imm_xIIfId",
"i_bs_get_integer_small_imm_rIfId",
"i_bs_get_integer_small_imm_xIfId","i_bs_get_utf16_rfId",
"i_bs_get_utf16_xfId","i_bs_get_utf8_rfd",
"i_bs_get_utf8_xfd","i_bs_init_IId","i_bs_init_bits_IId",
"i_bs_init_bits_fail_rjId","i_bs_init_bits_fail_xjId",
"i_bs_init_bits_fail_yjId","i_bs_init_bits_fail_heap_IjId",
"i_bs_init_bits_heap_IIId","i_bs_init_fail_rjId",
"i_bs_init_fail_xjId","i_bs_init_fail_yjId",
"i_bs_init_fail_heap_IjId","i_bs_init_heap_IIId",
"i_bs_init_heap_bin_IId","i_bs_init_heap_bin_heap_IIId",
"i_bs_match_string_rfII","i_bs_match_string_xfII",
"i_bs_private_append_jId","i_bs_put_utf16_jIs",
"i_bs_put_utf8_js","i_bs_restore2_rI","i_bs_restore2_xI",
"i_bs_save2_rI","i_bs_save2_xI","i_bs_skip_bits2_frxI",
"i_bs_skip_bits2_fryI","i_bs_skip_bits2_fxrI",
"i_bs_skip_bits2_fxxI","i_bs_skip_bits2_fxyI",
"i_bs_skip_bits_all2_frI","i_bs_skip_bits_all2_fxI",
"i_bs_skip_bits_imm2_frI","i_bs_skip_bits_imm2_fxI",
"i_bs_start_match2_rfIId","i_bs_start_match2_xfIId",
"i_bs_start_match2_yfIId","i_bs_utf16_size_sd",
"i_bs_utf8_size_sd","i_bs_validate_unicode_js",
"i_bs_validate_unicode_retract_j","i_bsl_jId","i_bsr_jId",
"i_bxor_jId","i_call_f","i_call_ext_e","i_call_ext_last_eP",
"i_call_ext_only_e","i_call_fun_I","i_call_fun_last_IP",
"i_call_last_fP","i_call_only_f","i_debug_breakpoint",
"i_element_rjsd","i_element_xjsd","i_element_yjsd",
"i_fadd_lll","i_fast_element_rjId","i_fast_element_xjId",
"i_fast_element_yjId","i_fcheckerror","i_fdiv_lll",
"i_fetch_rx","i_fetch_ry","i_fetch_xr","i_fetch_xx",
"i_fetch_xy","i_fetch_yr","i_fetch_yx","i_fetch_yy",
"i_fetch_rc","i_fetch_xc","i_fetch_yc","i_fetch_cr",
"i_fetch_cx","i_fetch_cy","i_fetch_cc","i_fetch_ss",
"i_fmul_lll","i_fnegate_ll","i_fsub_lll","i_func_info_IaaI",
"i_gc_bif1_jIsId","i_gc_bif2_jIId","i_gc_bif3_jIsId",
"i_generic_breakpoint","i_get_sd","i_get_map_element_frar",
"i_get_map_element_frax","i_get_map_element_fray",
"i_get_map_element_frxr","i_get_map_element_frxx",
"i_get_map_element_frxy","i_get_map_element_fxar",
"i_get_map_element_fxax","i_get_map_element_fxay",
"i_get_map_element_fxxr","i_get_map_element_fxxx",
"i_get_map_element_fxxy","i_get_map_element_fyar",
"i_get_map_element_fyax","i_get_map_element_fyay",
"i_get_map_element_fyxr","i_get_map_element_fyxx",
"i_get_map_element_fyxy","i_get_map_elements_fsI",
"i_get_tuple_element_rPr","i_get_tuple_element_rPx",
"i_get_tuple_element_rPy","i_get_tuple_element_xPr",
"i_get_tuple_element_xPx","i_get_tuple_element_xPy",
"i_get_tuple_element_yPr","i_get_tuple_element_yPx",
"i_get_tuple_element_yPy","i_has_map_field_fra",
"i_has_map_field_frr","i_has_map_field_frx",
"i_has_map_field_fry","i_has_map_field_fxa",
"i_has_map_field_fxr","i_has_map_field_fxx",
"i_has_map_field_fxy","i_has_map_field_fya",
"i_has_map_field_fyr","i_has_map_field_fyx",
"i_has_map_field_fyy","i_has_map_fields_fsI","i_hibernate",
"i_increment_rIId","i_increment_xIId","i_increment_yIId",
"i_int_bnot_jsId","i_int_div_jId","i_is_eq_f",
"i_is_eq_exact_f","i_is_eq_exact_immed_frc",
"i_is_eq_exact_immed_fxc","i_is_eq_exact_immed_fyc",
"i_is_eq_exact_literal_rfc","i_is_eq_exact_literal_xfc",
"i_is_eq_exact_literal_yfc","i_is_ge_f","i_is_lt_f",
"i_is_ne_f","i_is_ne_exact_f","i_is_ne_exact_immed_frc",
"i_is_ne_exact_immed_fxc","i_is_ne_exact_immed_fyc",
"i_is_ne_exact_literal_rfc","i_is_ne_exact_literal_xfc",
"i_is_ne_exact_literal_yfc","i_jump_on_val_rfII",
"i_jump_on_val_xfII","i_jump_on_val_yfII",
"i_jump_on_val_zero_rfI","i_jump_on_val_zero_xfI",
"i_jump_on_val_zero_yfI","i_loop_rec_fr","i_m_div_jId",
"i_make_fun_It","i_minus_jId","i_move_call_crf",
"i_move_call_ext_cre","i_move_call_ext_last_ePcr",
"i_move_call_ext_only_ecr","i_move_call_last_fPcr",
"i_move_call_only_fcr","i_new_bs_put_binary_jsIs",
"i_new_bs_put_binary_all_jsI","i_new_bs_put_binary_imm_jIs",
"i_new_bs_put_float_jsIs","i_new_bs_put_float_imm_jIIs",
"i_new_bs_put_integer_jsIs","i_new_bs_put_integer_imm_jIIs",
"i_plus_jId","i_put_tuple_rI","i_put_tuple_xI",
"i_put_tuple_yI","i_recv_set","i_rem_jId",
"i_return_time_trace","i_return_to_trace",
"i_select_tuple_arity_rfI","i_select_tuple_arity_xfI",
"i_select_tuple_arity_yfI","i_select_tuple_arity2_rfAfAf",
"i_select_tuple_arity2_xfAfAf",
"i_select_tuple_arity2_yfAfAf","i_select_val_rfI",
"i_select_val_xfI","i_select_val_yfI",
"i_select_val2_rfcfcf","i_select_val2_xfcfcf",
"i_select_val2_yfcfcf","i_times_jId","i_trim_I",
"i_wait_error","i_wait_error_locked","i_wait_timeout_fI",
"i_wait_timeout_fs","i_wait_timeout_locked_fI",
"i_wait_timeout_locked_fs","i_yield","if_end","init_y",
"init2_yy","init3_yyy","int_code_end","is_atom_fr",
"is_atom_fx","is_atom_fy","is_binary_fr","is_binary_fx",
"is_binary_fy","is_bitstring_fr","is_bitstring_fx",
"is_bitstring_fy","is_boolean_fr","is_boolean_fx",
"is_boolean_fy","is_float_fr","is_float_fx","is_float_fy",
"is_function_fr","is_function_fx","is_function_fy",
"is_function2_fss","is_integer_fr","is_integer_fx",
"is_integer_fy","is_integer_allocate_frII",
"is_integer_allocate_fxII","is_list_fr","is_list_fx",
"is_list_fy","is_map_fr","is_map_fx","is_map_fy",
"is_nil_fr","is_nil_fx","is_nil_fy",
"is_non_empty_list_test_heap_frIt","is_nonempty_list_fr",
"is_nonempty_list_fx","is_nonempty_list_fy",
"is_nonempty_list_allocate_frIt",
"is_nonempty_list_allocate_fxIt","is_number_fr",
"is_number_fx","is_number_fy","is_pid_fr","is_pid_fx",
"is_pid_fy","is_port_fr","is_port_fx","is_port_fy",
"is_reference_fr","is_reference_fx","is_reference_fy",
"is_tuple_fr","is_tuple_fx","is_tuple_fy",
"is_tuple_of_arity_frA","is_tuple_of_arity_fxA",
"is_tuple_of_arity_fyA","jump_f","label_L","line_I",
"loop_rec_end_f","move_nr","move_nx","move_rx","move_ry",
"move_xr","move_xx","move_xy","move_yr","move_yx","move_yy",
"move_cr","move_cx","move2_xxxx","move2_xyxy","move2_yxyx",
"move_call_xrf","move_call_yrf","move_call_last_xrfQ",
"move_call_last_yrfQ","move_call_only_xrf",
"move_deallocate_return_nrQ","move_deallocate_return_xrQ",
"move_deallocate_return_yrQ","move_deallocate_return_crQ",
"move_jump_fn","move_jump_fx","move_jump_fy","move_jump_fc",
"move_return_nr","move_return_xr","move_return_cr",
"move_x1_c","move_x2_c","new_map_jdII","node_r","node_x",
"node_y","normal_exit","on_load","put_list_rnx",
"put_list_rxr","put_list_rxx","put_list_ryx","put_list_xnx",
"put_list_xrr","put_list_xrx","put_list_xxr","put_list_xxx",
"put_list_xyr","put_list_xyx","put_list_ynx","put_list_yrr",
"put_list_yrx","put_list_yxr","put_list_yxx","put_list_yyr",
"put_list_yyx","put_list_rcr","put_list_rcx","put_list_rcy",
"put_list_xcr","put_list_xcx","put_list_xcy","put_list_ycr",
"put_list_ycx","put_list_ycy","put_list_crr","put_list_crx",
"put_list_cry","put_list_cxr","put_list_cxx","put_list_cxy",
"put_list_cyr","put_list_cyx","put_list_cyy","put_list_ssd",
"raise_ss","recv_mark_f","remove_message","return",
"return_trace","self_r","self_x","self_y","send",
"set_tuple_element_sdP","system_limit_j","test_arity_frA",
"test_arity_fxA","test_arity_fyA","test_heap_It",
"test_heap_1_put_list_Iy","timeout","timeout_locked",
"try_case_end_s","try_end_y","update_map_assoc_jsdII",
"update_map_exact_jsdII","wait_f","wait_locked_f",
"wait_unlocked_f"]
ok
2> 

 

回顧 [Erlang 0010] Erlang 熱更新 [ 鏈接 ],看看兩種調用時候為什么m:f(a)會觸發熱更新,測試代碼如下:

 

loop() ->
    receive
        code_switch ->
            m:loop();
        Msg ->
            io:format("."),
            loop()
    end.

 

%% t.S文件


{function, loop, 0, 9}.
  {label,8}.
    {line,[{location,"t.erl",14}]}.
    {func_info,{atom,t},{atom,loop},0}.
  {label,9}.
    {allocate,0,0}.
    {line,[{location,"t.erl",15}]}.
  {label,10}.
    {loop_rec,{f,12},{x,0}}.
    {test,is_eq_exact,{f,11},[{x,0},{atom,code_switch}]}.
    remove_message.
    {line,[{location,"t.erl",17}]}.
    {call_ext_last,0,{extfunc,m,loop,0},0}.
  {label,11}.
    remove_message.
    {move,{literal,"."},{x,0}}.
    {line,[{location,"t.erl",19}]}.
    {call_ext,1,{extfunc,io,format,1}}.
    {call_last,0,{f,9},0}.
  {label,12}.
    {wait,{f,10}}.

 

%% t.dis文件

 

00007F9D864F6C38: i_func_info_IaaI 0 t loop 0
00007F9D864F6C60: allocate_tt 0 0
00007F9D864F6C70: i_loop_rec_fr f(00007F9D864F6CF0) x(0)
00007F9D864F6C80: i_is_eq_exact_immed_frc f(00007F9D864F6CB8) x(0) code_switch
00007F9D864F6C98: remove_message
00007F9D864F6CA0: i_call_ext_last_eP m:loop/0 0
00007F9D864F6CB8: remove_message
00007F9D864F6CC0: i_move_call_ext_cre "." x(0) io:format/1
00007F9D864F6CD8: i_call_last_fP t:loop/0 0
00007F9D864F6CF0: wait_f f(00007F9D864F6C70)

   

 然后,使用這個方法,我把ErlangEfficiency Guide 里面大部分代碼都做了一下測試,目的是想從一個新的角度切入以前的問題,其中一個比較有意思的是:

 

bin()->
 Bin0 = <<0>>,                    %% 1

 Bin1 = <<Bin0/binary,1,2,3>>,    %% 2

 Bin2 = <<Bin1/binary,4,5,6>>,    %% 3

 Bin3 = <<Bin2/binary,7,8,9>>,    %% 4

 Bin4 = <<Bin1/binary,17>>,       %% 5 !!!

 {Bin4,Bin3}.                      %% 6

  

這個方法由於中間的變量都是已知的,編譯之后直接使用運算結果了:

{function, bin, 0, 14}.
  {label,13}.
    {line,[{location,"t.erl",25}]}.
    {func_info,{atom,t},{atom,bin},0}.
  {label,14}.
    {move,{literal,{<<0,1,2,3,17>>,<<0,1,2,3,4,5,6,7,8,9>>}},{x,0}}.
    return.

 

印象比較深刻的是 http://www.erlang.org/doc/efficiency_guide/functions.html#id67136 里面提到函數分支的例子:

atom_map1(one) -> 1;
atom_map1(two) -> 2;
atom_map1(three) -> 3;
atom_map1(Int) when is_integer(Int) -> Int;
atom_map1(four) -> 4;
atom_map1(five) -> 5;
atom_map1(six) -> 6.

atom_map2(one) -> 1;
atom_map2(two) -> 2;
atom_map2(three) -> 3;
atom_map2(four) -> 4;
atom_map2(five) -> 5;
atom_map2(six) -> 6;
atom_map2(Int) when is_integer(Int) -> Int.

atom_map3(Int) when is_integer(Int) -> Int;
atom_map3(one) -> 1;
atom_map3(two) -> 2;
atom_map3(three) -> 3;
atom_map3(four) -> 4;
atom_map3(five) -> 5;
atom_map3(six) -> 6.

 

對於atom_map1為什么有問題文檔上是這樣說的:

 

First the input value is compared to one, two, and three (using a single instruction that does a binary search; thus, quite efficient even if there are many values) to select which one of the first three clauses to execute (if any).

If none of the first three clauses matched, the fourth clause will match since a variable always matches. If the guard test is_integer(Int) succeeds, the fourth clause will be executed.

If the guard test failed, the input value is compared to four, five, and six, and the appropriate clause is selected. (There will be a function_clause exception if none of the values matched.)

 

   二分查找,優化等等,雖然道理能明白,但是具體到細節就有點恍惚,看下t.S文件就明白了,代碼太長被我折疊了,所謂的二分查找就是select_val指令實現的,可以看到atom_map2,atom_map3都是將所有的分支放在一起做二分查找,而atom_map1的執行過程被分成了三段;再回頭看上面的說明,是不是就清晰多了.

 

{function, atom_map1, 1, 33}.
  {label,32}.
    {line,[{location,"t.erl",77}]}.
    {func_info,{atom,t},{atom,atom_map1},1}.
  {label,33}.
    {test,is_atom,{f,37},[{x,0}]}.
    {select_val,{x,0},
                {f,37},
                {list,[{atom,three},
                       {f,34},
                       {atom,two},
                       {f,35},
                       {atom,one},
                       {f,36}]}}.
  {label,34}.
    {move,{integer,3},{x,0}}.
    return.
  {label,35}.
    {move,{integer,2},{x,0}}.
    return.
  {label,36}.
    {move,{integer,1},{x,0}}.
    return.
  {label,37}.
    {test,is_integer,{f,38},[{x,0}]}.
    return.
  {label,38}.
    {test,is_atom,{f,32},[{x,0}]}.
    {select_val,{x,0},
                {f,32},
                {list,[{atom,six},
                       {f,39},
                       {atom,five},
                       {f,40},
                       {atom,four},
                       {f,41}]}}.
  {label,39}.
    {move,{integer,6},{x,0}}.
    return.
  {label,40}.
    {move,{integer,5},{x,0}}.
    return.
  {label,41}.
    {move,{integer,4},{x,0}}.
    return.

 

 

 


{function, atom_map2, 1, 43}.
  {label,42}.
    {line,[{location,"t.erl",87}]}.
    {func_info,{atom,t},{atom,atom_map2},1}.
  {label,43}.
    {test,is_atom,{f,50},[{x,0}]}.
    {select_val,{x,0},
                {f,50},
                {list,[{atom,six},
                       {f,44},
                       {atom,five},
                       {f,45},
                       {atom,four},
                       {f,46},
                       {atom,three},
                       {f,47},
                       {atom,two},
                       {f,48},
                       {atom,one},
                       {f,49}]}}.
  {label,44}.
    {move,{integer,6},{x,0}}.
    return.
  {label,45}.
    {move,{integer,5},{x,0}}.
    return.
  {label,46}.
    {move,{integer,4},{x,0}}.
    return.
  {label,47}.
    {move,{integer,3},{x,0}}.
    return.
  {label,48}.
    {move,{integer,2},{x,0}}.
    return.
  {label,49}.
    {move,{integer,1},{x,0}}.
    return.
  {label,50}.
    {test,is_integer,{f,42},[{x,0}]}.
    return.


{function, atom_map3, 1, 52}.
  {label,51}.
    {line,[{location,"t.erl",97}]}.
    {func_info,{atom,t},{atom,atom_map3},1}.
  {label,52}.
    {test,is_integer,{f,53},[{x,0}]}.
    return.
  {label,53}.
    {test,is_atom,{f,51},[{x,0}]}.
    {select_val,{x,0},
                {f,51},
                {list,[{atom,six},
                       {f,54},
                       {atom,five},
                       {f,55},
                       {atom,four},
                       {f,56},
                       {atom,three},
                       {f,57},
                       {atom,two},
                       {f,58},
                       {atom,one},
                       {f,59}]}}.
  {label,54}.
    {move,{integer,6},{x,0}}.
    return.
  {label,55}.
    {move,{integer,5},{x,0}}.
    return.
  {label,56}.
    {move,{integer,4},{x,0}}.
    return.
  {label,57}.
    {move,{integer,3},{x,0}}.
    return.
  {label,58}.
    {move,{integer,2},{x,0}}.
    return.
  {label,59}.
    {move,{integer,1},{x,0}}.
    return.

  

 

  我非常喜歡匯豐銀行的一句宣傳語"不同的視角,打開迥然不同的世界",使用不同的視角對同一個技術問題也會有不同的理解.

 

 

參考資料:

[0] A Peek Inside the Erlang Compiler http://prog21.dadgum.com/127.html

[1] Erlang源碼匯編格式 http://blog.yufeng.info/archives/498

[2] Erlang代碼反編譯以及查看匯編碼 http://blog.yufeng.info/archives/1599

[3] 實驗Erlang語法對應的opcode 讓你對erlang理解更深 http://blog.yufeng.info/archives/34 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM