MiniGCC is an educational, self-hosting C compiler that translates a substantial subset of the C language into x86-64 assembly (AT&T syntax) for Linux. It generates standalone ELF executables and is capable of compiling its own source code (bootstrapping).
| Repository | Role |
|---|---|
| miniGCC | C compiler: C to x86-64 AT&T assembly |
| ld | this repository: assembly to a Linux ELF or a CVM module |
| cvm | the CVM / cvm2 bytecode interpreter |
| miniOS | the kernel that hosts the whole toolchain |
Fully capable of compiling its own source code to reach complete technical sovereignty. Verified through 3+ generation bootstrapping where the compiler reaches a fixed point (identical output).
- Primitive types:
int,char,float,double,void, plusunsigned/signedqualifiers in every position (bareunsignedisint;unsigned/signedfoldlong/int/charruns;shortabandons to theshorttypedef). Multi-declarator sharing holds:unsigned char a = 250, b = 150keeps every declarator unsigned, includingfor-init and block-scope typedefs. Proved bytests/t_unsigned.c(shared large values so a signedness drop is observable, chainedtypedef u32 uwordwith 32-bit wrap print,signed charload extension) with 12 scoped mutants dead long longis an 8-byte integer in all positions (locals, globals, members, params, returns, casts). Proved bytests/t_longlong.c- Fixed-width integers with true 1/2/4/8-byte storage:
int8_t,int16_t,int32_t,int64_t,uint8_t,uint16_t,uint32_t,uint64_t,uintptr_t,intptr_t, plusshort/unsigned short. Loads sign- or zero-extend to 64 bits, so sub-64 arithmetic, shifts and comparisons are exact, unsigned included; stores narrow to the width - Pointers (single and multi-level)
- Arrays (including 2D arrays)
- Typedefs: user scalar aliases record the base size and unsignedness
(
char1,float4, else 8) and chained aliases copy size, unsignedness and fnptr-ness (typedef u32 uword,typedef ureg_t ureg2); struct members capture a leadingunsigned/signedand sizelongas 8. Array continuations still need their own declaration (fail-closed). Proved bytests/t_struct_ul.c(signed-tag vs unsigned-tag member, truncated vs fulllongbase, alias-sized array stride),typedef struct {...} Name, chained function types, and theneg_typedef_arrcont.cdiagnostic - Structs with member access (
.and->operators) - Enums
if/elsewhileloopsforloops (including complex increment expressions)do/whileloopsswitch/case/defaultbreakandcontinuegotoand labelsreturn
- Arithmetic:
+,-,*,/,% - Comparison:
<,<=,>,>=,==,!= - Logical:
&&,||,! - Bitwise:
&,|,^,~,<<,>> - Assignment:
=,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=,++,-- - Ternary:
? : - Array subscripting:
arr[i] - Struct member access:
s.fieldandptr->field - Pointer dereference:
*ptr - Address-of:
&var - Comma operator
sizeofoperator (expressions and type-names)
- Function definitions and calls
- Any fixed argument count (System V AMD64 ABI): the first 6
integer/pointer arguments ride registers, the rest spill on the stack
in order with static parity padding, so
rspis 16-aligned at everycalland stack parameters land atrbp+16, rbp+24, .... Proved bytests/t_args7.c(7- and 8-arg direct and indirect calls) plus thetools/test_call_align.pyprobe (direct 6/7/8 and indirect 7 report the ABI-mandated alignment); arity mismatches stay caller bugs, never compiler errors - Function pointers:
ret (*name)(params)declarators for locals, globals, parameters, struct members and arrays; calls through variables, table elements and struct members (f(x),tbl[i](x),s.op(x),p->op(x),(*f)(x)); bare designators and&fdecay to addresses of defined functions (define-before-use);NULLassignment and comparison work; arithmetic, bitwise and shift on function pointers is a fail-closedarithmetic on function pointererror and calling a non-function value iscannot call non-function. Pointee signatures are unchecked (K&R-style): arity mismatches are caller bugs, not compiler errors. Boundaries, all fail-closed with diagnostics: no function-pointer typedefs, no(*f)[N]arrays-of vs pointers-to disambiguation beyond the(*name[N])(params)form, no member arrays of function pointers, no initializers on static/global declarators (assign at runtime), prototypes do not register designators - Variadic definitions (
int kprintf(const char *fmt, ...)) withva_list/__builtin_va_listandva_start/va_arg/va_end(plus the__builtin_spellings); every vararg rides a uniform 8-byte slot - Recursive functions supported
- Proper stack alignment (16-byte) per ABI
staticvariables (local file scope)externdeclarations (no assembly emission, link-time resolution)inline/__inline/__inline__(accepted, always emitted out-of-line)volatile/__volatile__qualifiers (accepted, fully spilled codegen)- Global variables (
.bsssection,.globlexported)
- Basic
asm(asm/__asm/__asm__, optionalvolatile) in function bodies and at top level, emitted verbatim (including multi-line templates built from adjacent string literals) - Extended
asmwith operands: inputsr a b c d D S m Nd, outputs=r =a =b =c =d =D =S =m(with optional&earlyclobber, accepted with identical codegen because every operand already owns a distinct home),%0-%9/%%/%=substitution,memory/cc/ register clobbers. Anything else is a fail-closed parse error.D/Spin%rdi/%rsi(homes 10/11); the scratch pool (%r10%r8%r9%rsi%rdi) excludes a pinned register, and a template that needs more scratch than remains is a fail-closedtoo many register asm operandserror, never a silent alias. Template%Nreferences always expand full-width: sub-width output stores (movb/movw/movlthrough%%dil/%%di/%%ediwritten literally) are the supported narrow-output form, matching the existingt_asm3style
__sync_fetch_and_add(lock xaddq, returns the old value),__sync_lock_test_and_set(plainxchgq),__sync_lock_release(store 0),__sync_synchronize(mfence), with arity checks. 64-bit only, matching the 8-byte int model
__attribute__/__attribute:packed(layout is already packed),aligned(N)on trailing declarators (emits.balign N; locals already satisfy it),noreturn/returns_twice/always_inlineaccepted; unknown attributes warn, a non-power-of-two alignment is an error
- Integer literals: decimal, octal
0...and hex0x...(normalized, so every consumer keeps working), withuUlLsuffixes; floats with exponents andfFlLsuffixes (stripped before.rodataemission, which GAS accepts) - String literals and character constants
- Full escape sequences in strings and chars (
\n,\t,\r,\f,\v,\a,\b,\0,\\,\",\',\xNN, octal\NNN) - Simple macro definitions via
#define, with constant-expression folding so#define N (1 << 8)and#define CH 'A'evaluate to real values (not 0) - Conditional compilation:
#ifdef,#ifndef,#if,#else,#endif - File inclusion via
#include(supports both<>and""styles) - Global and local variable allocation
- Global initializers: scalar constants, brace lists for arrays, and string
literals for
chararrays (which size an unsized array) and forchar *. Uninitialized globals stay in.bss; initialized ones go to.data. A pointer global initialized with a string literal keeps a zero slot and is filled in by rip-relative code emitted into_start, because the output is position independent and carries no load-time relocations. - Floating-point arithmetic (single and double precision)
- Type casting:
(int),(char),(float),(double),(type*) - Two-pass compilation for accurate stack size calculation
- Hash table-based symbol table for O(1) lookups
- Scope management with proper symbol cleanup
- Standalone ELF executables with
_startentry point (weak symbol, does not conflict with crt1.o)
- No support for
long doubleor bitfields - No standard library linkage; programs must use only built-in types and direct system calls
- The compiler uses 8-byte
intinternally (sizeof(int) == 8) while struct plain-intmembers stay 4 bytes (matching GCC's x86-64 ABI), sounsigned intwraparound is 64-bit instead of LP64 32-bit. The kernel needs LP64 (minifs.hsuperblockunsigned intfields are 4 bytes on disk), so this gates the kernel self-host milestone and is tracked as its own follow-up, not deferred silently - 64-bit
uint64_tvalues at or above 2^63 still use signed operations (integer</>always take the signed jump); sub-64 values are exact because loads zero-extend staticlocals re-initialize on every entry (no persistence);staticglobals persist correctly- Typedef'd globals accept no initializer; the final stash record only serves struct aliases and unexercised multi-name scalars, so its unsignedness argument is intentionally unpinned (equivalent mutant, documented here instead of killed)
- Pointer arithmetic and
++/--on multi-byte pointers are unscaled (correct only forchar *); array subscripting scales correctly - No unions (parsed but members accumulate in global struct table)
- Global initializers accept constants only: no address-of, no arithmetic on symbols, and no nested brace lists for 2D arrays
- Extended
asmhas no read-write (+) operands, and fixed homes are full-width registers;char *globals initialized with a string literal only materialize when linked with the siblingld
The compiler itself is a single C file that can be bootstrapped using any standard C compiler:
make # same as: gcc -std=c99 -Wall -Wextra -O2 -o minigcc minigcc.c
make test # bootstrap chain (test.sh) + feature suite (test_all.sh)To verify the compiler's self-hosting stability, run it through a 3-generation compilation cycle. The generated assembly should reach a fixed point (identical binary output):
# Generation 2: Compile minigcc.c using the Gen 1 binary
./minigcc minigcc.c > minigccg2.s
as minigccg2.s -o minigccg2.o
gcc -no-pie minigccg2.o -o minigccg2
# Generation 3: Compile minigcc.c using the Gen 2 binary
./minigccg2 minigcc.c > minigccg3.s
as minigccg3.s -o minigccg3.o
gcc -no-pie minigccg3.o -o minigccg3
# Generation 4 / Verification: Compile minigcc.c using the Gen 3 binary
./minigccg3 minigcc.c > minigccg4.s
# Check if the compiler has stabilized (outputs should be identical)
diff minigccg3.s minigccg4.sIf diff returns no output, the compiler has successfully achieved perfect self-hosting stability.
The chain above leans on GNU as/ld. With the sibling ld
assembler/linker, gcc is the only foreign tool left (generation 1); every
generation after it is compiled by miniGCC and linked by ld:
gcc -std=c99 -Wall -Wextra -O2 -o minigcc minigcc.c
./minigcc minigcc.c > minigccg2.s
ld -f elf -o minigccg2 minigccg2.s && chmod +x minigccg2
./minigccg2 minigcc.c > minigccg3.s
ld -f elf -o minigccg3 minigccg3.s && chmod +x minigccg3
./minigccg3 minigcc.c > minigccg4.s
diff minigccg3.s minigccg4.stest_ld_selfhost.sh automates this GNU-free chain end to end (fixed point
plus behavioural equivalence with generation 1) and runs as part of
test.sh. The same chain builds the minigcc.elf that ships on the MiniOS
ramdisk.
Note: The current version has been verified to bootstrap correctly through both chains —
g3.sandg4.sare identical.
make test # bootstrap chain (test.sh) + feature suite (test_all.sh)test_all.sh compiles every tests/t_*.c with both host gcc (reference)
and miniGCC and requires byte-identical stdout (30 runtime tests), plus
negative tests that must fail compilation with a diagnostic (10 tests):
arithmetic, control flow, pointers, arrays, strings, structs, enums,
typedefs, floats, hex/octal/suffixes, compound assignment, inline,
#include, macros, sizeof, recursion, global initializers, argv, basic
and extended asm, fixed-width integers, attributes, atomics and variadics.
- Compile a C source file to assembly:
./minigccg3 source.c > output.s- Assemble the output:
as output.s -o output.o- Link into an executable (no PIE for simplicity):
gcc -no-pie output.o -o output- Run the program:
./outputThe compiler now emits a .weak _start entry point for standalone executables:
./minigccg3 source.c > output.s
as output.s -o output.o
ld output.o -o output
./outputld encodes x86-64 machine code directly and bundles a mini libc, so the
output is a static PIE that runs on Linux and inside MiniOS:
./minigccg3 source.c > output.s
ld -f elf -o output output.s
chmod +x output
./outputThe same assembly, linked by ld -f cvm, becomes a CVM v2 module for the
cvm2 interpreter — the format that runs inside MiniOS as run file.cvm:
./minigccg3 source.c > output.s
ld -f cvm -o output.cvm output.s
# host: run it with the cvm2 interpreter
../cvm/cvm2/cvm output.cvm arg1
# inside MiniOS
miniOS> run minigcc.o source.c > output.s
miniOS> run ld.o -f cvm -o output.cvm output.s
miniOS> run output.cvmCVM modules receive a Linux-style argv (the module path is argv[0]), and
the interpreter's x86 stack model lets a module run with or without
arguments identically.
Given test.c:
int main(void) {
int a = 10;
int b = 2;
return a + b;
}Build and run:
./minigccg3 test.c > test.s
as test.s -o test.o
ld test.o -o test
./test
echo $? # Should print 12- The compiler writes assembly directly to
stdout; redirect to a file as shown in the examples - Error messages and diagnostics are printed to
stderr - The generated code uses the
leave/retepilogue and expects the stack to be 16-byte aligned at call sites as per the System V AMD64 ABI - Floating-point operations use the SSE instruction set (
movss,movsd,addss,addsd, etc.) - The compiler performs a two-pass analysis: the first pass calculates stack sizes, the second pass generates code
- Global variables are emitted to
.bsssection (zero-initialized) externdeclarations are recognized but do not emit assembly directives (linker resolves them)- String literals are stored in
.rodatasection - The symbol table uses open hashing with a 512-bucket table for O(1) average-case lookups
- The
_startentry point is emitted as a.weaksymbol —gcclinking uses crt1.o's_start,ldlinking uses the compiler's_start - Bounds-checked string operations prevent buffer overflows in all string copy paths
Signed: grisun0
LazyOwn RedTeam
Deployed with deploynator
This project has been analyzed by ReadMenator,
a zero-token polyglot static analysis tool. Regenerate with
pip install readmenator && readmenator . --rebuild. Analysis outputs:
- KNOWLEDGE_BASE.md -- Full architecture reference with all classes, functions, imports, dependency graphs, UML class diagrams, security audit findings, community analysis, taint paths, hotspots, cycles, and more.
- readmenator-agent/ -- Agent-friendly, grep-optimized index.
INDEX.md-- Quick reference: what each file doesSYMBOLS.md-- Every symbol with file:lineARCHITECTURE.md-- Internal dependency pairs + external importsAPI.md-- Public function contractsGOTCHAS.md-- Change warnings (blast radius, cycles, hotspots)SECURITY.md-- Findings by severity with fix hintsMEMORY.md-- Rules, workflow, style, deliverables, session logMANIFEST.json-- Freshness (git_commit + source_fingerprint) + read orderKB_<subsystem>.md-- Per-subsystem contextrecipes/*.md-- Grounded task guides (add-function, fix-cycle, ...)
- readmenator-wiki/ -- Navigable wiki (start here for the big picture).
index.md-- Entry point: overview, reading order, god nodes, connectionscommunity_*.md-- One synthesis page per code communityconnections.json-- Typed bridges between communitiesqueries.md-- Suggested questions + answer logREPORT.md-- Honest audit: coverage, confidence, limits
- readmenator-graphrag/ -- Zero-token GraphRAG index.
index.json+REPORTS.md-- Query withreadmenator . ask "<question>"
- readmenator-apidocs/ -- API reference (Sphinx/Doxygen style).
index.md+modules/*.md-- Signatures, parameters, returns, raises, examples, call graphsarchitecture.md-- Tiered architecture + execution flows (control flow, call tree, sequence)classes.md,genindex.md,COVERAGE.md-- Hierarchy, A-Z index, doc coverage + warningsindex.html+api/-- Offline searchable browser and machine-readable model (api/index.json,api/modules/*.jsonlone entity per line)
- readmenator-maps/ -- Interactive gallery (
index.html).- 5 system maps: architecture, workflow, sequence, dataflow, lifecycle
graph-force.html-- Force-graph explorer (2D/3D, reach, paths, lenses)graph-bundle.html-- Edge bundle explorer (circle + sphere)components.html-- Component diagrams
- docs/ -- Static site (GitHub Pages ready).
- Gallery copy +
llms.txt(agent entry point) +md/docs + video
- Gallery copy +
- readmenator-diagrams/ -- Component diagrams.
analysis.json+graph LRmarkdown + per-component pages +index.html
- readmenator-rules/ -- Suggested Semgrep rules (
*.yml),yara_rules.yar(YARA-lite) +exclusions.yaml(false-positive blocklist) - readmenator.mp4 -- Cinematic synthwave overview video
- .cursorrules -- Generated AI assistant rules
- readmenator_audit.sarif -- SARIF v2.1.0 audit (when security scan is on)
Cache (.readmenator_cache/), packaged agent skills (.claude/skills/), and the
GitHub wiki mirror are also produced on run/--rebuild.
AI agents: Read readmenator-wiki/index.md first for the big picture, then readmenator-agent/INDEX.md for grep-friendly lookup.
Developers: Read KNOWLEDGE_BASE.md for full architecture reference, open readmenator-maps/index.html for the visual gallery.