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
mojoler docs tutorial 04-abi-and-layout.md
Raw

docs/tutorial/04-abi-and-layout.md

# Unit 4 — ABI and layout

## Objectives

Compare one clang measurement with one Mojo measurement, field by field.

## Why

`RegisterPassable` means the compiler will not secretly insert a pointer. It does not mean the field order matches `erl_nif.h`. The diff does.

## Model

```text
clang offsetof(ErlNifFunc, fptr)  ==  reflect field fptr in Mojo
Optional[Pointer] None            ==  eight zero bytes
thin function                     ==  eight-byte code address
```

## Theory

On both measured LP64 targets, `ErlNifFunc` is 32 bytes and `ErlNifEntry` is 96. `ErlNifBinary` is 40. The C field `__spare__[2]` is `spare_0` and `spare_1` in Mojo. The layout diff keeps `spare_0` (the offsetof of `__spare__`) and drops `spare_1` before comparing, because C has one array field and Mojo has two slots.

Nullable callbacks in the entry are `Optional[Pointer[NoneType, MutUntrackedOrigin]]`. The code address of a thin function is the bits stored in the function value. `Pointer(to=callback)` is the stack slot. `prim.callback_opt` reads those bits.

`enif_open_resource_type`'s generated wrapper takes a generic origin inside the function type, and a concrete dtor does not convert to it. `native/own.mojo` calls the symbol with `external_call` and the concrete dtor type. That is a recorded hole in the wrapper, not a second layout.

## Lab

```sh
scripts/check-abi.sh
```

## Expected

`layout ok` and `dirty flags ok`. A mismatch prints a unified diff and exits 1.

## Code

`tools/abi/print_layout.mojo` uses `std.reflection` for the offsets. The C probe is a heredoc inside `scripts/check-abi.sh`. Neither file is passed to `mojo build` of a NIF.

## Failure

Editing `otp_nif.mojo` by hand makes `--check` fail. Regenerate with `python3 tools/abi/generate.py`. If the header hash changed because OTP moved, the generator's pin file should fail first.

## Exercise

Why is one generated file valid on both Darwin and the recorded Linux runner?

## Answer

E7 and E18 measured the same sizes and offsets. The generator still refuses to label a host probe as a different triple. The Linux *file* was not regenerated on Ubuntu for the later stages; that rerun is PENDING.