1. 程式人生 > >[Erlang 0109] From Elixir to Erlang Code

[Erlang 0109] From Elixir to Erlang Code

Elixir程式碼最終編譯成為erlang程式碼,這個過程是怎樣的?本文通過一個小測試做下探索.   編譯一旦完成,你就看到了真相 Elixir程式碼組織方式一方面和Erlang一樣才用非常扁平的程式碼模組結構,另一方面Elixir同時支援巢狀.Elixir比較方便的一點是可以在Elixir Shell中完成對模組的定義.看下面的方式:
iex> defmodule Math do
...>   def sum(a, b) do
...>     a + b
...>   end
...> end

iex> Math.sum(1, 2)
3
下面我們把程式碼放在m.ex模組中,模組的名字和程式碼檔案的名字是可以不一樣的,在編譯之後資料夾中新增了一個Elixir.Math.beam的檔案.換句話說,elixirc已經把m.ex檔案編譯成Elixir.Math.beam,按照Erlang對模組名稱和檔名一致性的要求,我們可以在Erlang的Shell中驗證一下:
[
[email protected]
elixir]# elixirc m.ex [[email protected] elixir]# ls Elixir.Math.beam m.ex [[email protected] elixir]# erl Erlang R16B01 (erts-5.10.2) [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] Eshell V5.10.2 (abort with ^G) 1> 'Elixir.Math':sum(12,23).
35 2>
   是不是和我們預期的一樣?之前提到過多次從beam中還原原始碼的方法,現在我們動手看下這個Elixir.Math.beam的Erlang程式碼是怎樣的,輸出我做了一下排版:
5> {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks("Elixir.Math",[abstract_code]).
{ok,{'Elixir.Math',
        [{abstract_code,
             {raw_abstract_v1,
                 [{attribute,
0,compile, {no_auto_import,[{bitsize,1},{apply,2}, {spawn,2}, {spawn_link,2}, {spawn_monitor,3}, {spawn_opt,2}, {spawn_opt,3}, {spawn,4}, {spawn_link,4}, {spawn_opt,4}, {spawn_opt,5}, {nodes,...}, {...}|...]}}, {attribute,1,file,{"/data2/elixir/m.ex",1}}, {attribute,1,module,'Elixir.Math'}, {attribute,1,export,[{'__info__',1},{sum,2}]}, {function,0,'__info__',1, [{clause,0, [{atom,0,functions}], [], [{cons,0,{...},...}]}, {clause,0,[{atom,0,macros}],[],[{nil,0}]}, {clause,0,[{atom,0,docs}],[],[{cons,...}]}, {clause,0,[{atom,0,...}],[],[{...}]}, {clause,0,[{atom,...}],[],[...]}, {clause,0,[{...}],[],...}]}, {function,2,sum,2, [{clause,2, [{var,2,a},{var,2,b}], [], [{op,3,...}]}]}]}}]}} 6> io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]). -compile({no_auto_import, [{bitsize, 1}, {apply, 2}, {spawn, 2}, {spawn_link, 2}, {spawn_monitor, 3}, {spawn_opt, 2}, {spawn_opt, 3}, {spawn, 4}, {spawn_link, 4}, {spawn_opt, 4}, {spawn_opt, 5}, {nodes, 0}, {disconnect_node, 1}, {integer_to_list, 2}, {integer_to_binary, 2}, {max, 2}, {min, 2}, {port_control, 3}, {port_connect, 2}, {port_command, 2}, {port_command, 3}, {port_close, 1}, {spawn_monitor, 1}, {spawn, 1}, {load_module, 2}, {spawn_link, 1}, {binary_to_float, 1}, {float_to_binary, 2}, {float_to_binary, 1}, {list_to_integer, 2}, {integer_to_binary, 1}, {binary_to_integer, 2}, {binary_to_integer, 1}, {check_old_code, 1}, {binary_part, 3}, {binary_part, 2}, {binary_to_term, 2}, {binary_to_existing_atom, 2}, {binary_to_atom, 2}, {atom_to_binary, 2}, {bitstring_to_list, 1}, {list_to_bitstring, 1}, {bit_size, 1}, {byte_size, 1}, {tuple_size, 1}, {is_bitstring, 1}, {list_to_existing_atom, 1}, {iolist_to_binary, 1}, {iolist_size, 1}, {is_boolean, 1}, {is_record, 3}, {is_record, 2}, {is_function, 2}, {is_function, 1}, {is_binary, 1}, {is_reference, 1}, {is_port, 1}, {is_pid, 1}, {is_number, 1}, {is_integer, 1}, {is_float, 1}, {is_tuple, 1}, {is_list, 1}, {is_atom, 1}, {error, 2}, {error, 1}, {is_process_alive, 1}, {demonitor, 2}, {demonitor, 1}, {monitor, 2}, {whereis, 1}, {unregister, 1}, {unlink, 1}, {tuple_to_list, 1}, {trunc, 1}, {tl, 1}, {time, 0}, {throw, 1}, {term_to_binary, 2}, {term_to_binary, 1}, {statistics, 1}, {split_binary, 2}, {spawn_link, 3}, {spawn, 3}, {size, 1}, {setelement, 3}, {self, 0}, {round, 1}, {registered, 0}, {register, 2}, {put, 2}, {purge_module, 1}, {processes, 0}, {process_info, 2}, {process_info, 1}, {process_flag, 3}, {process_flag, 2}, {pre_loaded, 0}, {pid_to_list, 1}, {open_port, 2}, {now, 0}, {nodes, 1}, {node, 0}, {node, 1}, {monitor_node, 2}, {module_loaded, 1}, {make_ref, 0}, {list_to_tuple, 1}, {list_to_pid, 1}, {list_to_integer, 1}, {list_to_float, 1}, {list_to_binary, 1}, {list_to_atom, 1}, {link, 1}, {length, 1}, {is_alive, 0}, {integer_to_list, 1}, {hd, 1}, {halt, 2}, {halt, 1}, {halt, 0}, {group_leader, 2}, {group_leader, 0}, {get_keys, 1}, {get, 1}, {get, 0}, {garbage_collect, 1}, {garbage_collect, 0}, {float_to_list, 2}, {float_to_list, 1}, {float, 1}, {exit, 2}, {exit, 1}, {erase, 1}, {erase, 0}, {element, 2}, {delete_module, 1}, {date, 0}, {check_process_code, 2}, {binary_to_term, 1}, {binary_to_list, 3}, {binary_to_list, 1}, {atom_to_list, 1}, {apply, 3}, {abs, 1}]}). -file("/data2/elixir/m.ex", 1). -module('Elixir.Math'). -export(['__info__'/1, sum/2]). '__info__'(functions) -> [{sum, 2}]; '__info__'(macros) -> []; '__info__'(docs) -> [{{sum, 2}, 2, def, [{a, [{line, 2}], nil}, {b, [{line, 2}], nil}], nil}]; '__info__'(moduledoc) -> {1, nil}; '__info__'(module) -> 'Elixir.Math'; '__info__'(atom) -> module_info(atom). sum(a, b) -> a + b. ok 7>

