Zulip Chat Archive

Stream: general

Topic: GCC JIT Binding for Lean4


Schrodinger ZHU Yifan (Sep 07 2023 at 04:40):

I am working on creating a GCC JIT Binding for Lean: https://github.com/SchrodingerZhu/lean-gccjit. (Named "Jit" as it is, libgccjit can actually be used as a pretty good AOT backend.)

It is still very very early stage with low-level direct bindings only. However, it gained the ability to emit its first executable just now!

def blockCheck1 (ctx: Context) : IO Unit := do
  let location ← ctx.newLocation "test.c" 3 3
  let int ← ctx.getType TypeEnum.Int
  let code ← ctx.newParam location int "code"
  let exit ← ctx.newFunction location FunctionKind.Imported int "exit" #[code] false
  let main ← ctx.newFunction location FunctionKind.Exported int "main" #[] false
  let entry ← main.newBlock "entry"
  let zero ← ctx.zero int
  let callExit ← ctx.newCall location exit #[zero]
  entry.endWithReturn location callExit

It is very likely that I have made many mistakes in creating the bindings. I am hoping that someone else interested can help audit the code and also help design Monad flavor higher-level interfaces.

Schrodinger ZHU Yifan (Sep 08 2023 at 15:23):

It is now able to compile a brainfuck program!

import «LeanGccJit»
import LeanGccJit.Version
import LeanGccJit.Types
import LeanGccJit.Unsafe

inductive BFItem :=
  | Right
  | Left
  | Inc
  | Dec
  | PutChar
  | GetChar
  | Loop (body : Array BFItem)
deriving Repr

partial def compileBF (ctx: Context) (putchar: Func) (getchar: Func) (main: Func)
  (block: Block) (gBuffer : LValue) (cursor : LValue) (one: RValue) (prog: Array BFItem)
  : IO Block := do
  let mut block := block
  for i in prog do
    match i with
    | BFItem.Right =>
      block.addAssignmentOp none cursor BinaryOp.Plus one
    | BFItem.Left =>
      block.addAssignmentOp none cursor BinaryOp.Minus one
    | BFItem.Inc =>
      let access ← ctx.newArrayAccess none (← gBuffer.asRValue) (← cursor.asRValue)
      block.addAssignmentOp none access BinaryOp.Plus one
    | BFItem.Dec =>
      let access ← ctx.newArrayAccess none (← gBuffer.asRValue) (← cursor.asRValue)
      block.addAssignmentOp none access BinaryOp.Minus one
    | BFItem.PutChar =>
      let access ← ctx.newArrayAccess none (← gBuffer.asRValue) (← cursor.asRValue)
      let ch ← ctx.newCall none putchar #[(← access.asRValue)]
      block.addEval none ch
    | BFItem.GetChar =>
      let access ← ctx.newArrayAccess none (← gBuffer.asRValue) (← cursor.asRValue)
      let ch ← ctx.newCall none putchar #[(← access.asRValue)]
      block.addAssignment none access ch
    | BFItem.Loop body =>
      let loop ← main.newBlock none
      let after ← main.newBlock none
      let access ← ctx.newArrayAccess none (← gBuffer.asRValue) (← cursor.asRValue)
      let access ← ctx.newCast none (← access.asRValue) (← ctx.getType TypeEnum.Bool)
      block.endWithConditional none access loop after
      let blk ← compileBF ctx putchar getchar main loop gBuffer cursor one body
      blk.endWithConditional none access loop after
      block := after
  pure block

def splitAtEnd  (prog: List Char) : (List Char × List Char) :=
  let rec loop (level: Nat) (prog: List Char) (acc: List Char) : (List Char × List Char) :=
    match prog, level with
    | '[' :: rest, level => loop (level + 1) rest ('[' :: acc)
    | ']' :: rest, 0 => (acc.reverse, rest)
    | ']' :: rest, level => loop (level - 1) rest (']' :: acc)
    | c :: rest, level => loop level rest (c :: acc)
    | _ , _ => panic! "unterminated loop"
  loop 0 prog []

partial def parseBFProg (prog: List Char) (compiled: Array BFItem) : Array BFItem :=
  match prog with
  | '>' :: rest => parseBFProg rest (compiled.push BFItem.Right)
  | '<' :: rest => parseBFProg rest (compiled.push BFItem.Left)
  | '+' :: rest => parseBFProg rest (compiled.push BFItem.Inc)
  | '-' :: rest => parseBFProg rest (compiled.push BFItem.Dec)
  | '.' :: rest => parseBFProg rest (compiled.push BFItem.PutChar)
  | ',' :: rest => parseBFProg rest (compiled.push BFItem.GetChar)
  | '[' :: rest =>
    let (body, tail) := splitAtEnd rest
    let bodyCompiled := parseBFProg body #[]
    parseBFProg tail (compiled.push (BFItem.Loop bodyCompiled))
  | _ => compiled

partial def compileBFToFile (path: System.FilePath) (prog: String) : IO Unit := do
  let ctx ← Context.acquire
  ctx.setIntOption IntOption.OptimizationLevel 3
  let int ← ctx.getType TypeEnum.Int
  let chParam ← ctx.newParam none int "ch"
  let putchar ← ctx.newFunction none FunctionKind.Imported int "putchar" #[chParam] false
  let getchar ← ctx.newFunction none FunctionKind.Imported int "getchar" #[] false
  let main ← ctx.newFunction none FunctionKind.Exported int "main" #[] false
  let buffer ← ctx.newArrayType none int 1024
  let gBuffer ← ctx.newGlobal none GlobalKind.Internal buffer "buffer"
  let cursor ← main.newLocal none int "cursor"
  let mut block ← main.newBlock "entry"
  let one ← ctx.one int
  block.addAssignment none cursor (← ctx.newRvalueFromUInt32 int 512)
  let last ← compileBF ctx putchar getchar main block gBuffer cursor one (parseBFProg prog.toList #[])
  let zero ← ctx.zero int
  last.endWithReturn none zero
  ctx.compileToFile OutputKind.Executable path.toString
  IO.println s!"{(← ctx.getFirstError)}"
  ctx.release

Schrodinger ZHU Yifan (Sep 08 2023 at 15:29):

Say "hello, world" with

  compileBFToFile "/tmp/bf" "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."

Schrodinger ZHU Yifan (Sep 26 2023 at 19:56):

First release published as https://github.com/SchrodingerZhu/lean-gccjit.
See also the documentation.

Thanks for offering help and suggestions during the progress!


Last updated: Feb 28 2026 at 14:05 UTC