5

I want to see what happens when elixir gets transformed into beam files. Is there any way to print in console or in a file how it is translated? I want to know what would this module look like in erlang.

I was thinking if there is a debug mode of elixir, which would output any of the above.

More specifically I have this example:

defmodule Test do
    def t1(a), do: a
    def t1(a, b \\ 2), do: a + b
end

The above code raises a warning, which is understanable considering what I've done. Basically I want to understand a bit more what's happening.

ipinak
  • 5,739
  • 3
  • 23
  • 41
  • If you're trying to get `a` only, you'll have to pass a `0` as the second parameter. If you don't pass a second parameter, you'll get `a + 2` – Onorio Catenacci Nov 23 '16 at 18:39
  • 1
    If i understood you correct, then you are wrong. If you call `Test.t1(0)` the output is `0`, but if you call `Test.t1(0, 4)` then the outcome is `4`. Basically everytime you call the function with one argument you end up invoking the first one. Btw, I'm on elixir 1.3.4 – ipinak Nov 23 '16 at 19:01

2 Answers2

9

First, you need to compile the Elixir module to a .beam file:

$ cat test.ex
defmodule Test do
    def t1(a), do: a
    def t1(a, b \\ 2), do: a + b
end
$ elixirc test.ex
warning: this clause cannot match because a previous clause at line 2 always matches
  test.ex:3

This will generate Elixir.Test.beam. Then, you can decompile this .beam to Erlang source using the following escript (I copied this from some answer here on Stackoverflow a while ago. Unfortunately I can't seem to locate the exact source but this code is in many answers here including this one.):

$ cat decompile.erl
#!/usr/bin/env escript

main([BeamFile]) ->
  {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(BeamFile,[abstract_code]),
  io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).

Then run it:

$ escript decompile.erl Elixir.Test.beam
-compile(no_auto_import).

-file("test.ex", 1).

-module('Elixir.Test').

-export(['__info__'/1, t1/1, t1/2]).

-spec '__info__'(attributes | compile | exports |
         functions | macros | md5 | module |
         native_addresses) -> atom() |
                      [{atom(), any()} |
                       {atom(), byte(), integer()}].

'__info__'(functions) -> [{t1, 1}, {t1, 2}];
'__info__'(macros) -> [];
'__info__'(info) ->
    erlang:get_module_info('Elixir.Test', info).

t1(a@1) -> a@1;
t1(x0@1) -> t1(x0@1, 2).

t1(a@1, b@1) -> a@1 + b@1.
Community
  • 1
  • 1
Dogbert
  • 212,659
  • 41
  • 396
  • 397
5

This probably should be more of a comment to @Dogbert’s answer above, but I would post it as a separate answer for the sake of formatting.

One does not need to create .ex files and invoke the compiler on them to produce beams:

{:module, _, bytecode, _} =
  defmodule Elixir.Test do
    def t1(a), do: a
    def t1(a, b \\ 2), do: a + b
  end
# File.write!("Elixir.Test.beam", bytecode)

now you might have had a beam file written (we have it stored in the bytecode variable by the way.)

NB: beam_lib:chunks/2 works if and only the beam contains unencrypted debug information (elixir beams by default do.)

Also, you don’t need to write decompiled erlang code, you might simply pass a binary there, directly in Elixir:

:beam_lib.chunks(bytecode, [:abstract_code])

To extract the code itself:

{:ok,{_,[abstract_code: {_, code}]}} = 
   bytecode |> :beam_lib.chunks([:abstract_code])

Now code contains the code, it should be enough to examine it, but you still are free to use erlang build-ins:

code |> :erl_syntax.form_list

or:

code |> :erl_syntax.form_list |> :erl_prettypr.format

The latter will give you the binary charlist, containing erlang code, exactly as in @Dogbert’s answer. Use IO.puts to output it.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160