Packages
Mojo NIFs for OTP 28. Generated ABI, checked terms, resources, dirty schedulers, and a Mix compiler. Mojo 1.1.0 is required at build time and is not vendored.
Current section
Files
Jump to
Current section
Files
docs/tutorial/06-beam-internals.md
# Unit 6 — BEAM internals for NIF authors
## Objectives
Follow one Elixir call into Mojo and back, and name what the VM will not forgive.
## Why
The callback runs on a scheduler thread, with a term environment that dies when the callback returns. Keeping that environment is a use-after-free that looks like a flaky test.
## Model
```text
Elixir process
-> scheduler
-> nif_init's function pointer
-> enif_get_long(env, argv[i])
-> enif_make_long(env, n)
-> term on the process heap
```
## Theory
A process has a heap and a mailbox. A NIF with flags 0 runs on the scheduler that entered it, and other work on that scheduler waits. Dirty flags move the call to another pool (unit 12).
`ERL_NIF_TERM` is an integer the width of a pointer. It is not a Mojo pointer, and it is not valid in another environment. `enif_make_copy` into an `enif_alloc_env` environment is how `retain_binary` keeps a term after the process dies.
`ArgumentError` is `enif_make_badarg`. A raised Mojo error is not that. The abort probe kills the VM. Do not use it in `mix test`'s own process.
Reductions are not consumed just because the function is native. `burn/1` does not pretend that `enif_consume_timeslice` preempts it. The function runs until the clock says it is done.
## Lab
```sh
mix test test/poc_test.exs test/term_test.exs
```
## Expected
`add(20, 22)` is 42. `copy_binary` of `<<1, 2, 3>>` is `{:ok, <<1, 2, 3>>, 3}`. A 7-bit bitstring is `{:error, :not_binary}`.
## Code
`native/term.mojo` `copy_binary` inspects, allocates a new buffer with `calloc`, copies bytes, builds a new binary, and `free`s both descriptors. The returned term is the only thing Elixir sees.
## Failure
`bad_lib` with a module name means `ErlNifEntry.name` and the Elixir module disagree, or `load_nif` ran in the wrong module. `nif_not_loaded` means the stub ran because `@on_load` did not succeed.
## Exercise
Why does a second `load_nif` of `Mojoler.POC` fail even though `reload` in the entry is null?
## Answer
OTP rejects it before the callback (E14). The error is `{:reload, ...}`.