BEAM optimization for functions with static return type?

The same value - a compiled constant - will be used each time. We can see this is indeed the case by inspecting the BEAM bytecode.

Let’s prepare an example module:

defmodule Test do
  def foo, do: %{foo: "bar"}
end

Save it as test.ex, compile with elixirc test.ex - the output will be the binary Elixir.Test.beam. We can disassemble the compiled module into the BEAM assembly with :beam_disasm.file/1. The output contains the section that interests us the most - the foo/0 function:

{:function, :foo, 0, 7,
   [{:line, 1}, 
    {:label, 6}, 
    {:func_info, {:atom, Test}, {:atom, :foo}, 0},
    {:label, 7}, 
    {:move, {:literal, %{foo: "bar"}}, {:x, 0}}, 
    :return]
 }

We have a function foo/0 that starts at label 7. The whole body of the function consists of moving a literal value to the X0 register (which is the return register on the BEAM - the function calling that one will expect the result in this exact location) and returning from the function. So we see a literal value is used.

But what does it mean “a literal value”? Each .beam file consists of several “chunks” that represent various things - the compiled code itself (Code chunk), public (exported) functions (ExpT), static atoms used in that module (Atom), etc. One of those chunks is the literals chunk called LitT. When the module is loaded, the chunk is unpacked and all the terms in there are placed somewhere in memory - they are constants that can be referenced multiple times (and since we know they don’t change and won’t go away they are skipped in garbage collection).

With the help of a function like below we can list all the literals from the chunk:

defmodule Literals do
  def literals(module) when is_atom(module) do
    path = :code.which(module)
    {:ok, {^module, [{'LitT', <<_::4*8, compressed::binary>>}]}} =
      :beam_lib.chunks(path, ['LitT'])
    <<_::4*8, records::binary>> = :zlib.uncompress(compressed)
    unpack(records)
  end

  defp unpack(<<>>) do
    []
  end
  defp unpack(<<len::4*8, record::binary-size(len), rest::binary>>) do
    [:erlang.binary_to_term(record) | unpack(rest)]
  end
end

For our Test module, Literals.literals(Test) gives [[foo: 0], %{foo: "bar"}] - which is the list of exported functions (used in the __info__(:functions) call) and our literal map.

13 Likes