如何編譯的?

  下面我們探究一下Elixir編譯的過程,切入點當然是elixirc,開啟這個指令碼: 可以看到完成了一些環境變數解析之後,最終是呼叫了elixir
exec "$SCRIPT_PATH"/elixir +compile "[email protected]"
OK,我們繼續跟進elixir,經過一番引數檢查,變數解析後,最後執行的命令類似下面:
 erl -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell  -s elixir start_cli -extra +compile
簡單回顧一下erlang 執行時環境啟動的引數,erl的引數分三種:加號+後面跟的是 emulator flags,單連字元"-"後面跟的是flags,init程序會完成這些引數的解析; -extra 後面跟的內容都會被當做是plain arguments. http://erlang.org/doc/man/erl.html
% erl +W w -sname arnie +R 9 -s my_init -extra +bertie
([email protected])1> init:get_argument(sname).
{ok,[["arnie"]]}
([email protected])2> init:get_plain_arguments().
["+bertie"]

 Here +W w and +R 9 are emulator flags. -s my_init is an init flag, interpreted by init. -sname arnie is a user flag, stored by init. It is read by Kernel and will cause the Erlang runtime system to become distributed. Finally, everything after -extra (that is, +bertie) is considered as plain arguments.

% erl -myflag 1
1> init:get_argument(myflag).
{ok,[["1"]]}
2> init:get_plain_arguments().
[]

 Here the user flag -myflag 1 is passed to and stored by the init process. It is a user defined flag, presumably used by some user defined application.

 書歸正傳,elixir程式碼裡面給我們後續跟進的線索"-s elixir start_cli",廢話少說,開啟檔案:
%% Boot and process given options. Invoked by Elixir's script.
start_cli() ->
  application:start(?MODULE),

  'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
ok,下面我們手工完成m.ex檔案的編譯過程(為了方便執行你可以去/elixir/lib/elixir/ebin資料夾),我們分兩步1.啟動elixir 2.呼叫編譯函式 'Elixir.Kernel.CLI':main(["+compile","m.ex"]).之所以要啟動elixir,是為了完成類似code_server的職責.
[[email protected] ebin]# erl
Erlang R16B01 (erts-5.10.2) [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.2  (abort with ^G)
1> application:start(elixir).
ok

2>  'Elixir.Kernel.CLI':main(["+compile","m.ex"]).
檢查資料夾中的檔案,是不是已經編譯好了. {ok,"今天就到這裡."} 如果你要繼續跟進程式碼,你可以看到:
  @doc """
  This is the API invoked by Elixir boot process.
  """
  def main(argv) do
    argv = lc arg inlist argv, do: String.from_char_list!(arg)

    { config, argv } = process_argv(argv, Kernel.CLI.Config.new)
    System.argv(argv)

    run fn ->
      command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
      command_errors  = lc { :error, msg } inlist command_results, do: msg
      errors          = Enum.reverse(config.errors) ++ command_errors

      if errors != [] do
        Enum.each(errors, &IO.puts(:stderr, &1))
        System.halt(1)
      end
    end, config.halt
  end
 defp process_argv(["+compile"|t], config) do
    process_compiler t, config
  end
最後,於"金蟬脫殼"上映之際小圖一張