sashaafm

sashaafm

Getting each stage of Elixir's compilation all the way to the BEAM bytecode

Piggy backing a bit on @dvcrn topic BEAM optimization for functions with static return type?, I’ve been trying to understand in a deeper manner how Elixir works internally to generate the BEAM’s bytecode. After reading way too many blog posts I’ve found some things:

  1. People either think that Elixir compiles directly to Erlang source code (.erl)
  2. Or people think that Elixir compiles directly to BEAM bytecode (.beam)

Both of these assumptions seem to be wrong. From the Elixir/Erlang Crash Course from Elixir’s official webpage, we can see that:

Elixir compiles into BEAM byte code (via Erlang Abstract Format).

Steps from Elixir source code to BEAM bytecode

So it’s not directly to Erlang source code, but it’s also not directly to BEAM bytecode. It is first transformed into Erlang Abstract Format (EAF). Continuing further into this topic, I’ve found a couple of blog posts, this one in particular BEAM by Example, where the author tells us the following:

Intermediate representations:

Erlang source code → Abstract Syntax Tree (‘P’) → expanded AST (‘E’) → Core Erlang (‘to_core’) → BEAM byte-code

So Elixir is first transforming to this Abstract Syntax Tree or Expanded AST intermediate representations. It should be something like this:

Elixir → Erlang Abstract Format → Core Erlang → BEAM bytecode


Note

I’ve also seen one or two posts online talking about Elixir being transformed into Erlang Forms. I’ve got no idea if these “Erlang Forms” are the same as one of the steps above or if they are an entirely different thing.


Now we’ve got a few different cases:

  • Elixir → EAF

This can be achieved through the :elixir Erlang module, that can be found here, like so:

expr = Macro.to_string(quote do: 1 + 2)
env  = :elixir.env_for_all([])
eaf  = :elixir.quoted_to_erl(expr, env)
# => Erlang Abstract Format of the quoted expression
  • EAF → Core Erlang
    and
  • Core Erlang → BEAM bytecode

I haven’t found a way to achieve these two steps. The further I’ve got is that Erlang’s compiling function can be used to get the various formats:

c(<file_name>, <format>)
c("file.erl", 'P')
c("file.erl", 'E')
c("file.erl", to_core)
c("file.erl", to

*BEAM Bytecode → Disassemble

This can be done either by c("file.erl, 'S'). or :beam_disasm.file/1, which I believe are the same thing, as far as I could find.

Example Gist

I’ve built this small Gist to better show the steps from an Erlang source code all the way to the disassembled bytecode.


Note

James Fish also spoke to me on Slack and told me to check out the :compile.forms/1 Erlang function. I don’t fully understand what this function actually does or returns. It seems to receive Erlang Abstract Format as an argument.


Erlang docs are sparse and usually scattered all around. I’ve only managed to gather some info about this topic from several sources, but I’d like to better understand this process of Elixir → BEAM. I’ve watched dozens of Elixir talks, but I don’t recall ever seeing this explained.

I’m hoping someone around here has some further knowledge on this :slight_smile:

First 7 of 7 Posts Switch mode

ibgib

ibgib

In case you haven’t seen it, here is another resource I think would interest you on this topic: “Implementing Languages on the BEAM” with @rvirding

I’m only half-way through so far, but it talks about the intermediate steps of any language (not just Elixir) that runs on top of the BEAM(!).

10
Post #1
OvermindDL1

OvermindDL1

I used to parse the erlang binaries a lot in the past, recently started something with it in Elixir as a typed experiment (ran out of time, bleh), which you can check out here if you want to see how to read type information and such (and show the general API): GitHub - OvermindDL1/typed_elixir · GitHub

sashaafm

sashaafm OP

That looks really interesting and could probably help my research @ibgib! It’s really long so I’ll probably watch it in chunks of 20 min :smile:

rvirding

rvirding

Creator of Erlang

So a quick answer here (more later) is the erlang compiler has 2 main entry points: :compile.file which compiles a text file; and :compile.forms which takes a list of pre-parsed forms. As you have seen you can specify how “far” in the compilation you want to go, whether to pre-expanded macros and parse transforms, core erlang, kernel erlang or just the BEAM instructions (without generating a .beam file). Try doing to_kernel, dkern and dlife for some more fun.

You can also specify what type the input should be, a little anyway, so for example the option :from_core means that the input, whether file of forms, is core erlang. This is what I use in the LFE compiler where I generated core erlang forms (there AST anyway) which I then compile with :compile.forms(forms, [:from_core|options]). I found this easier than generating erlang AST.

Almost all optimisation in the compiler is done on core erlang so I don’t “lose” anything by entering there.

The c("file.erl", 'S') compiles the file only to the BEAM instructions and prints them to a .S file while :beam_diasm:file/1 looks at the beam file and disassembles it. There is also a way which I can’t remember now where you can disassemble the actual code installed in the BEAM itself. This will be slightly different from what the other two give you as there is quite a lot of optimisation done at load time.

Most of the time you don’t need to know this except for the expansion of macros and parse transforms, but it is fun. About 12 min into my talk mentioned there is a slide on the passes of the compiler. It is a bit hard to see. IIRC there is another talk I gave on about the same thing where it is easier to see the slides.

13
Post #4
sashaafm

sashaafm OP

Thank you for the reply :slight_smile: Meanwhile, could you please confirm if this image is a correct representation of Erlang’s and Elixir’s intermediate forms from source code to bytecode?

EDIT: I see from the video that there should be Kernel Erlang before the BEAM Bytecode?
EDIT2: I seem to have found the slides from the other talk you mentioned: Slides about implementing Erlang languages

rvirding

rvirding

Creator of Erlang

This is how I interpret it anyway. I haven’t worked with the Elixir compiler so the person you really need to ask is @josevalim.

There are actually 2 passes between core and bytecode: kernel and life. The kernel pass converts it to kernel erlang where the code has been flattened, lambda lifted and the pattern matching has been compiled. The life pass does life time analysis of variables.

josevalim

josevalim

Creator of Elixir

Yes. To be more precise, instead of “Elixir”, you could have: Elixir Source Code → Elixir Macro Expansion → Erlang Abstract Format → …

16
Post #7
— All posts loaded —

Where Next?

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 91561 914
New
byu
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project. My initial shotgu...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
AstonJ
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
bjorng
We want to introduce a new native datatype to Erlang: native records. Although replacing all tuple records with native records is not our...
New

We're in Beta

About us Mission Statement