How to check the compiled BEAM bytecode in elixir?

Hi, everyone, I’m to elixir and have prior experience with python.

# I try this simple expression in iex.
# Seems that elixir is the same as python as a strong typed language.
iex(5)> 1+"1"
** (ArithmeticError) bad argument in arithmetic expression: 1 + "1"
    :erlang.+(1, "1")
    iex:5: (file)
# In python, I can use the builtin lib to see the compiled bytecode.
# Are there any similar tools in elixir?
>>> import dis
>>> dis.dis("1+'1'")
  1           0 LOAD_CONST               0 (1)
              2 LOAD_CONST               1 ('1')
              4 BINARY_ADD
              6 RETURN_VALUE

You can use quote to see the AST for a given statement:

$ iex
Erlang/OTP 25 [erts-13.2.2.7] [source] [64-bit] [smp:20:20] [ds:20:20:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.15.7) - press Ctrl+C to exit (type h() ENTER for help)
 iex(1)> quote do 1+"1" end
{:+, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]], [1, "1"]}
1 Like

Yes I try it and can get the AST tree of elixir code.
But I want to see the low-level BEAM bytecode.

# foo.ex
x=1+1
IO.puts "x=#{x}"

# I tried to use elixirc to compile foo.ex into BEAM file.
# But got nothing in current directory, except foo.ex.
# Instead elixirc seems to run the file directly.
# Where's the output file?
> elixirc ./foo.ex --verbose
x=2
Compiling foo.ex
> ls
foo.ex

# I also tried the `c` function in iex.
# Seems the same as elixirc.
$ iex
Erlang/OTP 27 [erts-15.1.2] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.17.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c("./foo.ex")
x=2
[]

Compiled BEAM code can’t exist outside of modules. Throw it in a module and you can see the bite code:

defmodule Foo do
  def test do
    1 + 1
  end
end

Then you’ll get .beam file with elixirc. I don’t think it’s going to be what you’re looking for but maybe?

1 Like

Yes I put the code into module, and it compiled.
Finally I get what I want.
Still a lot to learn to fully understand the bytecode, but just made a good start.
Thanks, everyone!

# foo.ex
defmodule Foo do
  def foox do
    123+"456"
  end
end

> elixirc foo.ex --verbose
Compiling foo.ex
> ls
Elixir.Foo.beam  foo.ex

$ iex
iex> Foo # iex automatically loads the BEAM file for us.
Foo
iex> :beam_lib.chunks(Foo, [:abstract_code])
# Output a quite long code.
# But at the end we can find the bytecode of `foox` method.
       {:function, 2, :foox, 0,
        [
          {:clause, 2, [], [],
           [
             {:op, 3, :+, {:integer, 3, 123},
              {:bin, 3,
               [{:bin_element, 3, {:string, 3, ~c"456"}, :default, :default}]}}
           ]}
        ]}
2 Likes

You might also be interested in GitHub - hrzndhrn/beam_file: BeamFile - A peek into the BEAM file which is a convenience wrapper for :beam_lib.

Here is an example use case from mix_install_examples/beam_file.exs at main · wojtekmach/mix_install_examples · GitHub

Mix.install([:beam_file])

defmodule MyServer do
  use GenServer

  def init(_) do
    {:ok, nil}
  end
end
|> BeamFile.elixir_code!()
|> IO.puts()
7 Likes