English | 日本語
LaCC is a minimalist C compiler that implements only the core language features you need to get simple C programs running.
- Primitive:
int,char,void,unsigned,long,long long,short,_Bool - Derived: pointer types (
T*), arrays (T[]) - Composite: structures (
struct), unions (union), enumerations (enum)
- Definition: specify parameter and return types
- Declaration & Invocation: define and call functions; use
returnto send back a value
Both global and local (stack) variable declarations are supported.
- Conditional Branching
if (condition) { … }else { … }
- Loops
for (init; condition; step) { … }
You can omit any or all of the three components of a for loop (initialization, condition, and step).while (condition) { … }do { … } while (condition);
- Loop Control
breakexits a loopcontinueskips to the next iteration
- Arithmetic:
+,-,*,/,% - Relational:
==,!=,<,<=,>,>= - Logical:
&&,||,! - Bitwise:
&,|,^,~,<<,>>
-
Include directive LaCC can process
#includestatements with double quotes (e.g.,#include "foo.h") and with angle brackets (e.g.,#include <bar.h>).
Quoted includes first look relative to the including file, while angle brackets search configured include paths.
/usr/includeand/usr/include/x86_64-linux-gnuare added automatically, but many system headers still rely on unsupported language features. -
Preprocessor macros
Object-like and function-like#definedirectives (including#stringizing and##token pasting) are expanded during tokenization.
Diagnostics that originate inside macro expansions point back to the original file/line so complex headers remain debuggable. -
Preprocessor conditionals
Conditional compilation with#if,#ifdef,#ifndef,#elif,#else, and#endifis supported, along with#undeffor removing macro definitions. -
Built-in predefined macros
__LACC__,__x86_64__,__LP64__, , and a handful of compat aliases are defined so typical Unix headers can detect the environment. -
Initializer lists for arrays, structs, and unions
- Arrays support local runtime expressions and nested initialization:
int values[3] = {a, b, c}; int matrix[2][3] = {{a, b}, {c, d}};
- Structs and unions support positional and designated initializers. Nested
structs and array members may also contain local runtime expressions:
struct A value = {{b, c, d}, e, f}; struct Pair pair = {.left = a, .right = b};
- Character arrays can be initialized from string literals:
char str[15] = "Hello, World!\n";
Runtime expressions are supported only for automatic local variables. Initializers for global and
staticobjects must be compile-time constants or supported string literals. - Arrays support local runtime expressions and nested initialization:
-
Extern declarations
LaCC supports external variable declarations with basic types, pointers, and arrays. -
Typedef support
LaCC supports thetypedefkeyword for creating type aliases. -
Type qualifiers & storage-class specifiers:
const,volatile,static, and pointer-only qualifiers seen in system headers (restrict,_Nullable,_Nonnull,__strong, etc.) are parsed and safely ignored. -
gotostatement and labels
LaCC supportsgotostatements and label definitions, allowing for non-linear control flow. -
Struct and Union member access
Both dot notation (.) for direct access and arrow notation (->) for pointer access are supported. -
Struct/union bit-fields
C-style bit-field declarations (with optional zero-width separators and unnamed fields) are parsed and contribute to layout checks. Code-gen still treats them as opaque aggregates. -
Binary and Hexadecimal numbers:
0b001011or0xFF2A -
Inline Assembly passthrough
__asm__, and variants withvolatile/clobber lists are recognized and skipped, allowing headers that embed inline assembly to preprocess successfully (no assembly is emitted by LaCC). -
Comment support
// single-line comment /* multi-line comment */
-
switchstatements andcase/defaultlabels
LaCC supportsswitchstatements withcaselabels for branching based on the value of an expression. -
Explicit type casting
LaCC allows explicit type casting between compatible pointer types. -
Ternary conditional operator LaCC supports the ternary conditional operator (
?:) for inline conditional expressions.
To keep system headers parsable, float and double are recognized only for parsing purposes.
- Arithmetic, comparisons, and code generation for floating-point types are not implemented yet.
sizeof(float)is treated as 4 andsizeof(double)as 8 (LP64).long doubleis also simplified to 8 bytes.- Header typedefs like
typedef float _Float32;andtypedef double _Float64;parse successfully, but do not rely on using these types in expressions for now.
Full floating-point semantics (dedicated TY_FLOAT / TY_DOUBLE, arithmetic, and ABI handling) may be added later. Until then, avoid expressions that require floating-point operations.
LaCC does not support the following:
- Floating-point operations: while
floatanddoubleare recognized for parsing andsizeof, arithmetic and codegen are not implemented - Inline assembly
- Variadic functions (macros such as
va_list,va_start, andva_argare not supported) - Nested functions (functions defined within other functions)
- Variable Length Arrays (VLAs)
LaCC only handles one .c file at a time — there's no support for separate compilation or linking multiple translation units.
LaCC lowers AST nodes to a control-flow-graph-based MIR before emitting assembly. Local variable stack offsets are assigned after MIR optimization, so locals removed by optimization do not leave unused stack space.
Optimization behavior depends on the optimization level:
-
-O0- Keeps locals in memory and skips SSA optimization.
- Uses liveness analysis and linear-scan register allocation for MIR virtual registers.
- Still applies parser-side constant folding and local instruction-selection simplifications, such as immediate operands.
-
-O1- Removes unreachable basic blocks.
- Converts MIR to SSA form using dominators, dominance frontiers, and phi nodes.
- Promotes scalar locals whose addresses are not taken to SSA values (mem2reg).
- Performs copy/phi propagation and dead pure-value elimination in SSA form.
- Converts phi nodes back to parallel copies before instruction emission.
- Uses CFG liveness and interference-graph coloring for register allocation, with copy preferences and spilling when needed.
Both levels omit unreachable internal functions, assign stack space only to locals still referenced by MIR, and reserve spill slots after register allocation.
-O is currently equivalent to -O1.
There is no separate -O2 (or higher) optimization pipeline yet.
To compare optimization results (assembly line counts), use:
make asmcmpFor MIR / register-allocation debugging output, use:
LACC_DUMP_MIR=1 ./build/lacc -O1 -S foo.c -o foo.s
LACC_DUMP_RA=1 ./build/lacc -O1 -S foo.c -o foo.s
LACC_DUMP_RA=1 LACC_DUMP_RA_FN=main ./build/lacc -O1 -S foo.c -o foo.sgit clone https://github.com/Latte72R/LaCC
cd LaCCAfter that, you have a few make targets to build and test your compiler:
make selfhostHere, a bootstrap compiler bootstrap is used to recompile the compiler source itself,
producing a self-hosted compiler named lacc.
This ensures that your compiler can correctly compile its own code.
make run FILE=./examples/lifegame.c
make run FILE=./examples/rotate.cThis command compiles and runs the specified C file using the self-hosted compiler lacc.
make unittestPassing all tests confirms that your self-hosted compiler behaves as expected.
The unit tests are located in the tests/unittest.c file.
make warntestThis command runs warning tests to ensure that the compiler correctly identifies and reports warnings.
The warning tests are located in the tests/warntest.c file.
make errortestThis command runs error tests to ensure that the compiler correctly identifies and reports errors.
The error tests are located in the tests/errortest.sh file.
make cleanRemoves the generated binaries and assembly files created during the build process.
make helpDisplays a list of available make targets and their descriptions.
LaCC is designed and maintained by student engineer Latte72 !
- Website: https://latte72.net/
- GitHub: @Latte72R
- X a.k.a Twitter: @Latte72R
- Qiita: @Latte72R
