ALB C backend
The ALB C backend is AdaLogic BASIC's most complete emitter: it turns an ALB program into C89 source code — deliberately the oldest, most portable C dialect — which then compiles on essentially any platform with essentially any compiler. Where FASM is the precision instrument and FASM16 is the retro floor, C is the everywhere backend.
Why C89, on purpose
The generated file targets C89 — ANSI C from 1989 — and the toolchain's own build script proves it: gcc -std=c89 -O2. That choice is a feature, not a compromise: C89 compiles on tiny embedded compilers, ancient vendor toolchains, and modern GCC/Clang/MSVC alike. ALB even typedefs the fixed-width types itself (int8_t…uint64_t, handling MSVC vs POSIX spellings) rather than assuming stdint.h exists — which it didn't, until C99.
The boilerplate is honest about what it needs:
#include <stdio.h>
#include <stddef.h> /* OFFSETOF forge */
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <setjmp.h> /* TRY/CATCH */
#include <limits.h>
The platform HAL
Immediately after the boilerplate comes a hardware/OS abstraction layer — emitted as ordinary #if blocks:
| Platform | Define | What gets included |
|---|---|---|
| Windows | ALB_PLAT_WIN | windows.h, winsock2, ws2tcpip |
| POSIX | ALB_PLAT_POSIX | unistd, pthread, semaphore, sockets, netinet/in |
So one emitted file carries both halves and the host compiler keeps the one that applies — real threads, real sockets, real timers on both families, selected at compile time. On Windows the generated main() even enables ENABLE_VIRTUAL_TERMINAL_PROCESSING first, because ALB learned the hard way that ANSI colors bleed through on Wine without it.
TRY/CATCH is setjmp — of course it is
ALB's structured error handling lowers to the classic C mechanism, done properly:
static jmp_buf ALB_Err_Stack[256]; /* Deeply nestable */
if (setjmp(ALB_Err_Stack[++ALB_Err_SP]) == 0) {
/* TRY body */
longjmp(ALB_Err_Stack[ALB_Err_SP], 1); /* THROW */
}
A 256-deep jump stack means TRY/CATCH nests 256 levels — far past any reasonable program, which is the correct kind of overkill.
Da Dumptruck — memory management as data
The C backend injects its object manager as ordinary code you can read:
#define ALB_GC_MAX_NODES 1024
typedef struct { uint8_t Alive; uint8_t Refs;
uint64_t Child_1; uint64_t Child_2; } ALB_GC_Node;
ALB_GC_Node ALB_GC_Grid[ALB_GC_MAX_NODES + 1] = {0};
A 1024-node grid. CLAIM allocates a node and hands you its index; DROP decrements the refcount; BIND links up to two children; SWEEP(chunk) walks an incrementing cursor releasing zero-ref nodes and cascading the decrements — incremental collection, so a sweep never stops the world, it just takes out a bounded chunk of garbage. The same design in miniature runs on 16-bit DOS.
The full feature surface
The C backend implements the largest slice of the language — including things that exist nowhere else in the pipeline:
- Complete file I/O — open/read/write/close/len/seek as a full suite
- The whole draw suite — put-pixel, line, rect, circle, triangle, fills, image blit, sound load/play (SDL3-linked)
- FFI in both directions —
IMPORT_C/IMPORT_DLL/IMPORT_SO/IMPORT_DYLIBlower to real header includes and a dynamic loader prelude; ALB can also emit shared libraries (-shared/-fPIC/-dynamiclib) - Bare metal —
PEEK/POKE/deref, for when the program really does need to touch the address - Compile-time oracles —
SIZEOF/OFFSETOFevaluated through the real C operators - The Prolog machinery — fact registration, queries, and the CUT operator (
Emit_Cut_Operator) for committed-choice logic - Contracts —
REQUIRE/ENSUREand runtime asserts - Branchless mode — the same mask-select optimization as FASM, expressed in C
- Temporal variables and reversible blocks — history rings and undoable arithmetic, in portable C
- Real concurrency —
ALB_Init_Threads()(the emitted comment reads "Spark da forge!"), genuine SPAWN dispatch and atomic sections — where FASM documents itself synchronous, C actually parallelizes
The learning runtime, in C
NEURAL TOPOLOGY, TRAIN NETWORK, INFER NETWORK, MARKOV MODEL, and PREDICT MARKOV are language-level statements, and the C backend implements them too — albt's Emit_C_Extended_Runtime forges the same learning layer as static C functions, injected only when a model or topology was actually declared:
ALB_Markov_Predict(matrix, states, current_state)— the argmax-over-a-transition-row predictorALB_NN_Apply_Activation— aswitchon the activation code (linear, ReLU, step, scaled-tanh)ALB_NN_Infer(meta, input, output)— the elegant part: a whole network described by one flatu64meta-descriptor —meta[0]is the layer count,meta[4..11]carry pointers to the size list, activation codes, per-layer offsets, weights, biases, and scratch stateALB_NN_Train(meta, train_data, expect_data, epochs)— the same perceptron-style delta rule as the asm twin: sign-of-error nudges on the output layer's bias and weights
Initial weights come from albt's deterministic Seed_Neural_Weight — structured compile-time guesses, never RNG — so a C build and a FASM build of the same program start from identical brains. (The ALB FASM backend keeps one learning feature to itself: EMOTION vectors and PREDICT MARKOV … WITH EMOTION.)
Not one output — a build kit
-t c doesn't just write a .c file. It stages a portable build kit — scripts for whatever machine you carry the folder to:
| Script | Toolchain |
|---|---|
build_windows_gcc.bat | gcc -std=c89 -O2 … -lSDL3 -lws2_32 -lwinmm -luser32 -lgdi32 … |
build_windows_cl.bat | MSVC cl /nologo + SDL3 import libs |
build_shared_windows.bat | DLL — gcc preferred, cl /LD fallback |
build_shared_posix.sh | cc -shared -fPIC … -ldl -lpthread -lm |
build_shared_macos.sh | cc -dynamiclib → .dylib |
Emit once on any machine; compile on whichever you deploy to. That is the whole philosophy of the backend in one folder.
Under the hood
Emit_Native_C (~4,500 lines, SPARK-annotated Ada like all its siblings) implements the shared Emit_* surface over the same two-buffer design (Buffer_Global for declarations, Buffer_Main for the main() body). Emitted trees land in c_<name>/.
The interesting part is the division of labor: albt hands the emitter an AST and the emitter streams text — C's job is to be readable, portable, and complete enough that the real optimizer (gcc -O2) can take it from there. Three different strategies for three different backends: FASM micro-optimizes in its macro layer, FASM16 strips down to fit, and C trusts the optimizing compiler the world spent fifty years building.
See also
- ALB FASM backend — the assembly sibling
- ALB FASM16 backend — the 16-bit sibling
- AdaLogic BASIC — the language
- History of Programming — C's place in the arc (1972, Ritchie, Unix)
- COBOL · FORTRAN · ALGOL — the other languages in the backend family tree