Exercises for Week 5#

Exercise overview#

This week’s exercises are about programmatic generation of LLVM. There are two workhorse modules that we give you:

  • ll.ml that is the module for representing LLVM programs.

  • cfgBuilder is the module for on-the-fly generation of LLVM programs.

Using Ll and CfgBuilder modules#

Ll module includes the auxiliary data structures for representing LLVM– programs. The main top-level declaration is that of prog. This module includes a pretty printer for LLVM programs in the function string_of_prog. CfgBuilder provides an API for representing LLVM fragments.

As a starting exercise, try out the following example programs from the lecture

module Sym = Symbol 
let symbol = Sym.symbol

(* minimal LLVM program returning 0 *)

let llprog_01 = let open Ll in 
   let cfg = { insns =  []; terminator = Ret (I64, Some (IConst64 0L))}, [] in

   { tdecls    = []
   ; extgdecls = []
   ; gdecls    = []
   ; extfuns   = [ (symbol "print_integer",  ([I64], Void))
                 ; (symbol "read_integer", ([], I64))]
   ; fdecls = [ (Sym.symbol "dolphin_main", 
                 { fty = ([],  I64); param = []; cfg}  
                )] 
   }
 
(* Different ways of expressing the following computation 
   
   var x = read_integer () ;
   var y = x + 1;
   print_integer (y);
   return 0;
   
*)

let llprog_02 = let open Ll in 
   let insns = [
      Some (symbol "x" ), Call (I64, (Gid (symbol "read_integer")), []) 
   ;  Some (symbol "y"), Binop (Add, I64, Id (symbol "x"), IConst64 1L)
   ;  None, Call (Void, Gid (symbol "print_integer"), [I64, Id (symbol "y")])
   ] in

   let cfg = { insns 
             ; terminator = Ret (I64, Some (IConst64 0L))}, [] in

   { tdecls    = []
   ; extgdecls = []
   ; gdecls    = []
   ; extfuns   = [ (symbol "print_integer",  ([I64], Void))
                 ; (symbol "read_integer", ([], I64))]
   ; fdecls = [ (Sym.symbol "dolphin_main", 
                 { fty = ([],  I64); param = []; cfg}  
                )] 
   }

let llprog_03 = let open Ll in 
   let sym_x = symbol "x" in 
   let sym_y = symbol "y" in 
   let sym_read_integer = symbol "read_integer" in 
   let sym_print_integer = symbol "print_integer" in 
   let insns = [
      Some sym_x, Call (I64, (Gid sym_read_integer), []) 
   ;  Some sym_y, Binop (Add, I64, Id sym_x, IConst64 1L)
   ;  None, Call (Void, Gid sym_print_integer, [I64, Id sym_y])
   ] in

   let cfg = { insns 
             ; terminator = Ret (I64, Some (IConst64 0L))}, [] in

   { tdecls    = []
   ; extgdecls = []
   ; gdecls    = []
   ; extfuns   = [ (symbol "print_integer",  ([I64], Void))
                 ; (symbol "read_integer", ([], I64))]
   ; fdecls = [ (Sym.symbol "dolphin_main", 
                 { fty = ([],  I64); param = []; cfg}  
                )] 
   }


let llprog_04 = let open Ll in let open CfgBuilder in  
   let sym_x = symbol "x" in 
   let sym_y = symbol "y" in 
   let sym_read_integer = symbol "read_integer" in 
   let sym_print_integer = symbol "print_integer" in 

   let b1 = empty_cfg_builder in 
   let b2 = add_insn (Some sym_x, Call (I64, Gid sym_read_integer, [])) b1 in 
   let b3 = add_insn (Some sym_y, Binop (Add, I64, Id sym_x, IConst64 1L)) b2 in 
   let b4 = add_insn (None      , Call (Void, Gid sym_print_integer, [I64, Id sym_y])) b3 in
   let b5 = term_block (Ret (I64, Some (IConst64 0L))) b4 in 
   let cfg = get_cfg b5 in 
   { tdecls    = []
   ; extgdecls = []
   ; gdecls    = []
   ; extfuns   = [ (symbol "print_integer",  ([I64], Void))
                 ; (symbol "read_integer", ([], I64))]
   ; fdecls = [ (Sym.symbol "dolphin_main", 
                 { fty = ([],  I64); param = []; cfg}  
                )] 
   }

let llprog_05 = let open Ll in let open CfgBuilder in  
   let sym_x = symbol "x" in 
   let sym_y = symbol "y" in 
   let sym_read_integer = symbol "read_integer" in 
   let sym_print_integer = symbol "print_integer" in 

   let i1 = add_insn (Some sym_x, Call (I64, Gid sym_read_integer, [])) in 
   let i2 = add_insn (Some sym_y, Binop (Add, I64, Id sym_x, IConst64 1L)) in 
   let i3 = add_insn (None      , Call (Void, Gid sym_print_integer, [I64, Id sym_y])) in

   let tr = term_block (Ret (I64, Some (IConst64 0L))) in

   let cfg = get_cfg (tr (i3 (i2 (i1 empty_cfg_builder)))) in 

   { tdecls    = []
   ; extgdecls = []
   ; gdecls    = []
   ; extfuns   = [ (symbol "print_integer",  ([I64], Void))
                 ; (symbol "read_integer", ([], I64))]
   ; fdecls = [ (Sym.symbol "dolphin_main", 
                 { fty = ([],  I64); param = []; cfg}  
                )] 
   }

let llprog_06 = let open Ll in let open CfgBuilder in  
   let sym_x = symbol "x" in 
   let sym_y = symbol "y" in 
   let sym_read_integer = symbol "read_integer" in 
   let sym_print_integer = symbol "print_integer" in 

   let i1 = add_insn (Some sym_x, Call (I64, Gid sym_read_integer, [])) in 
   let i2 = add_insn (Some sym_y, Binop (Add, I64, Id sym_x, IConst64 1L)) in 
   let i3 = add_insn (None      , Call (Void, Gid sym_print_integer, [I64, Id sym_y])) in
   let tr = term_block (Ret (I64, Some (IConst64 0L))) in

   let composed_block = seq_buildlets [i1; i2; i3; tr] in 

   let cfg = get_cfg (composed_block empty_cfg_builder) in 

   { tdecls    = []
   ; extgdecls = []
   ; gdecls    = []
   ; extfuns   = [ (symbol "print_integer",  ([I64], Void))
                 ; (symbol "read_integer", ([], I64))]
   ; fdecls = [ (Sym.symbol "dolphin_main", 
                 { fty = ([],  I64); param = []; cfg}  
                )] 
   }
    

let () = 
   let s = Ll.string_of_prog llprog_06 in 
   output_string stdout s 

More complicated example with CfgBuilder#

As a follow-up exercise, generate the code for a factorial function in LLVM using the available API.