Disassemble Elixir code

And check if Erlang dead code elimination works

Gaspar Chilingarov
Learn Elixir



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"

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">>)

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

{: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},

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.



Gaspar Chilingarov
Learn Elixir

I facilitate DevOps transition, help moving legacy applications to the cloud and write high-performance Elixir apps.