Skip to content
kostyaPublic

About

MyCompiler - A small IR for building programming languages.

Resources

Stars

29 stars

Watchers

2 watching

Forks

Latest commit

 

History

386 Commits

Folders and files

Repository files navigation

myc - MyCompiler

A small IR for building programming languages.

What is it?

  • Simple DSL over LLVM/QBE.
  • Your language AST -> mycIR -> [LLVM / QBE / C] -> binary.
  • ~31 stack-based opcodes.
  • The whole IR spec fits in an hour of reading.
  • Compiles to native code via LLVM, QBE, or C.
  • Fast compilation, "zero" overhead (I hope).
  • ~9000 lines in Crystal.
  • Includes mycc: a C11-like compiler built on myc (~4000 lines).

Why?

  • I was writing my own language and got tired of fighting with LLVM IR. SSA and basic blocks - X(.
  • Usually when you write your own language, you first build a parser and generate an AST. Then comes the hell stage: translating your AST into LLVM or another backend. Myc takes on all that complexity.
  • LLVM is complex.
  • Myc is simple and fun.
  • Stack-based opcodes are easy to emit from AST with a simple one-pass tree walk.
  • You're not locked into one backend. LLVM for speed, QBE for fast compiles, C for anywhere.

Current status

  • Alpha.
  • All 3 backends work smoothly.
  • ~900 tests × 9 backend modes - passed.
  • Mycc compiles 9000 lines of C from LangArena and 26/34 C files of the Lua interpreter.
  • Currently performs one transformation: inlining. More may come later.
  • Two modes: default (fast, Go-like) and final (slow compile, ~5-15% faster runtime).
  • QBE has a bug on Apple aarch64 with globals.

How it looks like?

FUNC :main
  BODY
    PUSH :MYC_BACKEND
    PUSH :MYC_FLAGS
    PUSH "Hello from (myc)MyCompiler (flags = %s, backend = %s)!\n"
    PRINTF 2
ENDFUNC

Benchmark: LangArena - Pure IR Compilation, Myc vs Clang

plot

This scatter plot shows the tradeoff between compile time and runtime. The ideal is the bottom-left corner: fast compiles, fast runtime. This is the Pareto frontier - you can't improve one without sacrificing the other. Measured on pure IR files (*.myc vs *.ll). Link to benchmark and reproduce steps.

mycIR

All opcodes self documented. Also see examples.

  • 25 main opcodes: PUSH, LOCAL, STORE, CALL, PARAM, BINARY, UNARY, FIELD, DEREF, ADDR, AS, SELECT, MALLOC, CREATE, INSPECT, PRINTF, STACK, SIZEOF, TO, INVOKE, LABEL, GOTO, ALLOCA, SLOT, VA
  • 6 Control flow: IF/THEN/ELSE, LOOP/INIT/COND/BODY/STEP, SWITCH/CASE, BREAK, NEXT, RET.
  • Types: STRUCT, ENUM/VARIANT, FLAT + void, bool, i8..i64, u8..u64, f32, f64, ptr, :indirect, :valist.

Install

Requires Crystal to compile the myc compiler.

Quick Start (compile and run first program).

echo 'FUNC main BODY PUSH "Hello myc\n" PRINTF 0 ENDFUNC' | crystal src/cli/llvm.cr r

Compile

# compile Myc IR C backend
crystal build src/cli/c.cr --release -o myc-c

# compile Myc IR LLVM backend
# requires LLVM >= 15.0, install it system wide or provide LLVM_CONFIG env variable
crystal build src/cli/llvm.cr --release -o myc-llvm 

# compile Myc IR Qbe backend
# uses `qbe` from PATH, or the one given by the QBE env variable,
# otherwise build it locally as shown below
git clone https://github.com/kostya/qbe.git plugins/qbe
cd plugins/qbe; make; cd -
crystal build src/cli/qbe.cr --release -o myc-qbe

Examples:

# run
./myc-llvm r examples/ir/mandel.myc

# compile and run
./myc-llvm c examples/ir/bf.myc && ./bf

# show llvm IR
./myc-llvm examples/ir/loop.myc d

# show qbe IR
./myc-qbe examples/ir/loop.myc d

# explain myc IR
./myc-llvm examples/ir/bf.myc e

# example brainfuck compiler with Myc
cd benchmark/brainfuck-compiler
python3 bf2myc.py mandel.bf | ../../myc-llvm run
cd ../../

mycc - an alternative C compiler.

As a proof of concept, I built mycc (with assist of LLM): a compiler for a subset of C (roughly C11) on top of myc. This proves that creating languages is easy. For parsing, I used libclang - it adds overhead, but it's the simplest solution for a POC. It is only 4000 lines of code and already compiles quite complex C code.

How it works:

C source -> SyntaxTree(libclang/clang.cr) -> TypedAST(mycc) -> IR(myc) -> [LLVM/QBE/C] -> binary

The most challenging part was SyntaxTree -> TypedAST. In the file ast_builder.cr - 2400 LOC. libclang returns a non-normalized Tree with many edge cases that need to be transformed into a consistent form. Additionally, C has a ton of implicit behavior.

The TypedAST(mycc) -> IR(myc) stage as expected, is one of the simplest (codegen.cr, 900 lines) - a single-pass generation of stack-based IR directly from the AST.

Limitations:

Missing features: atomic, long double, complex numbers, longjmp, bitfields, asm. I wouldn't try building sqlite with it. It has only been tested on arm64 and linux64.

mycc: build compiler.

Requires LLVM/libclang >= 20.

# sudo apt install llvm-20 libclang-20-dev
shards install
crystal build src/cli/mycc.cr --release -o ./mycc

mycc: usage example.

# compile file
./mycc c examples/mycc/sieve.c

# compile file with --backend option
./mycc c --backend llvm examples/mycc/sieve.c
./mycc c --backend qbe examples/mycc/sieve.c
./mycc c --backend c examples/mycc/sieve.c

# show myc dump
./mycc examples/mycc/sieve.c d

# show llvm ir dump
./mycc examples/mycc/sieve.c d | ./myc-llvm d

# show qbe dump
./mycc examples/mycc/sieve.c d | ./myc-qbe d

# include paths example
MYCC_INCLUDE='/opt/homebrew/include,/usr/local/include' ./mycc examples/mycc/sieve.c c

# Build object file for custom linking
./mycc o examples/mycc/sieve.c sieve.o
clang sieve.o -lm -o ./sieve

More benchmarks

Here

Plugins/Extensions

Languages Built on Myc

  • Avant - Systems Programming Language

License

Licensed under the Apache License, Version 2.0.

Thanks

About

MyCompiler - A small IR for building programming languages.

Resources

Stars

29 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages