| - | #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_<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 |
| + | |
| | [[Category:Backends]] |
| + | [[Category:AdaLogic BASIC]] |
| + | |