Go port of LLVM's Kaleidoscope Tutorial using the tinygo LLVM bindings.
Deer is a simple functional language demonstrating JIT compilation and native code generation via LLVM.
- LLVM-based JIT execution and native code generation
- User-defined unary/binary operators
- Mutable variables with
var/insyntax - Control flow:
if/then/else,forloops - String literals with escape sequences (
'hello\n') - Struct types with fields and methods
- Statically typed parameters and return types (
int,float,str, ...) - S-expression style AST dumping for debugging
- Precise source locations (file:line:col) in error messages
- Structured logging via
log/slog
- LLVM 21 development libraries
- Clang 21 (for linking final executables)
- Go 1.23+
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21
sudo apt install clang-21 liblld-21-devbrew install llvm@21Note for macOS users: If you have multiple LLVM versions installed (e.g.,
llvmandllvm@21), your shell profile may exportCPLUS_INCLUDE_PATHpointing at a different LLVM's libc++ headers. These headers may be incompatible with Clang 21. The Makefile automatically unsetsCPLUS_INCLUDE_PATH,C_INCLUDE_PATH, andLIBRARY_PATHon macOS so thatllvm@21's bundled libc++ is used.
make buildThe Makefile handles OS-specific configuration:
- macOS: Uses Homebrew's
llvm@21clang/clang++, unsets conflicting environment variables, and adds-Wl,-export_dynamicso the JIT can resolve host C functions at runtime. - Linux: Uses system
clang/clang++.
# JIT execute a program
./deer -e examples/hello.dr
# Compile to native executable
./deer -o hello examples/hello.dr && ./hello
# Dump tokens
./deer -o /tmp/hello.tok examples/hello.dr
# Dump AST
./deer -o /tmp/hello.ast examples/hello.dr
# Dump LLVM IR
./deer -d examples/hello.dr
# Verbose debug output
./deer -v -e examples/hello.dr# Comments start with # and go to end of line
# External function declarations
extern printd(x)
extern putchard(x)
extern print_str(s str) # str-typed parameter
# Function definition
def fib(x)
if x < 3 then
1
else
fib(x-1) + fib(x-2)
# Typed function definition (int is the default; float/str/etc. supported)
def add(x int, y int) int x + y
# Binary operator definition (precedence from 1-100)
def binary : 1 (x, y) y
# Unary operator definition
def unary !(v)
if v then 0 else 1
# Mutable variables
def example()
var a = 1, b = 2 in
a + b
# For loops
def printstars(n)
for i = 1, i < n, 1 in
putchard(42) # '*'
# String literals (single-quoted, with escapes)
def main()
print_str('Hello, Deer!') :
print_str('Escapes: \t tab, \n newline, \\ backslash, \' quote')
# Struct definition
def Point struct {
.x int,
.y int,
}
# Struct literal and field access
def origin() Point
Point { x: 0, y: 0 }
String literals are written with single quotes and support escape sequences:
| Escape | Meaning |
|---|---|
\n |
Newline |
\t |
Tab |
\r |
Carriage return |
\\ |
Backslash |
\' |
Single quote |
\0 |
Null byte |
Strings are represented at the LLVM IR level as i8* pointers to
null-terminated global byte arrays. The str type keyword marks a parameter
or return type as a string pointer.
The following built-in types are recognized in parameter and return position:
| Type | Kind | LLVM IR |
|---|---|---|
int |
integer | i64 |
float |
float | double |
str |
string | i8* |
bool |
integer | i64 |
byte |
integer | i64 |
short |
integer | i64 |
rune |
integer | i64 |
void |
void | void |
Untyped parameters default to int in def declarations and float in
extern declarations (matching the original Kaleidoscope behaviour).
See the examples/ directory:
- hello.dr - Print "Hello" using ASCII codes
- math.dr - Math function examples
- operators.dr - User-defined operators
- fib.dr - Iterative Fibonacci
- strings.dr - String literals and
print_str
printd(x)- Print a number as a floatprinti(x)- Print a number as integerputchard(x)- Print a single ASCII characterprintln(x)- Print with formatted outputprint_str(s)- Print a null-terminated string followed by a newline- Math functions via libc:
sin_,cos_,sqrt_,pow_,log_,exp_, etc.
The project ships with table-driven Go tests for the lexer, parser, and type system. Run them with:
make test
# or directly:
go test ./...lib.go- C runtime functions and cgo bindings. Inline C defines all host functions (print, math,print_str) and is used both for JIT symbol registration and native linking. The Go file declares them asexternand provides Go wrappers for JIT execution.codegen.go- LLVM IR generation and optimization (PassBuilder API)exec.go- JIT execution engine (MCJIT)parse.go- Recursive descent parserlex.go- Lexernodes.go- AST node typestypes.go- Type system:TypeKindenum, built-in type lookup, struct registry (StructDef/StructField)
- LLVM's Official C++ Kaleidoscope Tutorial
- Rob Pike's Lexical Scanning in Go
- Go bindings to LLVM (tinygo)
- TinyGo - Go for microcontrollers
- Replaced
go-spewwith litter for cleaner Go-syntax dumps - Added proper filename:line:column positions in errors and tokens
- Added support for
:as a statement separator (in addition to;) - Switched to
log/slogfor structured debug logging - Added more C standard library math functions
- Migrated from deprecated PassManager API to PassBuilder API
- Fixed MCJIT multi-statement execution (two-phase: codegen all, then run)
- Registered host C functions via
LLVMAddSymbolfor JIT symbol resolution - Added
-Wl,-export_dynamiclinker flag on macOS for JIT symbol visibility