Cuts the long straight lines of generated code into functions of a bounded size.
The Erlang compiler takes more than linear time for one large function: several of its passes
(the lint of the forms, the translation to Core Erlang, the optimizations of the SSA form, the
allocation of registers) do work that grows with the variables and blocks of a function, and the
emitter makes a function as long as the expression is (three statements for each operation).
x and x and … of 100 terms took the compiler 0.26 s, of 200 terms 1.0 s, and of 400 terms
4.9 s, whatever the analysis cost (D-113).
A body that is long is therefore cut into chunks. Each chunk but the last ends in a tail call of the next, which is a function of its own that takes the variables that the rest of the body reads, and the last has the expression the body ended in. The body is cut again where it is still long (a function of a filter, the branch of a conditional), so the cost of the compile is linear in the size of the expression, and the code that runs is the same statements in the same order: what a run reports, and where, does not change.
run(Bindings, E0) ->
{V1, E1} = 'Elixir.Feel.Runtime.Bindings':lookup(E0, Bindings, <<"x">>, {0, 1}),
...
run_1(Bindings, E31, V32);
run_1(Bindings, E31, V32) ->
...
{V90, E90}.A body is cut only after a statement that leaves few variables to carry (@max_carried): for
x + x + … they are four (the bindings, the environment, the value so far and the one just read). The emitter
puts the values of a long list or context in a variable of their own a few at a time (D-113), so that there are such
places in them too, and a fun with many parameters reads them from a tuple (D-113); a body that keeps many values
alive for some other reason has no such place, and stays one function. A body of
@chunk statements or fewer is not touched, so the code of the expressions that are usually
written is what the emitter made.
The names of the functions are run_1, run_2, and so on: the same few atoms for every
module, bounded by the size of the source.
The variables of the generated code are never bound twice (the emitter numbers them across branches and functions), which is what lets this module find what a statement reads by the names alone: a variable that a statement mentions and does not bind in a pattern of its own is read from outside it.
Summary
Functions
Returns the function and the functions that its long bodies are cut into, the function first.
Returns the number of statements in the forms: the length of the body of every clause, those of a case and of a fun
inside a body among them.
Functions
@spec split(:erl_parse.abstract_form()) :: [:erl_parse.abstract_form()]
Returns the function and the functions that its long bodies are cut into, the function first.
The function is returned as it was if no body of it is long.
iex> a = :erl_anno.new(0)
iex> function = {:function, a, :run, 1, [{:clause, a, [{:var, a, :E0}], [], [{:var, a, :E0}]}]}
iex> Feel.Backend.Beam.Chunks.split(function) == [function]
true
@spec statements([:erl_parse.abstract_form()]) :: non_neg_integer()
Returns the number of statements in the forms: the length of the body of every clause, those of a case and of a fun
inside a body among them.
It is what the Erlang compiler's work and memory are in proportion to (about 20 000 reductions and 6 to 28 KB for each, whatever
the expression is), and so what Feel.Limits bounds for the BEAM backend (:max_generated_statements, D-154). A list or a
string of constants is data and not statements.
iex> a = :erl_anno.new(0)
iex> body = [{:match, a, {:var, a, :X}, {:integer, a, 1}}, {:var, a, :X}]
iex> fun = {:fun, a, {:clauses, [{:clause, a, [], [], [{:integer, a, 2}]}]}}
iex> forms = [{:function, a, :run, 0, [{:clause, a, [], [], body ++ [fun]}]}]
iex> Feel.Backend.Beam.Chunks.statements(forms)
4