Plain-C reference core#
Can I get aether’s arithmetic as standalone C that gives the same bits?
Yes: aether_dsc.c_core is a pure-Python generator of C99 functions that
reproduce, bit for bit, what aether’s expression templates evaluate on the
host. A consumer such as hawk requests the functions it needs and inlines
the generated text into a standalone C file, with no aether header and no C++
compiler involved.
What it generates#
Each function is a static inline C99 function over small value structs.
The generator covers vectors (vec_dot, vec_norm, vec_sum,
vec_cross, the componentwise operators and math functions), matrices
(their elementwise mirrors plus mat_mv, mat_mm, mat_transpose and
vec_outer), quaternions (quat_mul, quat_conj, quat_recip,
quat_rotate and the pure/vec3 conversions) and four scalar formulas
(rsqrt, sign, clip, remainder). Every kind exists at both
real widths, f64 (double) and f32 (float); f32 text uses
the f-suffixed libm and never promotes to double. c_core.kinds()
lists the kinds. The format version tag is aether-c/1
(c_core.CORE_VERSION).
The contract#
The generated functions follow the evaluation order in the
Numerics contract: right-nested folds with no zero seed, division by
one reciprocal, neg as x * -1, norm as sqrt of the sum of
squares, no promotion. The page also holds the table of generated arithmetic
and the exact extent of the bit guarantee.
Build conditions#
The bit-identity holds under two conditions: compile the C translation unit
with -ffp-contract=off, and compare against the templates’ default
std:: host route (no AETHER_HOST_VECTOR_MATH, no fast-math). The
sign of a zero from min or max of +0 and -0 is the one
exception. The reasons are in Numerics contract.
Consuming it from Python#
The module uses only the standard library. Ask for a function by kind, shape and width, then emit the C text for it and everything it depends on:
from aether_dsc import c_core as C
f = C.function("vec_dot", shape=(3,), width="f64")
print(C.emit([f.name]))
emit writes the width typedef and struct types first, then the functions.
The typedefs are guarded (#ifndef AETHER_C_F64_VEC3_T), but the functions
are bare static inline and are emitted once per emit() call, so a
consumer concatenating several calls must de-duplicate by name (or make one
call with every name). f.deps lists the typedef and struct names a
function needs, and f.libm the libm functions it calls, so a consumer can
resolve dependencies and link libm only when required.
How it is pinned#
Three checks keep the generated text and the templates from drifting apart:
The
CCoregtest suite (tests/test_CCore.cppand.cu) compiles the corpus header and asserts that every generated function ismemcmp-equal to the corresponding template expression.tests/c_core/check_c99.shcompiles the corpus under a strict C99 gate: the umbrella header, and every part on its own.tools/c_core/mint_golden.py --checkverifies that the committed corpus (tests/c_core/aether_c_corpus.hand the md5-fenced parts undertests/c_core/corpus/) is exactly what the generator produces, and that no stale part is left behind. CI runs the last two before building; theCCoresuite runs in CI’s normal build-and-test job.
Regenerating the corpus#
The corpus is committed as one small header per width and shape
(corpus/f64/vec3.h, corpus/f32/mat2x3.h, …), with an umbrella
aether_c_corpus.h that includes them all. It changes only when the core
does. After changing the generator, re-mint the corpus and commit it with the
change:
PYTHONPATH=dsc python3 tools/c_core/mint_golden.py
Minting is refused under $CI: a gate that regenerates its own expected
value proves nothing.