Learning objectives
- Separate "Mojo can export a C function" from "the BEAM loaded a NIF".
- Read the minimum passing demonstration as an ExUnit assertion, not a feeling.
Why this matters
The tempting shortcut is to compile add, see a symbol, and declare the ABI solved. The BEAM will still refuse the library if the entry struct is the wrong size, the name string is wrong, or enif_make_long was never bound. The questions below are the ones the rest of the docs answer with command output.
Mental model
question --> experiment --> ledger row --> gate
|
+--> unexpected compiler error is itself an answerTheory
The questions, and where they were closed:
- Can this Mojo emit a loadable shared library with a C ABI symbol? Yes. Ledger E2 and E3.
- Can Mojo represent
ErlNifEntryandErlNifFuncfor erts 16.2, including padding? Yes. The sizes and offsets match the diagnostic C program. Ledger E5 and E7. - Can it export
nif_initwith default visibility? Yes.nm -gUshows a globalT _nif_init. Ledger E11. - Can it call the NIF API the way this OTP actually exposes it? Yes, by leaving
enif_get_long,enif_make_long, andenif_make_badargundefined. Not by callingenif_get_int64, and not by inventing the Windows callback table. Ledger E6 and E12. - Can it read terms, build a term, and report badarg? Yes. ExUnit covers 42, negatives, non-integers, and the signed 64-bit edges. Ledger E13.
- Can it be loaded in more than one OS process, and called repeatedly and concurrently? Yes for fresh processes, repeats, and 40 tasks. A second
load_nifin the same module is rejected by OTP 20+ rules. Ledger E13 and E14. - Which Mojo features need
initialize_runtime? The integeraddpath does not call it and still passes. Calling it twice from anothernif_initalso survived and returned 7. Thread-pool and GPU APIs were not in this stdlib. Ledger E15.
Minimum demonstration, met by mix test:
Mojoler.POC.add(20, 22) == 42through the Mojo NIF, with nm / otool checks in the same suite.
Hands-on exercise
Run scripts/test-isolated.sh. Success is 11 passed and a process that exits 0. A hang is cut off at 180 seconds because the script wraps Mix in perl -e 'alarm'.
Do not treat a green mojo build of experiments/abi/01_export_add/add.mojo as this exercise. That file has no nif_init.
Line-by-line walkthrough
The Elixir side of the minimum demonstration is the stub in lib/mojoler/poc.ex:
@on_load :load_nifruns when the module is loaded.Application.app_dir(:mojoler, "priv/native/libpoc")is the path without a suffix. OTP appends.soon this Darwin build, which is why the file is not named.dylib.add/2raises:nif_not_loadedonly if the native function was not installed. After a successful load, the BEAM replaces that body.
Under the hood
load_nif resolves nif_init, checks the major version and vm_variant, and then calls through ErlNifFunc.fptr. A wrong name field fails with a message that mentions the calling module. That happened during development when the test module called load_nif directly: the entry said Elixir.Mojoler.POC and the caller was Elixir.Mojoler.POCTest.
Failure modes
- Tests fail in
load_nifbefore any assertion: the.sodid not build, the rpath cannot seelibKGENCompilerRTShared.dylib, or dyld could not bindenif_get_long. Runscripts/inspect-abi.sh. addreturns a wrong integer: the term width or the overflow check drifted. Re-run the layout experiment before editing the arithmetic.- Claiming Linux works because the Mac run passed: that claim is out of bounds. The Linux claim is the green Actions log in ledger E18 and E19, and it covers CPU
add/2only.
Rustler and Zigler
Both projects treat "the shared object loads and an ExUnit test calls it" as the bar, and both generate the entry table. The questions here are the same questions their code generators had to get right. Stage 0 answers them by hand so the failure, if it comes back, is a compiler or ABI fact rather than a generator bug.
Checkpoint
- Which experiment is not yet a NIF?
01_export_add. - What string must
ErlNifEntry.namecontain?Elixir.Mojoler.POC. - Stretch: call
load_niffrom a different module and read thebad_liberror. Put the command and the text in the ledger if you keep the experiment.