Packages
mob
0.6.15
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.2
0.6.1
0.6.0
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.11
0.5.10
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.0
BEAM-on-device mobile framework for Elixir
Current section
Files
Jump to
Current section
Files
android/jni/mob_erts.zig
//! mob_erts.zig — Hand-declared FFI bindings for the BEAM's ERL_NIF surface.
//!
//! Companion to mob_zig.zig (which covers JNI / libc / Android log). This
//! module narrows in on the symbols that NIF authors call when writing
//! against `erl_nif.h`. We hand-declare what we use rather than @cImport'ing
//! erl_nif.h for the same reasons documented at the top of mob_zig.zig:
//! Zig 0.17-dev's @cImport is gone, translate-c is unreliable on deeply
//! nested headers, and the surface is small + stable so an auditable
//! hand declaration is easy to maintain.
//!
//! Phase 6b iter 3a introduces this file. It declares only what iter 3a's
//! NIFs (nif_platform, nif_log, nif_log2) need; later iters extend it as
//! their ported NIFs require more of the ERL_NIF surface. iter 3b adds the
//! list / tuple / map constructors, enif_get_int / enif_get_double,
//! enif_alloc_binary, and enif_inspect_iolist_as_binary for the test
//! harness NIFs.
//!
//! Authoritative reference: OTP 27+ `erl_nif.h` and `erl_nif_api_funcs.h`.
const std = @import("std");
// ── Core types ─────────────────────────────────────────────────────────────
/// ERL_NIF_TERM is `ErlNifUInt`, which is `unsigned long` on every platform
/// where BEAM is supported. c_ulong matches that and stays 64-bit on
/// aarch64-android (LP64), which is what we ship.
pub const ERL_NIF_TERM = c_ulong;
/// Opaque from the user's perspective — the BEAM owns the layout.
pub const ErlNifEnv = opaque {};
/// ErlNifPid is a struct with a single ERL_NIF_TERM. Marked `extern` so
/// alignment matches the C definition.
pub const ErlNifPid = extern struct {
pid: ERL_NIF_TERM,
};
/// Opaque mutex handle. enif_mutex_create returns one; the others take a
/// pointer to it.
pub const ErlNifMutex = opaque {};
/// Char encoding for enif_get_atom / enif_get_string / enif_make_string.
pub const ErlNifCharEncoding = c_int;
pub const ERL_NIF_LATIN1: ErlNifCharEncoding = 1;
pub const ERL_NIF_UTF8: ErlNifCharEncoding = 2;
/// Binary view. `data` points at heap-owned bytes; `size` is the length;
/// the trailing internal pointers (ref_bin, __spare__) are opaque to NIF
/// authors. Layout matches C exactly so `enif_inspect_binary(env, term, &bin)`
/// fills the same struct shape.
pub const ErlNifBinary = extern struct {
size: usize,
data: [*]u8,
ref_bin: ?*anyopaque = null,
__spare__: [2]?*anyopaque = .{ null, null },
};
/// NIF table entry. `fptr` follows the standard NIF signature
/// `ERL_NIF_TERM (*)(ErlNifEnv*, int argc, const ERL_NIF_TERM argv[])`.
pub const ErlNifFunc = extern struct {
name: [*:0]const u8,
arity: c_uint,
fptr: ?*const fn (env: ?*ErlNifEnv, argc: c_int, argv: [*]const ERL_NIF_TERM) callconv(.c) ERL_NIF_TERM,
flags: c_uint,
};
/// NIF dirty-job flags. Match the `ErlNifDirtyTaskFlags` enum in erl_nif.h —
/// these are the `flags` field values for ErlNifFunc entries that should
/// dispatch on a dirty scheduler. Plain CPU-bound work on the BEAM thread
/// (JSON parse, tree walks) uses CPU_BOUND; long-blocking I/O uses IO_BOUND.
pub const ERL_NIF_DIRTY_JOB_CPU_BOUND: c_uint = 1;
pub const ERL_NIF_DIRTY_JOB_IO_BOUND: c_uint = 2;
/// `ErlNifEntry` — the top-level NIF library descriptor. Returned by the
/// `<module>_nif_init` symbol that the `ERL_NIF_INIT` macro generates in C.
/// Iter 3d builds this struct manually in Zig (instead of via the C macro)
/// so the entire NIF surface — table, load callback, and entry returned to
/// the BEAM — lives in mob_nif.zig.
///
/// Major/minor + min_erts must match the headers the BEAM was built with.
/// We hard-code 2/18 + "erts-14.0" to match the bundled OTP 29 headers; if
/// you bump the OTP runtime, also bump these. `options = 1` allows dirty
/// NIFs (matches what the ERL_NIF_INIT macro emits today).
pub const ERL_NIF_MAJOR_VERSION: c_int = 2;
pub const ERL_NIF_MINOR_VERSION: c_int = 18;
pub const ERL_NIF_MIN_ERTS_VERSION: [*:0]const u8 = "erts-14.0";
pub const ERL_NIF_VM_VARIANT: [*:0]const u8 = "beam.vanilla";
/// ErlNifResourceTypeInit — declared opaque (we never construct one; only
/// its size is read from the entry to gate ABI compatibility). Size on
/// aarch64-linux: 5 pointers = 40 bytes (dtor/stop/down/dyncall + int).
pub const SIZEOF_ErlNifResourceTypeInit: usize = 40;
pub const ErlNifLoadFn = ?*const fn (env: ?*ErlNifEnv, priv_data: *?*anyopaque, load_info: ERL_NIF_TERM) callconv(.c) c_int;
pub const ErlNifReloadFn = ?*const fn (env: ?*ErlNifEnv, priv_data: *?*anyopaque, load_info: ERL_NIF_TERM) callconv(.c) c_int;
pub const ErlNifUpgradeFn = ?*const fn (env: ?*ErlNifEnv, priv_data: *?*anyopaque, old_priv: *?*anyopaque, load_info: ERL_NIF_TERM) callconv(.c) c_int;
pub const ErlNifUnloadFn = ?*const fn (env: ?*ErlNifEnv, priv_data: ?*anyopaque) callconv(.c) void;
pub const ErlNifEntry = extern struct {
major: c_int,
minor: c_int,
name: [*:0]const u8,
num_of_funcs: c_int,
funcs: [*]const ErlNifFunc,
load: ErlNifLoadFn,
reload: ErlNifReloadFn,
upgrade: ErlNifUpgradeFn,
unload: ErlNifUnloadFn,
vm_variant: [*:0]const u8,
options: c_uint,
sizeof_ErlNifResourceTypeInit: usize,
min_erts: [*:0]const u8,
};
// ── Term constructors ─────────────────────────────────────────────────────
pub extern fn enif_make_atom(env: ?*ErlNifEnv, name: [*:0]const u8) ERL_NIF_TERM;
pub extern fn enif_make_int(env: ?*ErlNifEnv, i: c_int) ERL_NIF_TERM;
pub extern fn enif_make_double(env: ?*ErlNifEnv, d: f64) ERL_NIF_TERM;
pub extern fn enif_make_badarg(env: ?*ErlNifEnv) ERL_NIF_TERM;
pub extern fn enif_make_binary(env: ?*ErlNifEnv, bin: *ErlNifBinary) ERL_NIF_TERM;
pub extern fn enif_make_string(env: ?*ErlNifEnv, str: [*:0]const u8, enc: ErlNifCharEncoding) ERL_NIF_TERM;
// List construction (iter 3b).
//
// `enif_make_list` in C is variadic with a count prefix; we expose the
// non-variadic `enif_make_list_from_array` and `enif_make_list_cell`
// (prepend) primitives. Fixed-arity helpers below are built on top.
pub extern fn enif_make_list_cell(env: ?*ErlNifEnv, car: ERL_NIF_TERM, cdr: ERL_NIF_TERM) ERL_NIF_TERM;
pub extern fn enif_make_list_from_array(env: ?*ErlNifEnv, arr: [*]const ERL_NIF_TERM, cnt: c_uint) ERL_NIF_TERM;
// Tuple construction (iter 3b). `enif_make_tuple` is variadic; the
// non-variadic `enif_make_tuple_from_array` is the underlying primitive.
pub extern fn enif_make_tuple_from_array(env: ?*ErlNifEnv, arr: [*]const ERL_NIF_TERM, cnt: c_uint) ERL_NIF_TERM;
// Map construction (iter 3b). Returns 1 on success, 0 on duplicate key.
// `keys` and `values` are parallel arrays of length `cnt`; `*map_out` is
// populated on success.
pub extern fn enif_make_map_from_arrays(
env: ?*ErlNifEnv,
keys: [*]const ERL_NIF_TERM,
values: [*]const ERL_NIF_TERM,
cnt: usize,
map_out: *ERL_NIF_TERM,
) c_int;
// Binary allocation (iter 3b). Returns 1 on success, 0 on OOM. The caller
// owns `bin.data` until it's wrapped via `enif_make_binary`, after which
// BEAM owns it.
pub extern fn enif_alloc_binary(size: usize, bin: *ErlNifBinary) c_int;
// 64-bit integer constructors (iter 3c). Used by the throttled gesture/
// scroll/drag/pinch senders for monotonic timestamps and sequence numbers.
//
// Symbol-name twist: OTP's `erl_nif_api_funcs.h` does
//
// #if SIZEOF_LONG == 8
// # define enif_make_int64 enif_make_long
// # define enif_make_uint64 enif_make_ulong
// #endif
//
// On aarch64-android (LP64 — `long` is 8 bytes) the real symbols in
// libbeam.a are `enif_make_long` / `enif_make_ulong`; `enif_make_int64`
// is just a preprocessor alias. Zig doesn't run the C preprocessor, so
// `extern fn enif_make_int64` would look for a literal symbol that
// doesn't exist on 64-bit and dlopen would fail at app launch with
// `cannot locate symbol "enif_make_int64"`.
//
// On armeabi-v7a (ILP32 — `long` is 4 bytes) the alias doesn't fire and
// `enif_make_int64` is a real symbol. We pick the right linker name at
// comptime via `@extern`.
const enif_make_int64_fn = @extern(
*const fn (?*ErlNifEnv, i64) callconv(.c) ERL_NIF_TERM,
.{ .name = if (@sizeOf(c_long) == 8) "enif_make_long" else "enif_make_int64" },
);
const enif_make_uint64_fn = @extern(
*const fn (?*ErlNifEnv, u64) callconv(.c) ERL_NIF_TERM,
.{ .name = if (@sizeOf(c_long) == 8) "enif_make_ulong" else "enif_make_uint64" },
);
pub inline fn enif_make_int64(env: ?*ErlNifEnv, i: i64) ERL_NIF_TERM {
return enif_make_int64_fn(env, i);
}
pub inline fn enif_make_uint64(env: ?*ErlNifEnv, i: u64) ERL_NIF_TERM {
return enif_make_uint64_fn(env, i);
}
// Term-env hop (iter 3c). enif_send delivers a message to a pid; the
// `msg_env` must be a "process-independent" env allocated via
// enif_alloc_env / freed via enif_free_env after the send returns.
// Terms in `msg_env` must originate there or be copied in via
// enif_make_copy.
pub extern fn enif_alloc_env() ?*ErlNifEnv;
pub extern fn enif_free_env(env: ?*ErlNifEnv) void;
pub extern fn enif_make_copy(dst: ?*ErlNifEnv, src_term: ERL_NIF_TERM) ERL_NIF_TERM;
pub extern fn enif_send(
caller_env: ?*ErlNifEnv,
to_pid: *const ErlNifPid,
msg_env: ?*ErlNifEnv,
msg: ERL_NIF_TERM,
) c_int;
pub extern fn enif_self(caller_env: ?*ErlNifEnv, pid: *ErlNifPid) ?*ErlNifPid;
// Pid resolution (iter 3c).
pub extern fn enif_get_local_pid(env: ?*ErlNifEnv, term: ERL_NIF_TERM, pid: *ErlNifPid) c_int;
pub extern fn enif_whereis_pid(env: ?*ErlNifEnv, name: ERL_NIF_TERM, pid: *ErlNifPid) c_int;
// Tuple inspectors (iter 3c).
pub extern fn enif_get_tuple(env: ?*ErlNifEnv, tpl: ERL_NIF_TERM, arity: *c_int, array: *[*]const ERL_NIF_TERM) c_int;
// Mutex (iter 3c). enif_mutex_create allocates; destroy + try-lock omitted
// — Mob only uses simple lock/unlock pairs and the mutexes live for the
// lifetime of the BEAM process (no destroy needed).
pub extern fn enif_mutex_create(name: [*:0]const u8) ?*ErlNifMutex;
pub extern fn enif_mutex_lock(mtx: ?*ErlNifMutex) void;
pub extern fn enif_mutex_unlock(mtx: ?*ErlNifMutex) void;
// ── Term inspectors ───────────────────────────────────────────────────────
/// Returns 1 on success, 0 on failure. Fills `bin` with the binary's
/// {size, data} view (no copy).
pub extern fn enif_inspect_binary(env: ?*ErlNifEnv, term: ERL_NIF_TERM, bin: *ErlNifBinary) c_int;
/// Returns 1 on success, 0 on failure. Like enif_inspect_binary, but
/// accepts an iolist (list of binaries/integers) and materialises a
/// contiguous binary view. Used when callers can pass either a plain
/// binary or an iolist (e.g. set_root/1).
pub extern fn enif_inspect_iolist_as_binary(env: ?*ErlNifEnv, term: ERL_NIF_TERM, bin: *ErlNifBinary) c_int;
/// Returns 1 on success, 0 on failure. Reads an Erlang charlist into a
/// fixed-size C string buffer (NUL-terminated on success).
pub extern fn enif_get_string(
env: ?*ErlNifEnv,
list: ERL_NIF_TERM,
buf: [*]u8,
len: c_uint,
enc: ErlNifCharEncoding,
) c_int;
/// Returns 1 on success, 0 on failure. Reads an atom name into a buffer
/// (NUL-terminated on success).
pub extern fn enif_get_atom(
env: ?*ErlNifEnv,
atom: ERL_NIF_TERM,
buf: [*]u8,
len: c_uint,
enc: ErlNifCharEncoding,
) c_int;
/// Read an integer term. Returns 1 on success, 0 on failure.
pub extern fn enif_get_int(env: ?*ErlNifEnv, term: ERL_NIF_TERM, ip: *c_int) c_int;
/// Read a double term. Returns 1 on success, 0 on failure.
pub extern fn enif_get_double(env: ?*ErlNifEnv, term: ERL_NIF_TERM, dp: *f64) c_int;
// ── Convenience wrappers ──────────────────────────────────────────────────
// Idiomatic Zig surface over the bare extern fns. Keeps NIF bodies tight.
/// Make an atom from a comptime-known string literal.
pub inline fn atom(env: ?*ErlNifEnv, comptime name: [:0]const u8) ERL_NIF_TERM {
return enif_make_atom(env, name.ptr);
}
/// The canonical `:ok` return.
pub inline fn ok(env: ?*ErlNifEnv) ERL_NIF_TERM {
return enif_make_atom(env, "ok");
}
/// The canonical `badarg` return — typed identically to `ok` so the call
/// sites read symmetrically.
pub inline fn badarg(env: ?*ErlNifEnv) ERL_NIF_TERM {
return enif_make_badarg(env);
}
/// Build an N-tuple from a comptime-known list of terms. Mirrors the C
/// `enif_make_tupleN` inlines but works for any arity via the underlying
/// `enif_make_tuple_from_array` primitive.
pub inline fn makeTuple(env: ?*ErlNifEnv, elems: anytype) ERL_NIF_TERM {
const arr: [elems.len]ERL_NIF_TERM = elems;
return enif_make_tuple_from_array(env, &arr, elems.len);
}
/// `{:error, Reason}` 2-tuple convenience.
pub inline fn errorTuple(env: ?*ErlNifEnv, reason: ERL_NIF_TERM) ERL_NIF_TERM {
return makeTuple(env, .{ enif_make_atom(env, "error"), reason });
}
/// Build a proper Erlang list from a slice of terms.
pub inline fn makeList(env: ?*ErlNifEnv, items: []const ERL_NIF_TERM) ERL_NIF_TERM {
return enif_make_list_from_array(env, items.ptr, @intCast(items.len));
}
/// Build a map from parallel key/value slices. Returns null on duplicate
/// key (matches the C convention of `enif_make_map_from_arrays` returning 0).
pub inline fn makeMap(env: ?*ErlNifEnv, keys: []const ERL_NIF_TERM, values: []const ERL_NIF_TERM) ?ERL_NIF_TERM {
std.debug.assert(keys.len == values.len);
var out: ERL_NIF_TERM = undefined;
if (enif_make_map_from_arrays(env, keys.ptr, values.ptr, keys.len, &out) == 0) return null;
return out;
}
/// Read a numeric term as a double, accepting either a double or an integer
/// term. Returns null if neither path succeeds. Mirrors a common pattern
/// in the test harness NIFs where Erlang callers may pass `100` or `100.0`
/// interchangeably for coordinates.
pub inline fn getNumber(env: ?*ErlNifEnv, term: ERL_NIF_TERM) ?f64 {
var d: f64 = 0;
if (enif_get_double(env, term, &d) != 0) return d;
var i: c_int = 0;
if (enif_get_int(env, term, &i) != 0) return @floatFromInt(i);
return null;
}