# Native compilation

Five targets, cross-compiled from any host to any of them:

| `--target=`     | format        |
|-----------------|---------------|
| `macos-aarch64` | Mach-O        |
| `linux-aarch64` | ELF           |
| `linux-x64`     | ELF           |
| `windows-x64`   | PE32+         |
| `windows-arm64` | PE32+         |

Executables are position-independent (ELF `ET_DYN` / PIE, matching Mach-O).
`--freestanding` drops the embedded startup runtime; the program supplies the
entry (`__c5_entry`, `#pragma entrypoint` or `--entry`). On Linux such an
image is placed at its link address (`ET_EXEC`), since nothing in it applies
load-time relocations, and carries no interpreter and no dynamic section
unless it binds a shared-library symbol, which the driver reports
(`-Wfreestanding-import`). It takes two `PT_LOAD`s, read-execute and
read-write, as `ld` lays out a static executable. EFI images are supported
through the PE subsystem selector.

## Instruction-set baseline

On x86_64 badc assumes x86-64-v3: AVX, AVX2, FMA3, BMI1, BMI2, LZCNT, MOVBE and
F16C on top of SSE4.2 and POPCNT, as in Intel Haswell, AMD Zen and later. On
AArch64 it assumes what the Apple M1 implements, less Apple's own extensions:
ARMv8.4-A with FP16, DotProd, FHM, AES, PMULL, SHA3 and SHA512, plus FRINTTS,
FlagM2, SB and SSBS from ARMv8.5-A, without BTI, BF16 or I8MM. The emitted code
may use any instruction of the set at any optimization level, and a processor
that lacks one is not a target. `-mno-sse` / `-mgeneral-regs-only` take the
floating-point and vector registers out of the set ([Hardening and code-model
knobs](#hardening-and-code-model-knobs)); nothing else narrows it, and the
driver refuses `-march=` and `-mtune=`. No instruction-set feature macro
(`__SSE2__`, `__AVX2__`, `__ARM_NEON`, ...) is predefined but
`__ARM_FEATURE_AES`, `__ARM_FEATURE_SHA2` and `__ARM_FEATURE_CRYPTO`, which
`-mcpu=`'s `+aes`, `+sha2` and `+crypto` modifiers define.


## Multiple translation units

A single `badc` invocation can mix `.c` sources, `.o` objects, and `.a`
archives:

```sh
badc -c foo.c bar.c               # emits foo.o + bar.o (ELF64 ET_REL, target pinned)
badc -o app foo.o bar.o           # links them into a final binary

badc --ar -o libfoo.a foo.c bar.c # bundles into a SysV ar(5) archive
badc -o app main.c -L. -l foo     # link against libfoo.a, gcc-style
```

badc ships its own linker -- there is no `ld` / `lld` / `link.exe`
dependency. Object files are standard ELF64 ET_REL relocatables: a `.text`
section of native machine code, `.data` / `.bss` for static storage,
`.symtab` / `.strtab` for the name table, and `.rela.text` carrying the
relocations the linker applies once each unit's final position is known. The
target is pinned at `-c` time, and the objects are also linkable by `ld` /
`lld`. The address of -- or a data load from -- an external symbol routes
through the GOT, so a badc `-c` object links into a PIE produced by the system
toolchain. Archives are ar(5) with a SysV-style symbol index.

The `full` cargo feature gates the entire pipeline; library consumers that do
not need multi-TU artifacts can opt out via
`default-features = false, features = ["std"]`.

`-l<name>` is resolved in the `-L` directories, then in the standard library
directories under `--sysroot=<dir>` (`usr/lib`, `lib`, their 64-bit and
multiarch variants on ELF; `usr/lib` and `usr/local/lib` on Mach-O). No other
directory is searched, on a native link too: the host's `/usr/lib` is an input
only when the command names it (`--sysroot=/`), so one command emits one image
on every host. The same root supplies the system headers the bundled set lacks
(`<zlib.h>`), probed after the bundled headers so a standard header keeps the
embedded copy. For a Mach-O target `$SDKROOT` is the default sysroot, as for
the platform's own tools; `--sysroot=` with no directory withdraws it.

Storage-class linkage follows C99 6.2.2: `static` at file scope is internal,
bare or `extern` declarations are external, and `extern T x;` with no defining
declaration becomes an unresolved external that the linker tries to satisfy
from the remaining objects or archive members.

## Linking

The linker also takes GNU-ld-shaped work directly: linker scripts
(`-T` / `--script`), `--emit-relocs`, `-z` keywords,
`--build-id`, link maps (`-Map`, `--print-map`), `--whole-archive` spans, and
symbol-export control (`--export-all`, `--export-data`). Invoked as `ld`,
`ld.badc`, or with `--ld`, badc presents a GNU ld persona with its own flag
table, which is what lets it stand in for `LD=` in an existing build --
including [the Linux kernel's](linux-kernel.md).

## What is supported

c5 covers most of C99, the C11 and C23 features real code gates on, and a wide
GCC extension surface. [`std-conformance.md`](std-conformance.md) enumerates
the rejected idioms, the divergent behavior, and the c5-only extensions.

### From the preprocessor side

The preprocessor predefines a standard set, double-underscore wrapped in the
gcc / clang / msvc convention so it does not collide with user identifiers:

```c
    __BADC_VERSION__   <crate version>   // string literal from Cargo.toml, e.g. "0.4.2"
    __BADC_TARGET__    "macos-aarch64"   // canonical target id (string literal)
    __aarch64__ / __arm64__              // AArch64 targets
    __x86_64__ / __amd64__               // x86_64 targets
    _WIN32 / _WIN64                      // Windows targets only
    __BADC_WINDOWS__                     // Windows targets only
    __APPLE__                            // macOS target only
    __linux__                            // Linux targets only
```

alongside the C99 / C11 set (`__STDC__`, `__STDC_VERSION__`, `__SIZEOF_*__`,
`__BYTE_ORDER__`, the `__ATOMIC_*` orders) and, under `--gnu`, the GCC
identity macros. `std-conformance.md` lists them all. `__DATE__` and
`__TIME__` are the time of translation in UTC, one instant for every unit
of an invocation; setting `SOURCE_DATE_EPOCH` fixes it, so a build that
expands either still emits the same bytes on every run.

Comparing the string-literal predefines with `#if X == "..."` / `!=` is a c5
extension over C99, which restricts a `#if` controlling expression to an integer
constant expression; a string is admitted in no other operand position
(`std-conformance.md` states the rule).

The MSVC/MinGW mimicry surface (`_MSC_VER` / `__MINGW32__` / `__int64` /
`__declspec` / etc.) lives in `libc/include/msvc_compat.h` and is opted into
per translation unit with `-include msvc_compat.h`.

## Headers and bindings

The header tells the compiler which dylibs / shared objects / DLLs the target
offers and which local names resolve to which exported symbols:

```c
#pragma dylib(libsystem, "/usr/lib/libSystem.B.dylib")
#pragma binding(libsystem::printf, "_printf")

int printf(char *fmt, ...);
```

The codegen drives its IAT / `.got` / `DT_NEEDED` records from these
declarations. When the source calls `printf`, the parser type-checks the call
against the prototype; the codegen looks up the binding to learn that the
loader should resolve `_printf` from `libSystem.B.dylib`. Switching target
swaps the header and the bindings change with it -- `printf` lands on bare
`printf` from `libc.so.6` on Linux, `printf` from `msvcrt.dll` on Windows.

Validation runs at codegen entry: every intrinsic the program *references*
must have a matching binding for the chosen target. An unused binding describes
the surface without pulling in what it names. A library a bundled header
declares reaches the image only when an
import binds through it, so including `<math.h>` without calling into it leaves
no `DT_NEEDED` behind, which is what `ld --as-needed` does. A `#pragma dylib`
in your own source is a load-time dependency and is recorded whether or not a
symbol binds through it: it is how a program names a library it reaches only by
runtime lookup, such as a framework whose initializer has to run before
`dlsym` or `objc_getClass` resolves a name.

### Source-driven build flags via `#pragma`

badc uses `#pragma`s to lighten the command line. Dylib bindings, exports,
alignment, the entry-point name, and the Windows subsystem each live next to
the code they configure, so the source carries enough context to build with a
bare `badc <file>`.

```c
#pragma once                       // single-inclusion guard for headers.
#pragma dylib(libc, "libc.so.6")   // declare a dylib c5 can bind into.
#pragma binding(libc::sin, "sin")  // map a portable name to its dylib symbol.
#pragma export(my_api)             // promote a function to a shared-object export.
#pragma pack(N) / pop / push       // override the default 8-byte struct alignment.
#pragma entrypoint(WinMain)        // override the default `main` entry point.
#pragma subsystem(windows)         // pick the PE subsystem (console | windows | native | efi_*).
```

`#pragma entrypoint(<name>)` lets the source declare a non-`main` entry without
a build-driver flag; the compiler resolves the name through the same
symbol-table lookup it uses for `main`. `#pragma subsystem(<kind>)` drives the
PE optional-header `Subsystem` byte. The accepted kinds are `console`
(default, `IMAGE_SUBSYSTEM_WINDOWS_CUI = 3`), `windows`
(`IMAGE_SUBSYSTEM_WINDOWS_GUI = 2`), `native` (`IMAGE_SUBSYSTEM_NATIVE = 1`,
with `nt` / `driver` as aliases), and the EFI variants `efi_application`,
`efi_boot_service_driver`, `efi_runtime_driver`, and `efi_rom`; `cui` and
`gui` name `console` and `windows`, and every kind is taken in any case and
with `-` for `_`. `--subsystem=<kind>` takes the same set through the same
lookup and overrides the pragma. With `console`
/ `windows`, `entrypoint(WinMain)` plus `subsystem(windows)` is what a Win32
GUI app needs to skip the loader's auto-attach to a console window. Non-PE
targets keep the default and ignore the directive, so the same source builds
for every OS.

Unknown `#pragma`s and unknown preprocessor directives warn rather than
failing the build. An `#include` that resolves through neither the search paths
nor the embedded headers is an error, as in gcc / clang; pass `-H` /
`--show-includes` for the gcc-`-H`-shape resolution trace on stderr.

## Reaching beyond the predefined set

If something is not available, declare it yourself, or use runtime linking with
`dlopen` / `dlsym` (or `LoadLibrary` / `GetProcAddress`):

```c
int main() {
    int *h, *fn;
    h = dlopen(0, 2);                  // RTLD_NOW
    fn = dlsym(h, "strlen");
    return fn("hello, world!");        // exits 13
}
```

`dlopen(NULL, RTLD_NOW)` returns the calling process's symbol scope -- libc on
POSIX, the loaded set on Windows.

What is reachable from each system:

* **macOS** -- `dlsym(h, "objc_msgSend")` gives the Objective-C runtime entry
  point. The CoreFoundation / AppKit / Foundation surfaces are one
  `dlopen("/System/Library/.../X.framework/X")` away.
* **Linux** -- `clock_gettime`, `nanosleep`, `pipe2`, the entire `pthread_*`
  family. Anything in `/usr/lib`'s sonames if you spell the path.
* **Windows** -- `dlopen` resolves to `LoadLibraryA`, so
  `dlopen("user32.dll", 0)` plus `dlsym(h, "MessageBoxA")` gives a callable
  Win32 API entry point.

## In-process JIT (`--jit`)

Same encoder and relocations as the AOT path. badc mmaps the result
executable, resolves libc through a runtime-built fake GOT, and calls `main`
directly via a transmuted function pointer. Parse, lower and exec happen inside
the badc process, with no subprocess and no on-disk binary:

```sh
badc --jit tests/fixtures/c/c4.c hello.c       # JIT'd c4 self-hosts hello.c
```

Five hosts are supported:

| host           | mapping                                                              |
|----------------|----------------------------------------------------------------------|
| Linux/aarch64  | mmap RW -> mprotect RX, manual `dc cvau` / `ic ivau`                 |
| Linux/x86_64   | mmap RW -> mprotect RX, hardware-coherent I-cache (no-op)            |
| macOS/aarch64  | mmap RWX + `MAP_JIT`, `pthread_jit_write_protect_np` toggle          |
| Windows/x86_64 | VirtualAlloc RW -> VirtualProtect RX, FlushInstructionCache (no-op)  |
| Windows/aarch64| VirtualAlloc RW -> VirtualProtect RX, FlushInstructionCache          |

libc is bound at JIT time: a writable fake GOT gets one entry per resolved
import, and the codegen's existing GOT relocations are patched against this
region. POSIX uses `dlopen(NULL, RTLD_NOW)` + `dlsym` to find each symbol in
the loaded process; Windows uses `LoadLibraryA` per declared dylib (kernel32,
msvcrt, ws2_32, ...) + `GetProcAddress`. macOS uses Apple's `MAP_JIT` plus the
per-thread W^X toggle the hardware requires on Apple Silicon.

## Optimizations

The codegen always lowers through an SSA intermediate representation and a
graph-coloring register allocator. A handful of cheap rewrites run
unconditionally; `--optimize` adds a set of SSA passes on top.

Always on: drop self-`mov`s and fuse compare + branch into `cmp` / `b.cond`
(or `cmp` / `jcc`) without materializing a `0`/`1` boolean in between. The
register allocator builds an interference graph over phi-congruence classes and
colors it greedily, spilling to frame slots only under pressure.

`--optimize` (`-O`, and the `-O1`/`-O2`/`-O3`/`-Os`/`-Oz`/`-Ofast`/`-Og`
spellings, which all select the same single level) runs mem2reg, inlining,
rotate and branch const-folding, and immediate dedup, and predefines `NDEBUG=1`
and `__OPTIMIZE__=1`.

`-O` contracts `a*b+c` into one fused multiply-add on both architectures, an
instruction of the [baseline](#instruction-set-baseline).

`examples/bench.rs` runs a few pure-computation workloads (`fib32`,
`quicksort-50k`, `matmul-50`) through the VM and the in-process JIT and reports
per-iteration timings:

```sh
cargo run --release --example bench -- --iter 10
```

Assembly and SSA snapshots of the test fixtures live under
`tests/snapshots/`, where a codegen change shows up as a reviewable diff.

## Hardening and code-model knobs

For kernel and firmware work the driver accepts the shapes those builds
require: `-mcmodel=small|kernel|tiny`, `-mno-sse` / `-mgeneral-regs-only`
(keep codegen off the FP/SIMD register file), `-mstrict-align`,
`-fPIC`/`-fpic`/`-fPIE`/`-fpie`, `-mindirect-branch=` and `-mfunction-return=`
(retpolines), `-mharden-sls=`, `-fcf-protection=branch` (`endbr64`),
`-mbranch-protection=none|bti|pac-ret|standard`, and the stack-protector
family below. Options badc does not implement are rejected rather than
accepted and ignored, so a configure-time probe gets a truthful answer.

`-ffixed-REG` keeps a register out of the allocator, so no compiler-chosen
value lives in it -- what the kernel asks for the shadow-call-stack register
(`-ffixed-x18`) and for the NEON registers a unit keeps guest or caller state
in (`-ffixed-q16` .. `-ffixed-q31`). Every architectural spelling names the
register (`x9` / `w9`, `q16` / `v16` / `d16` / `s16`, `rax` / `eax` / `ax` /
`al`, `r8` / `r8d` / `r8w` / `r8b`, `xmm5`). The ABI still passes arguments
and results through a reserved register, and an inline-asm operand, clobber
or `register` variable may still name it; the code generator's own scratch
picks avoid it, and an FP scratch it would have used moves to another
register outside the allocator's banks, into the callee-saved bank's tail
when none is left there. The stack and frame pointers, the AArch64 link
register and the scratch registers the code generator cannot give up (x16,
x17, x19; r10, r11) are refused, as is a reservation that leaves a function
with floating-point work no scratch register at all.

`-fstack-protector`, `-fstack-protector-strong` and `-fstack-protector-all`
select which functions carry a stack canary; `-fno-stack-protector` (the
default) selects none. The per-function rule is gcc's, read off the declared
automatic objects: a character array of at least `--param ssp-buffer-size=`
bytes (default 8) or a call to `alloca` for the plain form, plus any array,
any aggregate with an array member, and any object whose address the body
takes for `strong`. The canary occupies the frame's topmost slot, between
the locals and the saved return address; the prologue stores the guard there
and every return path -- including a tail call's teardown -- reloads it,
compares, and calls `__stack_chk_fail` on a mismatch.

`-mstack-protector-guard=global|tls|sysreg` says where the guard value comes
from, with `-mstack-protector-guard-reg=`, `-mstack-protector-guard-offset=` and
`-mstack-protector-guard-symbol=` as its operands. The default follows the
target: `%fs:0x28` on Linux/x86-64, the C library's `__stack_chk_guard` object
elsewhere. `tls` is the x86-64 segment-relative form the kernel selects
(`-mstack-protector-guard=tls`, `-mstack-protector-guard-reg=gs`,
`-mstack-protector-guard-symbol=__ref_stack_chk_guard`); `sysreg` is the
aarch64 form that reads a per-task offset above a system register
(`-mstack-protector-guard-reg=sp_el0`, `-mstack-protector-guard-offset=N`). The
family needs relocatable output -- the failure branch is a relocation against
`__stack_chk_fail` -- so `--jit` and `--interp` reject it, as do the Windows
targets, whose C library exports neither symbol.

`-ftrivial-auto-var-init=uninitialized|zero|pattern` (the kernel's
`CONFIG_INIT_STACK_ALL_ZERO` passes `zero`) initializes every automatic
object declared without an initializer -- scalars, aggregates, arrays and
variable-length arrays -- where its storage is established: the value is
supplied in the front end as an ordinary initializer, so every output mode
carries it and the `-O` promotion treats it as any written one. `pattern`
stores the byte `0xFE` gcc stores. A scalar that fits a register takes the
value as a literal of its own type; anything wider is filled byte-wise,
unrolled within the inline bound and as a store loop past it, and a
variable-length array's loop follows its allocation.
`__attribute__((uninitialized))` opts an object out, as does binding it to a
register with `asm`; a declaration a `goto` or `switch` jumps past is not
covered, as in gcc. `-fzero-init-padding-bits=standard|unions|all` is
accepted with every value and changes nothing: an automatic aggregate
initializer already zero-fills the whole object, padding included, before it
stores the members, for structs and unions alike.

`-Wframe-larger-than=<n>` reports a function whose stack frame exceeds `n`
bytes: what the prologue reserves below the return address, saved registers
and frame record included, `alloca` and variable-length arrays excluded. The
report breaks the size down by region: locals, spill slots, saved registers,
inline-asm scratch, an over-aligned region, the canary and the frame record. It
is the `frame-larger-than` row, so `-Werror=` makes it fatal and `-Wno-`
silences it, and `n` takes gcc's byte-size suffixes (`kB`, `KiB`, `MB`,
`MiB`, ...). Without the option no bound applies, as in gcc and clang; the
kernel passes `CONFIG_FRAME_WARN` through it.

`pac-ret` signs the return address of every function that stores the link
register: `paciasp` ahead of the prologue, `autiasp` after the last teardown
instruction of each epilogue, where sp -- the signing modifier -- holds its
function-entry value again. A frameless leaf stores no link register and is
left alone. `standard` is `bti+pac-ret`; a signed function opens with
`paciasp`, which is itself a landing pad for the branch types a function entry
is reached with, so it takes no separate `BTI C`. An aarch64 object built with
either claims the matching bits in a `.note.gnu.property`
`GNU_PROPERTY_AARCH64_FEATURE_1_AND` word. The linker intersects that word
across inputs, so the compiler sets a bit only where it emitted the
instructions.
