TIL: Inspecting compiled Elixir code
by Paweł Świątkowski
18 Dec 2024
You can use one very simple trick to inspect what bytecode (?) will actually be generated from your Elixir code. This was shared Matt Jones (al2o3cr) on Elixir Forum.
The trick consists of putting a simple annotation @compile :S
to the module. Let’s see it in action:
defmodule TestMod do
@compile :S
def call(x) do
do_call(x)
end
defp do_call(x) do
IO.puts(x)
end
end
Now when we compile it (elixir testmod.ex
), the compilation will fail, but will generate a testmod.ex.S
file. I won’t list the whole contents, but it starts like this:
{module, 'Elixir.TestMod'}. %% version = 0
{exports, [{'__info__',1},{call,1},{module_info,0},{module_info,1}]}.
{attributes, []}.
{labels, 20}.
… and somewhere down the line it has content related to our defined functions:
{function, call, 1, 11}.
{label,10}.
{line,[{location,"testmod.ex",3}]}.
{func_info,{atom,'Elixir.TestMod'},{atom,call},1}.
{label,11}.
{call_only,1,{f,13}}. % do_call/1
{function, do_call, 1, 13}.
{label,12}.
{line,[{location,"testmod.ex",7}]}.
{func_info,{atom,'Elixir.TestMod'},{atom,do_call},1}.
{label,13}.
{line,[{location,"testmod.ex",8}]}.
{call_ext_only,1,{extfunc,'Elixir.IO',puts,1}}.
How can we use it? For example to check how the resulting code changes if we change something. Let’s add another compile annotation:
defmodule TestMod do
@compile :S
@compile {:inline, do_call: 1}
def call(x) do
# ...
This instructs the compiler to inline the contents of the private function do_call
. And indeed, when we compare the relevant part, it now looks like this:
{function, call, 1, 11}.
{label,10}.
{line,[{location,"testmod.ex",4}]}.
{func_info,{atom,'Elixir.TestMod'},{atom,call},1}.
{label,11}.
{line,[{location,"testmod.ex",9}]}.
{call_ext_only,1,{extfunc,'Elixir.IO',puts,1}}.
Indeed, the call of puts
from Elixir.IO
is now a part of a body of the call
function.