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: Dec 20 2023 at 11:08 UTC