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

Compiles generated forms and manages the modules they become.

Generating code has operating costs that interpreting does not, and this module is
where they are handled:

  * **Atoms.** A module name is an atom, and atoms are never freed. Each distinct
    expression would mint one, so `module_name/1` derives the name from a hash of the
    IR and runtime cache stamp: the same expression on the same stamp gets the same module, and loading it twice is a
    no-op. New names have a VM-lifetime quota of 50 000, configured with
    `config :feel, :max_generated_modules, count`. Unloading and application restarts
    do not refund it. `:infinity` disables the quota; atom-table headroom is still
    checked. Existing names can always be reused.
  * **Replacement.** The VM keeps at most two versions of a module name, and loading
    another version kills processes still running the oldest. Names are therefore never
    reused for different code; a changed expression gets a new name. A module is
    never replaced: `load/2` leaves a loaded module alone, refuses to load over old
    code that something still runs, and loads of one name are serialized, so two
    processes that prepare the same expression at once load it once.
  * **Unloading.** `unload/1` retires a module once nothing runs it, by purging it
    softly and deleting it, and reports when it is still in use. The purge asks every
    process of the VM, so the time grows with their number; the figures are in the
    documentation of `Feel.Backend.Beam.release/1`, and so is the reason that unloading
    gives back the code of a module and not its atom.
Generated code is ephemeral and is rebuilt on upgrade. There is no persisted
BEAM-artifact import or runtime compatibility negotiation. `Feel.Runtime.abi/0`
is only a cache-generation stamp, included in the module-name hash.

Failures are `Feel.Error` values of kind `:backend` with codes `:compile_failed`,
`:load_failed` or `:module_in_use`. Exhausting the lifetime name quota raises a
`Feel.Error` of kind `:limit` with code `:too_many_generated_modules`; the backend
returns it as a preparation error.

# `load`

```elixir
@spec load(Feel.Backend.Beam.Emitter.forms(), module()) ::
  {:ok, module()} | {:error, Feel.Error.t()}
```

Compiles the forms to a binary and loads it as `module`.

Does nothing and returns `{:ok, module}` if a module with that name, compiled from
the same IR, is already loaded.

The errors have the kind `:backend`:

  * `:compile_failed` - the forms are not a valid module.
  * `:load_failed` - the VM refused the code.
  * `:module_in_use` - the name is taken by code that was unloaded while a process
    was still running it; try again when it has finished.

# `module_name`

```elixir
@spec module_name(Feel.Backend.ir()) :: module()
```

Returns the module name for an IR: a name under `Feel.Generated`, derived from a
hash of the IR. Creating the atom is deliberate; callers should expect one atom per
distinct IR. Raises `Feel.Error` with code `:too_many_generated_modules` before
interning a new name if the lifetime quota or atom-table headroom is exhausted.
Setting `:feel, :max_generated_modules` to zero permits only existing names.

The same IR on the same cache stamp always gives the same name, and the name has 128 bits of a SHA-256 hash
in it, so different IRs do not share one in practice.

    iex> ir = {:const, %{span: {0, 1}, type: :number}, Decimal.new(1)}
    iex> name = Feel.Backend.Beam.Loader.module_name(ir)
    iex> name == Feel.Backend.Beam.Loader.module_name(ir)
    true
    iex> String.starts_with?(Atom.to_string(name), "Elixir.Feel.Generated.E")
    true

# `unload`

```elixir
@spec unload(module()) :: :ok | {:error, :in_use}
```

Unloads a generated module. Returns `{:error, :in_use}` while a process is still
running its code.

It first soft-purges any old code, then deletes the current module and soft-purges
that code too. No running process is killed. If either purge finds active code,
it returns `{:error, :in_use}`; depending on the stage, the module can still be
available to new calls or await only its final purge. Retry after those calls end.
Unloading a module with no loaded or active old code is `:ok`.

---

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