Skip to content
ALB Wiki

Diff — ALB C backend

Comparing rev 1 → rev 2

-
#REDIRECT [[C backend]]
+
{{infobox
+
| title = ALB C backend
+
| target_flag = `-t c`
+
| produces = C89 source (.c) + staged build scripts
+
| output = Native .exe, shared library (.dll/.so/.dylib)
+
| compiler = gcc `-std=c89 -O2` · MSVC `cl` · any cc
+
| emitter_unit = `Emit_Native_C` (emit_native_c.adb)
+
| status = Most complete backend; `--build` supported
+
}}
+
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 [[ALB FASM backend|FASM]] is the precision instrument and [[ALB FASM16 backend|FASM16]] is the retro floor, C is the *everywhere* backend.
+
+
```alb One-liner
+
albt tool.alb -t c --build
+
→ c_tool/tool.c + build scripts → gcc/cl/cc → tool.exe
+
```
+
+
## 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:
+
+
```c
+
#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:
+
+
```c
+
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:
+
+
```c
+
#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 [[ALB FASM16 backend|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
+
+
```note One emitter trick worth seeing
+
`Emit_Program_Start` emits boilerplate, HAL, routines, and concurrency — then *remembers its position* (`Headers_End_Idx := Global_Len`) so forward declarations discovered later can be injected back into the header region. The buffer is a scratchpad, not a queue.
+
```
+
+
## 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_&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
+
+
- [[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
+
[[Category:Backends]]
+
[[Category:AdaLogic BASIC]]
+