# `Feel.Backend.Beam`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/backend/beam.ex#L1)

The backend that compiles an expression to a BEAM module.

`compile/2` emits the module with `Feel.Backend.Beam.Emitter`, loads it with
`Feel.Backend.Beam.Loader`, and returns an executable that holds the module name.
`run/3` calls the generated function, which calls only `Feel.Runtime` and the
built-in functions (`Feel.Builtin`). `release/1` unloads the module, which costs time and does not free its atom
(see there).

Compared with `Feel.Backend.Interpreter` it saves the walk over the IR, and for the expressions that only do scalar
work on declared names (numbers, strings and booleans read from the bindings, arithmetic, comparisons, `and`, `or`,
`not`, `if`, `between`, `in` of ranges) it saves much more: `Feel.Backend.Beam.Scalar` makes a function that reads the names
in one pattern, does the operations without an environment, an outcome or repeated declared-type checks, checks
the largest constructed string on the executed path against its per-value limit, and hands the run to the interpreter, from the start, whenever it is not sure of the answer (a
`null` that the operation reports, a name that is not bound, a value of another kind, an overflow, a limit; D-164). Those run in 0.29 to 0.50 of the
interpreter's time (`price * quantity * (1 + tax)` 0.50, three conditions joined by `and` 0.37, a `between` and a range
0.29, a chain of `if` 0.46; the interpreter takes 0.8 to 1.8 µs for them); a run that falls back takes what the
interpreter takes, within 10 %. What is left to the code of `Feel.Backend.Beam.Emitter` (everything with a list, a
context, a loop, a function, a call of a built-in function, a path or a date) saves the walk and is small: a mixed expression
took about 6% less time. The time that is left in the fast path is the runtime's decimal arithmetic. It has operating
costs: every
distinct expression becomes a loaded module and an atom, neither of which the VM frees by
itself, and the Erlang compiler takes time and memory in proportion to the code it is given
(a source of the size `:max_source_bytes` allows took 8 to 18 s and 0.6 to 1.4 GB). For expressions that
come from sources that are not trusted, or that are run only a few times, use the
interpreter. If this backend is used for them, `:max_generated_statements` of `Feel.Limits` (10 000 by default)
refuses an expression that would make more code than that, before the compiler is run (D-154, `compile/2`);
it bounds the compile of one expression, and not the modules and atoms that many distinct ones leave, nor how
many are compiled at once, which the caller bounds. A separate VM-lifetime quota caps fresh module names
at 50 000 by default (`:feel, :max_generated_modules`). See `Feel.Backend.Beam.Loader` for how modules are named and
retired. `Feel.Runtime.abi/0` supplies a cache stamp for generated names. Prepared expressions
must be regenerated on upgrade; persisted generated modules are unsupported.

It compiles every node that the analyzer produces, unary tests included, and raises
`ArgumentError` for any other node. Its results are the interpreter's, and a test runs both on
the same expressions.

# `t`

```elixir
@type t() :: %Feel.Backend.Beam{
  dialect: Feel.Dialect.t(),
  env: Feel.Runtime.Env.t() | nil,
  extensions: Feel.Extensions.t(),
  module: module()
}
```

# `compile`

```elixir
@spec compile(Feel.Backend.ir(), [Feel.Backend.compile_option()]) ::
  {:ok, t()} | {:error, Feel.Error.t()}
```

Compiles and loads the IR as a module.

It emits the forms with `Feel.Backend.Beam.Emitter.emit/2`, names the module with
`Feel.Backend.Beam.Loader.module_name/1` once the limit has let the forms through, and loads them with
`Feel.Backend.Beam.Loader.load/2`. An IR that is already loaded as a module is not
loaded again, so preparing one expression twice costs one module. An expression that is refused makes no module
and no atom when refused by a generation or module-name limit. A later compilation
or loading failure can leave the name interned.

Returns an error of kind `:backend` when the module cannot be built or loaded (D-38), and one of kind `:limit` with the
code `:too_large_to_compile` when the forms have more statements than `:max_generated_statements` of the limits allows
(D-154). Code `:too_many_generated_modules` reports the exhausted VM-lifetime name
quota or atom-table headroom. The generation limit is found before the Erlang compiler is run, which is where the work and the memory are: the compile of the
longest source takes 8 to 18 s and 0.6 to 1.4 GB, and the emit 0.6 s. A caller can run the same source with the interpreter.

## Options

The options of `t:Feel.Backend.compile_option/0`. The profile `:explain` is not implemented yet and raises
`ArgumentError` (`Feel.Backend.validate_profile!/1`). The `:dialect` and the `:extensions` are kept in the
executable and not in the module, which is the same for every dialect and every set of extensions (D-49,
D-81): the module is named by its IR, and a call of a function finds its function through the environment.

    iex> ir = {:const, %{span: {0, 1}, type: :number}, Decimal.new(42)}
    iex> {:ok, executable} = Feel.Backend.Beam.compile(ir, [])
    iex> Feel.Backend.Beam.run(executable, %{}, [])
    {:ok, Decimal.new(42), []}

# `release`

```elixir
@spec release(t()) :: :ok | {:error, :in_use}
```

Unloads the module of an executable.

Returns `{:error, :in_use}` while a process is still running its code; try again
later. A released executable cannot be run again; prepare the expression again, and
note that executables made from the same expression share one module, so releasing
one releases it for all.

## What a release costs

**Time, in proportion to the number of processes in the VM.** The code is deleted and
then purged, and a purge asks every process whether it still runs the code. It runs in
the code server, so every other call to the code server, a load of another module
included, waits for it. One release took this long with this many processes besides the
104 of an idle `mix run` (OTP 29.1.1, 10 schedulers, on a machine at a load average of
55 to 90, so a quiet machine is faster; the ranges are those of two runs of 15 and 40
releases each):

| Processes | Least | Median |
| --------: | ----: | -----: |
| 0 | 1.2 to 1.7 ms | 3 to 6 ms |
| 300 | 1.4 to 2.2 ms | 10 ms |
| 3,000 | 5 to 7 ms | 11 to 16 ms |
| 30,000 | 64 to 73 ms | 81 to 99 ms |
| 100,000 | 255 to 288 ms | 337 to 479 ms |

That is 2 to 5 µs for each process. These are processes that wait with a small heap;
a process with a large heap costs more (3,000 processes of about 80 KB each took 19 to
20 ms at least and 48 to 59 ms at the median). A call to the code server made 8 ms
after a release had started waited 5.5, 46 and 171 ms (medians) for it, with 3,000,
30,000 and 100,000 processes. Release an expression when it is discarded and not after
each run, and keep the executable while it can be run again: preparing the same
expression finds the module loaded.

**The atom, which is never freed.** The name of the module is an atom, and the VM frees
no atom, so a release gives back the code and not the name or lifetime quota. Each distinct expression
costs one atom (200 distinct expressions of each of four shapes took 200 atoms, and
releasing them all gave back none); the same expression prepared again costs none. The
table holds 1,048,576 atoms, and when it is full the VM stops, not only the expression.
The default lifetime quota stops new name creation at 50 000, before that table fills
from generated names alone; other host code can still consume atoms. Only a VM restart
reclaims the atoms and resets this quota. Use the interpreter for unbounded expression
streams and bound concurrent compilation and retained code when choosing BEAM.

# `run`

```elixir
@spec run(t(), Feel.bindings(), [Feel.run_option()]) :: Feel.result(Feel.Value.t())
```

Runs the generated function of an executable on a set of bindings.

Takes the run options of `Feel.run/3` and raises `ArgumentError` for any other, and
for an executable that has been released.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
