Skip to content
ALB Wiki

ALB C backend

This is an old revision of this page — revision 5 (2026-10-16 04:34:06 UTC). It may differ from the current revision.

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:

PlatformDefineWhat gets included
WindowsALB_PLAT_WINwindows.h, winsock2, ws2tcpip
POSIXALB_PLAT_POSIXunistd, 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_DYLIB lower 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/OFFSETOF evaluated through the real C operators
  • The Prolog machinery — fact registration, queries, and the CUT operator (Emit_Cut_Operator) for committed-choice logic
  • Contracts — REQUIRE/ENSURE and 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 predictor
  • ALB_NN_Apply_Activation — a switch on the activation code (linear, ReLU, step, scaled-tanh)
  • ALB_NN_Infer(meta, input, output) — the elegant part: a whole network described by one flat u64 meta-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 state
  • ALB_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:

ScriptToolchain
build_windows_gcc.batgcc -std=c89 -O2 … -lSDL3 -lws2_32 -lwinmm -luser32 -lgdi32 …
build_windows_cl.batMSVC cl /nologo + SDL3 import libs
build_shared_windows.batDLL — gcc preferred, cl /LD fallback
build_shared_posix.shcc -shared -fPIC … -ldl -lpthread -lm
build_shared_macos.shcc -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_&lt;name&gt;/.

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

References

  1. ALB Handbook — alb-lang.org/handbook — the language reference for all backends.
  2. ALB Technology page — alb-lang.org/technology — the sixteen-backend matrix.
  3. ALB source — github.com/rockyoliver454/AdaLogic-BASIC-ALB-Programming-Language — the emitter sources are Apache 2.0.
Categories: AdaLogic BASIC · Backends
This page was last edited 2026-10-16 04:34:06 UTC by ALB Team. What links here