Disassemble Elixir code
And check if Erlang dead code elimination works
Sometime to understand what happens under the hood of the Elixir program and how it is optimized you need go really low-level. Here is two ways to check what code was generated.
During compilation Elixir is translated first to Core Erlang and then by Erlang compiler — to BEAM code.
Test source code
Here is source code of module I’ll use for testing. I was interested if dead code inside if
will be removed from compiled version.
defmodule Test do
@flag1 false def init() do
IO.puts "Execute always" if @flag1 do
IO.puts "Execute in feature flag"
end 42
end
end
Retrieve Core Erlang / IR / Intermediate Representation
Run iex
in your project directory and enter following commands:
f = './_build/dev/lib/test/ebin/Elixir.Test.beam'
result = :beam_lib.chunks(f,[:abstract_code])
{:ok,{_,[{:abstract_code,{_,ac}}]}} = result
IO.puts :erl_prettypr.format(:erl_syntax.form_list(ac))
Note that I use charlist ''
and not string ""
because this code uses Erlang libraries which are designed around charlists.
This will produce pretty much readable Erlang code.
-file("lib/test.ex", 1).-module('Elixir.Test').-compile(no_auto_import).-export(['__info__'/1, init/0]).-spec '__info__'(attributes | compile | functions |
macros | md5 | module) -> atom() |
[{atom(), any()} |
{atom(), byte(), integer()}].'__info__'(module) -> 'Elixir.Test';
'__info__'(functions) -> [{init, 0}];
'__info__'(macros) -> [];
'__info__'(attributes) ->
erlang:get_module_info('Elixir.Test', attributes);
'__info__'(compile) ->
erlang:get_module_info('Elixir.Test', compile);
'__info__'(md5) ->
erlang:get_module_info('Elixir.Test', md5).init() ->
'Elixir.IO':puts(<<"Execute always">>),
case false of
false -> nil;
true -> 'Elixir.IO':puts(<<"Execute in feature flag">>)
end,
42.
Surprise! For some reason there is case
included with dead code inside.
Then I tried to look into lower level disassembly to determine what is happening actually.
Retrieve BEAM disassembly
Now let’s look inside compiler byte code and see what’s happening there.
f = ‘./_build/dev/lib/test/ebin/Elixir.Test.beam’
{:ok, beam} = File.read(f)
IO.inspect :beam_disasm.file(beam), pretty: true
Turns out Erlang compiler is quite smart and takes care of this code. BEAM code disassembly looks quite different from Erlang code, but you can make sense of it too :) Here is relevant part for function init
{
:function,
:init,
0,
7,
[
{:line, 1},
{:label, 6},
{:func_info, {:atom, Test}, {:atom, :init}, 0},
{:label, 7},
{:allocate, 0, 0},
{:move, {:literal, "Execute always"}, {:x, 0}},
{:line, 2},
{:call_ext, 1, {:extfunc, IO, :puts, 1}},
{:move, {:integer, 42}, {:x, 0}},
{:deallocate, 0},
:return
]
}
You can see that there is no mention of case
as it was removed.
About me
I’m Gaspar Chilingarov . I facilitate DevOps transition, help moving legacy applications to cloud and write high-performance Elixir apps.
Need help with your Elixir app or want prototype your next microservice in Elixir? DM me on Twitter or Github.
You can connect with me on Twitter, Facebook, LinkedIn and GitHub.
Found this post useful? Kindly tap the ❤ button below! :) Let’s spread word about Elixir.