GitHub - igor84/wcc: A compiler for subset of C as instructed by the "Writing a C compiler" book by Nora Sandler · GitHub
/" data-turbo-transient="true" />
Skip to content
Search or jump to...
Search code, repositories, users, issues, pull requests...
-->
Search
Clear
Search syntax tips
Provide feedback
--><br>We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
-->
Name
Query
To see all available qualifiers, see our documentation.
Cancel
Create saved search
Sign in
/;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up
Appearance settings
Resetting focus
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
igor84
wcc
Public
Notifications<br>You must be signed in to change notification settings
Fork
Star
main
BranchesTags
Go to file
CodeOpen more actions menu
Folders and files<br>NameNameLast commit message<br>Last commit date<br>Latest commit
History<br>46 Commits<br>46 Commits
.vscode
.vscode
.zed
.zed
fixtures
fixtures
src
src
.gitignore
.gitignore
LICENSE
LICENSE
Readme.md
Readme.md
build.zig
build.zig
build.zig.zon
build.zig.zon
View all files
Repository files navigation
Implementation of "Writing a C compiler" in Zig
This repository contains an implementation of a C compiler as described in the book "Writing a C compiler" by Nora Sandler. This is not a compiler that supports the full C standard, only what is covered by the book. It does implement all the extra features from the book though. The generated assembly is made for Linux systems, not for macOS, since that is what I am using.
The implementation also doesn't follow the book to the letter. In fact, I tried to apply Zig's data-oriented design as much as possible and minimize the data I keep and the number of allocations I make. I am aware that it can still be significantly improved on these fronts, but I will do that when I write my own language :D.
It took me 10 months in total. I worked on it mostly after work and on weekends, except during the final month and a half, when I had more free time and worked on it most days.
Project Structure
The compiler is organized into several key components, each handling a specific phase of the compilation process:
src/<br>├── main.zig - Main entry point and compilation pipeline orchestration<br>├── driver.zig - Compiler driver handling file I/O and process management<br>├── lexer.zig - Lexical analysis (tokenization)<br>├── Ast.zig - Abstract Syntax Tree definitions and operations<br>├── Parser.zig - Syntax analysis and AST construction<br>├── Sema.zig - Semantic analysis<br>├── Tacky.zig - Tacky IR definitions<br>├── TackyGen.zig - Intermediate code generation (Tacky IR)<br>├── Optimizer - Optimizer for Tacky IR<br>├── codegen.zig - x86_64 assembly code generation<br>├── RegAllocator.zig - Does register allocation and coalescing on assembly instructions<br>└── emit.zig - Assembly code emission
The lexer and AST structure are basically copied from the Zig compiler itself. An AST node looks like this:
pub const Node = struct {<br>tag: Tag,<br>main_token: TokenIndex,<br>data: Data = .{},
pub const Data = struct {<br>lhs: Index = Index.empty,<br>rhs: Index = Index.empty,<br>};
pub const Index = enum(u32) {<br>empty = 0,<br>_,
pub const Tag = enum(u8) {<br>program,<br>func_decl,<br>func_def,<br>func_call,<br>block,<br>return_stmt,<br>...
What Data.lhs and Data.rhs contain depends on the type of the node. For example:
.constant contains nothing
.return_stmt contains the index of its value node in lhs
.func_def contains the start and end index in the extras array which is just std.ArrayList(u32).
extras[start] contains the index of the func's body block node,
extras[start+1] contains the number of variables used in the body, which will be useful in the next phase to preallocate space for them,
extras[start+2] contains the function ID. This is a number that is actually an index of this function in the array of all functions,
extras[start+3] contains the function's return type,
extras[start+4] is useful only for function declarations, which share a similar structure, and represents a boolean that says whether the declaration is local or global,
extras[start+5..end] contains indices of function parameter nodes.
For nodes that keep a lot of data in extras, like .func_def, I also made a helper structure that extracts all the data from extras and gives you readable getters to fetch that data.
Tacky IR instructions are represented similarly:
pub const Instr = struct {<br>tag: Tag,<br>data: Data,
pub const Data = packed struct(u64) {<br>lhs: Index = Index.empty,<br>rhs: Index = Index.empty,<br>};
pub const Index = enum(u32) {<br>empty = 0,<br>_,<br>};
pub const Tag = enum(u8)...