Each row is something that was run in this checkout. Confidence is about the claim, not about the general future of Mojo.
| id | hypothesis | command | observed | interpretation | confidence | source |
|---|---|---|---|---|---|---|
| E1 | The pinned BEAM is OTP 28 / erts 16.2 and Elixir 1.20.2. | scripts/doctor.sh | elixir 1.20.2, erlang 28 erts-16.2, mojo Mojo 1.1.0 (8189361e), Darwin arm64 26.5 | Toolchain matches the pin. | high | doctor output |
| E2 | Mojo 1.1.0 can emit a shared library. | mojo build experiments/abi/01_export_add/add.mojo --emit shared-lib | nm -gU shows T _mojoler_add. otool -L lists the dylib, @rpath/libKGENCompilerRTShared.dylib, and libSystem.B.dylib. | A C ABI export works. The Mojo compiler runtime is a real dynamic dependency. | high | experiments/abi/OBSERVED.txt |
| E3 | The export lowers to a C integer function. | mojo build ... --emit llvm | define dso_local i64 @mojoler_add(i64 noundef %0, i64 noundef %1) then add and ret. Target arm64-apple-darwin25.5.0, CPU apple-m1. | Arguments and the return use the integer registers. This is an add, not a NIF. | high | experiments/abi/01_export_add/add.ll |
| E4 | fn is gone in this compiler. | mojo build of a file that used fn | error: 'fn' has been removed; use 'def' instead | Tutorials that still show fn do not match 1.1.0. | high | compiler diagnostic |
| E5 | OTP 28.3.1 NIF version is 2.17 and ERL_NIF_TERM is 8 bytes. | clang of experiments/abi/diagnostic/layout.c, then run it | ERL_NIF_MAJOR=2 ERL_NIF_MINOR=17, long=8, ERL_NIF_TERM size=8, ErlNifFunc sizeof=32, ErlNifEntry sizeof=96, ErlNifResourceTypeInit sizeof=40 | Field offsets in 05-otp-abi-map.md come from this run, not from a guess. | high | diagnostic program |
| E6 | enif_get_int64 is not a symbol on this OTP. | nm -gU of beam.smp; read erl_nif_api_funcs.h | _enif_get_long, _enif_make_long, _enif_make_badarg are defined. SIZEOF_LONG is 8, and the header macros enif_get_int64 to enif_get_long. | A NIF must call enif_get_long / enif_make_long. | high | header and beam.smp |
| E7 | Mojo struct layout matches those offsets. | mojo experiments/abi/03_layout/layout.mojo | NifFunc sizeof 32, fields at 0, 8, 16, 24. NifEntry sizeof 96, fields at 0, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88. optional_none_or 0. | RegisterPassable plus std.ffi integer aliases reproduced the C layout. None is a NULL pointer. | high | OBSERVED.txt |
| E8 | A thin function is an 8-byte code pointer. | mojo experiments/abi/02_pointers/callback.mojo | sizeof 8, result 42 | Safe to store in ErlNifFunc.fptr. Pointer(to=cb) was rejected as a technique after LLVM showed it takes the address of a stack slot. | high | IR of an earlier probe; this program's printout |
| E9 | There is no module-level mutable global. | compile a file-scope var | error: global variables are not supported | nif_init uses calloc for the entry and the function table. | high | compiler diagnostic |
| E10 | Literal C strings survive calloc. | mojo experiments/abi/04_lifetime/calloc_entry.mojo | printed add and arity 2 | ImmStaticOrigin pointers can be stored in the descriptor. | high | OBSERVED.txt |
| E11 | The built NIF exports only nif_init and imports the long-sized enif symbols. | scripts/inspect-abi.sh | T _nif_init. Undefined: _abort, _calloc, _enif_get_long, _enif_make_badarg, _enif_make_long, dyld_stub_binder. otool -L shows KGEN and libSystem. rpath is the venv modular/lib. | Link line -Xlinker -undefined -Xlinker dynamic_lookup left enif_* for dyld to bind inside beam.smp. | high | inspect-abi output |
| E12 | LLVM for the NIF calls those symbols directly. | mojo build native/poc.mojo --emit llvm | call i32 @enif_get_long, call i64 @enif_make_long, call i64 @enif_make_badarg, declare lines for those plus calloc and abort. Also compiler-inserted write, dup, fdopen, fflush, fclose, KGEN_CompilerRT_AlignedFree. | The NIF body is a direct call, not a C wrapper. The compiler also emits libc calls of its own. | high | /tmp/poc.ll from the recorded build |
| E13 | The BEAM loads the library and add(20, 22) is 42. | mix test | 11 passed | Includes negatives, overflow, non-integers, arity, 200 repeats, 40 concurrent tasks, a fresh OS process, and the nm assertions. | high | ExUnit |
| E14 | A second load is refused by OTP, not by Mojo. | Mojoler.POC.reload() inside ExUnit | {:error, {:reload, 'NIF library already loaded (reload disallowed since OTP 20).'}} | load, reload, and upgrade are NULL. OTP refuses the second load_nif before those callbacks would matter. | high | test/poc_test.exs |
| E15 | initialize_runtime() can run inside nif_init. | build experiments/abi/05_runtime/rtnif.mojo and load_nif it | runtime ping 7 after two calls to initialize_runtime() | Idempotent enough to call twice during init in this process. The add NIF does not need it. parallelize was not available without the max package. | medium | elixir one-shot |
| E16 | Small-int Elixir addition is cheaper than this NIF. | mix run --no-start -e with warmup 10_000, 5 trials of 100_000 | NIF microseconds [38188, 37250, 36213, 34686, 36222] (about 347–382 ns/call including the Elixir loop). Elixir + [24655, 24961, 23204, 23876, 23806] (about 232–250 ns/call). | The loop is inside both measurements. This is not a pure boundary cost, and it is not a reason to prefer the NIF. | medium | one process, no isolation between trials |
| E17 | A one-element GPU add can return 42 through a NIF on this Mac. | .venv/bin/mojo experiments/gpu/add_host.mojo, then mojo build experiments/gpu/gpu_nif.mojo --emit shared-lib and elixir -e loading experiments/gpu/libgpu | Host program printed gpu_add 42.0. Fresh BEAM printed gpu_nif 42 and exited 0. nm -gU shows only _nif_init. otool -L lists libKGENCompilerRTShared.dylib, libAsyncRTMojoBindings.dylib, and libSystem.B.dylib. | Mojo 1.1.0 plus MAX 26.6.0. std.gpu is not in this stdlib; the kernel is max.gpu. vector_addition adds one float32, grid_dim=1, block_dim=1. DeviceContext copies the float back and Int(result) becomes enif_make_long. The callback calls initialize_runtime() first. The entry name is Elixir.Mojoler.GPUProbe, function sum/0, library experiments/gpu/libgpu.so (OTP appends .so). has_accelerator() did not take the "no GPU" branch. One call, one process, Apple GPU. Not NVIDIA, not concurrent, not a long kernel, and not part of Mojoler.POC.add/2. | high | this machine, Apple GPU |
| E18 | The NIF struct layout is the same on Linux x86-64. | Actions run 39451722783, job nif, scripts/doctor.sh then scripts/run-abi-experiments.sh on ubuntu-24.04 | doctor: ready. Linux x86_64, Elixir 1.20.2, Erlang 28 erts-16.2, Ubuntu clang 18.1.3, Mojo 1.1.0 (8189361e). T mojoler_add with no leading underscore. Mojo layout: NifFunc 32 at 0, 8, 16, 24; NifEntry 96 at 0, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88; optional_none_or 0. Clang diagnostic: long=8, ERL_NIF_TERM size=8, the same offsets, ErlNifResourceTypeInit sizeof=40. runtime ping 7. | LP64 ELF matches the Darwin measurement. ELF exports have no underscore. ldd of the add-only library printed statically linked. Crashpad's "Failed to initialize Crashpad" is the Mojo driver; the script still exited 0. | high | job 118727989464, commit e6728ea |
| E19 | Mojoler.POC.add(20, 22) == 42 on that Linux runner, and the KGEN runtime is not a dynamic dependency of libpoc.so. | same run, scripts/test-isolated.sh; the ldd text is from the preceding red run 39428236198, whose only failure was the assertion that expected libKGENCompilerRTShared | Green run: Result: 11 passed in 0.6 seconds. Red run's ldd of priv/native/libpoc.so: linux-vdso.so.1, libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6, /lib64/ld-linux-x86-64.so.2. No libKGENCompilerRTShared. Doctor on the green run still finds that .so inside the venv. Link line that produced the library: -Xlinker --unresolved-symbols=ignore-all and -Xlinker -z -Xlinker undefs. The earlier flag --unresolved-symbols=ignore-in-shared-libs failed in 39365275751 with undefined reference to enif_make_long. | The CPU NIF loads under ld.so on Ubuntu 24.04 and the ExUnit suite passes, including the fresh OS process. Undefined enif_* symbols bind when beam.smp loads the library. On Linux this add image does not DT_NEEDED the KGEN shared library, unlike the Darwin otool -L result in E11. The workflow has no GPU. | high | green log plus the ldd printed by run 39428236198 |
| E20 | The generated ABI matches this Mac's headers, and a misspelled import is rejected. | scripts/check-abi.sh | Exit 0. abi mojo: matches, layout ok, unexpected undefined symbol: enif_make_lonq, misspelled import rejected, imports ok for libpoc, libtwice, libterm, libown, libsched, libdsl_arith, libdsl_bytes, libdsl_box, provenance ok, dirty flags ok. | One LP64 Mojo file matches Apple clang's layout of the allowlisted structs. Linux has not been re-run for this generator. | high | this Mac, Darwin arm64 |
| E21 | The ExUnit suite for stages 1–7 passes in one VM. | mix test | Result: 43 passed in 4.8 seconds. | Includes add/2, double/1, term edges, ownership, the heartbeat script, DSL rejection, and the abort probe. | high | this Mac |
| E22 | A dirty-CPU burn lets a one-scheduler VM heartbeat; the same burn on the normal scheduler does not. | scripts/measure-heartbeat.sh | beats 18 elapsed 200 then beats 0 elapsed 200, then heartbeat ok dirty=18 normal=0. | Flags come from abi_dirty_cpu() (1) and c_uint(0). The normal library is built in a temp directory. Elixir Task.yield returning nil did not stop burn/1. | high | this Mac, elixir --erl "+S 1" |
| E23 | An external app, an umbrella child, and a copied release all get 42. | scripts/release-candidate.sh | Consumer 1 passed, again after mix clean, umbrella child 1 passed, release 42, release ok. | The Mix compiler built the dependency. The copied release still resolved the venv rpath on this machine. KGEN was not bundled. | high | this Mac |
| E24 | CPU demo oracles match Elixir. | scripts/demo-cpu.sh | oracle 42, copy {:ok, <<1, 2, 3>>, 3}, borrow {:ok, 32, 1}, frozen <<4, 9>> token 1, retained <<7, 7>>, dirty_burn_us 49568, warm_add_us 4. | The warm number is one call, not a throughput claim. add/2 is not presented as faster than Elixir. | high | this Mac |
| E25 | The historical GPU NIF still returns 42, and a 4-wide batch returns 110, including a repeat and two concurrent calls. | scripts/gpu-batch.sh, then a second Elixir process for the concurrent line | gpu_nif 42, gpu_batch 110, gpu_batch_repeat 110. Concurrent inspect printed ~c"nn", which is the charlist [110, 110]. | CPU reference is 1+10+2+20+3+30+4+40. The batch NIF uses abi_dirty_cpu(). experiments/gpu/gpu_nif.mojo was not edited. No-device was not observed. NVIDIA and Linux GPU were not run. | high | this Mac, Apple GPU, MAX 26.6.0 |
| E26 | An aborting NIF takes down only the child BEAM. | scripts/probe-abort.sh inside mix test | The probe test passed. The script exits 0 only when the child exits non-zero. | This is abort, not a Mojo raise inside safe arithmetic. Product NIFs do not call it. | high | this Mac |
Unknowns still open: NVIDIA, any GPU call on Linux, the {:error, :no_device} branch on a machine that actually has no accelerator, macOS ARM64 in Actions (no runner is configured), and the Linux workflow for stages 1–10 (the file is updated; this commit has not been executed there). Mojoler.Own frees its private box in unload. The entry and function-table calloc blocks are still not freed, because OTP retains the entry; a second load_nif is rejected, so that residue is one pair of blocks per loaded module per process. Upgrade returns 1 and was not invoked.