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 and structs (with limitations)
- Array initialization with a list of integer constants:
int arr[3] = {3, 6, 2}; - String literal initialization for character arrays:
char str[15] = "Hello, World!\n"; - Designated initializers for structs/unions:
struct AB v = {.a = 1, .b = 2};
Initializer expressions must currently be integer constants (or string literals for
char[]). Nested array initializers beyond a single level are not supported. - Array initialization with a list of integer constants:
-
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 - Initializer lists do not yet support deeply nested array initializers
- 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 does not emit assembly directly from AST nodes. It implements optimization around MIR (an intermediate representation).
Optimization behavior depends on the optimization level:
-
-O0- Generates MIR, then runs normal register allocation and instruction emission.
- Does not run the main optimization passes (inline expansion / mem2reg / CFG cleanup).
- Still applies parser-side constant folding and local simplifications in the emitter where possible (for example, immediate-form instruction selection).
-
-O1- Includes all
-O0behavior plus staged MIR-level optimization passes. - Typical passes include:
- Conditional inlining of small
static inlinefunctions - Copy propagation / DCE
- Compare/branch fusion (for
cmp+setcc+jz-style patterns) - Unreachable block and unreferenced label pruning
- mem2reg (promotable locals to registers)
- CFG-based dead store elimination
- VReg compaction
- Constant folding at the emission stage
- Conditional inlining of small
- Includes all
-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
